Merged DEV/SWIFT to HEAD

25629: ALF-7069:
          - changed getNodes to a POST request
          - beefed up unit tests + some performance tests
          ALF-7070:
          - initial checkin, works end-to-end, still work-in-progress
          - unit + performance tests
   25630: ALF-7069: removed files that are no longer needed
   25640: Merged BRANCHES\DEV\SOLR to BRANCHES\DEV\SWIFT
      25079: SOLR check point: ALF-4259: SOLR Integration
      25217: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
      25315: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
      25577: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
      25604: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
      25610: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
   25651: - enabled OpenCMIS server ticket authentication 
          - added OpenCMIS client API (incomplete)
   25667: Merged BRANCHES/DEV/BM to BRANCHES/DEV/SWIFT:
      25030: Repo BM Sprint 1 - example using JMeter (WebDAV & CMIS)
      25054: Repo BM Sprint 1 - milestone 2
      25078: Repo BM sprint 1 - milestone 3 (ALF-6794)
   25675: ALF-7068: SOLR 075 Improved cache rebuild performance - delta + query cache warming
          - fix queries against un-optimized index
   25676: Merged BRANCHES/DEV/BM to BRANCHES/DEV/SWIFT: commit mergeinfo
   25683: RepoBM: OpenCMIS 
          - use shared libs (from 3rd-party project)
          - change default url (from ".../alfresco/opencmis-atom" to ".../alfresco/cmisatom")
   25767: ALF-7339: SOLR 020 Index track and build from SOLR
          - Initial hook up point and proto type for config
   25787: ALF-7070:
          - owner, associations, type conversions
          SOLR Client-side API to call into repository SOLR APIs
   25818: added webscripts root object as an entry point to OpenCMIS client sessions (local and remote)
   25855: Bug fix: keep CMIS connection manager reference

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28089 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2011-05-28 21:31:19 +00:00
parent effae9e773
commit a5f1ef9735
19 changed files with 2126 additions and 183 deletions

View File

@@ -5,6 +5,11 @@
<projects> <projects>
</projects> </projects>
<buildSpec> <buildSpec>
<buildCommand>
<name>org.eclipse.dltk.core.scriptbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name> <name>org.eclipse.jdt.core.javabuilder</name>
<arguments> <arguments>
@@ -23,5 +28,6 @@
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>rk.eclipse.javacc.javaccnature</nature> <nature>rk.eclipse.javacc.javaccnature</nature>
<nature>org.deved.antlride.core.nature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

View File

@@ -257,7 +257,10 @@
<bean id="solrDAO" class="org.alfresco.repo.domain.solr.ibatis.SOLRDAOImpl" init-method="init"> <bean id="solrDAO" class="org.alfresco.repo.domain.solr.ibatis.SOLRDAOImpl" init-method="init">
<property name="sqlSessionTemplate" ref="solrSqlSessionTemplate"/> <property name="sqlSessionTemplate" ref="solrSqlSessionTemplate"/>
<property name="dictionaryService" ref="dictionaryService"/>
<property name="nodeDAO" ref="nodeDAO"/>
<property name="qNameDAO" ref="qnameDAO"/> <property name="qNameDAO" ref="qnameDAO"/>
<property name="ownableService" ref="ownableService"/>
</bean> </bean>
</beans> </beans>

View File

@@ -53,6 +53,9 @@
order by txn.commit_time_ms ASC, txn.id ASC order by txn.commit_time_ms ASC, txn.id ASC
</select> </select>
<!--
TODO filter the from clauses depending on which where clauses have been selected
-->
<select id="select_Txn_Nodes" parameterType="NodeParameters" resultMap="result_Node"> <select id="select_Txn_Nodes" parameterType="NodeParameters" resultMap="result_Node">
select select
node.id as id, node.id as id,
@@ -66,12 +69,23 @@
join alf_node node on (txn.id = node.transaction_id) join alf_node node on (txn.id = node.transaction_id)
join alf_store store on (store.id = node.store_id) join alf_store store on (store.id = node.store_id)
<where> <where>
<if test="transactionIds != null"> <choose>
<when test="transactionIds != null">
txn.id in txn.id in
<foreach item="item" index="index" collection="transactionIds" open="(" separator="," close=")"> <foreach item="item" index="index" collection="transactionIds" open="(" separator="," close=")">
#{item} #{item}
</foreach> </foreach>
</if> </when>
<when test="fromTxnId != null and toTxnId != null">
<![CDATA[txn.id >= #{fromTxnId} and txn.id <= #{toTxnId}]]>
</when>
<when test="fromTxnId = null and toTxnId != null">
<![CDATA[txn.id <= #{toTxnId}]]>
</when>
<when test="fromTxnId != null and toTxnId == null">
<![CDATA[txn.id >= #{fromTxnId}]]>
</when>
</choose>
<if test="fromNodeId != null"> <if test="fromNodeId != null">
<![CDATA[and node.id >= #{fromNodeId}]]> <![CDATA[and node.id >= #{fromNodeId}]]>
</if> </if>

View File

@@ -47,6 +47,8 @@ import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.repo.node.integrity.IntegrityException; import org.alfresco.repo.node.integrity.IntegrityException;
import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.Authorization;
import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.version.VersionModel; import org.alfresco.repo.version.VersionModel;
@@ -145,23 +147,33 @@ public class AlfrescoCmisService extends AbstractCmisService
{ {
this.context = context; this.context = context;
// authenticate user AuthenticationUtil.pushAuthentication();
String user = context.getUsername();
String password = context.getPassword();
if ((user == null) || (user.length() == 0))
{
throw new CmisPermissionDeniedException("No user provided!");
}
if (password == null)
{
password = "";
}
try try
{ {
connector.getAuthenticationService().authenticate(user, password.toCharArray()); String currentUser = connector.getAuthenticationService().getCurrentUserName();
String user = context.getUsername();
String password = context.getPassword();
if (currentUser == null)
{
Authorization auth = new Authorization(user, password);
if (auth.isTicket())
{
connector.getAuthenticationService().validate(auth.getTicket());
} else
{
connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray());
}
} else if (currentUser.equals(connector.getProxyUser()))
{
if (user != null && user.length() > 0)
{
AuthenticationUtil.setFullyAuthenticatedUser(user);
}
}
} catch (AuthenticationException ae) } catch (AuthenticationException ae)
{ {
throw new CmisPermissionDeniedException(ae.getMessage(), ae); throw new CmisPermissionDeniedException(ae.getMessage(), ae);
@@ -173,7 +185,7 @@ public class AlfrescoCmisService extends AbstractCmisService
beginReadOnlyTransaction(); beginReadOnlyTransaction();
} catch (Exception e) } catch (Exception e)
{ {
connector.getAuthenticationService().clearCurrentSecurityContext(); AuthenticationUtil.popAuthentication();
if (e instanceof CmisBaseException) if (e instanceof CmisBaseException)
{ {
@@ -202,8 +214,7 @@ public class AlfrescoCmisService extends AbstractCmisService
} }
} finally } finally
{ {
// clean up AuthenticationUtil.popAuthentication();
connector.getAuthenticationService().clearCurrentSecurityContext();
context = null; context = null;
} }
} }

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.opencmis;
import java.util.Map;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper;
/**
* Factory for local OpenCMIS service objects.
*
* @author florian.mueller
*/
public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory
{
private static ThreadLocal<CmisServiceWrapper<AlfrescoCmisService>> THREAD_LOCAL_SERVICE = new ThreadLocal<CmisServiceWrapper<AlfrescoCmisService>>();
private static CMISConnector CMIS_CONNECTOR;
@Override
public void init(Map<String, String> parameters)
{
}
/**
* Sets the CMIS connector.
*/
public static void setCmisConnector(CMISConnector connector)
{
CMIS_CONNECTOR = connector;
}
@Override
public void destroy()
{
THREAD_LOCAL_SERVICE = null;
}
@Override
public CmisService getService(CallContext context)
{
CmisServiceWrapper<AlfrescoCmisService> wrapperService = THREAD_LOCAL_SERVICE.get();
if (wrapperService == null)
{
wrapperService = new CmisServiceWrapper<AlfrescoCmisService>(new AlfrescoCmisService(CMIS_CONNECTOR),
CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(),
CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth());
THREAD_LOCAL_SERVICE.set(wrapperService);
}
wrapperService.getWrappedService().beginCall(context);
return wrapperService;
}
}

View File

@@ -246,6 +246,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen
private Map<String, List<String>> kindToRenditionNames; private Map<String, List<String>> kindToRenditionNames;
private Map<String, NodeRef> rootNodeRefs = new ConcurrentHashMap<String, NodeRef>(1); private Map<String, NodeRef> rootNodeRefs = new ConcurrentHashMap<String, NodeRef>(1);
private Map<String, CMISRenditionMapping> renditionMapping = new ConcurrentHashMap<String, CMISRenditionMapping>(1); private Map<String, CMISRenditionMapping> renditionMapping = new ConcurrentHashMap<String, CMISRenditionMapping>(1);
private String proxyUser;
// OpenCMIS objects // OpenCMIS objects
private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS; private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS;
@@ -536,6 +537,16 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen
return dictionaryService; return dictionaryService;
} }
public void setProxyUser(String proxyUser)
{
this.proxyUser = proxyUser;
}
public String getProxyUser()
{
return proxyUser;
}
// -------------------------------------------------------------- // --------------------------------------------------------------
// Lifecycle methods // Lifecycle methods
// -------------------------------------------------------------- // --------------------------------------------------------------

View File

@@ -775,6 +775,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
} }
} }
public boolean exists(Long nodeId)
{
Pair<Long, Node> pair = nodesCache.getByKey(nodeId);
return pair != null && !pair.getSecond().getDeleted();
}
public boolean exists(NodeRef nodeRef) public boolean exists(NodeRef nodeRef)
{ {
NodeEntity node = new NodeEntity(nodeRef); NodeEntity node = new NodeEntity(nodeRef);

View File

@@ -118,6 +118,7 @@ public interface NodeDAO extends NodeBulkLoader
* @return Returns <tt>true</tt> if the node is present and undeleted * @return Returns <tt>true</tt> if the node is present and undeleted
*/ */
public boolean exists(NodeRef nodeRef); public boolean exists(NodeRef nodeRef);
public boolean exists(Long nodeId);
/** /**
* Get the current status of the node, including deleted nodes. * Get the current status of the node, including deleted nodes.

View File

@@ -0,0 +1,85 @@
package org.alfresco.repo.domain.solr;
/**
* Filters for node metadata results e.g. include properties, aspect, ... or not
*
* @since 4.0
*
*/
public class MetaDataResultsFilter
{
private boolean includeProperties = true;
private boolean includeAspects = true;
private boolean includeType = true;
private boolean includeAclId = true;
private boolean includeOwner = true;
private boolean includePaths = true;
private boolean includeAssociations = true;
private boolean includeNodeRef = true;
public boolean getIncludeNodeRef()
{
return includeNodeRef;
}
public void setIncludeNodeRef(boolean includeNodeRef)
{
this.includeNodeRef = includeNodeRef;
}
public boolean getIncludeAssociations()
{
return includeAssociations;
}
public void setIncludeAssociations(boolean includeAssociations)
{
this.includeAssociations = includeAssociations;
}
public boolean getIncludeProperties()
{
return includeProperties;
}
public void setIncludeProperties(boolean includeProperties)
{
this.includeProperties = includeProperties;
}
public boolean getIncludeAspects()
{
return includeAspects;
}
public void setIncludeAspects(boolean includeAspects)
{
this.includeAspects = includeAspects;
}
public boolean getIncludeType()
{
return includeType;
}
public void setIncludeType(boolean includeType)
{
this.includeType = includeType;
}
public boolean getIncludeAclId()
{
return includeAclId;
}
public void setIncludeAclId(boolean includeAclId)
{
this.includeAclId = includeAclId;
}
public boolean getIncludeOwner()
{
return includeOwner;
}
public void setIncludeOwner(boolean includeOwner)
{
this.includeOwner = includeOwner;
}
public boolean getIncludePaths()
{
return includePaths;
}
public void setIncludePaths(boolean includePaths)
{
this.includePaths = includePaths;
}
}

View File

@@ -0,0 +1,24 @@
package org.alfresco.repo.domain.solr;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.namespace.QName;
public interface NodeMetaData
{
public NodeRef getNodeRef();
public List<Path> getPaths();
public QName getNodeType();
public Long getNodeId();
public Long getAclId();
public String getOwner();
public Map<QName, Serializable> getProperties();
public Set<QName> getAspects();
public List<ChildAssociationRef> getChildAssocs();
}

View File

@@ -0,0 +1,104 @@
package org.alfresco.repo.domain.solr;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.namespace.QName;
/**
*
* @since 4.0
*
*/
public class NodeMetaDataEntity implements NodeMetaData
{
private Long nodeId;
private NodeRef nodeRef;
private String owner;
private QName nodeType;
private Long aclId;
private Map<QName, Serializable> properties;
private Set<QName> aspects;
private List<Path> paths;
private List<ChildAssociationRef> childAssocs;
public String getOwner()
{
return owner;
}
public void setOwner(String owner)
{
this.owner = owner;
}
public NodeRef getNodeRef()
{
return nodeRef;
}
public void setNodeRef(NodeRef nodeRef)
{
this.nodeRef = nodeRef;
}
public List<Path> getPaths()
{
return paths;
}
public void setPaths(List<Path> paths)
{
this.paths = paths;
}
public QName getNodeType()
{
return nodeType;
}
public void setNodeType(QName nodeType)
{
this.nodeType = nodeType;
}
public Long getNodeId()
{
return nodeId;
}
public void setNodeId(Long nodeId)
{
this.nodeId = nodeId;
}
public Long getAclId()
{
return aclId;
}
public void setAclId(Long aclId)
{
this.aclId = aclId;
}
public Map<QName, Serializable> getProperties()
{
return properties;
}
public void setProperties(Map<QName, Serializable> properties)
{
this.properties = properties;
}
public Set<QName> getAspects()
{
return aspects;
}
public void setAspects(Set<QName> aspects)
{
this.aspects = aspects;
}
public List<ChildAssociationRef> getChildAssocs()
{
return childAssocs;
}
public void setChildAssocs(List<ChildAssociationRef> childAssocs)
{
this.childAssocs = childAssocs;
}
}

View File

@@ -0,0 +1,93 @@
package org.alfresco.repo.domain.solr;
import java.util.List;
/**
* Stores node meta data query parameters for use in SOLR DAO queries
*
* @since 4.0
*/
public class NodeMetaDataParameters
{
private List<Long> transactionIds;
private Long fromTxnId;
private Long toTxnId;
// default is 'all' results
private int maxResults = 0;
private Long fromNodeId;
private Long toNodeId;
private List<Long> nodeIds;
public int getMaxResults()
{
return maxResults;
}
public void setMaxResults(int maxResults)
{
this.maxResults = maxResults;
}
public List<Long> getNodeIds()
{
return nodeIds;
}
public void setNodeIds(List<Long> nodeIds)
{
this.nodeIds = nodeIds;
}
public void setTransactionIds(List<Long> txnIds)
{
this.transactionIds = txnIds;
}
public List<Long> getTransactionIds()
{
return transactionIds;
}
public Long getFromTxnId()
{
return fromTxnId;
}
public void setFromTxnId(Long fromTxnId)
{
this.fromTxnId = fromTxnId;
}
public Long getToTxnId()
{
return toTxnId;
}
public void setToTxnId(Long toTxnId)
{
this.toTxnId = toTxnId;
}
public Long getFromNodeId()
{
return fromNodeId;
}
public void setFromNodeId(Long fromNodeId)
{
this.fromNodeId = fromNodeId;
}
public Long getToNodeId()
{
return toNodeId;
}
public void setToNodeId(Long toNodeId)
{
this.toNodeId = toNodeId;
}
}

View File

@@ -6,16 +6,22 @@ import java.util.Set;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
/** /**
* Stores node parameters for use in SOLR DAO queries * Stores node query parameters for use in SOLR DAO queries
* *
* @since 4.0 * @since 4.0
*/ */
public class NodeParameters public class NodeParameters
{ {
private List<Long> transactionIds; private List<Long> transactionIds;
private Long fromTxnId;
private Long toTxnId;
private Long fromNodeId; private Long fromNodeId;
private Long toNodeId; private Long toNodeId;
// default is 'all' results
private int maxResults = 0;
private String storeProtocol; private String storeProtocol;
private String storeIdentifier; private String storeIdentifier;
@@ -29,6 +35,16 @@ public class NodeParameters
private List<Long> includeAspectIds; private List<Long> includeAspectIds;
private List<Long> excludeAspectIds; private List<Long> excludeAspectIds;
public int getMaxResults()
{
return maxResults;
}
public void setMaxResults(int maxResults)
{
this.maxResults = maxResults;
}
public boolean getStoreFilter() public boolean getStoreFilter()
{ {
return (storeProtocol != null || storeIdentifier != null); return (storeProtocol != null || storeIdentifier != null);
@@ -64,6 +80,26 @@ public class NodeParameters
return transactionIds; return transactionIds;
} }
public Long getFromTxnId()
{
return fromTxnId;
}
public void setFromTxnId(Long fromTxnId)
{
this.fromTxnId = fromTxnId;
}
public Long getToTxnId()
{
return toTxnId;
}
public void setToTxnId(Long toTxnId)
{
this.toTxnId = toTxnId;
}
public Long getFromNodeId() public Long getFromNodeId()
{ {
return fromNodeId; return fromNodeId;
@@ -164,4 +200,9 @@ public class NodeParameters
this.excludeTypeIds = excludeTypeIds; this.excludeTypeIds = excludeTypeIds;
} }
public boolean isIncludeNodesTable()
{
return (getFromNodeId() != null || getToNodeId() != null || getIncludeTypeIds() != null || getExcludeTypeIds() != null || getIncludeAspectIds() != null || getExcludeAspectIds() != null);
}
} }

View File

@@ -12,8 +12,33 @@ import org.alfresco.repo.domain.node.Node;
// TODO - permit shortened form of QNames for e.g. aspects i.e. cm:content vs {http://www.alfresco.org/model/content/1.0}content? // TODO - permit shortened form of QNames for e.g. aspects i.e. cm:content vs {http://www.alfresco.org/model/content/1.0}content?
public interface SOLRDAO public interface SOLRDAO
{ {
/**
* Get the transactions from either minTxnId or fromCommitTime, optionally limited to maxResults
*
* @param minTxnId greater than or equal to minTxnId
* @param fromCommitTime greater than or equal to transaction commit time
* @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results
* @return list of transactions
*/
public List<Transaction> getTransactions(Long minTxnId, Long fromCommitTime, int maxResults); public List<Transaction> getTransactions(Long minTxnId, Long fromCommitTime, int maxResults);
public void getNodes(NodeParameters nodeParameters, int maxResults, NodeQueryCallback callback);
/**
* Get the nodes satisfying the constraints in nodeParameters
*
* @param nodeParameters set of constraints for which nodes to return
* @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results
* @param callback a callback to receive the results
*/
public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback);
/**
* Returns metadata for a set of node ids
*
* @param nodeIds a set of nodeIds for which to return node metadata
* @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results
* @param callback a callback to receive the results
*/
public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback);
/** /**
* The interface that will be used to give query results to the calling code. * The interface that will be used to give query results to the calling code.
@@ -28,4 +53,18 @@ public interface SOLRDAO
*/ */
boolean handleNode(Node node); boolean handleNode(Node node);
} }
/**
* The interface that will be used to give query results to the calling code.
*/
public static interface NodeMetaDataQueryCallback
{
/**
* Handle a node.
*
* @param node the node meta data
* @return Return <tt>true</tt> to continue processing rows or <tt>false</tt> to stop
*/
boolean handleNodeMetaData(NodeMetaData nodeMetaData);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,35 @@
package org.alfresco.repo.domain.solr.ibatis; package org.alfresco.repo.domain.solr.ibatis;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback;
import org.alfresco.repo.domain.node.NodeEntity; import org.alfresco.repo.domain.node.NodeEntity;
import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.repo.domain.solr.MetaDataResultsFilter;
import org.alfresco.repo.domain.solr.NodeMetaData;
import org.alfresco.repo.domain.solr.NodeMetaDataEntity;
import org.alfresco.repo.domain.solr.NodeMetaDataParameters;
import org.alfresco.repo.domain.solr.NodeParameters; import org.alfresco.repo.domain.solr.NodeParameters;
import org.alfresco.repo.domain.solr.SOLRDAO; import org.alfresco.repo.domain.solr.SOLRDAO;
import org.alfresco.repo.domain.solr.SOLRTransactionParameters; import org.alfresco.repo.domain.solr.SOLRTransactionParameters;
import org.alfresco.repo.domain.solr.Transaction; import org.alfresco.repo.domain.solr.Transaction;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.SqlSessionTemplate;
@@ -19,21 +38,42 @@ import org.mybatis.spring.SqlSessionTemplate;
* *
* @since 4.0 * @since 4.0
*/ */
// TODO Freemarker requires the construction of a model which means that lists and maps need to be built up in memory
// - consider building the JSON in a more streaming manner, without building up of these data structures.
// downside: loss of separation of model and view
// upside: better performance?
public class SOLRDAOImpl implements SOLRDAO public class SOLRDAOImpl implements SOLRDAO
{ {
private static final Log logger = LogFactory.getLog(SOLRDAOImpl.class);
private static final String SELECT_TRANSACTIONS = "alfresco.solr.select_Txns"; private static final String SELECT_TRANSACTIONS = "alfresco.solr.select_Txns";
private static final String SELECT_NODES = "alfresco.solr.select_Txn_Nodes"; private static final String SELECT_NODES = "alfresco.solr.select_Txn_Nodes";
private DictionaryService dictionaryService;
private NodeDAO nodeDAO;
private QNameDAO qnameDAO; private QNameDAO qnameDAO;
private OwnableService ownableService;
private SqlSessionTemplate template; private SqlSessionTemplate template;
public void setOwnableService(OwnableService ownableService)
{
this.ownableService = ownableService;
}
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
{ {
this.template = sqlSessionTemplate; this.template = sqlSessionTemplate;
} }
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
public void setNodeDAO(NodeDAO nodeDAO)
{
this.nodeDAO = nodeDAO;
}
public void setQNameDAO(QNameDAO qnameDAO) public void setQNameDAO(QNameDAO qnameDAO)
{ {
@@ -45,9 +85,15 @@ public class SOLRDAOImpl implements SOLRDAO
*/ */
public void init() public void init()
{ {
PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
PropertyCheck.mandatory(this, "nodeDAO", nodeDAO);
PropertyCheck.mandatory(this, "qnameDAO", qnameDAO);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
/**
* {@inheritDoc}
*/
public List<Transaction> getTransactions(Long minTxnId, Long fromCommitTime, int maxResults) public List<Transaction> getTransactions(Long minTxnId, Long fromCommitTime, int maxResults)
{ {
if(minTxnId == null && fromCommitTime == null && (maxResults == 0 || maxResults == Integer.MAX_VALUE)) if(minTxnId == null && fromCommitTime == null && (maxResults == 0 || maxResults == Integer.MAX_VALUE))
@@ -74,7 +120,10 @@ public class SOLRDAOImpl implements SOLRDAO
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
// TODO should create qnames if don't exist? // TODO should create qnames if don't exist?
public void getNodes(NodeParameters nodeParameters, int maxResults, NodeQueryCallback callback) /**
* {@inheritDoc}
*/
public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback)
{ {
List<NodeEntity> nodes = null; List<NodeEntity> nodes = null;
NodeQueryRowHandler rowHandler = new NodeQueryRowHandler(callback); NodeQueryRowHandler rowHandler = new NodeQueryRowHandler(callback);
@@ -103,9 +152,10 @@ public class SOLRDAOImpl implements SOLRDAO
nodeParameters.setIncludeAspectIds(new ArrayList<Long>(qnamesIds)); nodeParameters.setIncludeAspectIds(new ArrayList<Long>(qnamesIds));
} }
if(maxResults != 0 && maxResults != Integer.MAX_VALUE) if(nodeParameters.getMaxResults() != 0 && nodeParameters.getMaxResults() != Integer.MAX_VALUE)
{ {
nodes = (List<NodeEntity>)template.selectList(SELECT_NODES, nodeParameters, new RowBounds(0, maxResults)); nodes = (List<NodeEntity>)template.selectList(SELECT_NODES, nodeParameters,
new RowBounds(0, nodeParameters.getMaxResults()));
} }
else else
{ {
@@ -118,6 +168,195 @@ public class SOLRDAOImpl implements SOLRDAO
} }
} }
/**
* A dumb iterator that iterates over longs in sequence.
*
*/
private static class SequenceIterator implements Iterable<Long>
{
private long fromId;
private long toId;
private long counter;
SequenceIterator(Long fromId, Long toId)
{
this.fromId = (fromId == null ? 1 : fromId.longValue());
this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue());
this.counter = this.fromId;
}
@Override
public Iterator<Long> iterator()
{
counter = this.fromId;
return new Iterator<Long>() {
@Override
public boolean hasNext()
{
return counter <= toId;
}
@Override
public Long next()
{
return counter++;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
}
/**
* {@inheritDoc}
*/
public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback)
{
int maxResults = nodeMetaDataParameters.getMaxResults();
NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback);
boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE);
boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType());
boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties());
boolean includeAspects = (resultFilter == null ? true : resultFilter.getIncludeAspects());
boolean includePaths = (resultFilter == null ? true : resultFilter.getIncludePaths());
boolean includeNodeRef = (resultFilter == null ? true : resultFilter.getIncludeNodeRef());
boolean includeAssociations = (resultFilter == null ? true : resultFilter.getIncludeAssociations());
boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner());
Iterable<Long> iterable = null;
if(nodeMetaDataParameters.getNodeIds() != null)
{
iterable = nodeMetaDataParameters.getNodeIds();
}
else
{
iterable = new SequenceIterator(nodeMetaDataParameters.getFromNodeId(), nodeMetaDataParameters.getToNodeId());
}
// pre-cache nodes?
// TODO does this cache acls, etc for the node?
List<NodeRef> nodeRefs = new ArrayList<NodeRef>(100);
int i = 1;
for(Long nodeId : iterable)
{
if(isLimitSet && i++ > maxResults)
{
break;
}
if(!nodeDAO.exists(nodeId))
{
continue;
}
Pair<Long, NodeRef> pair = nodeDAO.getNodePair(nodeId);
nodeRefs.add(pair.getSecond());
}
if(logger.isDebugEnabled())
{
logger.debug("SOLRDAO caching " + nodeRefs.size() + " nodes");
}
nodeDAO.cacheNodes(nodeRefs);
i = 1;
for(Long nodeId : iterable)
{
if(isLimitSet && i++ > maxResults)
{
break;
}
if(!nodeDAO.exists(nodeId))
{
// ignore deleted node?
// TODO nodeDAO doesn't cache anything for deleted nodes. Should we be ignoring delete node meta data?
continue;
}
NodeMetaDataEntity nodeMetaData = new NodeMetaDataEntity();
nodeMetaData.setNodeId(nodeId);
Pair<Long, NodeRef> pair = nodeDAO.getNodePair(nodeId);
nodeMetaData.setAclId(nodeDAO.getNodeAclId(nodeId));
if(includeType)
{
QName nodeType = nodeDAO.getNodeType(nodeId);
nodeMetaData.setNodeType(nodeType);
}
if(includeProperties)
{
Map<QName, Serializable> props = nodeDAO.getNodeProperties(nodeId);
nodeMetaData.setProperties(props);
}
if(includeAspects)
{
Set<QName> aspects = nodeDAO.getNodeAspects(nodeId);
nodeMetaData.setAspects(aspects);
}
// paths may change during get i.e. node moved around in the graph
if(includePaths)
{
List<Path> paths = nodeDAO.getPaths(pair, false);
nodeMetaData.setPaths(paths);
}
if(includeNodeRef)
{
nodeMetaData.setNodeRef(pair.getSecond());
}
if(includeAssociations)
{
final List<ChildAssociationRef> childAssocs = new ArrayList<ChildAssociationRef>(100);
nodeDAO.getChildAssocs(nodeId, null, null, null, false, false, new ChildAssocRefQueryCallback()
{
@Override
public boolean preLoadNodes()
{
// already cached above
return false;
}
@Override
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> childNodePair)
{
childAssocs.add(childAssocPair.getSecond());
return true;
}
@Override
public void done()
{
}
});
nodeMetaData.setChildAssocs(childAssocs);
// TODO non-child associations
// Collection<Pair<Long, AssociationRef>> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId);
}
if(includeOwner)
{
// cached in OwnableService
nodeMetaData.setOwner(ownableService.getOwner(pair.getSecond()));
}
rowHandler.processResult(nodeMetaData);
}
}
/** /**
* Class that passes results from a result entity into the client callback * Class that passes results from a result entity into the client callback
*/ */
@@ -143,4 +382,30 @@ public class SOLRDAOImpl implements SOLRDAO
more = callback.handleNode(row); more = callback.handleNode(row);
} }
} }
/**
* Class that passes results from a result entity into the client callback
*/
protected class NodeMetaDataQueryRowHandler
{
private final NodeMetaDataQueryCallback callback;
private boolean more;
private NodeMetaDataQueryRowHandler(NodeMetaDataQueryCallback callback)
{
this.callback = callback;
this.more = true;
}
public void processResult(NodeMetaData row)
{
if (!more)
{
// No more results required
return;
}
more = callback.handleNodeMetaData(row);
}
}
} }

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.ParameterCheck;
/**
* Helper to process username / password pairs passed to the remote tier
*
* Identifies whether username / password is a ticket.
*
* Is ticket, if one of the following is true:
*
* a) Username == "ROLE_TICKET" (in any case) b) Username is not specified (i.e.
* null) c) Username is zero length
*/
public class Authorization
{
public static String TICKET_USERID = PermissionService.ROLE_PREFIX + "TICKET";
private String username;
private String password;
private String ticket;
/**
* Construct
*
* @param authorization
*/
public Authorization(String authorization)
{
ParameterCheck.mandatoryString("authorization", authorization);
int idx = authorization.indexOf(':');
if (idx == -1)
{
setUser(null, authorization);
} else
{
setUser(authorization.substring(0, idx), authorization.substring(idx + 1));
}
}
/**
* Construct
*
* @param username
* @param password
*/
public Authorization(String username, String password)
{
setUser(username, password);
}
private void setUser(String username, String password)
{
this.username = username;
this.password = password;
if (username == null || username.length() == 0 || username.equalsIgnoreCase(TICKET_USERID))
{
this.ticket = password;
}
}
public String getUserName()
{
return username;
}
public String getPassword()
{
return password;
}
public char[] getPasswordCharArray()
{
return password == null ? null : password.toCharArray();
}
public boolean isTicket()
{
return ticket != null;
}
public String getTicket()
{
return ticket;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import junit.framework.TestCase;
/**
* Test Authorization
*/
public class AuthorizationTest extends TestCase
{
private static String USER = "user";
private static String PASSWORD = "pass";
public void testInvalidAuthorization()
{
try
{
new Authorization(null);
fail();
}
catch(IllegalArgumentException e)
{
}
try
{
new Authorization("username:password:invalid");
fail();
}
catch(IllegalArgumentException e)
{
}
}
public void testAuthorization()
{
Authorization auth1 = new Authorization(USER, PASSWORD);
assertUserPass(USER, PASSWORD, auth1);
Authorization auth2 = new Authorization("", PASSWORD);
assertTicket("", PASSWORD, auth2);
Authorization auth3 = new Authorization(null, PASSWORD);
assertTicket(null, PASSWORD, auth3);
Authorization auth4 = new Authorization(Authorization.TICKET_USERID, PASSWORD);
assertTicket(Authorization.TICKET_USERID, PASSWORD, auth4);
Authorization auth5 = new Authorization(Authorization.TICKET_USERID.toLowerCase(), PASSWORD);
assertTicket(Authorization.TICKET_USERID.toLowerCase(), PASSWORD, auth5);
}
public void testUserPass()
{
Authorization auth1 = new Authorization(USER + ":" + PASSWORD);
assertUserPass(USER, PASSWORD, auth1);
Authorization auth2 = new Authorization(":" + PASSWORD);
assertTicket("", PASSWORD, auth2);
Authorization auth3 = new Authorization(PASSWORD);
assertTicket(null, PASSWORD, auth3);
Authorization auth4 = new Authorization(Authorization.TICKET_USERID + ":" + PASSWORD);
assertTicket(Authorization.TICKET_USERID, PASSWORD, auth4);
Authorization auth5 = new Authorization(Authorization.TICKET_USERID.toLowerCase() + ":" + PASSWORD);
assertTicket(Authorization.TICKET_USERID.toLowerCase(), PASSWORD, auth5);
}
private void assertUserPass(String user, String pass, Authorization auth)
{
assertEquals(user, auth.getUserName());
assertEquals(pass, auth.getPassword());
assertFalse(auth.isTicket());
assertNull(auth.getTicket());
}
private void assertTicket(String user, String pass, Authorization auth)
{
assertEquals(user, auth.getUserName());
assertEquals(pass, auth.getPassword());
assertTrue(auth.isTicket());
assertEquals(pass, auth.getTicket());
}
}

View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- xsi:schemaLocation="http://www.alfresco.org/model/dictionary/1.0 modelSchema.xsd" -->
<model name="teststm:solrModel"
xmlns="http://www.alfresco.org/model/dictionary/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<description>Alfresco SOLR Test Model</description>
<author>Alfresco</author>
<published>2011-02-22</published>
<version>0.1</version>
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/model/solrtest/1.0" prefix="teststm"/>
</namespaces>
<constraints/>
<types>
<type name="teststm:testobject">
<title>SOLR Object</title>
<parent>cm:content</parent>
<properties>
<property name="teststm:mlTextProp">
<title>mlTextProp</title>
<type>d:mltext</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:boolProp">
<title>boolProp</title>
<type>d:boolean</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:longProp">
<title>lonProp</title>
<type>d:long</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:floatProp">
<title>floatProp</title>
<type>d:float</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:doubleProp">
<title>doubleProp</title>
<type>d:double</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:dateProp">
<title>dateProp</title>
<type>d:date</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:dateTimeProp">
<title>dateTimeProp</title>
<type>d:datetime</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:qnameProp">
<title>qnameProp</title>
<type>d:qname</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:nodeRefProp">
<title>nodeRefProp</title>
<type>d:noderef</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:childAssocProp">
<title>childAssocProp</title>
<type>d:childassocref</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:assocProp">
<title>assocProp</title>
<type>d:assocref</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:pathProp">
<title>pathProp</title>
<type>d:path</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:categoryProp">
<title>categoryProp</title>
<type>d:category</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:localeProp">
<title>localeProp</title>
<type>d:locale</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:versionProp">
<title>versionProp</title>
<type>d:version</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:periodProp">
<title>periodProp</title>
<type>d:period</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
<property name="teststm:anyProp">
<title>anyProp</title>
<type>d:any</type>
<mandatory enforced="true">true</mandatory>
<index enabled="false">
</index>
<constraints>
</constraints>
</property>
</properties>
<mandatory-aspects>
<aspect>cm:auditable</aspect>
</mandatory-aspects>
</type>
</types>
</model>