I'm trying to update each entity with an own #EntityListeners({ ModelListener.class }).
ModelListener implements #PrePersist with a method like this:
#PrePersist
protected void prePersist(final ModelBase modelBase) {
modelBase.setUpdatedBy(currentUser);}
My question is how to get the current user.
I already tried several things:
1) transport the information over EJB
#EJB
private UserModul userModul;
The userModul is always null, when prePersist is called. It seems the EJB-context is not available.
2) Fill a static ThreadLocal
static ThreadLocal<User> currentUser = new ThreadLocal<User>();
Because of the static attribute each classloader (each request in webservers) holds its own instance. I tried with a #WebFilter to fill the ThreadLocal. But it seems I have no faces context.
public void doFilter(final ServletRequest request, ...){
FacesContext.getCurrentInstance();} //always null
Also in request (com.sun.enterprise.web.pwc.connector.coyote.PwcCoyoteRequest) I don't find any information of loggedin user.
What is the most common way to save user information?
Related
in this tutorial about the EventCloud example app:
https://aspnetboilerplate.com/Pages/Documents/Articles/Developing-MultiTenant-SaaS-ASP.NET-CORE-Angular/index.html
the text states: the creation of a new entity must be done using the static method "Create" in the "Event" class (not using "new Entity(....)")
1) so my first question is: which design pattern is this? Factory? Builder? other?
[Table("AppEvents")]
public class Event : FullAuditedEntity<Guid>, IMustHaveTenant
{
......
....
...
/// <summary>
/// We don't make constructor public and forcing to create events using <see cref="Create"/> method.
/// But constructor can not be private since it's used by EntityFramework.
/// Thats why we did it protected.
/// </summary>
protected Event()
{
}
public static Event Create(int tenantId, string title, DateTime date, string description = null, int maxRegistrationCount = 0)
{
var #event = new Event
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Title = title,
Description = description,
MaxRegistrationCount = maxRegistrationCount
};
#event.SetDate(date);
#event.Registrations = new Collection<EventRegistration>();
return #event;
}
....
...
2) the second question:
than the article says...
Event Manager .... All Event operations should be executed using this class... (EventManager)
ok, the CreateAsync method call the repository insert method, is the static "Event.Create" internaly called from the repository insert method? if yes, could you indicate me the point in the abp source code?
or is it an internal matter of EntityFramework?
public class EventManager : IEventManager
{
......
....
..
public async Task CreateAsync(Event #event)
{
await _eventRepository.InsertAsync(#event);
}
Here are my answers:
1-) Event is being created with a static factory method. There are 2 ways to create an entity in Domain Driven Design.
Creating with static factory methods: It's a convenient way of creating business entities. And this method is being used in EventCloud. The only downside of this method is it's static! If your entity is holding state it's not good for testability. But there are 3 advantages of this approach;
They have names: for example Event.CreatePublicEvent(), Create.PrivateEvent()
They can cache: You can cache them in a private static HashSet or Dictionary.
They can subtype.
Creating with a constructor: If you have only one constructor then creating an object through its public constructor is the most convenient approach in Domain Driven Design. As long as you make parameterless constructor protected or private. Besides, an entity should be responsible for its own data integrity and validity so you have to set all business related public properties as private setter and you should allow them to change through public methods.
Further information, see https://www.yegor256.com/2017/11/14/static-factory-methods.html
2-) EventManager is a domain service that is used for business logic. And Event.Create() is being used in the EventAppService class. Click here to see where exactly is being executed. Even Event.Create() method consists of a single line of code but it's open for extension.
I hope that will be useful ;)
Happy coding...
I have a JPA entity, with its attibutes and several NamedQueries.
I'm trying to log some information "any time the entity is used for something", i.e.:
any time any of its NamedQueries is invoked
any time the entity is used in a Query q = em.createQuery("SELECT....FROM thisEntity a, otherEntity b WHERE.....");
any time any of its attributes is accessed
The information i want to log must include the invoker class name and invoker method, among other.
I guess this must be achieved through interceptors, but i'm not sure for example if interceptors allow me to intercept the accesses to the class throw its NamedQueries.
You could achieve that using callback methods like #PrePersist, #PostPersist, #PostLoad, #PreUpdate, #PostUpdate, #PreRemove, #PostRemove inside the entity classes.
For example
public class EntityA {
...
#PrePersist
public void beforePersist(){
//Log information
}
}
Additionally you could use that callback methods in listener classes.
public class EntityListenerA{
#PrePersist
public void beforePersist(EntityA ob) {
//Log information
}
}
#EntityListeners(EntityListenerA.class)
public class EntityA {
...
}
In your case I supose you must use the callback #PostLoad depending on the query.
Hope this help
I have a form - Workflow where there are fields like wfName, assignedUser, dueDate, turnAroundTime. etc.
It is backed by an entity Workflow with a reference to the User entity as Many-to-One.
When a change is made to the assignedUser field( it is an email address) and the form is submitted, I get a Unique-constraint violation error on the USER entity.
I am not trying to achieve this. I only want to replace the User in the Workflow entity.
The save function is performed by a Stateful session bean, with an EXTENDED persistence context.
Am I missing something here? Is this the correct way to updated information in a referenced field?
While setting the updated User I am doing
User user = workflow.getUser();
//This user has its email address changed on the screen so getting a fresh reference of the new user from the database.
user = entitManager.createQuer("from User where email_address=:email_address").setParameter("email_address", user.getEmailAddress).getSingleResult();
//This new found user is then put back into the Workflow entity.
workflow.setUser(user);
entityManager.merge(workflow);
No exception is thrown at the time these lines are executed, but later in the logs I find that it threw a
Caused by: java.sql.SQLException: ORA-00001: unique constraint (PROJ.UK_USER_ID) violated
There is no cascading configuration present in the entities.
The following is the association code for the entities-
The workflow-User relation
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", nullable = false)
#NotNull
public GwpsUser getUser() {
return user;
}
public void setUserByUserId(User user) {
this.user = user;
}
The User-Workflow Relation
#OneToMany(fetch = FetchType.LAZY, mappedBy = "User")
public Set<Workflow> getWorkflowsForUserId() {
return workflowsForUserId;
}
public void setWorkflowsForUserId(
final Set<Workflow> WorkflowsForUserId) {
this.workflowsForUserId = workflowsForUserId;
}
In the SFSB I have two methods loadWorkflow() and saveWorkflow().
#Begin(join = true)
#Transactional
public boolean loadProofData(){
//Loading the DataModel here and the conversation starts
}
If I add flushMode = FlushModeType.MANUAL inside #Begin. The saveWorkflow() method saves the data properly, only for the first time. I have to go somewhere else and then come back to this page if I want to make any further changes.
The saveWorkflow() method looks like
#SuppressWarnings("unchecked")
public boolean saveWorkflow() throws FileTransferException {
//Do some other validations
for (Workflow currentWorkflow : workflowData) {
User user = currentWorkflow.getUser();
//This user has its email address changed on the screen so getting a fresh reference of the new user from the database.
user = entitManager.createQuery("from User where email_address=:email_address").setParameter("email_address", user.getEmailAddress).getSingleResult();
//This new found user is then put back into the Workflow entity.
currentWorkflow.setUser(user);
}
//Do some other things
}
Not using the merge() method here, but still the problem persists.
Why are you calling merge? Is the workflow detached (serialized)?
If it is not detched, you should not call merge, just change the object and it should be updated.
You should have a setUser method, not setUserByUserId? Not sure how this is working, perhaps include your full code. Your get/set method might be corrupting your objects, in general it is safer to annotate fields instead of method to avoid code in your get/set method to cause odd side-effects.
Ensure you are not creating two copies of the object, it seems your merge is somehow doing this. Enable logging and include the SQL. Calling flush() directly after your merge will cause any errors to be raise immediately.
Using Spring MVC on the server, we have your basic REST API:
#Controller
#RequestMapping(value="/entities")
public class EntityController
{
//GET /entities
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public List<Entity> getEntities()
...
//GET /entities/{id}
#RequestMapping(value="/{id}", method=RequestMethod.GET)
#ResponseBody
public Entity getEntity(#PathVariable Long id)
...
//POST /entities
#RequestMapping(method=RequestMethod.POST, consumes="application/json")
#ResponseBody
public Entity createEntity(#RequestBody Entity entity)
...
//PUT /entities
#RequestMapping(method=RequestMethod.PUT, consumes="application/json")
#ResponseBody
public Entity updateEntity(#RequestBody Entity entity)
...
}
This all works just fine. Now I'm wanting to be able to create or update mutliple Entitys with one request. My first thought was to add this:
#RequestMapping(method=RequestMethod.PUT, consumes="application/json")
#ResponseBody
public List<Entity> updateEntities(#RequestBody List<T> entities)
It would have the same URL as the updateEntity but handle lists ([...]). The updateEntity would handle a single object ({...}). However, on server startup I got the following error:
java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'entityController' bean method public java.util.List<foo.bar.Entity> foo.bar.EntityController.updateEntities(java.util.List<foo.bar.Entity>) to {[/entities],methods=[PUT],params=[],headers=[],consumes=[application/json],produces=[],custom=[]}: There is already 'entityController' bean method public foo.bar.Entity foo.bar.EntityController.updateEntity(foo.bar.Entity) mapped.
So, from what I gather, Spring doesn't like two different methods with the same #RequestMapping, even though the #RequestBody is different.
This leads me to two questions. First, am I going about this the correct RESTful way? Am I in line with RESTful principles when I'm doing a PUT to the same URL and just allowing the request body to be a single object or a list? Would there be another correct way of doing this that Spring would like? (Ok, so the first question was actually three...)
The 2nd question is if there is something I can add to the #RequestMapping annotation that would differentiate the two methods enough, but keep the same REST API?
Thanks for any light you can shed on this.
#JsonIgnoreProperties(ignoreUnknown = true) for each model
I have done on this way...with Two model: User and Tree
#RequestMapping(value = "/users/testBean", method = RequestMethod.POST, consumes={"application/json","application/xml"}, produces={"application/json","application/xml"})
public #ResponseBody List<User> testBean(#RequestBody Object object) {
System.out.println("testBean called");
System.out.println(object.toString());
ObjectMapper mapper=new ObjectMapper();
User user =mapper.convertValue(object, User.class);
Tree tree =mapper.convertValue(object, Tree.class);
System.out.println("User:"+user.toString());
System.out.println("Tree:"+tree.toString());
return userService.findAll();
}
Tried to find the answer on the Web but failed. Should be simple for pro Spring Devs... so here it comes:
In few words I want to bind the List of interface type: List to the form and get the data back (possibly modified by user via form. The problem is that it doesn't work :(
my code (short version) - command/model class which is passed to the form:
public class RoomsFormSearchResultCommand extends RoomsFormSearchCommand {
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<Room>(),
FactoryUtils.instantiateFactory(Room.class));
public List<IRoom> getRoomsList() {
return roomsList;
}
public void setRoomsList(final List<IRoom> roomsList) {
this.roomsList = roomsList;
}
(...)
then in the form I use it like that (short version):
<form:form method="post" action="reserve" commandName="roomsResultsCmd">
(...)
<c:forEach var="room" items="${roomsResultsCmd.roomsList}"
varStatus="status">
<tr>
<td><form:input path="roomsList[${status.index}].roomNumber" readonly="true"/>
(...)
The form is displayed fine but after submitting it I get:
2012-01-22 21:31:55 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [wyspa] in context with path [/wyspa] threw exception [Request processing failed; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'roomsList[0]' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Illegal attempt to get property 'roomsList' threw exception; nested exception is org.springframework.beans.NullValueInNestedPathException: Invalid property 'roomsList' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Could not instantiate property type [com.wyspa.entity.IRoom] to auto-grow nested property path: java.lang.InstantiationException: com.wyspa.entity.IRoom] with root cause
org.springframework.beans.NullValueInNestedPathException: Invalid property 'roomsList' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Could not instantiate property type [com.wyspa.entity.IRoom] to auto-grow nested property path: java.lang.InstantiationException: com.wyspa.entity.IRoom
at org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:633)
at org.springframework.beans.BeanWrapperImpl.growCollectionIfNecessary(BeanWrapperImpl.java:863)
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:770)
at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperImpl.java:555)
(...)
The deal is then when I change the List to "instances" list everything works fine!
public class RoomsFormSearchResultCommand extends RoomsFormSearchCommand {
#SuppressWarnings("unchecked")
//notice that the List is now List<Room>
private List<Room> roomsList = LazyList.decorate(new ArrayList<Room>(),
FactoryUtils.instantiateFactory(Room.class));
In this case data is passed to the controller in proper way.
Since I am used to devlop on interfaces and I am pretty crazy about it I would REALLY prefer not to translate the List<IRoom> (which comes back from services) to List<Room> which seems to suit Spring. Is it possible to work with List<IRoom> in this case or Spring just doesn't support it?
//Of course Room implements IRoom - but I guess you already got that...
I would be VERY happy for any help/suggestions!
Best Regards,
Nirwan
I have exact the same problem. Changing to following won't fix the problem. It looks spring binding ignores the factory utils and tries to instantiate the null object itself:
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<IRoom>(),
FactoryUtils.instantiateFactory(Room.class));
The workaround is to set auto grow nested path off in your controller:
#InitBinder protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.setAutoGrowNestedPaths(false);
super.initBinder(request, binder);
}
The problem is you'll lose the handy nested path like user.account.address.street. You have to make sure none of user, account, addresss is null. It does cause a lot of problems. That's why I came here, see if I can find better solution.
If you don't actually need the list to auto-grow, you can store the form object in the session to avoid the nasty side effects of disabling auto-growing nested paths.
#Controller
#SessionAttributes(types = RoomsFormSearchResultCommand.class)
public final class SearchController {
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.setAutoGrowNestedPaths(false);
}
#RequestMapping(method = RequestMethod.GET)
public String showForm(final Model model) {
RoomsFormSearchResultCommand form = ... // create or load form
model.addAttribute(form);
}
#RequestMapping(method = RequestMethod.POST)
public String onSubmitUpdateCart(
#ModelAttribute final RoomsFormSearchResultCommand form,
final BindingResult result,
final SessionStatus status) {
// if result has no errors, just set status to complete
status.setComplete();
}
}
Try the following lines
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<IRoom>(),
FactoryUtils.instantiateFactory(Room.class));
don't have time to try that myself, but it would make sense.