Article summary

Summary

Discusses how to create an Adobe Experience Manager application that persists data in the JCR. This article uses an OSGi bundle that uses the JCR API to persist data. In addition, a custom form action is used to submit data to the Experience Manager Service. 

System users cannot be used to log in normally, only by background processes. The admin user is not a system user, so you cannot use the admin user in a service user mapping like this. You have to create a new system user and assign them the appropriate permissions.If you would like to read more of the background on this change, take a look at https://issues.apache.org/jira/browse/SLING-3854.

Digital Marketing Solution(s) Adobe Experience Manager
Audience
Developer (intermediate)
Required Skills
Java, JQuery, JCR API
AEM Versions(s) Adobe Experience Manager 6.4
Video  

Download

Introduction

You can create an Adobe Experience Manager 6.4 application that persists application data in the Java Content Repository (JCR). When storing data within the JCR, each record can be saved as a separate node. A node can contain properties that store data values. To store data in Nodes, you can use the Node API.  

For example, assume that your Experience Manager application stores your organization’s employee data. Each node can represent a different employee. In this article, a basic form is built by using Experience Manager Form components. 

clientA
An Experience Manager Form

The following illustration shows employee nodes. Each time the form is submitted, a new node is created. 

employees
Employee nodes in the JCR

Each node contains properties that store employee details, such as the name value.

employees1
Employees data stored in the JCR

Application logic that persists data from the JCR is implemented as an OSGi bundle that is built using Declarative Services (DS) and Maven. DS is used to inject a ResourceResolverFactory instance into the service. The OSGi bundle is a managed component, which means that the OSGi service container creates the ResourceResolverFactory instance.

Create an Experience Manager System User

Create an Experience Manager System User that can access JCR data located at content/employees. The user account must have privileges, as shown in the following illustration.  

Permissions
User permissions to the JCR

Create a System user account

To successfully query JCR data, create an Experience Manager System user by performing these tasks. 

1. Open http://localhost:4502/crx/explorer/index.jsp.

2. Login as admin.

3. Click User Administration.

4. Click Create System User. Name the user data (data is used in this article).

5. Set the UserId. 

6. Click Save

7.  Access the user page at http://localhost:4502/useradmin. 

8.  Select the data user.

9. From the right-hand pane, select the Permissions tab. 

10. Expand the content tab and then select the employees row. 

11. Click all the checkboxes that represent the permissions (click the top row in permissions for this example). 

12. Click the Save button located in the top menu bar (located above the Path heading).  

Configure the Sling Mapper Service

The next step is to configure the Apache Sling Service User Mapper service by adding a new entry. Enter the following value:

AEM64_PersistJCR.core:datapersist=data

where:

  • AEM64_PersistJCR.core – is the Bundle-SymbolicName value of the OSGi bundle this is developed in the upcoming sections of this article.
  • core:datapersist– the name of the sub service (you reference this value in a Java Map object)
  • data – the system user account with data privileges for content/employees. 

The following illustration shows an entry for this service. 

mapping3
An entry in the Apache Sling Service Mapping Service

To create an entry in the Apache Sling Mapper service, perform these tasks:

1. Go to the Apache Sling Mapper service at http://localhost:4502/system/console/configMgr. 

2. Click OSGI, Configurations. 

2. Scroll to an entry named Apache Sling Service User Mapper Service.

3. Enter the value AEM64_PersistJCR.core:datapersist=data.

4. Click Save. 

Create an Experience Manager Maven 13 archetype project

You can create an Experience Manager archetype project by using the Maven archetype plugin. In this example, assume that the working directory is C:\AdobeCQ. 

Maven
Default files created by the Maven archetype plugin

To create an Experience Manager archetype project, perform these steps:

1. Open the command prompt and go to your working directory (for example, C:\AdobeCQ).

2. Run the following Maven command:

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=com.adobe.granite.archetypes -DarchetypeArtifactId=aem-project-archetype -DarchetypeVersion=13 -DarchetypeCatalog=https://repo.adobe.com/nexus/content/groups/public/

3. When prompted, specify the following information:

  • groupId - AEM64_PersistJCR
  • artifactId - AEM64_PersistJCR
  • version - 1.0-SNAPSHOT
  • package - com.adobe.persist
  • appsFolderName - AEM64_PersistJCR
  • artifactName - AEM64_PersistJCR
  • componentGroupName - AEM64_PersistJCR
  • contentFolderName - AEM64_PersistJCR
  • cssId - AEM64_PersistJCR
  • packageGroup - AEM64_PersistJCR
  • siteName - AEM64_PersistJCR

4. WHen prompted, specify Y.

5. Once done, you will see a message like:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:42 min
[INFO] Finished at: 2016-04-25T14:34:19-04:00
[INFO] Final Memory: 16M/463M
[INFO] ------------------------------------------------------------------------

6. Change the working directory to AEM64_PersistJCR and then enter the following command.

mvn eclipse:eclipse

After you run this command, you can import the project into Eclipse as discussed in the next section.

Add Java files to the Maven project using Eclipse

To make it easier to work with the Maven generated project, import it into the Eclipse development environment, as shown in the following illustration.

project
The Eclipse Import Project dialog

Add the following Java files to the com.adobe.persist.core package:

  • A Java interface named EmployeeInter.
  • A Java class named EmployeeImpl that implements the EmployeeInter interface.

EmployeeInter interface

The following code represents the EmployeeInter interface. This interface contains a method signature named  addEmployeeData. The implementation logic for this method is located in the EmployeeImpl class. 

The following Java code represents the EmployeeInter interface. 

package com.adobe.persist.core;

public interface EmployeeInter {
	 
   
    public void addEmployeeData(String address, String age,String job, String name, String salary, String start); 
    
 
}

EmployeeImpl class

The EmployeeImpl class uses the following declarative services annoations

  • @Component – defines the class as a component
  • @Reference – injects a service into the component. 

For information about these annotations, see Official OSGi Declarative Services Annotations in AEM.

In this development article, a ResourceResolverFactory instance is injected into the addEmployeeData method. This instance is required to create a Session instance that lets you persist data inot the JCR. To inject a ResourceResolverFactory instance, you use the @Reference annotation to define a class member, as shown in the following example.

//Inject a Sling ResourceResolverFactory
@Reference
private ResourceResolverFactory resolverFactory;

Within the addEmployeeData method, you reference the entry that you specified in the Apache Sling Mapper service. You use this to create a Session object, as shown here.

 //Persist data into the AEM JCR
      public void addEmployeeData(String address, String age,String job, String name, String salary, String start)
      {
      	 Map<String, Object> param = new HashMap<String, Object>();
         param.put(ResourceResolverFactory.SUBSERVICE, "datapersist");
         ResourceResolver resolver = null;
            
          
           try {
                         
               resolver = resolverFactory.getServiceResourceResolver(param);
               session = resolver.adaptTo(Session.class);

Note:

When you open a JCR session, there is a reference to the JCR repository object. Every session will consume some memory unless the logout() method is called explicitly. If you do not call this call and create lots of sessions, you risk an out-of-memory exception by your JVM, which terminates the CQ instance. A single leaked session isn’t a problem, but if you have hundreds or thousands of leaked sessions, it might turn into a problem. For more information, see CQ development patterns – Sling ResourceResolver and JCR sessions.

The following Java code represents the EmployeeImpl  class. For each employee record that is submitted, a new node is created at /content/employees by using the Node API.

package com.adobe.persist.core;

//DS Annotations
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference; 


import org.w3c.dom.Document;
import org.w3c.dom.Element;
   
   
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
   
import javax.jcr.Repository; 
import javax.jcr.SimpleCredentials; 
import javax.jcr.Node; 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.HashMap; 
import java.util.Map; 

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

//QUeryBuilder APIs
import com.day.cq.search.QueryBuilder; 
import com.day.cq.search.Query; 
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.result.SearchResult;
import com.day.cq.search.result.Hit; 


//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceResolver; 
//import org.apache.sling.api.resource.Resource; 


import org.apache.jackrabbit.commons.JcrUtils;
import javax.jcr.Session;
import javax.jcr.Node; 


@Component
public class EmployeeImpl implements EmployeeInter {
   
  /** Default log. */
  protected final Logger log = LoggerFactory.getLogger(this.getClass());
           
  private Session session;
   
  @Reference
  private QueryBuilder builder;
               
  //Inject a Sling ResourceResolverFactory
  @Reference
  private ResourceResolverFactory resolverFactory;
   
       
      //Persist data into the AEM JCR
      public void addEmployeeData(String address, String age,String job, String name, String salary, String start)
      {
      	 Map<String, Object> param = new HashMap<String, Object>();
         param.put(ResourceResolverFactory.SUBSERVICE, "datapersist");
         ResourceResolver resolver = null;
            
          
           try {
                         
               //Invoke the adaptTo method to create a Session used to create a QueryManager
               resolver = resolverFactory.getServiceResourceResolver(param);
               session = resolver.adaptTo(Session.class);
               
               //Create a node that represents the root node
               Node root = session.getRootNode(); 
               
               log.info("IN EMPLOYEE DATA");  
               
             //Get the content node in the JCR
               Node content = root.getNode("content/employees");
               
              //For this demo - create a random number
               java.util.Random r = new java.util.Random();
               int low = 10;
               int high = 1000;
               int result = r.nextInt(high-low) + low;
               
               String numberValue = "employee"+result; //assign a randon value to node name 
                            
              //content/customer does not exist -- create it
               Node employeeRoot = content.addNode(numberValue,"nt:unstructured");
               
               employeeRoot.setProperty("address", address); 
               employeeRoot.setProperty("age", age); 
               employeeRoot.setProperty("job", job); 
               employeeRoot.setProperty("name", name); 
               employeeRoot.setProperty("salary", salary); 
               employeeRoot.setProperty("start", start); 
               employeeRoot.setProperty("status", "employee"); 
               
            // Save the session changes and log out
               session.save(); 
               session.logout();
               
           }
           catch(Exception e)
           {
          	 e.printStackTrace();
           }
      }
      
   }

Note:

Before running this Java code, ensure that you install the employee package that is located at the start of this article. 

Modify the Maven POM file

Add the following POM dependency to the POM file located at C:\AdobeCQ\AEM64_PersistJCR.

<dependency>
    <groupId>com.adobe.aem</groupId>
    <artifactId>uber-jar</artifactId>
    <version>6.4.0</version>
    <classifier>apis</classifier>
    <scope>provided</scope>
</dependency>
               
  <dependency>
       <groupId>org.apache.geronimo.specs</groupId>
       <artifactId>geronimo-atinject_1.0_spec</artifactId>
       <version>1.0</version>
       <scope>provided</scope>
   </dependency>

When you add new Java classes under core, you need to modify a POM file to successfully build the OSGi bundle. You modify the POM file located at C:\AdobeCQ\AEM64_PersistJCR\core. The following code represents this POM file.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 |  Copyright 2017 Adobe Systems Incorporated
 |
 |  Licensed under the Apache License, Version 2.0 (the "License");
 |  you may not use this file except in compliance with the License.
 |  You may obtain a copy of the License at
 |
 |      http://www.apache.org/licenses/LICENSE-2.0
 |
 |  Unless required by applicable law or agreed to in writing, software
 |  distributed under the License is distributed on an "AS IS" BASIS,
 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 |  See the License for the specific language governing permissions and
 |  limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>AEM64_PersistJCR</groupId>
        <artifactId>AEM64_PersistJCR</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>AEM64_PersistJCR.core</artifactId>
    <packaging>bundle</packaging>
    <name>AEM64_PersistJCR - Core</name>
    <description>Core bundle for AEM64_PersistJCR</description>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.sling</groupId>
                <artifactId>maven-sling-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <!-- Import any version of javax.inject, to allow running on multiple versions of AEM -->
                        <Import-Package>javax.inject;version=0.0.0,*</Import-Package>
                        <Sling-Model-Packages>
                            com.adobe.persist.core
                        </Sling-Model-Packages>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- OSGi Dependencies -->
        <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
     
        <dependency>
            <groupId>org.apache.geronimo.specs</groupId>
            <artifactId>geronimo-atinject_1.0_spec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.cmpn</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.annotation</artifactId>
        </dependency>
        <!-- Other Dependencies -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.jcr</groupId>
            <artifactId>jcr</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.models.api</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </dependency>
        <dependency>
            <groupId>junit-addons</groupId>
            <artifactId>junit-addons</artifactId>
        </dependency>
    </dependencies>
</project>

Build the OSGi bundle using Maven

To build the OSGi bundle by using Maven, perform these steps:

  1. Open the command prompt and go to the C:\AdobeCQ\AEM64_PersistJCR.
  2. Run the following maven command: mvn -PautoInstallPackage install.
  3. The OSGi component can be found in the following folder: C:\AdobeCQ\AEM64_PersistJCR\core\target. The file name of the OSGi component is AEM64_PersistJCR.core-1.0-SNAPSHOT.jar.

The command -PautoInstallPackage automatically deploys the OSGi bundle to Experience Manager.

After you deploy the OSGi bundle, you will be able to see it in the Apache Felix Web Console (http://localhost:4502/system/console/configMgr).

OSGi
Apache Felix Web Console Bundles view

Build the AEM Form using Form components

Build the Experience Manager form by using Form components. In this article, the Form is built by using the Touch UI and dragging and dropping the form components from the side rail to the Experience Manager page, as shown in the following illustration. 

MoveFields1
A form Text field component being dragged onto an AEM page

Ensure form components show up in page

When you build the form, you may notice that components do not show up in the side rail. To address this situtation, you need to add Form components to the editable template policy. Refer to the video at the end of this article for information on how to perform this use case.

FormA
Ensure Form components are added to the policy of the editable template

Create the form

Create a form located in an Experience Manager page. Perform these tasks:

1. Click the Adobe Experience Manager in the upper left corner.

2. Navigate to Sites.

3. Click Create, page. 

4. Select AEM64_PersistJCR, English,  as shown in this illustration. 

FormB
The AEM64_PersistJCR page

5. Drag and Drop the Form Container component into the page.

FormC
Add the Form Container component

6. Drag six text fields onto your form named:

  •  formName - lets the user enter the employee name
  • formPosition- lets the user enter the employee position
  • formOffice - lets the user enter the employee office
  • formAge - lets the user enter the employee age
  • formDate - lets the user enter the employee start date 
  • formSal - lets the user enter the employee salary  

7. Drop the Submit button onto the form

Create a custom form action for the Experience Manager form

Create a custom form action for the form so the posted data is sent to the EmployeeImpl service that you created. To create a custom form action, you setup nodes and properties within the AEM repository. In addition, you define a JSP file named post.POST.jsp. When the user fills out the form and clicks the submit button, form data is posted to the post.POST.jsp. This JSP captures the submitted data and passes the data to a custom service defined within an OSGi bundle.

CA
A custom form action defined in the JCR

To create a custom form action, perform these tasks:

1. Log in to CRXDE Lite at http://{server}:{port}/crx/de/index.jsp. Create a node with the property sling:Folder with the name customFormAction in the /apps/AEM64_PersistJCR/components/content.

2. Add the following property to the customFormAction node:

  • sling:resourceType (String) - foundation/components/form/action
  • componentGroup (String ) - .hidden

3. Under customFormAction, create a file named post.POST.jsp. Add the following code.

<%@include file="/libs/fd/af/components/guidesglobal.jsp" %>
<%@include file="/libs/foundation/global.jsp"%>
<%@page import="com.day.cq.wcm.foundation.forms.FormsHelper,
             org.apache.sling.api.resource.ResourceUtil,
             org.apache.sling.api.resource.ValueMap" %>
<%@taglib prefix="sling"
                uri="http://sling.apache.org/taglibs/sling/1.0" %>
<%@taglib prefix="cq"
                uri="http://www.day.com/taglibs/cq/1.0"
%>
<cq:defineObjects/>
<sling:defineObjects/>
<%
  
     String formName = request.getParameter("formName"); 
     String formPosition = request.getParameter("formPosition"); 
 	 String formOffice = request.getParameter("formOffice"); 
	 String formAge = request.getParameter("formAge");
     String formDate = request.getParameter("formDate"); 
	 String formSalary = request.getParameter("formSal");  
 
   com.adobe.persist.core.EmployeeInter data = sling.getService(com.adobe.persist.core.EmployeeInter.class);
   data.addEmployeeData(formOffice, formAge,formPosition, formName, formSalary, formDate);


%>

This code captures the posted form fields that are submitted from the form. It then creates an instance of com.adobe.persist.core.EmployeeInter object. Finally it invokes the EmployeeInter object's addEmployeeData() method and passes posted form values.

4. Open the Form again in Edit view. Click on Form Start and click the wrench icon. In the dialog, click the Advanced Tab. Select customFormAction from the drop-down list.

CA1
The custom form action showing up in the Form Action drop-down

5. Click Ok.

Submit data to the Experience Manager JCR

To submit data to the Experience Manager JCR, enter the following URL:

http://localhost:4502/editor.html/content/AEM64_PersistJCR/en.html

Enter values and click the Submit button. The following video shows this use case.


Note:

If you do not create a system user and configure the Sling Mapping Service, as described in this article, this example application does not work.  

See also

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy