unic / neba

Lightning fast and simple content mapping for Apache Sling and Adobe AEM
https://neba.io
Apache License 2.0
55 stars 14 forks source link

Preload models in the controller #145

Closed ivoleitao closed 8 years ago

ivoleitao commented 8 years ago

Hi, I'm currently developing a project with NEBA and Adobe Experience Manager. My website features a lot of transactions under the hood (it's a selfcare) and I have a user context stored in session. Some of the information contained in the user context is needed to fill some component models before they are used in sightly. Having a Spring background and looking at the example provided in (https://github.com/unic/publication-neba-adaptto-2015/blob/master/src/main/java/to/adapt/neba/impl/contact/ContactController.java) I though that I can use something like this:

@ModelAttribute
public Contact prepareFormModel(@ResourceParam Contact contact) {
    return contact;
}

The objective is to preload the model with some data before it's used at the sightly level. I'm currently trying to use this with the latest snapshot version of 4.0, AEM 6.1 and with a component that is added to the page.

I found in the documentation http://neba.io/userguide.html#api_PreMapping-and-PostMapping the @PostMapping annotation which I'm currently using (and it works) but somehow and taking in consideration my background does not feel completely right since the model is normally preloaded via the controller

I've also found that I can intercept the models via ResourceModelPostProcessor namely the processBeforeMapping method but I receive in here calls for all the lifecycle events of all the models. Also once again is detached from the controller...

<T> T processBeforeMapping(T resourceModel,
                         org.apache.sling.api.resource.Resource resource,
                         org.springframework.beans.factory.BeanFactory factory)

Is there an alterantive way of accomplishing this ? Or since there is a single page like orientation in the way neba is supposed to be used most of the data is normally fetched from ajax calls and this not a typically a need and therefore not addressed ?

DaniRey commented 8 years ago

Hi Ivo

We generally try to avoid context information in models. But depending on the exact situation, it might be the best solution. Given the limitations of sightly, chances are it indeed is the best solution.

Could you share some more details about such a model? Ideally a complete implementation using @PostMapping annotation? I guess it is easier to discuss this with a concrete example.

I could think of two additional options, to the ones you proposed. Inject a Spring Service (you might use gemini blueprint to generate a Spring Proxy for an OSGi Service, if needed) which can provide the needed context. Then provide information via Model from the Service using getter-methods. Use an AnnotatedFieldMapper and create a custom annotation, which you might use to provide the contextual information to your model.

Cheers, Daniel

ivoleitao commented 8 years ago

Hello DaniRey first of all tnks for your response.

Well we are starting with the implementation so we are exploring the alternatives right now. However I can give you an example based on a previous project where we needed this kind of support.

The use case is relatively simple, imagine a user that has a number of itens in his/her portfolio. As a first step after login e needs to select one of this itens and at that moment the portal makes a call to a backend service where a lot of information is fetched and stored in session (this could be httpsession but preferably something more robust like a redis or an hazelcast instance). Since this information takes some time to obtain we cache it in our session implementation avoiding costly calls to the backend server at a later point in time. After selection he / she can make operations specific to that portfolio item and most of that operations need contextual information that is obtained with the previous call.

Prototyping a little what I've said previously and using the current support in NEBA:


@ResourceModel(types = "myapp/components/mycomponent")
public class MyComponentModel {
                @Inject
                private Context context;

                @Path("jcr:title")
                private String text;

                @Unmapped
                private String myContextProperty;

                public String getText() {
                               return text;
                }

                public void setText(String text) {
                               this.text = text;
                }

                public String getContextProperty() {
                               return contextproperty;
                }

                public void setContextProperty(String contextPrioperty) {
                               this.contextProperty = contextProperty;
                }

                @PostMapping
                public void postMapping() {
                               this.contextProperty = context.getContextProperty();
                }
…
}

This is something quite usual in the type of applications that I do, the difference being that this information in a pure spring implementation is injected in some method in the controller following more closely the mvc principle.

Best Regards, Ivo Leitão

DaniRey commented 8 years ago

Hi Ivo

I see several solutions. We generally avoid the usage of an explicit controller, if not needed. Instead we use either neba:defineObjects/ for JSP (http://neba.io/userguide.html#nebaDefineObjects) or neba.js in combination with Sightly's data-sly-use (https://github.com/unic/neba/blob/develop/delivery-aem/src/main/resources/vault-work/jcr_root/apps/neba/neba.js). Both times we use standard sling to resolve the current resource and delegate its rendering to the correct view. Then the view requests the best matching model from NEBA. As you would not have an explicit controller in this case, there wouldn't be a solution to enhance your model from the controller. In this case I would go with the Code you showed, but change it to lazy loading instead of PostMapping.

` @ResourceModel(types = "myapp/components/mycomponent") public class MyComponentModel { @Inject private Context context;

            @Path("jcr:title")
            private String text;

            @Unmapped
            private String myContextProperty;

            public String getText() {
                           return text;
            }

            public void setText(String text) {
                           this.text = text;
            }

            public String getContextProperty() {
                           if (this.contextProperty == null) {
                                     this.myContextProperty= context.getContextProperty();
                           }
                           return this.myContextProperty;
            }

            public void setContextProperty(String contextPrioperty) {
                           this.myContextProperty= contextProperty;
            }

… }`

This way you don't execute method on the context, as long as you don't have to.

Another option would be to return a ModelAndView object from your Spring controller. This could than contain the NEBA model provided via @ResourceParam and additional data provided from your controller. But I'm not sure if the view resolution for sightly works. @olaf-otto you worked on this, as far as I know.

We mostly used Spring controllers to provide JSON responses, and therefore used @ResponseBodyand gson, instead of returning a view name or a ModelAndView object.

Or you could explicitly call setter methods on the NEBA model from your controller.

Or, if you don't need to combine the NEBA model data and your context data, you could use two separate data-sly-use statements in your sightly view, one to retrieve the NEBA model via neba.js, the other to retrieve your context object.

What do you think about those options?

Best Regards, Dani

ivoleitao commented 8 years ago

Once again Dani tnks a lot for your input. I'm going to try the approaches that you suggested. Also I think I have now a clearer picture of the uses cases that NEBA addresses and with that in mind I think I can adapt our solution to this model.

Best Regards, Ivo Leitão

olaf-otto commented 8 years ago

Hi all,

Interesting use case! @ivoleitao, I understand you have session-scoped data and request-scoped (form) data. I also suspect you update session-scoped data during controller invocations.

I currently don't see the need to copy context data to the resource model - perhaps this stems from the fact that the resource model, form model / session data model are not seaparate?

Personally, I'd try to keep these models separate, because of potential confusion, leak into session scope, serializable constraints etc.

For instance, why not use an explicit session-scoped form model (@Scope(WebApplicationContext.SCOPE_SESSION)) to hold your session data and inject that into your @ResourceModel? This requires enabling the session scope in the application context (see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-scopes-custom).

You could then simply provide this model to your view via a getter in your resource model, keeping them separated. The session-scoped form model itself could the be populated via @PostConstruct or in the initial request of your self-service conversation.

On a different note, I have just completed work on #144, which might be very valuable for you use case, as it enables the use of Spring's form tag library in Sling and allows using Sling scripts as model views. Basically, you could use pure Spring forms once this is merged to develop.

Let us know if we can be of any more help!

Cheers, Olaf

DaniRey commented 8 years ago

Hi @ivoleitao

We're currently working on an updated version of the documentation for NEBA. We would very much appreciate your support with this, i.e. giving hints for improvements and pointers to unclear paragraphs.

Would you be willing to support us? Whats the best way to reach you? You might as well send me a direct message on twitter @danireych

Cheers, Dani

joaotab commented 8 years ago

Hi, I'm @ivoleitao's coworker, just to let you know Ivo is currently on annual leave and might take some time to reply.

We'd be glad to help.

Cheers, João

ivoleitao commented 8 years ago

Hi @DaniRey sorry for the late response, as told by @joaotab I'm currently on annual leave and I will return on the 24th. We have a big project running in AEM with NEBA and if we can contribute in some way we'd be glad to help. If you have any pre-release version of the documentation please send to ivo.leitao at gmail.com and I'll try to take a look at it. I'll forward it @joaotab and we will both send you some comments and suggestions. You can also reach me via my twitter account @ivoleitao if you wish

Cheers, Ivo

olaf-otto commented 8 years ago

Hi @ivoleitao and @joaotab! As you saw, NEBA 4 was released in the mean time, but any comments, questions, ideas you'd like to share for the docs are still more than welcome! Also note the new sample project at https://github.com/unic/neba-sample-project :-)

joaotab commented 8 years ago

Hi @olaf-otto! Thanks for the update, I'm sorry we didn't had the chance to contribute more for this version but as @ivoleitao mentioned we're still taking our first steps with NEBA. Surely we will have more thoughts as we use it on a day to day basis!

The documentation has helped very much, so far the only thing I had more trouble with was understanding the installation part with maven on AEM. I'll create a separate issue for it.

Cheers, João

olaf-otto commented 8 years ago

Thanks @joaotab, I've provided feedback in #149. Did the above comments answer your questions? If so, I'd close this issue.

Cheers, Olaf

ivoleitao commented 8 years ago

Hi Olaf ! Yes you can close this issue.

Tnks a lot for the help ! Cheers, Ivo

olaf-otto commented 8 years ago

Thank you for the interesting discussion - closing!