How do I get a selector from a sling Resource - aem

I have two Sling Models:
#Model(adaptables = {SlingHttpServletRequest.class, Resource.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class VideoGridItem {
#SlingObject
private Resource resource;
#SlingObject
private SlingHttpServletRequest slingHttpServletRequest;
#PostConstruct
public void initVideoGridItem() {
String[] selectors = slingHttpServletRequest.getRequestPathInfo().getSelectors();
insideGrid = selectors == null || selectors.length == 0 ? false : Arrays.stream(selectors).anyMatch("grid"::equals);
url = URLUtils.addHTMLIfPage(resource.getResourceResolver(), linkUrl);
}
}
and
#Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class VideoListing {
private List<String> videoResourcePaths;
#PostConstruct
final void init() {
}
}
I call the VideoGridItem component (technically the resource which references the model) from the video-listing component using HTL:
<sly data-sly-list.videoResourcePath="${model.videoResourcePaths}">
<sly data-sly-resource="${videoResourcePath # wcmmode='disabled', addSelectors='grid'}" data-sly-unwrap="true"></sly>
</sly>
Now, when I debug the code, inside initVideoGridItem, slingHttpServletRequest is null. Fair enough, this resource isn't being directly requested, but I still need to be able to access the selector "grid". Is there a way I can do this from the VideoGridItem.resource?

Use the #org.apache.sling.models.annotations.injectorspecific.Self annotation instead of #SlingObject for the resource and slingHttpServletRequest fields. The self injector will inject the adaptable object itself (i.e. the Sling request) as well as objects that are adaptable from the same (the resource).
Assuming you always need the selector value for your component to function, you should remove Resource.class from the list of adaptable types in your #Model annotation. This will prevent your model class from being adapted from a Resource object, which will cause the slingHttpServletRequest field to be null and your #PostConstruct method will throw a NullPointerException.

Sorry I didn't reply sooner, but I found my defect and moved on. The issue was that I was creating a VideoGridItem by adapting it from a resource in another place in the code and of course Sling couldn't inject a request. I am now accounting for the null request and my code is working well. Thanks for your answer!

Related

How to know if a class is an #Entity (javax.persistence.Entity)?

How can I know if a class is annotated with javax.persistence.Entity?
Person (Entity)
#Entity
#Table(name = "t_person")
public class Person {
...
}
PersonManager
#Stateless
public class PersonManager {
#PersistenceContext
protected EntityManager em;
public Person findById(int id) {
Person person = this.em.find(Person.class, id);
return person;
}
I try to do it with instance of as the following
#Inject
PersonManager manager;
Object o = manager.findById(1);
o instanceof Entity // false
however the result is false, shouldn't it be true?
While the existing answers provide a (somehow) working solution, some things should be noted:
Using an approach based on Reflection implies (a) Performance Overhead and (b) Security Restrictions (see Drawbacks of Reflection).
Using an ORM-specific (here: Hibernate) approach risks portability of the code towards other execution environments, i.e., application containers or other customer-related settings.
Luckily, there is a third JPA-only way of detecting whether a certain Java class (type) is a (managed) #Entity. This approach makes use of standardized access to the javax.persistence.metamodel.MetaModel. With it you get the method
Set < EntityType > getEntities();
It only lists types annotated with #Entity AND which are detected by the current instance of EntityManager you use. With every object of EntityType it is possible to call
Class< ? > getJavaType();
For demonstration purposes, I quickly wrote a method which requires an instance of EntityManager (here: em), either injected or created ad-hoc:
private boolean isEntity(Class<?> clazz) {
boolean foundEntity = false;
Set<EntityType<?>> entities = em.getMetamodel().getEntities();
for(EntityType<?> entityType :entities) {
Class<?> entityClass = entityType.getJavaType();
if(entityClass.equals(clazz)) {
foundEntity = true;
}
}
return foundEntity;
}
You can provide such a method (either public or protected) in a central place (such as a Service class) for easy re-use by your application components. The above example shall just give a direction of what to look for aiming at a pure JPA approach.
For reference see sections 5.1.1 (page 218) and 5.1.2 (page 219f) of the JPA 2.1 specification.
Hope it helps.
If the statement
sessionFactory.getClassMetadata( HibernateProxyHelper.getClassWithoutInitializingProxy( Person.class ) ) != null;
is true, than it is an entity.
#NiVer's answer is valid. But, if you don't have a session or sessionFactory at that point you could use Reflection. Something like:
o.getClass().getAnnotation(Entity.class) != null;

What is the right way to get Page object via Sling Model annotation

I have a property with the path to required page in a content file
...
<some_block
...
sling:resourceType="some_path_to_some_component"
somePage="some_path_to_page"
.../>
...
suitable HTL component some-component.html
<div data-sly-use.some_model="org.example.SomeModel">
...
</div>
and model class SomeModel.java
package org.example;
...
import com.day.cq.wcm.api.Page;
...
#Model(adaptables = { SlingHttpServletRequest.class, Resource.class },
defaultInjectionStrategy = DefaultInjectionStrategy.REQUIRED)
public class RelatedContentBlock {
#ValueMapValue
private Page somePage;
...
}
I easily can get the required Page object using #Inject and #Via annotations, but why can't I grab it with the #ValueMapValue annotation? I tried to use all the possible variants including via attribute and so on. And yes, I can get it from the pageManager, but what's wrong with #ValueMapValue?
Thanks in advance!
The documentation that you linked of the #ValueMapValue annotation has the answer you are looking for:
Annotation to be used on either methods, fields or constructor parameter to let Sling Models inject a value from the ValueMap of the current resource.
The important part is:
inject a value from the ValueMap
A Page is not a ValueMap. Therefore, this annotation can not be used to inject a page.
This annotation is mainly used to inject page properties. Because page properties (or resource properties for that matter) are stored in a ValueMap. That is why you can use the #ValueMapValue annotation to inject the jcr:title of a page:
#ValueMapValue(name = "jcr:title")
private String title;
This is equivalent to (pseudo code):
final ValueMap pageProperites = Page.getProperties();
final String title = pageProperties.get("jcr:title", "" /* default */);

Abstract components via org.osgi.service.component annotations

I am migrating from org.apache.felix.scr annotations to org.osgi.service.component annotations. I have a set of Components that inherit from a common abstract class. In the felix case, I can use a #Component annotation with the option componentAbstract=true on the super class, and then use #Reference annotation in the super class. I cannot find how to migrate this to osgi annotations.
Is it possible to use Component annotations in a super class of a Component? And if so, what is then the appropriate way to handle the properties and metatype generation?
So, what I am looking for, is something like this
/* No component definition should be generated for the parent, as it is
abstract and cannot be instantiated */
#Component(property="parent.property=parentValue")
public abstract class Parent {
#Reference
protected Service aService;
protected activate(Map<String,Object> props) {
System.out.println("I have my parent property: "+props.get("parent.property"));
#Override
public abstract void doSomething();
}
/* For this class, the proper Component definition should be generated, also
including the information coming from the annotations in the parent */
#Component(property="child.property=childValue")
public class Child extends Parent {
#Activate
public activate(Map<String,Object> props) {
super.activate(props);
System.out.println("I have my child property: "+props.get("child.property"));
}
public void doSomething() {
aService.doSomething();
}
}
By default BND will not process DS annotations in parent classes. You can change that with -dsannotations-options: inherit but please see http://enroute.osgi.org/faq/ds-inheritance.html why you shouldn't!
2021-02-23 UPDATE: It seems like the page mentioned above is no longer available. I don't know if it was moved elsewhere or simply removed but its content (in Markdown format) is still available on GitHub: https://github.com/osgi/osgi.enroute.site/blob/pre-R7/_faq/ds-inheritance.md

Spring List of interface type data binding - how?

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.

JAX-RS #PathParam to inject in class member variable?

I want to do something like this:
#Stateless
#Path("/sensors/{sensorid}/version")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
#Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
public class SensorVersionRestView extends VersionRestView{
#PathParam("sensorid")
private String sensorid;
#GET
#Path("count")
// so the complete path is i.e.
// domain.com/rs/sensors/111211/version/count
public void getCount() {
// do something with the sensorId....
}
}
But the only thing I get is null on runtime (I use Glassfish v3 with Jersey). The compiler and eclipse never mentions a problem with the #PathParam at the member class variable.
What's wrong with my construct?
The main problem is, why I doesn't want to use the whole path on each method in this class, that there exists another class which handles some rest operations on the sensor layer (deomain.com/rs/sensors/count i.e.)
I believe you need to change it to this:
#Stateless
#Path("/sensors/{sensorid}/version")
public class SensorVersionRestView extends VersionRestView {
#GET
#Path("count")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
#Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
// domain.com/rs/sensors/111211/version/count
public void getCount(#PathParam("sensorid") String sensorid) {
// do something with the sensorId....
}
}
Because injection occurs at object
creation time, use of this annotation
on resource class fields and bean
properties is only supported for the
default per-request resource class
lifecycle. Resource classes using
other lifecycles should only use this
annotation on resource method
parameters. - JSR-311 Javadocs
You should be able to annotate fields with #PathParam as long as the resource class lifecyle is per-request. By default the life-cycle of root resource classes is per-request.
EDIT: I don't think you can achieve this using EJBs. If you remove the #Stateless annotation, it should work.