SlideShare a Scribd company logo
1 of 53
Download to read offline
CIRCUIT – An Adobe Developer Event
Presented by ICF Interactive
Content API’s
for AEM
Bryan Williams
ICF Interactive
Bryan.Williams@icfi.com
@brywilliams
Bryan Williams
ICF Interactive (10+ years)
Working with CQ/AEM over 6 years
AEM Developer Certified (Beta)
Prize Question #1
?	
  
What do I mean by Content API?
•  Read only
•  Controlled
•  Possibly public but not necessarily
•  Usually an afterthought
•  Disclaimer: Not production code
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
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/
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)
Sling Models
•  Automates mapping of Sling objects such
as resources, request, etc. to POJOs
•  Available out of the box in AEM 6
Why Jackson?
•  Popular
•  Able to produce JSON or XML
•  Lots of features
•  We were already using it
Groovy
•  An object-oriented programming language
for the Java platform
•  Dynamic in nature
•  Reduced syntax
•  Traits
Prize Question #2
?:
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.)
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
Sample Article Page
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	
  
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	
  
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	
  
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	
  
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	
  
Paul Michelotti Blog
h;p://citytechinc.com/us/en/blog/2015/03/groovy-­‐component-­‐composiHon-­‐with-­‐traits.html	
  
Groovy	
  Component	
  ComposiHon	
  With	
  Traits	
  
	
  
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	
  
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	
  
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	
  
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
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	
  
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	
  
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	
  
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	
  
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	
  
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	
  
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	
  
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	
  
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	
  
Content API Service
•  Calls repository layer
•  (Guava) caching
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	
  
Content API Repository Layer
•  Constructs proper QueryBuilder query
•  Instantiates appropriate models from
results
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	
  
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	
  
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	
  
StorySearchResult
@EqualsAndHashCode
@XmlRootElement(name = "result")
class StorySearchResult {
List<Story> stories
long totalResults
long start
}
Bedrock 	
   	
  CQ	
  Component	
  Plugin	
  
Sling	
  Models 	
   	
  Jackson	
  
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	
  
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	
  
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
}
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
}
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
}
}
Versioning
•  Like I said, not OOTB for Jackson
•  Need something like @Since in GSON
•  Jackson Filters
Filters
•  Encoding
•  Externalizing URLs
Testing with Prosper
•  Integration testing using Spock/Groovy
•  Specifically for AEM testing
•  Builders for Nodes/Pages
•  Uses Sling Mocks
Publish vs Author
•  May want internal apps to access author
•  replicatedDate is not what it seems
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
Bryan Williams
ICF Interactive
Bryan.Williams@icfi.com
@brywilliams

More Related Content

What's hot

Adobe Experience Manager Core Components
Adobe Experience Manager Core ComponentsAdobe Experience Manager Core Components
Adobe Experience Manager Core ComponentsGabriel Walt
 
AEM Sightly Template Language
AEM Sightly Template LanguageAEM Sightly Template Language
AEM Sightly Template LanguageGabriel Walt
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMAdobeMarketingCloud
 
Thoughts on Component Resuse
Thoughts on Component ResuseThoughts on Component Resuse
Thoughts on Component ResuseJustin Edelson
 
Adobe AEM core components
Adobe AEM core componentsAdobe AEM core components
Adobe AEM core componentsLokesh BS
 
AEM and Sling
AEM and SlingAEM and Sling
AEM and SlingLo Ki
 
AEM Sightly Deep Dive
AEM Sightly Deep DiveAEM Sightly Deep Dive
AEM Sightly Deep DiveGabriel Walt
 
12 hot features to engage and save time with aem 6.2
12 hot features to engage and save time with aem 6.212 hot features to engage and save time with aem 6.2
12 hot features to engage and save time with aem 6.2Tricode (part of Dept)
 
Dynamic components using SPA concepts in AEM
Dynamic components using SPA concepts in AEMDynamic components using SPA concepts in AEM
Dynamic components using SPA concepts in AEMBojana Popovska
 
Extra aem development tools by Justin Edelson
Extra aem development tools by Justin EdelsonExtra aem development tools by Justin Edelson
Extra aem development tools by Justin EdelsonAEM HUB
 
Three WEM Dev Tricks
Three WEM Dev TricksThree WEM Dev Tricks
Three WEM Dev TricksGabriel Walt
 
AEM 6.0 Touch-optimized UI
AEM 6.0 Touch-optimized UIAEM 6.0 Touch-optimized UI
AEM 6.0 Touch-optimized UIGilles Knobloch
 
SPA Editing with Sling to the rescue - adaptTo() 2021
SPA Editing with Sling to the rescue - adaptTo() 2021 SPA Editing with Sling to the rescue - adaptTo() 2021
SPA Editing with Sling to the rescue - adaptTo() 2021 Hanish Bansal
 
10 reasons to migrate from AEM 5 to 6.1
10 reasons to migrate from AEM 5 to 6.110 reasons to migrate from AEM 5 to 6.1
10 reasons to migrate from AEM 5 to 6.1Tricode (part of Dept)
 
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEM
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEMDo more with LESS, Handlebars, Coffeescript and other Web Resources in AEM
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEMBob Paulin
 
Dynamic Components using Single-Page-Application Concepts in AEM/CQ
Dynamic Components using Single-Page-Application Concepts in AEM/CQDynamic Components using Single-Page-Application Concepts in AEM/CQ
Dynamic Components using Single-Page-Application Concepts in AEM/CQNetcetera
 
Bridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEMBridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEMrbl002
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMconnectwebex
 

What's hot (20)

Adobe Experience Manager Core Components
Adobe Experience Manager Core ComponentsAdobe Experience Manager Core Components
Adobe Experience Manager Core Components
 
AEM Sightly Template Language
AEM Sightly Template LanguageAEM Sightly Template Language
AEM Sightly Template Language
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEM
 
Thoughts on Component Resuse
Thoughts on Component ResuseThoughts on Component Resuse
Thoughts on Component Resuse
 
Adobe AEM core components
Adobe AEM core componentsAdobe AEM core components
Adobe AEM core components
 
AEM and Sling
AEM and SlingAEM and Sling
AEM and Sling
 
AEM Sightly Deep Dive
AEM Sightly Deep DiveAEM Sightly Deep Dive
AEM Sightly Deep Dive
 
12 hot features to engage and save time with aem 6.2
12 hot features to engage and save time with aem 6.212 hot features to engage and save time with aem 6.2
12 hot features to engage and save time with aem 6.2
 
Sling Dynamic Include
Sling Dynamic IncludeSling Dynamic Include
Sling Dynamic Include
 
Dynamic components using SPA concepts in AEM
Dynamic components using SPA concepts in AEMDynamic components using SPA concepts in AEM
Dynamic components using SPA concepts in AEM
 
Extra aem development tools by Justin Edelson
Extra aem development tools by Justin EdelsonExtra aem development tools by Justin Edelson
Extra aem development tools by Justin Edelson
 
Three WEM Dev Tricks
Three WEM Dev TricksThree WEM Dev Tricks
Three WEM Dev Tricks
 
Sling Models Overview
Sling Models OverviewSling Models Overview
Sling Models Overview
 
AEM 6.0 Touch-optimized UI
AEM 6.0 Touch-optimized UIAEM 6.0 Touch-optimized UI
AEM 6.0 Touch-optimized UI
 
SPA Editing with Sling to the rescue - adaptTo() 2021
SPA Editing with Sling to the rescue - adaptTo() 2021 SPA Editing with Sling to the rescue - adaptTo() 2021
SPA Editing with Sling to the rescue - adaptTo() 2021
 
10 reasons to migrate from AEM 5 to 6.1
10 reasons to migrate from AEM 5 to 6.110 reasons to migrate from AEM 5 to 6.1
10 reasons to migrate from AEM 5 to 6.1
 
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEM
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEMDo more with LESS, Handlebars, Coffeescript and other Web Resources in AEM
Do more with LESS, Handlebars, Coffeescript and other Web Resources in AEM
 
Dynamic Components using Single-Page-Application Concepts in AEM/CQ
Dynamic Components using Single-Page-Application Concepts in AEM/CQDynamic Components using Single-Page-Application Concepts in AEM/CQ
Dynamic Components using Single-Page-Application Concepts in AEM/CQ
 
Bridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEMBridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEM
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEM
 

Viewers also liked

When dispatcher caching is not enough...
When dispatcher caching is not enough...When dispatcher caching is not enough...
When dispatcher caching is not enough...Jakub Wadolowski
 
Introducing Apache Jackrabbit OAK
Introducing Apache Jackrabbit OAKIntroducing Apache Jackrabbit OAK
Introducing Apache Jackrabbit OAKYash Mody
 
Aem authentication vs idp
Aem authentication vs idpAem authentication vs idp
Aem authentication vs idpSaroj Mishra
 
CIRCUIT 2015 - Hybrid App Development with AEM Apps
CIRCUIT 2015 - Hybrid App Development with AEM AppsCIRCUIT 2015 - Hybrid App Development with AEM Apps
CIRCUIT 2015 - Hybrid App Development with AEM AppsICF CIRCUIT
 
CIRCUIT 2015 - AEM Infrastructure Automation with Chef Cookbooks
CIRCUIT 2015 - AEM Infrastructure Automation with Chef CookbooksCIRCUIT 2015 - AEM Infrastructure Automation with Chef Cookbooks
CIRCUIT 2015 - AEM Infrastructure Automation with Chef CookbooksICF CIRCUIT
 
Adobe AEM Commerce with hybris
Adobe AEM Commerce with hybrisAdobe AEM Commerce with hybris
Adobe AEM Commerce with hybrisPaolo Mottadelli
 
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructures
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructuresMaster Chef class: learn how to quickly cook delightful CQ/AEM infrastructures
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructuresFrançois Le Droff
 
AEM (CQ) eCommerce Framework
AEM (CQ) eCommerce FrameworkAEM (CQ) eCommerce Framework
AEM (CQ) eCommerce FrameworkPaolo Mottadelli
 

Viewers also liked (12)

When dispatcher caching is not enough...
When dispatcher caching is not enough...When dispatcher caching is not enough...
When dispatcher caching is not enough...
 
Cqcon
CqconCqcon
Cqcon
 
Introducing Apache Jackrabbit OAK
Introducing Apache Jackrabbit OAKIntroducing Apache Jackrabbit OAK
Introducing Apache Jackrabbit OAK
 
(Re)discover your AEM
(Re)discover your AEM(Re)discover your AEM
(Re)discover your AEM
 
Aem authentication vs idp
Aem authentication vs idpAem authentication vs idp
Aem authentication vs idp
 
AEM - Client Libraries
AEM - Client LibrariesAEM - Client Libraries
AEM - Client Libraries
 
REST in AEM
REST in AEMREST in AEM
REST in AEM
 
CIRCUIT 2015 - Hybrid App Development with AEM Apps
CIRCUIT 2015 - Hybrid App Development with AEM AppsCIRCUIT 2015 - Hybrid App Development with AEM Apps
CIRCUIT 2015 - Hybrid App Development with AEM Apps
 
CIRCUIT 2015 - AEM Infrastructure Automation with Chef Cookbooks
CIRCUIT 2015 - AEM Infrastructure Automation with Chef CookbooksCIRCUIT 2015 - AEM Infrastructure Automation with Chef Cookbooks
CIRCUIT 2015 - AEM Infrastructure Automation with Chef Cookbooks
 
Adobe AEM Commerce with hybris
Adobe AEM Commerce with hybrisAdobe AEM Commerce with hybris
Adobe AEM Commerce with hybris
 
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructures
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructuresMaster Chef class: learn how to quickly cook delightful CQ/AEM infrastructures
Master Chef class: learn how to quickly cook delightful CQ/AEM infrastructures
 
AEM (CQ) eCommerce Framework
AEM (CQ) eCommerce FrameworkAEM (CQ) eCommerce Framework
AEM (CQ) eCommerce Framework
 

Similar to CIRCUIT - Content APIs for AEM

Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0
Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0
Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0Arun Gupta
 
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010Arun Gupta
 
Developing your first application using FI-WARE
Developing your first application using FI-WAREDeveloping your first application using FI-WARE
Developing your first application using FI-WAREFermin Galan
 
Toms introtospring mvc
Toms introtospring mvcToms introtospring mvc
Toms introtospring mvcGuo Albert
 
Learning To Run - XPages for Lotus Notes Client Developers
Learning To Run - XPages for Lotus Notes Client DevelopersLearning To Run - XPages for Lotus Notes Client Developers
Learning To Run - XPages for Lotus Notes Client DevelopersKathy Brown
 
Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Michał Orman
 
Spring first in Magnolia CMS - Spring I/O 2015
Spring first in Magnolia CMS - Spring I/O 2015Spring first in Magnolia CMS - Spring I/O 2015
Spring first in Magnolia CMS - Spring I/O 2015Tobias Mattsson
 
Spring design-juergen-qcon
Spring design-juergen-qconSpring design-juergen-qcon
Spring design-juergen-qconYiwei Ma
 
OSGi and Spring Data for simple (Web) Application Development
OSGi and Spring Data  for simple (Web) Application DevelopmentOSGi and Spring Data  for simple (Web) Application Development
OSGi and Spring Data for simple (Web) Application DevelopmentChristian Baranowski
 
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...mfrancis
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responsesdarrelmiller71
 
Intorduction of Playframework
Intorduction of PlayframeworkIntorduction of Playframework
Intorduction of Playframeworkmaltiyadav
 
Spring 3 - An Introduction
Spring 3 - An IntroductionSpring 3 - An Introduction
Spring 3 - An IntroductionThorsten Kamann
 
Customizing the Document Library
Customizing the Document LibraryCustomizing the Document Library
Customizing the Document LibraryAlfresco Software
 
React on Rails - RailsConf 2017 (Phoenix)
 React on Rails - RailsConf 2017 (Phoenix) React on Rails - RailsConf 2017 (Phoenix)
React on Rails - RailsConf 2017 (Phoenix)Jo Cranford
 
[React Native Tutorial] Lecture 6: Component, Props, and Network
[React Native Tutorial] Lecture 6: Component, Props, and Network[React Native Tutorial] Lecture 6: Component, Props, and Network
[React Native Tutorial] Lecture 6: Component, Props, and NetworkKobkrit Viriyayudhakorn
 
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptx
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptxWRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptx
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptxsalemsg
 
Web Components v1
Web Components v1Web Components v1
Web Components v1Mike Wilcox
 
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015Matt Raible
 

Similar to CIRCUIT - Content APIs for AEM (20)

Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0
Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0
Spark IT 2011 - Simplified Web Development using Java Server Faces 2.0
 
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
 
Developing your first application using FI-WARE
Developing your first application using FI-WAREDeveloping your first application using FI-WARE
Developing your first application using FI-WARE
 
Toms introtospring mvc
Toms introtospring mvcToms introtospring mvc
Toms introtospring mvc
 
Learning To Run - XPages for Lotus Notes Client Developers
Learning To Run - XPages for Lotus Notes Client DevelopersLearning To Run - XPages for Lotus Notes Client Developers
Learning To Run - XPages for Lotus Notes Client Developers
 
Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1
 
Spring first in Magnolia CMS - Spring I/O 2015
Spring first in Magnolia CMS - Spring I/O 2015Spring first in Magnolia CMS - Spring I/O 2015
Spring first in Magnolia CMS - Spring I/O 2015
 
Spring design-juergen-qcon
Spring design-juergen-qconSpring design-juergen-qcon
Spring design-juergen-qcon
 
OSGi and Spring Data for simple (Web) Application Development
OSGi and Spring Data  for simple (Web) Application DevelopmentOSGi and Spring Data  for simple (Web) Application Development
OSGi and Spring Data for simple (Web) Application Development
 
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responses
 
Modern android development
Modern android developmentModern android development
Modern android development
 
Intorduction of Playframework
Intorduction of PlayframeworkIntorduction of Playframework
Intorduction of Playframework
 
Spring 3 - An Introduction
Spring 3 - An IntroductionSpring 3 - An Introduction
Spring 3 - An Introduction
 
Customizing the Document Library
Customizing the Document LibraryCustomizing the Document Library
Customizing the Document Library
 
React on Rails - RailsConf 2017 (Phoenix)
 React on Rails - RailsConf 2017 (Phoenix) React on Rails - RailsConf 2017 (Phoenix)
React on Rails - RailsConf 2017 (Phoenix)
 
[React Native Tutorial] Lecture 6: Component, Props, and Network
[React Native Tutorial] Lecture 6: Component, Props, and Network[React Native Tutorial] Lecture 6: Component, Props, and Network
[React Native Tutorial] Lecture 6: Component, Props, and Network
 
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptx
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptxWRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptx
WRStmlDSQUmUrZpQ0tFJ4Q_a36bc57fe1a24dd8bc5ba549736e406f_C2-Week2.pptx
 
Web Components v1
Web Components v1Web Components v1
Web Components v1
 
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015
#NoXML: Eliminating XML in Spring Projects - SpringOne 2GX 2015
 

More from ICF CIRCUIT

CIRCUIT 2015 - Monitoring AEM
CIRCUIT 2015 - Monitoring AEMCIRCUIT 2015 - Monitoring AEM
CIRCUIT 2015 - Monitoring AEMICF CIRCUIT
 
CIRCUIT 2015 - Akamai: Caching and Beyond
CIRCUIT 2015 - Akamai:  Caching and BeyondCIRCUIT 2015 - Akamai:  Caching and Beyond
CIRCUIT 2015 - Akamai: Caching and BeyondICF CIRCUIT
 
CIRCUIT 2015 - Free Beer and Testing
CIRCUIT 2015 - Free Beer and TestingCIRCUIT 2015 - Free Beer and Testing
CIRCUIT 2015 - Free Beer and TestingICF CIRCUIT
 
CIRCUIT 2015 - Responsive Websites & Grid-Based Layouts
CIRCUIT 2015 - Responsive Websites & Grid-Based LayoutsCIRCUIT 2015 - Responsive Websites & Grid-Based Layouts
CIRCUIT 2015 - Responsive Websites & Grid-Based LayoutsICF CIRCUIT
 
CIRCUIT 2015 - Glimpse of perceptual diff
CIRCUIT 2015 - Glimpse of perceptual diffCIRCUIT 2015 - Glimpse of perceptual diff
CIRCUIT 2015 - Glimpse of perceptual diffICF CIRCUIT
 
CIRCUIT 2015 - Orchestrate your story with interactive video and web content
CIRCUIT 2015 -  Orchestrate your story with interactive video and web contentCIRCUIT 2015 -  Orchestrate your story with interactive video and web content
CIRCUIT 2015 - Orchestrate your story with interactive video and web contentICF CIRCUIT
 
How to migrate from any CMS (thru the front-door)
How to migrate from any CMS (thru the front-door)How to migrate from any CMS (thru the front-door)
How to migrate from any CMS (thru the front-door)ICF CIRCUIT
 
Maximize the power of OSGi in AEM
Maximize the power of OSGi in AEM Maximize the power of OSGi in AEM
Maximize the power of OSGi in AEM ICF CIRCUIT
 
CIRCUIT 2015 - 10 Things Apache Sling Can Do
CIRCUIT 2015 - 10 Things Apache Sling Can DoCIRCUIT 2015 - 10 Things Apache Sling Can Do
CIRCUIT 2015 - 10 Things Apache Sling Can DoICF CIRCUIT
 
Circuit 2015 Keynote - Carsten Ziegeler
Circuit 2015 Keynote -  Carsten ZiegelerCircuit 2015 Keynote -  Carsten Ziegeler
Circuit 2015 Keynote - Carsten ZiegelerICF CIRCUIT
 

More from ICF CIRCUIT (10)

CIRCUIT 2015 - Monitoring AEM
CIRCUIT 2015 - Monitoring AEMCIRCUIT 2015 - Monitoring AEM
CIRCUIT 2015 - Monitoring AEM
 
CIRCUIT 2015 - Akamai: Caching and Beyond
CIRCUIT 2015 - Akamai:  Caching and BeyondCIRCUIT 2015 - Akamai:  Caching and Beyond
CIRCUIT 2015 - Akamai: Caching and Beyond
 
CIRCUIT 2015 - Free Beer and Testing
CIRCUIT 2015 - Free Beer and TestingCIRCUIT 2015 - Free Beer and Testing
CIRCUIT 2015 - Free Beer and Testing
 
CIRCUIT 2015 - Responsive Websites & Grid-Based Layouts
CIRCUIT 2015 - Responsive Websites & Grid-Based LayoutsCIRCUIT 2015 - Responsive Websites & Grid-Based Layouts
CIRCUIT 2015 - Responsive Websites & Grid-Based Layouts
 
CIRCUIT 2015 - Glimpse of perceptual diff
CIRCUIT 2015 - Glimpse of perceptual diffCIRCUIT 2015 - Glimpse of perceptual diff
CIRCUIT 2015 - Glimpse of perceptual diff
 
CIRCUIT 2015 - Orchestrate your story with interactive video and web content
CIRCUIT 2015 -  Orchestrate your story with interactive video and web contentCIRCUIT 2015 -  Orchestrate your story with interactive video and web content
CIRCUIT 2015 - Orchestrate your story with interactive video and web content
 
How to migrate from any CMS (thru the front-door)
How to migrate from any CMS (thru the front-door)How to migrate from any CMS (thru the front-door)
How to migrate from any CMS (thru the front-door)
 
Maximize the power of OSGi in AEM
Maximize the power of OSGi in AEM Maximize the power of OSGi in AEM
Maximize the power of OSGi in AEM
 
CIRCUIT 2015 - 10 Things Apache Sling Can Do
CIRCUIT 2015 - 10 Things Apache Sling Can DoCIRCUIT 2015 - 10 Things Apache Sling Can Do
CIRCUIT 2015 - 10 Things Apache Sling Can Do
 
Circuit 2015 Keynote - Carsten Ziegeler
Circuit 2015 Keynote -  Carsten ZiegelerCircuit 2015 Keynote -  Carsten Ziegeler
Circuit 2015 Keynote - Carsten Ziegeler
 

Recently uploaded

SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfSeasiaInfotech2
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesZilliz
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 

Recently uploaded (20)

SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdf
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector Databases
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 

CIRCUIT - Content APIs for AEM

  • 1. CIRCUIT – An Adobe Developer Event Presented by ICF Interactive Content API’s for AEM Bryan Williams ICF Interactive
  • 2. Bryan.Williams@icfi.com @brywilliams Bryan Williams ICF Interactive (10+ years) Working with CQ/AEM over 6 years AEM Developer Certified (Beta)
  • 4.
  • 5. What do I mean by Content API? •  Read only •  Controlled •  Possibly public but not necessarily •  Usually an afterthought •  Disclaimer: Not production code
  • 6.
  • 7. 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
  • 8. 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/
  • 9. 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)
  • 10. Sling Models •  Automates mapping of Sling objects such as resources, request, etc. to POJOs •  Available out of the box in AEM 6
  • 11. Why Jackson? •  Popular •  Able to produce JSON or XML •  Lots of features •  We were already using it
  • 12. Groovy •  An object-oriented programming language for the Java platform •  Dynamic in nature •  Reduced syntax •  Traits
  • 14. 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.)
  • 15. 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
  • 17. 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  
  • 18. 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  
  • 19. 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  
  • 20. 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  
  • 21. 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  
  • 23. 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  
  • 24. 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  
  • 26. 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
  • 27. 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  
  • 28. 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  
  • 29. 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  
  • 30. 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  
  • 31. 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  
  • 32. 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  
  • 33. 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  
  • 34. 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  
  • 35. 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  
  • 36. Content API Service •  Calls repository layer •  (Guava) caching
  • 37. 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  
  • 38. Content API Repository Layer •  Constructs proper QueryBuilder query •  Instantiates appropriate models from results
  • 39. 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  
  • 40. 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  
  • 41. 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  
  • 42. StorySearchResult @EqualsAndHashCode @XmlRootElement(name = "result") class StorySearchResult { List<Story> stories long totalResults long start } Bedrock    CQ  Component  Plugin   Sling  Models    Jackson  
  • 43. 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  
  • 44. 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  
  • 45. 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 }
  • 46. 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 }
  • 47. 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 } }
  • 48. Versioning •  Like I said, not OOTB for Jackson •  Need something like @Since in GSON •  Jackson Filters
  • 50. Testing with Prosper •  Integration testing using Spock/Groovy •  Specifically for AEM testing •  Builders for Nodes/Pages •  Uses Sling Mocks
  • 51. Publish vs Author •  May want internal apps to access author •  replicatedDate is not what it seems
  • 52. 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