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

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 */);

Related

Access AEM component data as JSON within HTL (Sightly)

I have created a component the fields of which have been mapped to a sling model. To get the data of the sling as JSON I have enabled Sling exporter as shown in the code below -
#Model(adaptables = { Resource.class }, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, resourceType = "XXX/components/content/XXX")
#Exporter(name = "jackson", extensions = "json")
public interface ProofPointsModel {
#Inject
List<ProofPointsList> getProofPoint();
#Model(adaptables = { Resource.class }, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
interface ProofPointsList {
#Inject
String getProofText();
#Inject
String getProofIcon();
}
}
This works perfectly and I am able to see the JSON data when hit the end point from my browser.
I want to render this entire json object in my component's HTL. Is there an elegant way of doing this? I dont want to create an additional request to retrieve this data.
Basically I want to call this sling exporter from within my component and render the json object as is.
Thanks
Unfortunately, HTL does not allow doing these "server-side includes". The workaround is to expose the JSON in a getJson method of your model: Get .model.json as String

How do I get a selector from a sling Resource

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!

Access to a property in a specific resource via sightly

I want to access to a specific property in resource.
The main resource hat two children and the app is in the first one. I want to get a property from the second child.
Can i find something like :
${resource.parent.child[1].valueMap.title}
Thanks!
To start - note that the order of the children may not be guaranteed, unless you're using sling:OrderedFolder or some other ordered type. So trying to get the "second" child may not even make sense.
Having said that, there may some valid use cases that I am not thinking of for needing to get the second child -- as far as I can tell you will need to create a Java or JS object and make use of the Use Api.
Simple Example Java object
package apps.your_app.components.yourComponent;
import com.adobe.cq.sightly.WCMUsePojo;
import org.apache.sling.api.resource.Resource;
import java.util.Iterator;
public class Model extends WCMUsePojo {
#Override
public void activate() throws Exception {
//do some stuff if needed
}
public Resource getSecondSibling() {
Resource parent = getResource().getParent();
Resource secondSib = null;
Iterator<Resource> children = parent.listChildren();
//find the second child
for (int i = 0; i < 2; i++)
secondSib = children.next();
return secondSib;
}
}
Using it in the sightly:
<sly data-sly-use.model="Model">${model.secondSibling.propertyName}</sly>
Here's another example that i used with converting the content to JSON. The contents of the JSON as parsed Objects and each Object has Attributes.
<div data-sly-use.jsonHelper="${'com.service.helpers.JSONHelper'
#json=model.getRawJson}">
${jsonHelper.parsedJSON[item].commodityList[subitem].name}
...
</div>

kotlin data class + bean validation jsr 303

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets

How to add structural links to Jersey/Moxy/JAXB XML without altering the model

I mean "structural links" in the HATEOAS/hypermedia API sense. The more general question is how to augment the generated XML with data that depends on both the entity being marshalled, and also on the environment (in this case, at least the absolute URL).
I'm using Jersey 2.9 with Moxy 2.5 as the JAXB provider.
From this model:
package testing;
import java.util.ArrayList;
import java.util.List;
public class Planet {
private int id = 1;
private String name = "test";
private double radius = 3.0;
private String href;
private List<Moon> moons = new ArrayList<Moon>(0);
public void addMoon(Moon moon) {
moons.add(moon);
}
}
...plus Moon class
I want to get something like this XML (and the equivalent JSON):
<planet href="http://mytestserver/rest/planets/test">
<name>test</name>
<radius>3.0</radius>
<moons>
<moon href="http://mytestserver/rest/moons/moon1">
<name>moon1</name>
</moon>
<moon href="http://mytestserver/rest/moons/moon2">
<name>moon2</name>
</moon>
</moons>
</planet>
The model has no "href" field, nor can one be added. Ideally I could use UriBuilder to grab these paths straight from the resource classes.
So far I've come up with several possiblities. Can I ask you to consider which (if any) has the most legs, and then how you would work around the shortcomings of that method?
1. Augment the model with AspectJ (or Javassist).
And then use the existing declarative linking mechanisms in Jersey, all of which rely on there being a field in the model to receive the generated links. This obviously won't work if you don't have AspectJ in your build process and/or balk at exotic techniques like byte code manipulation.
2. Post-process the generated XML and JSON
For example, in a MessageBodyWriter:
ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext context = resolver.getContext(type);
Marshaller m = context.createMarshaller();
<--- here, marshall to e.g. a DOM then transform that
<--- then manipulate the JSON structures
I have absolutely no idea how to do any of that, hence the lack of code. There may be other ways to hook into the XML generation process, but as far as I can see none of Jersey's or JAXB's event handlers or interceptors actually allow you to manipulate the generated XML/JSON.
3. Use a Moxy XMLTransformationMapping
For example:
XML binding:
<java-type name="Planet" xml-customizer="testing.HrefCustomizer">
Customizer:
public class HrefCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
XMLTransformationMapping xtm = new XMLTransformationMapping();
xtm.addFieldTransformer("#href", new HrefWriter());
descriptor.addMapping(xtm);
}
}
Transformer:
public class HrefWriter implements FieldTransformer {
#Override
public Object buildFieldValue(Object instance, String fieldName,
Session session) {
return "href"; // constant value just for proof-of-concept
}
#Override
public void initialize(AbstractTransformationMapping mapping) {
// TODO Auto-generated method stub
}
}
I have two problems with this approach:
It was so hard to find any documentation on it that I wonder if it is in fact unsupported usage.
I can't see how the transformer is going to get a UriBuilder to work with. At minimum it would need the root URL of the rest service.
4. Slightly different Moxy xml-transform approach
If we decide we can't provide the transformer with any meaningful context at instantiation time, the customizer is adding no value and we can simplify the above to just this:
<java-type name="Planet">
<xml-root-element/>
<java-attributes>
<xml-transformation java-attribute="name">
<xml-write-transformer transformer-class="testing.HrefWriter" xml-path="#href"/>
</xml-transformation>
<xml-element java-attribute="name"/>
With the slight oddity that we are hanging the transformer off another field ("name", in this example).
5. ?????
Or, I'm completely barking up the wrong tree. Help!!
AspectJ approach
Synopsis
Use AspectJ to add a field to the model classes (called "href" in this example)
Add the Jersey #InjectLink annotation to that field
Jersey will then populate the field with the right URL as defined by the resource class
Specify the marshaling of the href field using an external mapping file.
You could also specify the marshaling of href by adding JAXB annotations to it via the same AspectJ intertype declaration mechanism.
Example code
These are the most informative bits. See http://lagod.id.au/blog/?p=494 for the full example.
The aspect
package testing;
import org.glassfish.jersey.linking.InjectLink;
import org.glassfish.jersey.linking.Binding;
public aspect HrefInjector {
private String Planet.href;
declare #field : * Planet.href : #InjectLink(
resource=Services.class,
style=InjectLink.Style.ABSOLUTE
) ;
private String Moon.href;
declare #field : * Moon.href : #InjectLink(
resource=Services.class,
method="moon",
bindings={#Binding(
name="moonid", value="${instance.name}"
)},
style=InjectLink.Style.ABSOLUTE
) ;
}
Model classes
POJOs with no REST-specific cruft. See Jersey + Moxy + JAXB - how to marshal XML without annotations.
package testing;
import java.util.ArrayList;
import java.util.List;
public class Planet {
private int id = 1;
private String name = "test";
private double radius = 3.0;
private List<Moon> moons = new ArrayList<Moon>(0);
public void addMoon(Moon moon) {
moons.add(moon);
}
}
package testing;
public class Moon {
private String name;
// No-arg constructor is a requirement of JAXB
public Moon() {
}
public Moon(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Resource class
This is a standard JAX-RS resource class. For demo purposes, we're just returning freshly instantiated model instances.
package testing;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/services")
#Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public class Services {
private Planet initPlanet() {
Planet p = new Planet();
p.addMoon(new Moon("moon1"));
p.addMoon(new Moon("moon2"));
return p;
}
#GET
public Planet planet () {
return initPlanet();
}
#GET #Path("/moons/{moonid}")
public Moon moon (#PathParam("moonid") String name) {
return new Moon(name);
}
}
Moxy mapping file
Note that you can choose for any given type whether or not you want to actually marshal the href field. In fact, by using multiple mapping files, you can include the href field in some representations and not in others.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="testing"
xml-mapping-metadata-complete="true"
xml-accessor-type="NONE">
<java-types>
<java-type name="Planet">
<xml-root-element/>
<java-attributes>
<xml-attribute java-attribute="href"/>
<xml-element java-attribute="name"/>
<xml-element java-attribute="radius"/>
<xml-element java-attribute="moons" name="moon">
<xml-element-wrapper name="moons"/>
</xml-element>
</java-attributes>
</java-type>
<java-type name="Moon">
<xml-root-element/>
<java-attributes>
<xml-attribute java-attribute="href"/>
<xml-element java-attribute="name"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Sample output
Ta-dah! Structural links derived automatically from the JAX-RS resource class without altering model source code. Because we're using Moxy, we also get JSON for free.
<planet href="http://localhost:8080/reststructlinks/rest/services">
<name>test</name>
<radius>3.0</radius>
<moons>
<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon1">
<name>moon1</name>
</moon>
<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon2">
<name>moon2</name>
</moon>
</moons>
</planet>