Reputation: 7285
Fist it's not the wrong object but it is not the correct reference as I have diagnosed the problem.
I have an editor where I am editing an object that contains another list editor of more beans.
I have a TagCollectionProxy and this contains a list of TagProxy objects. The problem occurs when I save the TagCollectionProxy with the attached List objects. My entity locator is looking up the object in the database and setting the value but then I don't have a reference to that object. This is new and I am not sure why this is happening now.
This debug message when I am calling save on the object. I have put a message in the Tag class on setValue and you can see that I am setting the correct value on the domain entity. The id matches. The problem comes in the next message for my save(TagCollection) in my dao class. I am iterating over the entities and printing the value but the value has not changed for that entity.
2014-08-29T15:34:15.912-0500|INFO: Setting value Tag2asdfasdfasdfasdf for id 294 Reference cpbx.rialto.dms.Tag@1135696
2014-08-29T15:34:15.944-0500|INFO: Tag name => asdfasdf Tag value => Tag2 id => 294 ref => cpbx.rialto.dms.Tag@1893889
2014-08-29T15:34:15.944-0500|INFO: Id of tag is 294
Thanks in advance for any help, I am loosing hair by the minute.
This is my Activity that saves the object. Notice that I am saving the TagCollectionProxy that contains the list of Tags.
public class TagCollectionEditActivity extends AbstractActivity<TagCollectionEditView> implements
TagCollectionEditView.Presenter {
private Receiver<Long> saveReceiver = new Receiver<Long>() {
@Override
public void onSuccess(Long response) {
logger.info("saved tagCollection with id of " + response);
clientFactory.getPlaceController().goTo(new TagCollectionsPlace());
}
public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
for (ConstraintViolation<?> violation : violations) {
logger.warning(violation.getMessage());
}
getDisplay().getEditorDriver().setConstraintViolations(violations);
}
public void onFailure(ServerFailure failure) {
logger.log(Level.SEVERE, "Failed to save tag collection " + failure.getMessage());
getDisplay().showError(failure);
}
};
private static final Logger logger = Logger.getLogger(TagCollectionEditActivity.class.getName());
private IClientFactory clientFactory;
private TagCollectionDaoRequest editContext = null;
public TagCollectionEditActivity(IClientFactory clientFactory) {
super(new TagCollectionEditView(clientFactory.getResources(), clientFactory.getEventBus()));
this.clientFactory = clientFactory;
}
@Override
protected void bindToView() {
getDisplay().setPresenter(this);
final TagCollectionEditPlace place = (TagCollectionEditPlace) getPlace();
Long tagCollectionId = place.getTagCollectionId();
if (tagCollectionId == null) {
createTagCollection();
} else {
findCollection(tagCollectionId);
}
}
private void findCollection(Long tagCollectionId) {
clientFactory.tagCollectionRequest().find(tagCollectionId).with(getEntityProperties())
.fire(new Receiver<TagCollectionProxy>() {
@Override
public void onSuccess(TagCollectionProxy tagCollection) {
editContext = clientFactory.tagCollectionRequest();
editContext.save(tagCollection).with(getDisplay().getEditorDriver().getPaths()).to(saveReceiver);
getDisplay().getEditorDriver().edit(tagCollection, editContext);
GWT.log("Context is " + editContext.hashCode());
}
});
}
private void createTagCollection() {
editContext = clientFactory.tagCollectionRequest();
TagCollectionProxy tagCollection = editContext.create(TagCollectionProxy.class);
editContext.save(tagCollection).with(getDisplay().getEditorDriver().getPaths()).to(saveReceiver);
tagCollection.setTags(new ArrayList<TagProxy>());
getDisplay().getEditorDriver().edit(tagCollection, editContext);
}
@Override
public void onSave() {
RequestContext context = getDisplay().getEditorDriver().flush();
context.fire();
}
public String[] getEntityProperties() {
return new String[] { "tags", "deviceFamily" };
}
@Override
public void onCancel() {
clientFactory.getPlaceController().goTo(new TagCollectionsPlace());
}
}
Here is my tag collection proxy where I define my DomainEntityLocator
@ProxyFor(value = TagCollection.class, locator = DomainEntityLocator.class)
public interface TagCollectionProxy extends DomainEntityProxy {
public List<TagProxy> getTags();
public void setTags(List<TagProxy> tags);
public DeviceFamilyProxy getDeviceFamily();
public void setDeviceFamily(DeviceFamilyProxy deviceFamily);
}
Here is my locator that uses the database to look up objects using a JPA entityManager.
public class DomainEntityLocator extends Locator<DomainEntity, Long> {
private static EntityManager em = null;
static {
Context initCtx;
try {
initCtx = new InitialContext();
// perform JNDI lookup to obtain container-managed entity manager
em = (javax.persistence.EntityManager) initCtx.lookup("java:comp/env/persistence/DomainEntityManager");
} catch (NamingException e) {
throw new RuntimeException("Unable to get the domain entity manager");
}
}
public DomainEntityLocator() {
}
@Override
public DomainEntity create(Class<? extends DomainEntity> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public DomainEntity find(Class<? extends DomainEntity> clazz, Long id) {
return em.find(clazz, id);
}
@Override
public Class<DomainEntity> getDomainType() {
return DomainEntity.class;
}
@Override
public Long getId(DomainEntity domainObject) {
return domainObject.getId();
}
@Override
public Class<Long> getIdType() {
return Long.class;
}
@Override
public Object getVersion(DomainEntity domainObject) {
return 0;
}
}
Update
After some investigation, I realized that the JPA on the server side was to blame for my issue. I have come to the conclusion that the DomainEntityLocator is fetching the entity but is not wrapping that in a transaction. So the DomainEntityLocator#find() method will retrieve the entity and the ServiceLayer will modify the value on the entity. The problem I have is that the find method is not wrapped in a transaction and since the entity manager I am retrieving is not managed the entity manager is not flushing the changes. Hence I do not see the updates on the value.
On Topic Question #2
Is there a common pattern to adding transaction management to the Entity Locator so that it will persist the object after request factory calls the setters. I used to do this with a simple servlet filter but the project I am working on requires java ee for maintaining this kind of functionality. I can't make the DomainEntityLocator a Stateless bean because of the way the ServiceLayer will look it up. Would it be best to extend the RequestFactoryServlet and wrap the doPost in a tranasction? This seems like the most logical idea. Any suggestions?
Thanks again Thomas
Upvotes: 0
Views: 126
Reputation: 7285
Here is what I did to solve the problem with EJB and RequestFactory.
After figuring out that the RequestFactory method was not being wrapped in a transaction for the entity manager I had two choices that came to mind. One was I could add a UserTransaction to the Entity locator's find and create methods but I decided to wrap the entire request in a transaction and this works well for my case since my request factory methods should pretty much always be in a transaction.
I extended the requestfactory servlet and added a UserTransaction to the doPost.
@WebServlet("/rf/secure/dao")
public class DmsRequestFactoryServlet extends RequestFactoryServlet {
private static final long serialVersionUID = -521670028586842819L;
private static final Logger logger = Logger.getLogger(DmsRequestFactoryServlet.class.getName());
@Resource
private UserTransaction tx;
public static class SimpleExceptionHandler implements ExceptionHandler {
@Override
public ServerFailure createServerFailure(Throwable throwable) {
logger.log(Level.SEVERE, "Unable to complete request", throwable);
return new ServerFailure("Server Error: " + (throwable == null ? null : throwable.getMessage()), throwable
.getClass().getName(), ExceptionUtils.getStackTrace(throwable), true);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
try {
tx.begin();
super.doPost(request, response);
tx.commit();
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed request ", e);
}
}
public DmsRequestFactoryServlet() {
super(new SimpleExceptionHandler());
}
}
Upvotes: 0
Reputation: 64561
On the server side, RequestFactory will load each object by its ID, using your locator, so the TagCollection
will be loaded (with its list of Tag
) and each Tag
will be loaded too using the DomainEntityLocator
(assuming that's how they're declared in their @ProxyFor
).
The setters will be called on the objects returned by the locator, not the ones from the TagCollection
's getTags
list.
You have to make sure they are the same instances.
With JPA, that means using the "open session in view" (aka "session per request") pattern. A JPA session uses a cache, so sharing the same session across the whole HTTP request will make it sure that the same instances of Tag
are used whenever loaded from the TagCollection
or directly by their ID.
Upvotes: 2