Article summary

Summary

Discusses how to integrate Adobe Experience Manager 6.4 and Solr server. This article uses DS (R6) annotations. 

Also a special thank you to community member Lokesh BS for testing this Community Article to ensure it works.

Digital Marketing Solution(s) Adobe Experience Manager (Adobe CQ)
Audience
Developer (intermediate)
Required Skills
JCR nodes, JavaScript, HTML
Version Adobe Experience Manager 6.4
Video https://youtu.be/QnbLVljYNao

Download

Note:

To watch an Ask the AEM Commuity Experts session on this use case, see https://helpx.adobe.com/experience-manager/kt/eseminars/ask-the-expert/aem-apache-solr.html?cq_ck=1543364758613.

Note:

Install this AEM Package by using Package Manager. It contains the OSGi bundle already. You can skip the section in this article that talks about How TO build the OSGi bundle. Or you can build it to learn using Solr Java APIs. But this package is required to install to follow this article. 

If you do build the OSGi bundle, then simply replace the OSGi bundle with the one you build. As mentioned in the video located in this article, you need to whitelist this OSGi bundle - SolrAem.core.

Introduction

You can integrate SOLR with Adobe Experience Manager 6.4 to use searching. Solr is the popular, blazing-fast, open source enterprise search platform built on Apache Lucene. Solr is highly reliable, scalable and fault tolerant, providing distributed indexing, replication and load-balanced querying, automated fail-over and recovery, centralized configuration and more. Solr powers the search and navigation features of many of the world's largest internet sites. For more information, see Solr.

The following illustration shows the SOLR client.

SOLRClient
The SOLR web client

This community article walks you though how to integrate Experience Manager and SOLR so that an Experience Manager component can use SOLR to perform searches, as shown in the following illustration. 

Component
An Experience Manager 6.4 component uses SOLR to perform searches on We Retail content

The following are benefits of Solr:

• Full control over the Solr document model
• Control over boosting specific fields in Solr document
• Real time indexing is within your control
• Comes handy when multiple heterogeneous systems are contributing for indexing

The article creates an OSGi bundle that uses classes located in the org.apache.solr.client.solrj.impl.HttpSolrClient package. This OSGi bundle performs tasks related to indexing values with the Solr server (this is performed later in this article). For information about these API, see Class HttpSolrClient.

Create an Experience Manager Maven Project

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

maven
Default files created by an Adobe Maven Archetype project

To create an AEM archetype 13 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 - AemSolr2
  • artifactId - AemSolr2
  • version - 1.0-SNAPSHOT
  • package - com.adobe.aem
  • appsFolderName - AemSolr2
  • artifactName - AemSolr2
  • componentGroupName - AemSolr2
  • contentFolderName - AemSolr2
  • cssId - AemSolr2
  • packageGroup - AemSolr2
  • siteName - AemSolr2

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 AemSolr2 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.

Import the project into 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. 

project2
Default project files created by using a Maven Archetype

Note:

If desired, downlaod a ZIP file of this Java project, including the configured POM files here. (Instead of copying all of these Java files. Simply extract the ZIP and build the MAVEN project.)

The next step is to add these Java files to the project:

  • SolrSearchService - a Java interface added to the com.adobe.aem.core package. 
  • SolrServerConfiguration - a Java interface added to the com.adobe.aem.core package
  • SolrSearchServiceImpl - a Java class that implements SolrSearchService
  • SolrServerConfigurationImpl -a Java class that implements  SolrServerConfiguration
  • IndexContentToSolr - a servlet that indexes Solr content
  • DeleteIndexesFromSolr - a servlet that deletes indexes from Solr
  • SolrUtils - a utlity Java class 
  • MySimpleService - a Java class required to read OSGi configuration values

SolrSearchService

The SolrSearchService is a Java interface that defines the service's operations. Add the following Java interface to the com.adobe.aem.core package. 

package com.adobe.aem.core;

import java.io.IOException;

import javax.jcr.RepositoryException;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;

import com.day.cq.search.result.SearchResult;

public interface SolrSearchService {

	JSONArray crawlContent(String resourcePath, String resourceType);

	JSONArray createPageMetadataArray(SearchResult results)
			throws RepositoryException;

	JSONObject createPageMetadataObject(Resource pageContent);

	boolean indexPageToSolr(JSONObject indexPageData, HttpSolrClient server)
			throws JSONException, SolrServerException, IOException;

	boolean indexPagesToSolr(JSONArray indexPageData, HttpSolrClient server)
			throws JSONException, SolrServerException, IOException;

}

SolrServerConfiguration

The SolrServerConfiguration is a Java interface that defines the service's operations. Add the following Java interface to the com.adobe.aem.core package. 

package com.adobe.aem.core;

public interface SolrServerConfiguration {
	
	
	public String getSolrProtocol();
	 
	public String getSolrServerName() ;

	public String getSolrServerPort() ;

	public String getSolrCoreName() ;
	
	public String getContentPagePath() ;

}

MySimpleService

The following Java code represents the MySimpleService interface. Notice the name of the configuration is AEM Solr Search - Solr Configuration Service (this value is displayed under the configuration view). Also, for each configuration value that is defined, an @AttributeDefinition annotation is specified.

package com.adobe.aem.core;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

 
@ObjectClassDefinition(name = "AEM Solr Search - Solr Configuration Service", description = "Service Configuration")
public @interface MySimpleService {
     
	@AttributeDefinition(name = "Protocol", defaultValue ="http", description = "Configuration value")
    String protocolValue();
	
	
	@AttributeDefinition(name = "Solr Server Name", defaultValue ="localhost", description = "Server name or IP address")
    String serverName();
    
	@AttributeDefinition(name = "Solr Server Port", defaultValue ="8983", description = "Server port")
    String serverPort();
	
	@AttributeDefinition(name = "Solr Core Name", defaultValue ="collection", description = "Core name in solr server")
    String serverCollection();
	
	@AttributeDefinition(name = "Content page path", defaultValue ="/content/we-retail", description = "Content page path from where solr has to index the pages")
    String serverPath();
     
}

SolrSearchServiceImpl

The SolrSearchServiceImpl is a Java implementation class. Add the following Java code to the class located in the com.adobe.aem.core.impl package. 

package com.adobe.aem.core.impl;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.aem.core.SolrSearchService;
import com.adobe.aem.core.SolrServerConfiguration;
import com.adobe.aem.core.utils.SolrUtils;
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;

@Component
public class SolrSearchServiceImpl implements SolrSearchService {

	private static final Logger LOG = LoggerFactory
			.getLogger(SolrSearchServiceImpl.class);

	@Reference
	private QueryBuilder queryBuilder;

	@Reference
	private SlingRepository repository;

	@Reference
	SolrServerConfiguration solrConfigurationService;

	/**
	 * This method takes path and type of resource to perform search in JCR
	 * 
	 * @param resourcePath
	 * @param resourceType
	 * @return JSONArray with resources metadata
	 */
	@Override
	public JSONArray crawlContent(String resourcePath, String resourceType) {

		Map<String, String> params = new HashMap<String, String>();
		params.put("path", resourcePath);
		params.put("type", resourceType);
		params.put("p.offset", "0");
		params.put("p.limit", "10000");

		Session session = null;

		try {
			session = repository.loginAdministrative(null);
			Query query = queryBuilder.createQuery(
					PredicateGroup.create(params), session);
			
			SearchResult searchResults = query.getResult();
			
			LOG.info("Found '{}' matches for query",
					searchResults.getTotalMatches());
			if (resourceType.equalsIgnoreCase("cq:PageContent")) {
				return createPageMetadataArray(searchResults);
			}
			
		} catch (RepositoryException e) {
			LOG.error("Exception due to", e);
		} finally {
			if(session != null && session.isLive()) {

                session.logout();

            }
		}
		return null;

	}

	/**
	 * This method takes search result of content pages and creates a JSON array
	 * object with properties
	 * 
	 * @param results
	 * @return
	 * @throws RepositoryException
	 */
	@Override
	public JSONArray createPageMetadataArray(SearchResult results)
			throws RepositoryException {
		JSONArray solrDocs = new JSONArray();
		for (Hit hit : results.getHits()) {
			Resource pageContent = hit.getResource();
			ValueMap properties = pageContent.adaptTo(ValueMap.class);
			String isPageIndexable = properties.get("notsolrindexable",
					String.class);
			if (null != isPageIndexable && isPageIndexable.equals("true"))
				continue;
			JSONObject propertiesMap = createPageMetadataObject(pageContent);
			solrDocs.put(propertiesMap);
		}

		return solrDocs;

	}

	/**
	 * This method creates JSONObject which has all the page metadata which is used to index in Solr server
	 * @param It takes resource of type cq:PageContent to extract the page metadata
	 * @return Json object with page's metadata
	 */
	@Override
	public JSONObject createPageMetadataObject(Resource pageContent) {
		Map<String, Object> propertiesMap = new HashMap<String, Object>();
		propertiesMap.put("id", pageContent.getParent().getPath());
		propertiesMap.put("url", pageContent.getParent().getPath() + ".html");
		ValueMap properties = pageContent.adaptTo(ValueMap.class);
		String pageTitle = properties.get("jcr:title", String.class);
		if (StringUtils.isEmpty(pageTitle)) {
			pageTitle = pageContent.getParent().getName();
		}
		propertiesMap.put("title", pageTitle);
		propertiesMap.put("description", SolrUtils.checkNull(properties.get(
				"jcr:description", String.class)));
		propertiesMap.put("publishDate", SolrUtils.checkNull(properties.get(
				"publishdate", String.class)));
		propertiesMap.put("body","");
		propertiesMap.put("lastModified", SolrUtils.solrDate(properties.get(
				"cq:lastModified", Calendar.class)));
		propertiesMap.put("contentType", "page");
		propertiesMap.put("tags", SolrUtils.getPageTags(pageContent));
		return new JSONObject(propertiesMap);
	}


	/**
	 * This method connects to the Solr server and indexes page content using Solrj api. This is used by bulk update handler (servlet)
	 * @param Takes Json array and iterates over each object and index to solr
	 * @return boolean true if it indexes successfully to solr server, else false. 
	 */
	@Override
	public boolean indexPagesToSolr(JSONArray indexPageData,HttpSolrClient server) throws JSONException, SolrServerException,
			IOException {
		
		if (null != indexPageData) {

			for (int i = 0; i < indexPageData.length(); i++) {
				JSONObject pageJsonObject = indexPageData.getJSONObject(i);
				SolrInputDocument doc = createPageSolrDoc(pageJsonObject);
				server.add(doc);
			}
			server.commit();
			return true;
		}

		return false;
	}

	/**
	 * This method connects to the Solr server and indexes page content using Solrj api. This is used by transport handler
	 * @param Takes Json object and index to solr
	 * @return boolean true if it indexes successfully to solr server, else false. 
	 */
	@Override
	public boolean indexPageToSolr(JSONObject indexPageData,HttpSolrClient server) throws JSONException, SolrServerException,
			IOException {
		if (null != indexPageData) {
			SolrInputDocument doc = createPageSolrDoc(indexPageData);
			server.add(doc);
			server.commit();
			return true;
		}

		return false;
	}

	
	private SolrInputDocument createPageSolrDoc(JSONObject pageJsonObject) throws JSONException {
		
		SolrInputDocument doc = new SolrInputDocument();
		doc.addField("id", pageJsonObject.get("id"));
		doc.addField("title", pageJsonObject.get("title"));
		doc.addField("body", pageJsonObject.get("body"));
		doc.addField("url", pageJsonObject.get("url"));
		doc.addField("description", pageJsonObject.get("description"));
		doc.addField("lastModified", pageJsonObject.get("lastModified"));
		doc.addField("contentType", pageJsonObject.get("contentType"));
		doc.addField("tags", pageJsonObject.get("tags"));
		return doc;

	}


}

Note:

For code brevity, this code example uses repository.loginAdministrative(null) rmethod. This is used simply to keep the code shorter. If you are using this in Production, the recommended way is to create a system user and use the Sling Mapping Service. For more information, see Querying Adobe Experience Manager 6 data using the Sling getServiceResourceResolver method.

To ensure that the code works, you must whitelist the AEM bundle. Use the Symbolic name of the OSGi bundle. For more information, see https://forums.adobe.com/thread/2355506

SolrServerConfigurationImpl

The following Java code represents the SolrServerConfigurationImpl class. 

package com.adobe.aem.core.impl;

import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;

import com.adobe.aem.core.SolrServerConfiguration ; 
import com.adobe.aem.core.MySimpleService; 
 
 
@Component(service = SolrServerConfiguration.class,configurationPolicy=ConfigurationPolicy.REQUIRE)
@Designate(ocd = MySimpleService.class)
public class SolrServerConfigurationImpl implements SolrServerConfiguration {
     
    // to use the OSGi annotations
    // use version 3.2.0 of maven-bundle-plugin
 
    private MySimpleService config;
    
    private String solrProtocol;
    
    private String solrServerName ;

	private String solrServerPort ;

	private String solrCoreName ;
	
	private String contentPagePath ;
    
     

    @Activate
    public void activate(MySimpleService config) {
        this.config = config;
        
        
        //Populate the solrProtocol data member
        this.solrProtocol = config.protocolValue();
        this.solrServerName = config.serverName();
        this.solrServerPort = config.serverPort(); 
        this.solrCoreName = config.serverCollection(); 
        this.contentPagePath = config.serverPath(); 
        
    }
 
         
    public String getSolrProtocol() {
		return this.solrProtocol;
	}
    
    public String getSolrServerName() {
		return this.solrServerName;
	}

	public String getSolrServerPort() {
		return this.solrServerPort;
	}

	public String getSolrCoreName() {
		return this.solrCoreName;
	}
	
	public String getContentPagePath() {
		return this.contentPagePath;
	}
 
}

SolrUtils class

The SolrUtils class contains helper methods.  

package com.adobe.aem.core.utils;

import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;

import com.day.cq.tagging.Tag;
import com.day.cq.wcm.api.Page;

public final class SolrUtils {
	// private constructor to avoid unnecessary instantiation of the class
	private SolrUtils() {
	}


	/**
	 * This method is used to extract the tags from the content page
	 * 
	 * @param pageContent
	 * @return Array of tags which are attached to the page. Empty array if no
	 *         tags are attached
	 */
	public static String[] getPageTags(Resource pageContent) {
		Page page = pageContent.getParent().adaptTo(Page.class);
		Tag tags[] = page.getTags();
		String tagsArray[] = new String[tags.length];
		for (int i = 0; i < tags.length; i++) {
			Tag tag = tags[i];
			tagsArray[i] = tag.getTitle();
		}
		return tagsArray;
	}


	/**
	 * This method converts jcr formatted date to Solr specification format
	 * 
	 * @param Takes input as Calendar
	 * @return Solr formatted date of type string
	 */
	public static String solrDate(Calendar cal) {
		SimpleDateFormat dateFormat = new SimpleDateFormat(
				"YYYY-MM-DD'T'hh:mm:ss");
		return dateFormat.format(cal.getTime()) + "Z";
	}

	/**
	 * This method returs "" if string is null.
	 * 
	 * @param Takes
	 *            input as string
	 * @return String value. if string value is "null" then ""
	 */
	public static String checkNull(String property) {
		if (StringUtils.isEmpty(property)) {
			return "";
		}
		return property;

	}

}

IndexContentToSolr Servlet

The following Java code represents the IndexContentToSolr sling servlet. This servlet acts as a bulk update to index content pages and assets to the configured Solr server.

package com.adobe.aem.core.servlets;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.osgi.framework.Constants;
import org.apache.sling.api.servlets.HttpConstants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.aem.core.SolrSearchService;
import com.adobe.aem.core.SolrServerConfiguration;

/**
 * 
 * This servlet acts as a bulk update to index content pages and assets to the
 * configured Solr server
 *
 */

@Component(service=Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Solr Index Servlet",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.paths="+ "/bin/solr/push/pages"
           })		
public class IndexContentToSolr extends SlingAllMethodsServlet {

	private static final long serialVersionUID = 1L;
	private static final Logger LOG = LoggerFactory
			.getLogger(IndexContentToSolr.class);

	@Reference
	SolrServerConfiguration solrConfigurationService;

	@Reference
	SolrSearchService solrSearchService;

	@Override
	protected void doGet(SlingHttpServletRequest request,
			SlingHttpServletResponse response) throws ServletException,
			IOException {
		doPost(request, response);

	}

	@Override
	protected void doPost(SlingHttpServletRequest request,
			SlingHttpServletResponse response) throws ServletException,
			IOException {
		response.setContentType("text/html");
		String indexType = request.getParameter("indexType");
		final String protocol = solrConfigurationService.getSolrProtocol();
		final String serverName = solrConfigurationService.getSolrServerName();
		final String serverPort = solrConfigurationService.getSolrServerPort();
		final String coreName = solrConfigurationService.getSolrCoreName();
		final String pagesResourcePath = solrConfigurationService
				.getContentPagePath();
		String URL = protocol + "://" + serverName + ":" + serverPort
				+ "/solr/" + coreName;
		HttpSolrClient server = new HttpSolrClient(URL);
		
		if (indexType.equalsIgnoreCase("indexpages")) {
			try {
				JSONArray indexPageData = solrSearchService.crawlContent(pagesResourcePath, "cq:PageContent");
				
				boolean resultindexingPages = solrSearchService.indexPagesToSolr(indexPageData, server);
				if (resultindexingPages == true) {
					response.getWriter()
							.write("<h3>Successfully indexed content pages to Solr server </h3>");
				} else {
					response.getWriter().write("<h3>Something went wrong</h3>");
				}
			} catch (Exception e) {
				LOG.error("Exception due to", e);
				response.getWriter()
						.write("<h3>Something went wrong. Please make sure Solr server is configured properly in Felix</h3>");
			}

		} else {
			response.getWriter().write("<h3>Something went wrong</h3>");
		}

	}

}

DeleteIndexesFromSolr

The following Java code represents the DeleteIndexesFromSolr sling servlet. This servlet deletes all the indexes from the configured Solr server.

package com.adobe.aem.core.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.Servlet;
import org.osgi.framework.Constants;
import org.apache.sling.api.servlets.HttpConstants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.aem.core.SolrServerConfiguration;


/**
 * 
 * @author kavarana 
 * This servlet deletes all the indexes from the configured Solr server
 *
 */
@Component(service=Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Solr Delete Index",
                "sling.servlet.methods=" + HttpConstants.METHOD_POST,
                "sling.servlet.paths="+ "/bin/solr/delete/all/indexes"
           })		
public class DeleteIndexesFromSolr extends SlingAllMethodsServlet {
	private static final long serialVersionUID = 1L;
	private static final Logger LOG = LoggerFactory
			.getLogger(DeleteIndexesFromSolr.class);
	@Reference
	SolrServerConfiguration solrConfigurationService;
	
	@Override
    protected void doPost(final SlingHttpServletRequest reqest,
            final SlingHttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html");
		final String protocol = solrConfigurationService.getSolrProtocol();
		final String serverName = solrConfigurationService.getSolrServerName();
		final String serverPort = solrConfigurationService.getSolrServerPort();
		final String coreName = solrConfigurationService.getSolrCoreName();
		String URL = protocol + "://" + serverName + ":" + serverPort
				+ "/solr/" + coreName;
		HttpSolrClient server = new HttpSolrClient(URL);
		try {
			server.deleteByQuery("*:*");
			server.commit();
			server.close();
			response.getWriter().write("<h3>Deleted all the indexes from solr server </h3>");
		} catch (SolrServerException e) {
			LOG.error("Exception due to", e);
		}
		
        
    }
}

Modify the Maven POM file

Add the following POM dependencies to the POM file located at C:\AdobeCQ\AemSolr2.

<!-- ====================================================================== -->
    <!-- D E P E N D E N C I E S                                                -->
    <!-- ====================================================================== -->
    <dependencyManagement>
         <dependencies>
            <!-- OSGi Dependencies -->
            <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>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.3.2</version>
                <scope>provided</scope>
            </dependency>
             <dependency>
                <groupId>org.apache.servicemix.bundles</groupId>
                <artifactId>org.apache.servicemix.bundles.solr-solrj</artifactId>
                <version>5.4.1_1</version>
            </dependency>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>osgi.core</artifactId>
                <version>6.0.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>osgi.cmpn</artifactId>
                <version>6.0.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>osgi.annotation</artifactId>
                <version>6.0.1</version>
                <scope>provided</scope>
            </dependency>
            <!-- Logging Dependencies -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.5.11</version>
                <scope>provided</scope>
            </dependency>
            <!-- Adobe AEM Dependencies -->
            <dependency>
                <groupId>com.adobe.aem</groupId>
                <artifactId>uber-jar</artifactId>
                <version>6.3.0</version>
                <classifier>apis</classifier>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.adobe.cq</groupId>
                <artifactId>core.wcm.components.all</artifactId>
                <type>zip</type>
                <version>2.0.4</version>
            </dependency>
            <!-- Apache Sling Dependencies -->
            <dependency>
                <groupId>org.apache.sling</groupId>
                <artifactId>org.apache.sling.models.api</artifactId>
                <version>1.0.0</version>
                <scope>provided</scope>
            </dependency>
            <!-- Servlet API -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.1</version>
                <scope>provided</scope>
            </dependency>
            <!-- JCR -->
            <dependency>
                <groupId>javax.jcr</groupId>
                <artifactId>jcr</artifactId>
                <version>2.0</version>
                <scope>provided</scope>
            </dependency>
            <!-- Taglibs -->
            <dependency>
                <groupId>com.day.cq.wcm</groupId>
                <artifactId>cq-wcm-taglib</artifactId>
                <version>5.7.4</version>
                <scope>provided</scope>
            </dependency>

            <!-- Testing -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-simple</artifactId>
                <version>1.5.11</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-core</artifactId>
                <version>2.7.22</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit-addons</groupId>
                <artifactId>junit-addons</artifactId>
                <version>1.4</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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\AemSolr2\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>AemSolr2</groupId>
        <artifactId>AemSolr2</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>AemSolr2.core</artifactId>
    <packaging>bundle</packaging>
    <name>AemSolr2 - Core</name>
    <description>Core bundle for AemSolr2</description>
     <build>
		<plugins>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-scr-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<extensions>true</extensions>
				<configuration>
					<instructions>
						<!-- <Embed-Dependency> artifactId1, artifactId2;inline=true </Embed-Dependency> -->
						<Import-Package>
							!com.ibm.*,
							!com.sun.*,
							!javax.jmdns.*,
							!javax.jms.*,
							!jline.*,
							!org.jboss.netty.*,
							!org.relaxng.datatype.*,
							*
						</Import-Package>
						<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
						<Embed-Directory>OSGI-INF/lib</Embed-Directory>
						<Embed-Transitive>false</Embed-Transitive>
						<Sling-Model-Packages>
							com.adobe.aem.core
						</Sling-Model-Packages>
					</instructions>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<profiles>
		<!-- Development profile: install only the bundle -->
		<profile>
			<id>autoInstallBundle</id>
			<activation>
				<activeByDefault>false</activeByDefault>
			</activation>
			<build>
				<plugins>
					<plugin>
						<groupId>org.apache.sling</groupId>
						<artifactId>maven-sling-plugin</artifactId>
						<configuration>
							<!-- Note that this requires /apps/solr/install to exist!! -->
							<!-- This is typically the case when ui.apps is deployed first -->
							<!-- Otherwise, create /apps/solr/install manually (CRXDE|Lite) -->
							<slingUrlSuffix>/apps/solr/install/</slingUrlSuffix>
							<failOnError>true</failOnError>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
	</profiles>
	<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>
        <!-- Solr Dependencies -->
        <dependency>
            <groupId>org.apache.servicemix.bundles</groupId>
            <artifactId>org.apache.servicemix.bundles.solr-solrj</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.noggit</groupId>
            <artifactId>noggit</artifactId>
            <version>0.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.5</version>
        </dependency>
    </dependencies>
</project>

Note:

The POM files contains the dependencies you need to build the OSGi bundle. 

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 C:\AdobeCQ\AemSolr2.
  2. Run the following maven command: mvn -PautoInstallPackage install.
  3. The OSGi component can be found in the following folder: C:\AdobeCQ\AemSolr2\core\target. The file name of the OSGi component is AemSolr2.core-1.0-SNAPSHOT.jar.

The command -PautoInstallPackage automatically deploys the OSGi bundle to AEM.

Setup the Solr Server

Download and install the Solr server (solr-6.2.0.zip ) from the following URL:

http://archive.apache.org/dist/lucene/solr/

Ensure that you select version 6.2.0, as shown in the following illustration.

solrserverfiles
Solr server files

Note:

To install Solr server, you need to have Java 1.8 installed.  

Unzip the file so that you are able to view the following file structure.

zip
The contents of the Solr zip file

You can start the Solr server by opening the command prompt and changing the working directory to the bin folder located in the directory in which you extracted the ZIP file. Run the following command:

solr start -e cloud -noprompt

Once the server successfully starts, you will see a message in the command prompt similiar to the following illustration.

SOLRServer
Solr Server started

To verify that Solr is running, go to the following URL:

http://localhost:8983/solr/#/

You should see the Solr web client.

SOLRClient
Solr web client

Create a new collection

The next task is to create a new collection by using the web client. The collection that you create is the single search index that is used in this article. For more information, see Apache Solr Reference Guide.

To create a collection,

1. From the web client, select  Collection in the right hand column (shown above).

2 . Click Add Collection.

3. Name the new collection: collection.

 

Configure AEM to use Solr Server

Configure AEM to use Solr server. Go to the following URL:

http://localhost:4502/system/console/configMgr

Search for AEM Solr Search - Solr Configuration Service and enter the following values:

  • Protocol - http
  • Solr Server name - localhost
  • Solr Server Port: 8983
  • Solr Core Name - collection (references the collection you created)
  • Content page path - /content/we-retail

The following illustration shows these values.

ConfigValues
Solr configuration values

Next, index values with Solr by going to the following AEM page.

http://localhost:4502/etc/solr/solrindexer.html

This is the admin console that lets you create a SOLR document from the content pages. You can also delete the index documents from this page. Select Index Pages from the Index Options drop-down control and click Index to Solr as shown in the following illusration.

solrsuccess
Indexing with Solr

This page is invoking the IndexContentToSolr sling servlet (you can see the source code of this servlet in the OSGi section of this article). If the index operation is successful, a message confirming the operation was successful appears, as shown in the previous illustration.  

You can validate if the SOLR docs are created by going to the following URL:

http://localhost:8983/solr/#/collection/query

Select collection from the drop-down control and click the Execute Query button. If successful, you will see the result set that contains We Retail content. 

ClientQuery
Solr result set

View Solr results in an AEM component

In CRXDE lite, open the following HTML file:

/apps/solr/components/content/solrsearch/solrsearch.html

and add the following script at the top of the page:

<script src="https://code.jquery.com/jquery-3.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous"></script>  

To access the component that display Solr values, enter the following URL: 

http://localhost:4502/content/solr/en.html

Specify the term content in search bar and click search. You will see the following values returned by Solr. The following video shows a successful integration between Solr and Experience Manager 6.4

 

Integrating SOLR with Adobe Experience Manager 6.4

Integrating SOLR with Adobe Experience Manager 6.4

See also

Congratulations, you have just created an AEM sample application that integrates with Solr server. Please refer to the Experience League page for more AEM content that discuss how to build AEM services/applications.

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