+ All Categories
Home > Technology > CIRCUIT 2015 - Content API's For AEM Sites

CIRCUIT 2015 - Content API's For AEM Sites

Date post: 19-Aug-2015
Category:
Upload: circuit
View: 53 times
Download: 3 times
Share this document with a friend
53
CIRCUIT – An Adobe Developer Event Presented by ICF Interactive Content API’s for AEM Bryan Williams ICF Interactive
Transcript
Page 1: CIRCUIT 2015 - Content API's For AEM Sites

CIRCUIT – An Adobe Developer Event Presented by ICF Interactive

Content API’s for AEM

Bryan Williams

ICF Interactive

Page 2: CIRCUIT 2015 - Content API's For AEM Sites

[email protected]

@brywilliams

Bryan Williams ICF Interactive (10+ years) Working with CQ/AEM over 6 years AEM Developer Certified (Beta)

Page 3: CIRCUIT 2015 - Content API's For AEM Sites

Prize Question #1

?  

Page 4: CIRCUIT 2015 - Content API's For AEM Sites
Page 5: CIRCUIT 2015 - Content API's For AEM Sites

What do I mean by Content API?

•  Read only •  Controlled •  Possibly public but not necessarily •  Usually an afterthought •  Disclaimer: Not production code

Page 6: CIRCUIT 2015 - Content API's For AEM Sites
Page 7: CIRCUIT 2015 - Content API's For AEM Sites

Why not use something else?

•  Not saying you shouldn’t •  Security : Control of who can access your

data •  Encapsulation : Granular control of what data

is exposed •  Simplicity : The easier for the consumer to

understand the better •  Conformity : Maybe you need to conform to a

particular spec •  Aggregation : Some data might be coming

from outside the repository •  Versioning : Backwards compatibility

Page 8: CIRCUIT 2015 - Content API's For AEM Sites

Technologies

•  Bedrock –  https://github.com/Citytechinc/bedrock

•  CQ Component (Maven) Plugin –  https://github.com/Citytechinc/cq-component-maven-plugin

•  Sling Models –  https://sling.apache.org/documentation/bundles/models.html

•  Jackson –  https://github.com/FasterXML/jackson

•  Prosper –  https://github.com/Citytechinc/prosper

•  Groovy –  http://www.groovy-lang.org/

Page 9: CIRCUIT 2015 - Content API's For AEM Sites

What are Bedrock, CQCP and Prosper?

•  Bedrock : Open source library that contains common utilities, decorators, abstract classes, tag libraries and Javascript modules for bootstrapping and simplifying AEM projects

•  CQ Component (Maven) Plugin : Generates many of the artifacts necessary for the creation of a CQ component based on the information provided by the component’s backing Java class

•  Prosper : An integration testing library for AEM projects using Spock (a Groovy-based testing framework)

Page 10: CIRCUIT 2015 - Content API's For AEM Sites

Sling Models

•  Automates mapping of Sling objects such as resources, request, etc. to POJOs

•  Available out of the box in AEM 6

Page 11: CIRCUIT 2015 - Content API's For AEM Sites

Why Jackson?

•  Popular •  Able to produce JSON or XML •  Lots of features •  We were already using it

Page 12: CIRCUIT 2015 - Content API's For AEM Sites

Groovy

•  An object-oriented programming language for the Java platform

•  Dynamic in nature •  Reduced syntax •  Traits

Page 13: CIRCUIT 2015 - Content API's For AEM Sites

Prize Question #2

?:

Page 14: CIRCUIT 2015 - Content API's For AEM Sites

Layers of a Content API

•  Component Models : Referring to both page and content components and their corresponding backing beans

•  Servlet : Takes the request and calls the appropriate service based on selectors

•  Service : Responsible for getting the appropriate data and possibly caching

•  Query Builder : API for making queries to the repository

•  Filters : Last minute cleanup of outgoing data (externalize URLs, etc.)

Page 15: CIRCUIT 2015 - Content API's For AEM Sites

Non-Page Components

•  Children of stories •  Returned in getBody() of Story model •  Custom and OOTB components must

have backing bean •  Models identified by path convention

circuit2015/groovy/components/page/stories/article

= com.bryanw.conferences.circuit2015.groovy.components.page.stories.article.Article

Page 16: CIRCUIT 2015 - Content API's For AEM Sites

Sample Article Page

Page 17: CIRCUIT 2015 - Content API's For AEM Sites

Article @Model(adaptables = Resource, adapters = [Article, Story], defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) @Component(value = "Article", name = "article", actions = ["text:Article", "-", "edit"], group = '.hidden', path = 'groovy/components/page/stories', resourceSuperType = 'circuit2015/groovy/components/page/global', disableTargeting = true, tabs = [@Tab(title = PROPERTIES_LABEL), @Tab(title = MAIN_IMAGE_LABEL)]) @AutoInstantiate(instanceName = "article") @Slf4j("LOG") class Article extends AbstractStoryComponent implements AbstractStoryRequiredImage, SeoReadyStory { @JsonView(JacksonViews.DetailView) List<AbstractCircuit2015Component> getBody() { Optional<ComponentNode> mainParNode = getComponentNode(MAIN_PAR) if (mainParNode.present) { List<AbstractCircuit2015Component> components =

mainParNode.get().componentNodes.collect { it.resource.adaptTo(AbstractCircuit2015Component) } - null return components } [] } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 18: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryComponent abstract class AbstractStoryComponent extends AbstractCircuit2015Component implements Story { protected static final String MAIN_PAR = "mainpar" @Inject private PageManager pageManager @Inject @Named('jcr:title') @DialogField(fieldLabel = "Title", name = './jcr:title', required = true, tab = PROPERTIES_INDEX) @TextField String title @Inject @Named('jcr:description') @DialogField(fieldLabel = "Description", name = './jcr:description', tab = PROPERTIES_INDEX) @TextArea @JsonView(JacksonViews.DetailView) String description @Inject @DialogField(fieldLabel = "Published date", name = './publishedDate', required = true, tab = PROPERTIES_INDEX) @DateTime Date publishedDate @Inject @DialogField(fieldLabel = "Author Bio Path", name = "./authorBioPath", tab = PROPERTIES_INDEX) @PathField @JsonIgnore String authorBioPath @JsonView(JacksonViews.DetailView) Link getStoryLink() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).link } @JsonView(JacksonViews.ListView) String getStoryHref() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).href } @JsonView(JacksonViews.DetailView) Bio getAuthorBio() { pageManager.getPage(authorBioPath)?.getContentResource()?.adaptTo(Bio) } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 19: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryComponent abstract class AbstractStoryComponent extends AbstractCircuit2015Component implements Story { protected static final String MAIN_PAR = "mainpar" @Inject private PageManager pageManager @Inject @Named('jcr:title') @DialogField(fieldLabel = "Title", name = './jcr:title', required = true,

tab = PROPERTIES_INDEX) @TextField String title @Inject @Named('jcr:description') @DialogField(fieldLabel = "Description", name = './jcr:description', tab = PROPERTIES_INDEX) @TextArea @JsonView(JacksonViews.DetailView) String description @Inject @DialogField(fieldLabel = "Published date", name = './publishedDate', required = true,

tab = PROPERTIES_INDEX) @DateTime Date publishedDate

...

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 20: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryComponent …

@Inject @DialogField(fieldLabel = "Author Bio Path", name = "./authorBioPath",

tab = PROPERTIES_INDEX) @PathField @JsonIgnore String authorBioPath @JsonView(JacksonViews.DetailView) Link getStoryLink() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).link } @JsonView(JacksonViews.ListView) String getStoryHref() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).href } @JsonView(JacksonViews.DetailView) Bio getAuthorBio() { pageManager.getPage(authorBioPath)?.getContentResource()?.adaptTo(Bio) } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 21: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryComponent abstract class AbstractStoryComponent extends AbstractCircuit2015Component implements Story { protected static final String MAIN_PAR = "mainpar" @Inject private PageManager pageManager @Inject @Named('jcr:title') @DialogField(fieldLabel = "Title", name = './jcr:title', required = true, tab = PROPERTIES_INDEX) @TextField String title @Inject @Named('jcr:description') @DialogField(fieldLabel = "Description", name = './jcr:description', tab = PROPERTIES_INDEX) @TextArea @JsonView(JacksonViews.DetailView) String description @Inject @DialogField(fieldLabel = "Published date", name = './publishedDate', required = true, tab = PROPERTIES_INDEX) @DateTime Date publishedDate @Inject @DialogField(fieldLabel = "Author Bio Path", name = "./authorBioPath", tab = PROPERTIES_INDEX) @PathField @JsonIgnore String authorBioPath @JsonView(JacksonViews.DetailView) Link getStoryLink() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).link } @JsonView(JacksonViews.ListView) String getStoryHref() { pageManager.getContainingPage(resource).adaptTo(ComponentNode).href } @JsonView(JacksonViews.DetailView) Bio getAuthorBio() { pageManager.getPage(authorBioPath)?.getContentResource()?.adaptTo(Bio) } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 22: CIRCUIT 2015 - Content API's For AEM Sites

Paul Michelotti Blog

h;p://citytechinc.com/us/en/blog/2015/03/groovy-­‐component-­‐composiHon-­‐with-­‐traits.html  

Groovy  Component  ComposiHon  With  Traits    

Page 23: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryOptionalImage trait AbstractStoryRequiredImage implements ComponentNode { @Inject @Named('mainImageCaption') @DialogField(fieldLabel = 'Main Image Caption', name = 'mainImageCaption',

fieldName = 'mainImageCaption', tab = MAIN_IMAGE_INDEX, ranking = 200D, additionalProperties = [@Property(name = 'name', value = ’.mainImageCaption')])

@TextField private String mainImageCaption @Inject @ImageInject(path = 'mainImage') @DialogField(fieldLabel = 'Main Image', name = 'mainImage', fieldName =

'mainImage', tab = MAIN_IMAGE_INDEX, required = true, ranking = 201D, additionalProperties = [@Property(name = "name", value = './mainImage')])

@Html5SmartImage(allowUpload = false, name = "mainImage", tab = false, height = 400)

private Image mainImage public String getMainImageCaption() { mainImageCaption } public Circuit2015Image getMainImage() { mainImage ? new Circuit2015Image(src: mainImage.src) : null } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 24: CIRCUIT 2015 - Content API's For AEM Sites

AbstractStoryRequiredImage trait AbstractStoryRequiredImage implements ComponentNode { @Inject @Named('mainImageCaption') @DialogField(fieldLabel = 'Main Image Caption', name = 'mainImageCaption',

fieldName = 'mainImageCaption', tab = MAIN_IMAGE_INDEX, ranking = 200D, required = true, additionalProperties = [@Property(name = 'name', value = './mainImageCaption')])

@TextField private String mainImageCaption @Inject @ImageInject(path = 'mainImage') @DialogField(fieldLabel = 'Main Image', name = 'mainImage', fieldName =

'mainImage', tab = MAIN_IMAGE_INDEX, required = true, ranking = 201D, additionalProperties = [@Property(name = "name", value = './mainImage')])

@Html5SmartImage(allowUpload = false, name = "mainImage", tab = false, height = 400)

private Image mainImage public String getMainImageCaption() { mainImageCaption } public Circuit2015Image getMainImage() { mainImage ? new Circuit2015Image(src: mainImage.src) : null } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 25: CIRCUIT 2015 - Content API's For AEM Sites

maven-scr-plugin <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-scr-plugin</artifactId> <executions> <execution> <id>generate-scr-scrdescriptor</id> <goals> <goal>scr</goal> </goals> </execution> </executions> <configuration> <scanClasses>true</scanClasses> <excludes> com/bryanw/conferences/circuit2015/groovy/story/

AbstractStoryOptionalImage*, com/bryanw/conferences/circuit2015/groovy/story/

AbstractStoryRequired*, com/bryanw/conferences/circuit2015/groovy/story/

SeoReadyStory* </excludes> </configuration> </plugin>

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 26: CIRCUIT 2015 - Content API's For AEM Sites

Content API Servlet

•  Accepts page paths only •  Accepts XML/JSON extension •  Accepts “story” and “stories” selector •  Constructs search parameters •  Passes current path and search

parameters on to service

Page 27: CIRCUIT 2015 - Content API's For AEM Sites

ContentApiServlet

@SlingServlet(resourceTypes = [ NameConstants.NT_PAGE ], selectors = [ "stories", "story" ], extensions = [ EXTENSION_JSON, "xml" ], methods = [ "GET" ]) @Slf4j("LOG") public class ContentApiServlet extends XmlOrJsonResponseServlet { @Reference ContentApiService contentApiService @Override protected final void doGet(final SlingHttpServletRequest slingRequest, final SlingHttpServletResponse slingResponse) { RequestPathInfo requestPathInfo = slingRequest.requestPathInfo slingResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate") slingResponse.setHeader("Expires", "0"); StorySearchParameters storySearchParameters = buildStorySearchParameters(slingRequest) if (requestPathInfo.selectors.contains('stories')) { writeResponse(slingResponse, requestPathInfo.extension, JacksonViews.ListView, contentApiService.getStories(storySearchParameters)) } else { writeResponse(slingResponse, requestPathInfo.extension, JacksonViews.DetailView, contentApiService.getStory(slingRequest.resource)) } } static private StorySearchParameters buildStorySearchParameters(final SlingHttpServletRequest slingHttpServletRequest) { StorySearchParameters storySearchParameters = new StorySearchParameters() storySearchParameters.setBaseResource(slingHttpServletRequest.resource) slingHttpServletRequest.parameterMap.each { key, value -> if (key == 'type') { storySearchParameters.setType(resolveStoryTypeClass(slingHttpServletRequest.getParameter('type'))) } else { storySearchParameters[key as String] = (value as String[])[0] } }

storySearchParameters } static private Class<? extends Story> resolveStoryTypeClass(String type) { Class<? extends Story> storyTypeClass storyTypeClass = type ? Class.forName("com.bryanw.conferences.circuit2015.groovy.components.page.stories.${type}.${type.capitalize()}").asSubclass(Story) : Story storyTypeClass } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 28: CIRCUIT 2015 - Content API's For AEM Sites

ContentApiServlet.doGet()

@Override protected final void doGet(final SlingHttpServletRequest slingRequest,

final SlingHttpServletResponse slingResponse) {

RequestPathInfo requestPathInfo = slingRequest.requestPathInfo slingResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate")

slingResponse.setHeader("Expires", "0"); StorySearchParameters storySearchParameters =

buildStorySearchParameters(slingRequest)

if (requestPathInfo.selectors.contains('stories')) { writeResponse(slingResponse, requestPathInfo.extension,

JacksonViews.ListView, contentApiService.getStories(storySearchParameters))

} else { writeResponse(slingResponse, requestPathInfo.extension,

JacksonViews.DetailView, contentApiService.getStory(slingRequest.resource))

} }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 29: CIRCUIT 2015 - Content API's For AEM Sites

ContentApiServlet.buildStorySearchParameters()

static private StorySearchParameters buildStorySearchParameters( final SlingHttpServletRequest slingRequest) { StorySearchParameters searchParams = new StorySearchParameters() storySearchParameters.setBaseResource(slingRequest.resource) slingRequest.parameterMap.each { key, value -> if (key == 'type') { String typeParam = slingRequest.getParameter('type') searchParams.setType(resolveStoryTypeClass(typeParam)) } else { searchParams[key as String] = (value as String[])[0] } }

searchParams }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 30: CIRCUIT 2015 - Content API's For AEM Sites

ContentApiServlet.resolveStoryTypeClass()

private Class<? extends Story> resolveStoryTypeClass(String type) { Class<? extends Story> storyTypeClass String packagePrefix = 'com.bryanw.conferences.circuit2015.groovy.components.page.stories' storyTypeClass = type ? Class .forName("${packagePrefix}.${type}.${type.capitalize()}") .asSubclass(Story) : Story storyTypeClass }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 31: CIRCUIT 2015 - Content API's For AEM Sites

StorySearchParameters

@EqualsAndHashCode class StorySearchParameters { Class<Story> type Resource baseResource String text String start String limit Map<String, String> searchables = [:] def propertyMissing(String name, value) { searchables[name] = value } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 32: CIRCUIT 2015 - Content API's For AEM Sites

XmlOrJsonResponseServlet

@Slf4j("LOG") class XmlOrJsonResponseServlet extends SlingAllMethodsServlet { public static final String DEFAULT_DATE_FORMAT = "MM/dd/yyyy hh:mm aaa z"; private static final DateFormat MAPPER_DATE_FORMAT = new SimpleDateFormat(DEFAULT_DATE_FORMAT, Locale.US) private static final XmlFactory XML_FACTORY = new XmlFactory() public void writeResponse(final SlingHttpServletResponse response, final String extension, final Class<JacksonViews.View> view, final Object object) { if ("xml" == extension) { XmlMapper xmlMapper = new XmlMapper().setDateFormat(MAPPER_DATE_FORMAT) as XmlMapper writeXmlResponse(response, xmlMapper, view, object) } else { ObjectMapper jsonMapper = new ObjectMapper().setDateFormat(MAPPER_DATE_FORMAT) writeJsonResponse(response, jsonMapper, view, object) } } protected static void writeXmlResponse(final SlingHttpServletResponse response, final XmlMapper xmlMapper, final Class<JacksonViews.View> view, final Object object) { MediaType mediaType = MediaType.XML_UTF_8 response.setContentType(mediaType.withoutParameters().toString()) response.setCharacterEncoding(mediaType.charset().get().name()) final ToXmlGenerator generator = XML_FACTORY.createGenerator(response.getWriter()) xmlMapper.writerWithView(view).writeValue(generator, object) } protected void writeJsonResponse(final SlingHttpServletResponse response, final ObjectMapper mapper, final Class<JacksonViews.View> view, final Object object) throws IOException { response.setContentType(MediaType.JSON_UTF_8.withoutParameters().toString()); response.setCharacterEncoding(MediaType.JSON_UTF_8.charset().get().name()); final JsonGenerator generator = new JsonFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) .createGenerator(response.getWriter()); mapper.writerWithView(view).writeValue(generator, object); } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 33: CIRCUIT 2015 - Content API's For AEM Sites

XmlOrJsonResponseServlet.writeResponse()

public void writeResponse( final SlingHttpServletResponse response, final String extension, final Class<JacksonViews.View> view, final Object object) { if ("xml" == extension) { XmlMapper xmlMapper =

new XmlMapper() .setDateFormat(MAPPER_DATE_FORMAT) as XmlMapper

writeXmlResponse(response, xmlMapper, view, object) } else { ObjectMapper jsonMapper =

new ObjectMapper() .setDateFormat(MAPPER_DATE_FORMAT)

writeJsonResponse(response, jsonMapper, view, object) } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 34: CIRCUIT 2015 - Content API's For AEM Sites

XmlOrJsonResponseServlet.writeXmlResponse()

protected void writeXmlResponse( final SlingHttpServletResponse response, final XmlMapper xmlMapper, final Class<JacksonViews.View> view, final Object object) { MediaType mediaType = MediaType.XML_UTF_8 response.setContentType(

mediaType.withoutParameters().toString()) response.setCharacterEncoding(

mediaType.charset().get().name()) ToXmlGenerator generator = XML_FACTORY

.createGenerator(response.getWriter()) xmlMapper.writerWithView(view)

.writeValue(generator, object) }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 35: CIRCUIT 2015 - Content API's For AEM Sites

XmlOrJsonResponseServlet.writeJsonResponse()

protected void writeJsonResponse( final SlingHttpServletResponse response, final ObjectMapper mapper,

final Class<JacksonViews.View> view, final Object object){

MediaType mediaType = MediaType.JSON_UTF_8 response.setContentType( mediaType.withoutParameters().toString()); response.setCharacterEncoding( mediaType.charset().get().name()); JsonGenerator generator = new JsonFactory() .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) .createGenerator(response.getWriter()); mapper.writerWithView(view) .writeValue(generator, object);

}

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 36: CIRCUIT 2015 - Content API's For AEM Sites

Content API Service

•  Calls repository layer •  (Guava) caching

Page 37: CIRCUIT 2015 - Content API's For AEM Sites

DefaultContentApiService

@Component @Service(ContentApiService) @Slf4j("LOG") class DefaultContentApiService extends AbstractCacheService

implements ContentApiService { @Reference private ContentApiRepository contentApiRepository @Override StorySearchResult getStories(StorySearchParameters storySearchParameters) { // Caching should go here contentApiRepository.search(storySearchParameters) } @Override Story getStory(Resource storyResource) { storyResource?.getChild(JcrConstants.JCR_CONTENT)?.adaptTo(Story) } @Override protected Logger getLogger() { LOG } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 38: CIRCUIT 2015 - Content API's For AEM Sites

Content API Repository Layer

•  Constructs proper QueryBuilder query •  Instantiates appropriate models from

results

Page 39: CIRCUIT 2015 - Content API's For AEM Sites

DefaultContentApiRepository

@Component @Service(ContentApiRepository) @Slf4j("LOG") class DefaultContentApiRepository implements ContentApiRepository { @Reference private QueryBuilder queryBuilder @Override public StorySearchResult search(StorySearchParameters storySearchParameters) { Session session = storySearchParameters.baseResource.resourceResolver.adaptTo(Session) String start = storySearchParameters.start ? storySearchParameters.start : '0' String limit = storySearchParameters.limit ? storySearchParameters.limit : '100' PredicateGroup mainGroup = new PredicateGroup(); mainGroup.add(new Predicate("path").set("path", storySearchParameters.baseResource.path)) mainGroup.add(new Predicate("type").set("type", "cq:PageContent")) mainGroup.add(new Predicate("property").set(Predicate.ORDER_BY, "publishedDate")) mainGroup.add(new Predicate("property").set("${Predicate.PARAM_SORT}.${Predicate.PARAM_SORT}", Predicate.SORT_DESCENDING)) mainGroup.add(new Predicate("property").set(Predicate.PARAM_OFFSET, start)) mainGroup.add(new Predicate("property").set(Predicate.PARAM_LIMIT, limit)) if (storySearchParameters.text) { mainGroup.add(new Predicate("fulltext").set("fulltext", storySearchParameters.text)) } mainGroup.add(createResourceTypePredicate(storySearchParameters)) storySearchParameters.searchables.each { key, value -> // Add whatever other search predicates you want to allow here } Query query = queryBuilder.createQuery(mainGroup, session); SearchResult searchResult = query.getResult(); StorySearchResult storyResult = new StorySearchResult(); storyResult.stories = searchResult.hits.collect { it.resource.adaptTo(Story) } - null storyResult.setTotalResults(searchResult.totalMatches); storyResult.setStart(start as Long); storyResult } private Predicate createResourceTypePredicate(StorySearchParameters storySearchParameters) { Predicate resourceTypePredicate = new Predicate("property") resourceTypePredicate.set("property", SLING_RESOURCE_TYPE_PROPERTY) if (!storySearchParameters.type || storySearchParameters.type.name == Story.name) { resourceTypePredicate.set("operation", "like") resourceTypePredicate.set("value", "circuit2015/groovy/components/page/stories/%") } else { //String typeName = storySearchParameters.baseResource.class.simpleName.toLowerCase() String typeName = storySearchParameters.type.simpleName resourceTypePredicate.set("value", "circuit2015/groovy/components/page/stories/${typeName.toLowerCase()}") } resourceTypePredicate } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 40: CIRCUIT 2015 - Content API's For AEM Sites

DefaultContentApiRepository.search()

@Override public StorySearchResult search(StorySearchParameters storySearchParameters) { Session session = storySearchParameters.baseResource.resourceResolver.adaptTo(Session) String start = storySearchParameters.start ? storySearchParameters.start : '0' String limit = storySearchParameters.limit ? storySearchParameters.limit : '100' PredicateGroup mainGroup = new PredicateGroup(); mainGroup.add(new Predicate("path").set("path", storySearchParameters.baseResource.path)) mainGroup.add(new Predicate("type").set("type", "cq:PageContent")) mainGroup.add(new Predicate("property").set(Predicate.ORDER_BY, "publishedDate")) mainGroup.add(new Predicate("property").set("${Predicate.PARAM_SORT}.${Predicate.PARAM_SORT}",

Predicate.SORT_DESCENDING)) mainGroup.add(new Predicate("property").set(Predicate.PARAM_OFFSET, start)) mainGroup.add(new Predicate("property").set(Predicate.PARAM_LIMIT, limit)) if (storySearchParameters.text) { mainGroup.add(new Predicate("fulltext").set("fulltext", storySearchParameters.text)) } mainGroup.add(createResourceTypePredicate(storySearchParameters)) storySearchParameters.searchables.each { key, value -> // Add whatever other search predicates you want to allow here } Query query = queryBuilder.createQuery(mainGroup, session); SearchResult searchResult = query.getResult(); StorySearchResult storyResult = new StorySearchResult(); storyResult.stories = searchResult.hits.collect { it.resource.adaptTo(Story) } - null storyResult.setTotalResults(searchResult.totalMatches); storyResult.setStart(start as Long); storyResult }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 41: CIRCUIT 2015 - Content API's For AEM Sites

DefaultContentApiRepository.createResourceTypePredicate()

private Predicate createResourceTypePredicate( StorySearchParameters storySearchParameters) {

Predicate resourceTypePredicate = new Predicate("property") resourceTypePredicate.set("property”,SLING_RESOURCE_TYPE_PROPERTY) if (!storySearchParameters.type || storySearchParameters.type.name == Story.name) { resourceTypePredicate.set("operation", "like") resourceTypePredicate.set("value",

"circuit2015/groovy/components/page/stories/%") } else {

String typeName = storySearchParameters.type.simpleName resourceTypePredicate.set("value",

"circuit2015/groovy/components/page/stories/$ {typeName.toLowerCase()}")

} resourceTypePredicate }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 42: CIRCUIT 2015 - Content API's For AEM Sites

StorySearchResult

@EqualsAndHashCode @XmlRootElement(name = "result") class StorySearchResult { List<Story> stories long totalResults long start }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 43: CIRCUIT 2015 - Content API's For AEM Sites

ResourceTypeImplementationPicker

@Component @Service(ImplementationPicker) @Property(name = Constants.SERVICE_RANKING, intValue = 1000) @Slf4j("LOG")

public class ResourceTypeImplementationPicker implements ImplementationPicker { public Class pick(Class adapterType, Class[] implTypes, Object adaptable) {

Class pickedClass = null

if (adaptable instanceof Resource) { Resource resource = adaptable as Resource

String resourceType = resource.getResourceType() pickedClass = implTypes.find {

if (it instanceof AbstractCircuit2015Component) { (it as AbstractCircuit2015Component).conventionalResourceType() == resourceType }

} }

pickedClass }

}

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 44: CIRCUIT 2015 - Content API's For AEM Sites

ClassNameImplementationPicker

@Component @Service(ImplementationPicker) @Property(name = Constants.SERVICE_RANKING, intValue = 1001) @Slf4j("LOG")

class ClassNameImplementationPicker implements ImplementationPicker { public Class pick(Class adapterType, Class[] implTypes, Object adaptable) {

Class classNameClass = null if (adaptable instanceof Resource) { Resource resource = adaptable as Resource

String className = resource.properties?.get("className") classNameClass = implTypes.find {

it.name == className } }

classNameClass } }

Bedrock    CQ  Component  Plugin  Sling  Models    Jackson  

Page 45: CIRCUIT 2015 - Content API's For AEM Sites

http://mydomain.com/circuit-2015-demo.stories.json

{ "stories": [

{ "title": "CIRCUIT Promo Video", "publishedDate": "08/12/2015 10:49 AM CDT", "seoTitle": "CIRCUIT Promo Video", "seoDescription": "Maximas vero virtutes iacere omnis necesse", "mainImage": { "src": "/content/dam/circuit-2015-demo/images/promo.png" }, "mainImageCaption": "CIRCUIT Promo Video Image", "index": 0, "storyHref": "/content/circuit-2015-demo/circuit-promo-video.html"

”videoHref": "https://www.youtube.com/watch?v=r2mFb1dIiug" }, { "title": "Article 1", "publishedDate": "08/06/2015 01:21 AM CDT", "seoTitle": "Article One", "seoDescription": "Maximas vero virtutes iacere omnis necesse", "mainImage": { "src": "/content/dam/circuit-2015-demo/images/article1banner.jpg" }, "mainImageCaption": "Article 1 Banner Image", "index": 0, "storyHref": "/content/circuit-2015-demo/article-1.html" } ],

"totalResults": 2, "start": 0 }

Page 46: CIRCUIT 2015 - Content API's For AEM Sites

http://mydomain.com/circuit-2015-demo.stories.json?type=article

{ "stories": [

{ "title": "Article 1",

"publishedDate": "08/06/2015 01:21 AM CDT", "seoTitle": "Article One", "seoDescription": "Maximas vero virtutes iacere omnis necesse",

"mainImage": { "src": "/content/dam/circuit-2015-demo/images/article1banner.jpg" },

"mainImageCaption": "Article 1 Banner Image", "index": 0, "storyHref": "/content/circuit-2015-demo/article-1.html"

} ],

"totalResults": 1, "start": 0

}

Page 47: CIRCUIT 2015 - Content API's For AEM Sites

http://mydomain.com/circuit-2015-demo/article-1.story.json

{ "title": "Article 1", "description": "Maximas vero virtutes iacere omnis necesse", "publishedDate": "08/06/2015 01:21 AM CDT", "seoTitle": "Article One", "seoDescription": "Maximas vero virtutes iacere omnis necesse", "body": [ { "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n", "index": 0 } ], "mainImage": { "src": "/content/dam/circuit-2015-demo/images/article1banner.jpg" }, "mainImageCaption": "Article 1 Banner Image", "index": 0, "authorBio": { "firstName": "Bryan", "lastName": "Williams", "twitter": "@brywilliams", "description": "Maximas vero virtutes iacere omnis necesse", "mainImage": { "src": "/content/dam/circuit-2015-demo/images/bryanw-profile.png" }, "mainImageCaption": "Bryan Williams Bio Image", "index": 0 }, "storyLink": { "path": "/content/circuit-2015-demo/article-1", "extension": "html", "suffix": "", "href": "/content/circuit-2015-demo/article-1.html", "selectors": [ ], "queryString": "", "external": false, "target": "_self", "title": "", "properties": { }, "empty": false } }

Page 48: CIRCUIT 2015 - Content API's For AEM Sites

Versioning

•  Like I said, not OOTB for Jackson •  Need something like @Since in GSON •  Jackson Filters

Page 49: CIRCUIT 2015 - Content API's For AEM Sites

Filters

•  Encoding •  Externalizing URLs

Page 50: CIRCUIT 2015 - Content API's For AEM Sites

Testing with Prosper

•  Integration testing using Spock/Groovy •  Specifically for AEM testing •  Builders for Nodes/Pages •  Uses Sling Mocks

Page 51: CIRCUIT 2015 - Content API's For AEM Sites

Publish vs Author

•  May want internal apps to access author •  replicatedDate is not what it seems

Page 52: CIRCUIT 2015 - Content API's For AEM Sites

Conclusions •  Bedrock

–  https://github.com/Citytechinc/bedrock –  Provided us with useful classes, annotations and model injectors

•  CQ Component (Maven) Plugin –  https://github.com/Citytechinc/cq-component-maven-plugin –  Allowed us to create dialogs without writing a single XML file

•  Sling Models –  https://sling.apache.org/documentation/bundles/models.html –  Wired up our backing beans for us

•  Jackson –  https://github.com/FasterXML/jackson –  Let us define the details of bean to JSON/XML conversion

•  Prosper –  https://github.com/Citytechinc/prosper –  Simplified tests

•  Groovy –  http://www.groovy-lang.org/ –  Less code

Page 53: CIRCUIT 2015 - Content API's For AEM Sites

Bryan Williams ICF Interactive [email protected] @brywilliams


Recommended