Tuesday, December 24, 2013

Configuring Solr and Alfresco on different tomcat

Alfresco supports Solr Enterprise search platform which is open source enterprise search platform.

In new alfresco version, Solr is default search engine and If you have installed alfresco using setup wizard then it comes with alfresco bundle. In newer version Solr is not deployed under tomcat/webapps due to different behavior of tomcat 7.

In your production environment, You might need solr to run on different application server than alfresco. So Here I would describe detail steps to configure solr on different standalone application server.

Assuming you have standalone tomcat running in your env on port 8080 and SSL port 8443 and have existing alfresco installed on another tomcat on port 9090 and SSL port 8444

We will refer 
TOMCAT_HOME_ALFRESCO  - where Alfresco is installed
TOMCAT_HOME_SOLR - where Solr is installed
SOLR_HOME - where we will extract all solr related file

Step 1: Get  alfresco-enterprise-solr-4.2.0.zip and extract it to SOLR_HOME

Step 2: Create solr.xml file under TOMCAT_HOME_SOLR\conf\Catalina\localhost and copy context.xml file to this solr.xml. [You can get context.xml file under SOLR_HOME]

Step 3: Modify solr.xml and edit docBase and solr/home
docBase should point to - SOLR_HOME/apache-solr-1.4.1.war
solr/home should point to - SOLR_HOME

For example:
<?xml version="1.0" encoding="utf-8"?>
<Context docBase="D:/Alfresco_SOLR/solr/apache-solr-1.4.1.war" debug="0" crossContext="true">
<Environment name="solr/home" type="java.lang.String" value="D:/Alfresco_SOLR/solr" override="true"/>
</Context>
view raw solr.xml hosted with ❤ by GitHub
Step 4: Edit solrcore.properties to set data.dir.root where your Solr index will be stored. Modify it for both core - workspace and archive

File location:
SOLR_HOME\solr\workspace-SpacesStore\conf
SOLR_HOME\solr\archive-SpacesStore\conf

For example:
data.dir.root=D:/Alfresco_SOLR/solr/solr-index

You can define same value for both cores. It will create sub-directory for each core.

Step 5: Edit solrcore.properties. Set your alfresco server detail for both core.

alfresco.host=localhost
alfresco.port=9090
alfresco.port.ssl=8444

Before we go ahead lets generate Secure keys for communication between Alfresco and Solr

Step 6: Edit generate_keystores.bat (Windows) and generate_keystores.sh(Linux) located at SOLR_HOME\solr\alf_data\keystore and set below environment variable.

ALFRESCO_HOME
ALFRESCO_KEYSTORE_HOME
SOLR_HOME

For example:
ALFRESCO_HOME = D:\MyAlfresco
ALFRESCO_KEYSTORE_HOME=D:\Alfresco_SOLR\solr\alf_data\keystore
SOLR_HOME=D:/Alfresco_SOLR/solr

Also make sure you have proper value set for JAVA_HOME, REPO_CERT_DNAME and SOLR_CLIENT_CERT_DNAME .

Step 7: Run this script. And set dir.keystore value in your alfresco-global.properties file.

For example:
dir.keystore=D:/Alfresco_SOLR/solr/alf_data/keystore

Step 8: Edit server.xml file for both Solr and Alfresco's tomcat to use the keystore and truststore for https requests

This SSL connector settings are changed from 4.1 so make sure you configure it as below.
Removed configurations: maxSavePostSize
Added/changed configurations: clientAuth="want" maxHttpHeaderSize="32768"

Configure server.xml file location located at <TOMCAT_HOME_ALFRESCO>/conf/server.xml on connector port 8444
<Connector port="8444" protocol="org.apache.coyote.http11.Http11Protocol"
SSLEnabled="true" maxThreads="150" scheme="https"
keystoreFile="D:/Alfresco_SOLR/solr/alf_data/keystore/ssl.keystore"
keystorePass="kT9X6oe68t" keystoreType="JCEKS" secure="true" connectionTimeout="240000"
truststoreFile="D:/Alfresco_SOLR/solr/alf_data/keystore/ssl.truststore"
truststorePass="kT9X6oe68t" truststoreType="JCEKS" clientAuth="want" sslProtocol="TLS"
allowUnsafeLegacyRenegotiation="true"
maxHttpHeaderSize="32768" maxSavePostSize="-1"/>
Configure server.xml file location located at <TOMCAT_HOME_SOLR>/conf/server.xml on connector port 8443
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
SSLEnabled="true" maxThreads="150" scheme="https"
keystoreFile="D:/Alfresco_SOLR/solr/alf_data/keystore/ssl.keystore"
keystorePass="kT9X6oe68t" keystoreType="JCEKS" secure="true" connectionTimeout="240000"
truststoreFile="D:/Alfresco_SOLR/solr/alf_data/keystore/ssl.truststore"
truststorePass="kT9X6oe68t" truststoreType="JCEKS" clientAuth="want" sslProtocol="TLS"
allowUnsafeLegacyRenegotiation="true"
maxHttpHeaderSize="32768" maxSavePostSize="-1"/>
view raw server_solr.xml hosted with ❤ by GitHub

Step 9: Edit your alfresco-global.properties file.
Set search subsytem, dir.keystore which we already set in above step and solr server detail.

index.subsystem.name=solr
dir.keystore=D:/Alfresco_SOLR/solr/alf_data/keystore
solr.port.ssl=8443
solr.host=localhost
solr.port=8080
solr.secureComms=https

Step 10: Edit tomcat-users.xml located at TOMCAT_HOME_SOLR/conf to configure an identity for the Alfresco server.

<user username="CN=Alfresco Repository, OU=Unknown, O=Alfresco Software Ltd., L=Maidenhead, ST=UK, C=GB" roles="repository" password="null"/>
Step 11: Edit tomcat-users.xml located at TOMCAT_HOME_ALFRESCO/conf to configure an identity for the Alfresco server.

<user username="CN=Alfresco Repository Client, OU=Unknown, O=Alfresco Software Ltd., L=Maidenhead, ST=UK, C=GB" roles="repoclient" password="null"/>
Now, we are done with required configurations start your alfresco and Solr tomcat server and verify Solr indexes and search.
While searching on alfresco You might get below error

org.apache.tomcat.util.net.jsse.JSSESupport handShake WARNING: SSL server initiated renegotiation is disabled, closing connection

OR in solr tomcat logs you get below timeout error

WARN  [org.alfresco.solr.tracker.CoreTracker] Tracking communication timed out.

These error shows issue in communication between alfresco and solr over SSL connection.
Please check it for troubleshooting your issue - http://docs.alfresco.com/4.0/topic/com.alfresco.enterprise.doc/concepts/solr-troubleshooting.html

Alfresco version: 4.2EE


Friday, December 6, 2013

Alfresco Share webscript Connection timeout


Recently We faced HTTP connection timeout issue in Alfresco share webscript while calling repository webscrtipt.
This timeout occurs for several reasons like high load on server or custom logical operation on huge amount of data so alfresco explorer takes too much time to respond etc.
So sometimes you might need to change this timeout parameters used by Share remote client. Here I will describe how we can change them.

Initially, this parameters was hard coded, now you can set them through configuration in newer version of alfresco.
Connection timeout default value is 10s.

Need to follow below steps

Step 1:
Locate spring-webscript-application-context.xml. This settings are defined in this file.

This file is in spring-webscripts-1.2.0-SNAPSHOT.jar [tomcat\webapps\alfresco\WEB-INF\lib] and go to org/springframework/extensions/webscripts/.

Step 2:
Search for "connector.remoteclient.abstract" and copy entire bean definition.

<bean id="connector.remoteclient.abstract" abstract="true">
<!-- name of authentication ticket that can be optionally applied to proxied URLs -->
<!-- the ticket value is set programmatically by the connector making use of the RemoteClient instance -->
<property name="ticketName"><value>alf_ticket</value></property>
<!-- default encoding to use when decoded responses when none is set in the response - set to null for platform default -->
<property name="defaultEncoding"><null/></property>
<!-- default Content-Type header value to use for PUT or POST responses when none is supplied -->
<property name="defaultContentType"><value>application/octet-stream</value></property>
<!-- size of the internal byte buffer used when proxying responses -->
<property name="bufferSize"><value>4096</value></property>
<!-- the http.connection.timeout value in milliseconds to apply to HTTP connections -->
<property name="connectTimeout"><value>15000</value></property>
<!-- the http.socket.timeout value in milliseconds to apply to HTTP connections -->
<property name="readTimeout"><value>120000</value></property>
<!-- the maximum number of redirects to process from a GET request while maintaining cookies -->
<property name="maxRedirects"><value>10</value></property>
<!-- the RemoteClient will automatically use an HTTP proxy if the JVM property 'http.proxyHost' is set
but this feature can be disabled by setting this bean property to false -->
<property name="allowHttpProxy"><value>true</value></property>
<!-- the RemoteClient will automatically use an HTTPS proxy if the JVM property 'https.proxyHost' is set
but this feature can be disabled by setting this bean property to false -->
<property name="allowHttpsProxy"><value>true</value></property>
<!-- the set of headers to remove when proxying a request from a client to a remote endpoint -->
<property name="removeRequestHeaders">
<set>
<value>Cookie</value>
</set>
</property>
<!-- the set of headers to remove when proxying a response from a remote endpoint to a client -->
<property name="removeResponseHeaders">
<set>
<value>Set-Cookie</value>
</set>
</property>
<!-- http.tcp.nodelay setting - default is true -->
<property name="httpTcpNodelay"><value>true</value></property>
<!-- http.connection.stalecheck setting - default is true -->
<property name="httpConnectionStalecheck"><value>true</value></property>
</bean>

Step 3:
Go to tomcat\shared\classes\alfresco\web-extension and create custom-slingshot-application-context.xml if you don't have else modify this file.

Copy remoteclient bean defination into this and update "connectTimeout" value as per your requirement.

Step 4:
Restart server and verify.

You can increase logs to debug if any issue.
log4j.logger.org.springframework.extensions.webscripts.connector.RemoteClient=debug

NOTE: This might affect your system performance. So till possible rectify your code which is causing this issue.

Alfresco version: 4.2

Hope this helps!

Wednesday, December 4, 2013

Alfresco share custom evaluator to check node associations

In Share, we have evaluator based on that templates, action, indicator can be displayed.
We also can have our own custom evaluator . 

I see - many has requirement to show/hide action based on content/folder associations and which is NOT available OOB.
So, In this example I will show you how you can implement it.

We will implement custom evaluator which checks if content has "cm:references" association then show OOB action - "Delete Document". You can have your custom action or any other action.

First we will start with Share side changes.

Step1:  Define custom evaluator

Create file - custom-documentlibrary-context.xml and put it to \tomcat\shared\classes\alfresco\web-extension

Add below code
<beans>
<bean id="custom.evaluator.doclib.hasAssociation" class="com.custom.web.evaluator.HasAssociationEvaluator">
<property name="associations">
<list>
<value>cm:references</value>
</list>
</property>
</bean>
</beans>

Step 2: Create custom evaluator class.

This class calls alfresco repository webscript which accepts content noderef and association name and
checks if content has given association or not and accordingly return true or false.
package com.custom.web.evaluator;
import java.util.ArrayList;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.web.evaluator.BaseEvaluator;
import org.json.JSONException;
import org.json.simple.JSONObject;
import org.springframework.extensions.surf.RequestContext;
import org.springframework.extensions.surf.ServletUtil;
import org.springframework.extensions.surf.exception.ConnectorServiceException;
import org.springframework.extensions.surf.support.ThreadLocalRequestContext;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.connector.Connector;
import org.springframework.extensions.webscripts.connector.Response;
/**
* Check if node has given association
*
* @author niketa.patel
*
*/
public class HasAssociationEvaluator extends BaseEvaluator
{
private ArrayList<String> associations;
@Override
public boolean evaluate(final JSONObject jsonObject)
{
boolean result = false;
if (this.associations.size() == 0)
{
return false;
}
try
{
// check if node has given association or not
result = this.hasNodeAssociation(jsonObject, this.associations);
} catch (final Exception err)
{
throw new AlfrescoRuntimeException("Failed to run action evaluator: " + err.getMessage());
}
return result;
}
/**
*
* Method to check if node has given association or not
*
* @param actionObject
* @param associations
* @return
* @throws ConnectorServiceException
* @throws JSONException
*/
public final Boolean hasNodeAssociation(final JSONObject actionObject, final ArrayList<String> associations)
{
Boolean result = false;
try
{
final JSONObject node = (JSONObject) actionObject.get("node");// get node
final String contentRef = (String) node.get("nodeRef");// get noderef of content
final RequestContext rc = ThreadLocalRequestContext.getRequestContext();// get request context
final String userId = rc.getUserId();
final Connector conn = rc.getServiceRegistry().getConnectorService().getConnector("alfresco", userId, ServletUtil.getSession());// get connector
// custom repository webscript which checks if noderef has given association or not
final String url = "/node/association/" + contentRef.replace("://", "/") + "/" + associations.get(0).replace(":", "_");
final Response response = conn.call(url);// get response
if (Status.STATUS_OK == response.getStatus().getCode())// make sure we are getting valid response
{
final org.json.JSONObject scriptResponse = new org.json.JSONObject(response.getResponse());
result = scriptResponse.getBoolean("hasAssocValue");
}
} catch (final ConnectorServiceException e)
{
throw new AlfrescoRuntimeException("Failed to connect repository: " + e.getMessage());
} catch (final JSONException e)
{
throw new AlfrescoRuntimeException("Failed to parse JSON string: " + e.getMessage());
}
return result;
}
public void setAssociations(final ArrayList<String> associations)
{
this.associations = associations;
}
}

Step 3:  Add new custom evaluator for delete document action in share-config-custom.xml
<config evaluator="string-compare" condition="DocLibActions">
<actions>
<action id="document-delete" type="javascript" label="actions.document.delete">
<param name="function">onActionDelete</param>
<permissions>
<permission allow="true">Delete</permission>
</permissions>
<evaluator>custom.evaluator.doclib.hasAssociation</evaluator>
</action>
</actions>
</config>
So now we are done with Share side changes.
We need to create alfresco repository webscript which checks if node has given association or not. This would be simple javascript webscript

Step1: Create desc file . node-association.get.desc.xml.
Put in to extension folder: \tomcat\shared\classes\alfresco\extension\templates\webscripts\custom\example\node\association
<webscript>
<shortname>Check if node has given association or not</shortname>
<description>Check if node has given association or not</description>
<url>/node/association/{store_type}/{store_id}/{id}/{assoc}</url>
<format default="json">argument</format>
<authentication>user</authentication>
</webscript>
Step 2: Create node-association.get.js
//get paramter from url
var storeType = url.templateArgs.store_type;
var storeId = url.templateArgs.store_id;
var id = url.templateArgs.id;
//crete node
var contentStringRef = storeType + "://" + storeId + "/" + id;
//get association
var association = url.templateArgs.assoc;
association = association.replace("_",":");
//get script node object
var contentRef = search.findNode(contentStringRef);
//check if node has given association or not
if (contentRef.assocs[association] != null)
{
model.hasAssoc = true;
}
else
{
model.hasAssoc = false;
}

Step 3: Create node-association.get.json.ftl
{
"hasAssocValue":"${hasAssoc?string}"
}

Deploy your custom code and restart server. Verify Delete action for content with and without cm:reference association.
I have not considered multiple association, you can change it same way.

Hope its helpful!