I have java web application, that is being used by multiple users. By users I mean people that actually connect to application and do stuff with it.
Every user can edit a schema, that has it's ID. At the moment, multiple users can edit same schema at the same time. I want to fix that, without using a database/table.
What I tried so far:
There's an EDIT button, that users click to edit certain schema. When they click it, a method is triggered.
protected SessionLockModSchema sessionLockModSchema = new SessionLockModSchema();
protected Model model; //schema model object
public void buttonClick(ClickEvent event) {
//button logic goes here
}
I figured I might create a bean with schema ID, when this method is triggered.
protected SessionLockModSchema sessionLockModSchema = new SessionLockModSchema();
protected Model model;
public void buttonClick(ClickEvent event) {
//button logic goes here
this.sessionLockModSchema.lockSchema(model);
}
When I have this bean created, all that I need to do is check if it exists, for the next user.
protected Model model;
public void buttonClick(ClickEvent event) {
if(!this.sessionLockModSchema.isSchemaLocked(model){
//button logic goes here
this.sessionLockModSchema.lockSchema(model);
}
}
In theory this sounded good to me, edit button wouldn't trigger if there was a bean created with that schema. But bean is not created (or at least only one user can access it). Here's sessionLockModSchema class:
public class SessionLockModSchema{
ApplicationContext context;
GenericApplicationContext ctx;
public SessionLockModSchema(){
if(ctx == null){
this.ctx = new GenericApplicationContext();
}
}
public void lockSchema(Model model){
String beanName = "model-"+model.getId();
BeanDefinitionBuilder bDBuilder = BeanDefinitionBuilder .rootBeanDefinition(String.class);
bDBuilder.setScope("prototype");
this.ctx.registerBeanDefinition(beanName, bDBuilder.getBeanDefinition());
//appcontext.close();
}
public boolean isSchemaLocked(Model model){
String beanName = "model-"+model.getId();
Object objectRef = null;
try{
//ctx.refresh();
objectRef = this.ctx.getBean(beanName);
}catch(NoSuchBeanDefinitionException e){
// TODO:
}catch(IllegalStateException e){
// TODO:
}
boolean isLocked;
if(objectRef == null){
isLocked = false;
}else{
isLocked = true;
}
return isLocked;
}
}
To clarify my question, I get IllegalStateException saying that beanFactory must be refreshed, if I do however refresh (commented ctx.refresh), I get that no such bean exists. Any advices on this? What am I doing wrong?
What about something like that (I don't see why you would need spring managed beans here):
public enum SessionLockModSchema {
INSTANCE;
private final Set<String> lockedModels = new HashSet<>;
public void lockSchema(Model model){
synchronized(lockedModels) {
lockedModels.add("" + model.getId());
}
}
public void unlockSchema(Model model){
synchronized(lockedModels) {
lockedModels.remove("" + model.getId());
}
}
public void isSchemaLocked(Model model){
synchronized(lockedModels) {
return lockedModels.contains("" + model.getId());
}
}
}
usage (something like that):
public void buttonClick(ClickEvent event) {
if (SessionLockModSchema.INSTANCE.isLocked(model) {
try {
SessionLockModSchema.INSTANCE.lockSchema(model);
// do something with "model"
} finally {
SessionLockModSchema.INSTANCE.unlockSchema(model);
}
}
}
Related
again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}
I'm usnig Google App Engine with Java, and using Datastore via JPA.
I want to save profile image for each user:
#Entity
public class User implements Serializable {
#Id
private String userId;
private String imageKey; // saves BlobKey.getKeyString()
// ... other fields and getter/setter methods ...
}
If user wants to change their profile image, I (should) update imageKey field AND delete old image associated with old imageKey.
But, the document of Blobstore for Python says:
Deleting a Blobstore value occurs separately from datastore transactions. If you call this method during a datastore transaction, it takes effect immediately, regardless of whether the transaction commits successfully or is retried.
This seems that I can't make updating imageKey and deleting old image as one atomic action, and it also would affect to Java.
This is my attempt to do this work:
public class Engine implements ServletContextListener {
private EntityManagerFactory emf;
private BlobstoreService blobstoreService;
// Servlet will call getUser, modify returned object, and call updateUser with that object
public User getUser(final String userId) throws EngineException {
return doTransaction(new EngineFunc<User>() { // Utility method for create Entity manager and start transaction
#Override
public User run(EntityManager em) throws EngineException {
return em.find(User.class, key);
}
});
}
public void updateUser(final User user) throws EngineException {
doTransaction(new EngineFunc<Void>() {
#Override
public Void run(EntityManager em) throws EngineException {
em.merge(user);
return null;
}
});
user.purgeOldImages(blobstoreService);
}
// ... Other methods ...
}
public class User {
#Transient
private transient Set<String> oldImageList = new CopyOnWriteArraySet<>();
public void setImageKey(String imageKey) {
if (this.imageKey != null) {
oldImageList.add(this.imageKey);
}
this.imageKey = imageKey;
if (imageKey != null) {
oldImageList.remove(imageKey);
}
}
public void purgeOldImages(BlobstoreService blobService) {
Set<BlobKey> toDelete = new HashSet<>();
for (String s : oldImageList) {
toDelete.add(new BlobKey(s));
oldImageList.remove(s);
}
blobService.delete(toDelete.toArray(new BlobKey[0]));
}
// ... Other methods ...
}
I think this is neither "beautiful" nor correct code.
Is there the right way to do this?
I'm using GWT 2.4 by using the MVP pattern
I have this situation:
In one presenter (let's call it a TreePresenter since it shows a tree) i have the following code:
display.getSelectedItem().addSelectionHandler(new SelectionHandler<TreeItem>() {
#Override
public void onSelection(SelectionEvent<TreeItem> event) {
....
evtBus.fireEvent(new SelectCategoryEvent(item.getText()));
.....
}
});
And in my AppController class I have this situation:
eventBus.addHandler(SelectCategoryEvent.TYPE, new SelectCategoryEventHandler() {
#Override
public void onSelectCategory(SelectCategoryEvent event) {
saveHistoryEvent(event);
}
});
When i select one itm in the three the event is correctly fired by the instruction
evtBus.fireEvent(new SelectCategoryEvent(item.getText()));
But in my AppController the event is not propagated and I can't handle it in the code
eventBus.addHandler(SelectCategoryEvent.TYPE, new SelectCategoryEventHandler() {
#Override
public void onSelectCategory(SelectCategoryEvent event) {
saveHistoryEvent(event);
}
});
Can anybody tell me the reason?
Thank
Angelo
I'll give you some detail; I built a class for my own history management; I built this class:
public class GwtHistoryEventsMgr {
private Map<String, List<GwtEvent>> tknEvts;
private HandlerManager eventBus;
public GwtHistoryEventsMgr(HandlerManager evtBus){
tknEvts = new HashMap<String, List<GwtEvent>>();
this.eventBus = evtBus;
}
private void saveHistoryEvent( GwtEvent event ){
List<GwtEvent> eventi = null;
if( tknEvts.containsKey(History.getToken()) ){
eventi = tknEvts.get(History.getToken());
}else{
eventi = new ArrayList<GwtEvent>();
}
eventi.add(event);
tknEvts.put(History.getToken(), eventi);
}
public void addEvtHandlers(){
//Aggiungo gli handler
eventBus.addHandler(CustomEvent.TYPE, new CustomEventHandler() {
#Override
public void onEvent(CustomEvent event) {
saveHistoryEvent(event);
}
});
}
public List<GwtEvent> getTokenTransWidgetEvents(String token){
if( tknEvts.containsKey(token) ){
return tknEvts.remove(token);
}else{
return null;
}
}
}
Then in my AppController constructor I wrote this code:
public AppController(HandlerManager eventBus, StandardDispatchAsync dispatcher){
super(null);
this.eventBus = eventBus;
this.dispatcher = dispatcher;
//Gestione history
histMgr = new GwtHistoryEventsMgr(eventBus);
histMgr.addEvtHandlers();
}
This means that I should be pretty sure that the AppController registers itself to the events I want (note: I strongly reduced the code...but all the code is in the way I wrote)
Then, since I use client-side reflection, I did, where I use the client side reflection, this code (after that all widgets have been initialized):
#SuppressWarnings("rawtypes")
private void generateHistoricalEvents(){
List<GwtEvent> eventi = hisMgr.getTokenTransWidgetEvents(History.getToken());
//Ci sono eventi....vuol dire back del browser cliccato
if( eventi != null ){
for (GwtEvent gwtEvent : eventi) {
this.eventBus.fireEvent(gwtEvent);
}
}
}
According to me it's all OK; may you tell me where the problem is?
Thank you
Angelo
I'm trying to select first root node of the CellTree after asynchronous data fetching from server. Here is my code:
public class MyTreeModel implements TreeViewModel{
private MyServiceAsync myService = GWT.create(MyService.class);
public <T> NodeInfo<?> getNodeInfo(T value) {
Cell<MyTO> cell = new AbstractCell<MyTO>() {
#Override
public void render(Context context, MyTO value, SafeHtmlBuilder sb) {
//rendering node...
}
};
return new DefaultNodeInfo<MyTO>(value instanceof MyTO ?
createBranchDataProvider((MyTO)value) : //fetching child nodes
cerateRootDataProvider(), cell); //fetching root nodes
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyTO) {
MyTO to = (MyTO)value;
return to.isLeafNode();
}
return false;
}
private AbstractDataProvider<MyTO> cerateRootDataProvider() {
AsyncDataProvider<MyTO> dataProvider = new AsyncDataProvider<MyTO>() {
#Override
protected void onRangeChanged(HasData<MyTO> display) {
AsyncCallback<List<MyTO>> callback = new AsyncCallback<List<MyTO>>() {
#Override
public void onSuccess(List<MyTO> result) {
updateRowCount(result.size(), true);
updateRowData(0, result);
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
};
myService.getRootNodes(callback);
}
};
return dataProvider;
}
private AbstractDataProvider<MyTO> createBranchDataProvider(final MyTO value) {
AsyncDataProvider<MyTO> dataProvider = new AsyncDataProvider<MyTO>() {
#Override
protected void onRangeChanged(HasData<MyTO> display) {
AsyncCallback<List<MyTO>> callback = new AsyncCallback<List<MyTO>>() {
#Override
public void onSuccess(List<MyTO> result) {
updateRowCount(result.size(), true);
updateRowData(0, result);
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
};
myService.getChildNodes(value.getId(), callback);
}
};
return dataProvider;
}
For data which stored on client side solution looks pretty simple: we could just call something like
tree.getRootTreeNode().setChildOpen(0, true, true);
but if we want to fetch data asynchronously we will catch IndexOutOfBoundsException in case we try the same immediately after creation tree, because data is not obtained yet. How can I know when onSuccess() event will be fired in cerateRootDataProvider()? Or I could use another solution? Please suggest something.
I see two options for calling
tree.getRootTreeNode().setChildOpen(0, true, true);
in a right moment:
Direct dependency on presenter: add a constructor for your MyTreeModel object. Send corresponding presenter as a parameter. Add and call something like presenter.onDataLoadingComplete() at the end of onSuccess method.
Custom event: create your custom event. Fire it at the end of onSuccess method. Subscribe to it in some place where you can call access `tree``object. Call necessary code.
The problem is that ContactDocuments removed with editor.getList().remove(index) are still passed for validation making it impossible to save edited Contact. For example requestContext.save() will fail with onConstraintViolation if I add new ContactDocument with editor.getList().add() then immideatly remove it because of #NotNull violation on some fields.
I have simple relation: Contact has many ContactDocuments. I'm trying to edit Contact with request factory and editors framework. ContactDocuments is annotated with JSR-303 for basic validation.
My ListEditor for ContactDocuments collection:
public class ContactDocumentListEditor extends Composite implements IsEditor<ListEditor<ContactDocumentProxy, ContactDocumentEditor>>, HasRequestContext<List<ContactDocumentProxy>> {
private RequestContext requestContext;
interface ViewUiBinder extends UiBinder<Widget, ContactDocumentListEditor> {}
private final static ViewUiBinder uiBinder = GWT.create(ViewUiBinder.class);
private final ListEditor<ContactDocumentProxy, ContactDocumentEditor> editor = ListEditor.of(new DocumentEditorSource());
#UiField
VerticalPanel container;
#UiField
Button addContactDocumentButton;
#UiHandler("addContactDocumentButton")
void addContactDocumentButtonClick(ClickEvent event) {
addNewContactDocument();
}
public ContactDocumentListEditor() {
initWidget(uiBinder.createAndBindUi(this));
}
private class DocumentEditorSource extends EditorSource<ContactDocumentEditor> {
#Override
public ContactDocumentEditor create(final int index) {
final ContactDocumentEditor documentEditor = new ContactDocumentEditor();
documentEditor.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
removeDocumentEditor(index);
}
});
container.insert(documentEditor, index);
return documentEditor;
}
#Override
public void dispose(ContactDocumentEditor subEditor) {
container.remove(subEditor);
}
#Override
public void setIndex(ContactDocumentEditor subEditor, int index) {
container.insert(subEditor, index);
}
}
#Override
public ListEditor<ContactDocumentProxy, ContactDocumentEditor> asEditor() {
return editor;
}
#Override
public void setRequestContext(RequestContext ctx) {
requestContext = ctx;
}
private void addNewContactDocument() {
ContactDocumentProxy newDocument = requestContext.create(ContactDocumentProxy.class);
editor.getList().add(newDocument);
}
private void removeDocumentEditor(int index) {
editor.getList().remove(index);
}
}
My Presenter to drive it:
final ContactRequestContext contactRequestContext = contactRequestContextProvider.get();
contactRequestContext.save(contact);
driver.edit(contact, contactRequestContext);
driver.flush().fire(new Receiver<Void>() {
#Override
public void onSuccess(Void response) {
// success
}
#Override
public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
for (ConstraintViolation violation : violations) {
logger.info("Contact constraint violation: " + violation.getPropertyPath() + " = " + violation.getMessage());
}
driver.setConstraintViolations(violations);
}
});
How can I fix it?
Either defer the creation of the real proxy by using a double (create a class that implements your proxy interface, create an instance of that class rather than an RF proxy, and on or after flush create the real proxies to replace the fake ones; that way you'll never create a proxy that won't be sent to the server).
Or if possible only validate your objects as part of others, never individually (don't validate a ContactDocument on its own, only as part of validating a Contact, using #Valid on the property containing the ContactDocuments): this can be accomplished using a ServiceLayerDecorator overriding the validate method, possibly combined with a #GroupSequence on either or both classes and the ServiceLayerDecorator validating using a specific group other than Default.
Related issue: Cannot remove proxy from RequestContext editing context