Java tutorial
/* * #%L * Alfresco Solr Client * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * 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/>. * #L% */ package org.alfresco.solr.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.httpclient.AlfrescoHttpClient; import org.alfresco.httpclient.AuthenticationException; import org.alfresco.httpclient.GetRequest; import org.alfresco.httpclient.PostRequest; import org.alfresco.httpclient.Response; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.NamespaceDAO; import org.alfresco.repo.index.shard.ShardState; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.Path.AttributeElement; import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.cmr.repository.datatype.TypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConverter.Converter; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO8601DateFormat; import org.alfresco.util.Pair; import org.apache.commons.codec.EncoderException; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.util.DateUtil; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.extensions.surf.util.URLEncoder; // TODO error handling, including dealing with a repository that is not responsive (ConnectException in sendRemoteRequest) // TODO get text content transform status handling /** * Http client to handle SOLR-Alfresco remote calls. * * @since 4.0 */ public class SOLRAPIClient { protected final static Logger log = LoggerFactory.getLogger(SOLRAPIClient.class); private static final String GET_ACL_CHANGESETS_URL = "api/solr/aclchangesets"; private static final String GET_ACLS = "api/solr/acls"; private static final String GET_ACLS_READERS = "api/solr/aclsReaders"; private static final String GET_TRANSACTIONS_URL = "api/solr/transactions"; private static final String GET_METADATA_URL = "api/solr/metadata"; private static final String GET_NODES_URL = "api/solr/nodes"; private static final String GET_CONTENT = "api/solr/textContent"; private static final String GET_MODEL = "api/solr/model"; private static final String GET_MODELS_DIFF = "api/solr/modelsdiff"; private static final String CHECKSUM_HEADER = "XAlfresco-modelChecksum"; private AlfrescoHttpClient repositoryHttpClient; private SOLRDeserializer deserializer; private DictionaryService dictionaryService; private JsonFactory jsonFactory; private NamespaceDAO namespaceDAO; public SOLRAPIClient(AlfrescoHttpClient repositoryHttpClient, DictionaryService dictionaryService, NamespaceDAO namespaceDAO) { this.repositoryHttpClient = repositoryHttpClient; this.dictionaryService = dictionaryService; this.namespaceDAO = namespaceDAO; this.deserializer = new SOLRDeserializer(namespaceDAO); this.jsonFactory = new JsonFactory(); } /** * Get the ACL ChangeSets * * @param fromCommitTime the lowest commit time (optional) * @param minAclChangeSetId the lowest ChangeSet ID (optional) * @param maxResults the maximum number of results (a reasonable value only) * @return the ACL ChangeSets in order of commit time and ID */ public AclChangeSets getAclChangeSets(Long fromCommitTime, Long minAclChangeSetId, Long toCommitTime, Long maxAclChangeSetId, int maxResults) throws AuthenticationException, IOException, JSONException { StringBuilder url = new StringBuilder(GET_ACL_CHANGESETS_URL); StringBuilder args = new StringBuilder(); if (fromCommitTime != null) { args.append("?").append("fromTime").append("=").append(fromCommitTime); } if (minAclChangeSetId != null) { args.append(args.length() == 0 ? "?" : "&").append("fromId").append("=").append(minAclChangeSetId); } if (toCommitTime != null) { args.append(args.length() == 0 ? "?" : "&").append("toTime").append("=").append(toCommitTime); } if (maxAclChangeSetId != null) { args.append(args.length() == 0 ? "?" : "&").append("toId").append("=").append(maxAclChangeSetId); } if (maxResults != 0 && maxResults != Integer.MAX_VALUE) { args.append(args.length() == 0 ? "?" : "&").append("maxResults").append("=").append(maxResults); } url.append(args); GetRequest req = new GetRequest(url.toString()); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException( GET_ACL_CHANGESETS_URL + " return status:" + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString(3)); } JSONArray aclChangeSetsJSON = json.getJSONArray("aclChangeSets"); List<AclChangeSet> aclChangeSets = new ArrayList<AclChangeSet>(aclChangeSetsJSON.length()); for (int i = 0; i < aclChangeSetsJSON.length(); i++) { JSONObject aclChangeSetJSON = aclChangeSetsJSON.getJSONObject(i); long aclChangeSetId = aclChangeSetJSON.getLong("id"); long commitTimeMs = aclChangeSetJSON.getLong("commitTimeMs"); int aclCount = aclChangeSetJSON.getInt("aclCount"); AclChangeSet aclChangeSet = new AclChangeSet(aclChangeSetId, commitTimeMs, aclCount); aclChangeSets.add(aclChangeSet); } Long maxChangeSetCommitTime = null; if (json.has("maxChangeSetCommitTime")) { maxChangeSetCommitTime = json.getLong("maxChangeSetCommitTime"); } Long maxChangeSetIdOnServer = null; if (json.has("maxChangeSetId")) { maxChangeSetIdOnServer = json.getLong("maxChangeSetId"); } // Done return new AclChangeSets(aclChangeSets, maxChangeSetCommitTime, maxChangeSetIdOnServer); } /** * Get the ACLs associated with a given list of ACL ChangeSets. The ACLs may be truncated for * the last ACL ChangeSet in the return values - the ACL count from the * {@link #getAclChangeSets(Long, Long, Long, Long, int) ACL ChangeSets}. * * @param aclChangeSets the ACL ChangeSets to include * @param minAclId the lowest ACL ID (may be <tt>null</tt>) * @param maxResults the maximum number of results to retrieve * @return the ACLs (includes ChangeSet ID) */ public List<Acl> getAcls(List<AclChangeSet> aclChangeSets, Long minAclId, int maxResults) throws AuthenticationException, IOException, JSONException { StringBuilder url = new StringBuilder(GET_ACLS); StringBuilder args = new StringBuilder(); if (minAclId != null) { args.append("?").append("fromId").append("=").append(minAclId); } if (maxResults >= 0) { args.append(args.length() == 0 ? "?" : "&").append("maxResults").append("=").append(maxResults); } url.append(args); JSONObject jsonReq = new JSONObject(); JSONArray aclChangeSetIdsJSON = new JSONArray(); List<Long> aclChangeSetIds = new ArrayList<Long>(); for (AclChangeSet aclChangeSet : aclChangeSets) { Long aclChangeSetId = aclChangeSet.getId(); aclChangeSetIdsJSON.put(aclChangeSetId); aclChangeSetIds.add(aclChangeSetId); } jsonReq.put("aclChangeSetIds", aclChangeSetIdsJSON); PostRequest req = new PostRequest(url.toString(), jsonReq.toString(), "application/json"); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException( GET_ACL_CHANGESETS_URL + " return status:" + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString(3)); } JSONArray aclsJSON = json.getJSONArray("acls"); List<Acl> acls = new ArrayList<Acl>(aclsJSON.length()); for (int i = 0; i < aclsJSON.length(); i++) { JSONObject aclJSON = aclsJSON.getJSONObject(i); long aclChangeSetId = aclJSON.getLong("aclChangeSetId"); long aclId = aclJSON.getLong("id"); Acl acl = new Acl(aclChangeSetId, aclId); acls.add(acl); } // Done return acls; } /** * Get the ACL readers for a given list of ACLs * * @param acls the ACLs * @return the readers for the ACLs */ public List<AclReaders> getAclReaders(List<Acl> acls) throws AuthenticationException, IOException, JSONException { StringBuilder url = new StringBuilder(GET_ACLS_READERS); JSONObject jsonReq = new JSONObject(); JSONArray aclIdsJSON = new JSONArray(); List<Long> aclIds = new ArrayList<Long>(); for (Acl acl : acls) { Long aclId = acl.getId(); aclIdsJSON.put(aclId); aclIds.add(aclId); } jsonReq.put("aclIds", aclIdsJSON); PostRequest req = new PostRequest(url.toString(), jsonReq.toString(), "application/json"); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException(GET_ACLS_READERS + " return status:" + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString(3)); } JSONArray aclsReadersJSON = json.getJSONArray("aclsReaders"); List<AclReaders> aclsReaders = new ArrayList<AclReaders>(aclsReadersJSON.length()); for (int i = 0; i < aclsReadersJSON.length(); i++) { JSONObject aclReadersJSON = aclsReadersJSON.getJSONObject(i); long aclId = aclReadersJSON.getLong("aclId"); JSONArray readersJSON = aclReadersJSON.getJSONArray("readers"); List<String> readers = authorityListFromJSON(readersJSON); JSONArray deniedJSON = aclReadersJSON.getJSONArray("denied"); List<String> denied = authorityListFromJSON(deniedJSON); long aclChangeSetId = aclReadersJSON.getLong("aclChangeSetId"); String tenantDomain = aclReadersJSON.getString("tenantDomain"); if (tenantDomain == null) { tenantDomain = TenantService.DEFAULT_DOMAIN; } AclReaders aclReaders = new AclReaders(aclId, readers, denied, aclChangeSetId, tenantDomain); aclsReaders.add(aclReaders); } // Done return aclsReaders; } /** * Convert a JSON array of authorities to a simple Java List<String> * * @param jsonArray JSONArray * @return List<String> * @throws JSONException */ private List<String> authorityListFromJSON(JSONArray jsonArray) throws JSONException { List<String> authorities = new ArrayList<String>(jsonArray.length()); for (int j = 0; j < jsonArray.length(); j++) { String authority = jsonArray.getString(j); authorities.add(authority); } return authorities; } public Transactions getTransactions(Long fromCommitTime, Long minTxnId, Long toCommitTime, Long maxTxnId, int maxResults) throws AuthenticationException, IOException, JSONException { try { return getTransactions(fromCommitTime, minTxnId, toCommitTime, maxTxnId, maxResults, null); } catch (EncoderException e) { // Can not happen .... throw new IOException(e); } } public Transactions getTransactions(Long fromCommitTime, Long minTxnId, Long toCommitTime, Long maxTxnId, int maxResults, ShardState shardState) throws AuthenticationException, IOException, JSONException, EncoderException { URLCodec encoder = new URLCodec(); StringBuilder url = new StringBuilder(GET_TRANSACTIONS_URL); StringBuilder args = new StringBuilder(); if (fromCommitTime != null) { args.append("?").append("fromCommitTime").append("=").append(fromCommitTime); } if (minTxnId != null) { args.append(args.length() == 0 ? "?" : "&").append("minTxnId").append("=").append(minTxnId); } if (toCommitTime != null) { args.append(args.length() == 0 ? "?" : "&").append("toCommitTime").append("=").append(toCommitTime); } if (maxTxnId != null) { args.append(args.length() == 0 ? "?" : "&").append("maxTxnId").append("=").append(maxTxnId); } if (maxResults != 0 && maxResults != Integer.MAX_VALUE) { args.append(args.length() == 0 ? "?" : "&").append("maxResults").append("=").append(maxResults); } if (shardState != null) { args.append(args.length() == 0 ? "?" : "&"); args.append(encoder.encode("baseUrl")).append("=") .append(encoder.encode(shardState.getShardInstance().getBaseUrl())); args.append("&").append(encoder.encode("hostName")).append("=") .append(encoder.encode(shardState.getShardInstance().getHostName())); args.append("&").append(encoder.encode("template")).append("=") .append(encoder.encode(shardState.getShardInstance().getShard().getFloc().getTemplate())); for (String key : shardState.getShardInstance().getShard().getFloc().getPropertyBag().keySet()) { String value = shardState.getShardInstance().getShard().getFloc().getPropertyBag().get(key); if (value != null) { args.append("&").append(encoder.encode("floc.property." + key)).append("=") .append(encoder.encode(value)); } } for (String key : shardState.getPropertyBag().keySet()) { String value = shardState.getPropertyBag().get(key); if (value != null) { args.append("&").append(encoder.encode("state.property." + key)).append("=") .append(encoder.encode(value)); } } args.append("&").append(encoder.encode("instance")).append("=") .append(encoder.encode("" + shardState.getShardInstance().getShard().getInstance())); args.append("&").append(encoder.encode("numberOfShards")).append("=").append( encoder.encode("" + shardState.getShardInstance().getShard().getFloc().getNumberOfShards())); args.append("&").append(encoder.encode("port")).append("=") .append(encoder.encode("" + shardState.getShardInstance().getPort())); args.append("&").append(encoder.encode("stores")).append("="); for (StoreRef store : shardState.getShardInstance().getShard().getFloc().getStoreRefs()) { if (args.charAt(args.length() - 1) != '=') { args.append(encoder.encode(",")); } args.append(encoder.encode(store.toString())); } args.append("&").append(encoder.encode("isMaster")).append("=") .append(encoder.encode("" + shardState.isMaster())); args.append("&").append(encoder.encode("hasContent")).append("=") .append(encoder.encode("" + shardState.getShardInstance().getShard().getFloc().hasContent())); args.append("&").append(encoder.encode("shardMethod")).append("=").append( encoder.encode(shardState.getShardInstance().getShard().getFloc().getShardMethod().toString())); args.append("&").append(encoder.encode("lastUpdated")).append("=") .append(encoder.encode("" + shardState.getLastUpdated())); args.append("&").append(encoder.encode("lastIndexedChangeSetCommitTime")).append("=") .append(encoder.encode("" + shardState.getLastIndexedChangeSetCommitTime())); args.append("&").append(encoder.encode("lastIndexedChangeSetId")).append("=") .append(encoder.encode("" + shardState.getLastIndexedChangeSetId())); args.append("&").append(encoder.encode("lastIndexedTxCommitTime")).append("=") .append(encoder.encode("" + shardState.getLastIndexedTxCommitTime())); args.append("&").append(encoder.encode("lastIndexedTxId")).append("=") .append(encoder.encode("" + shardState.getLastIndexedTxId())); } url.append(args); GetRequest req = new GetRequest(url.toString()); Response response = null; List<Transaction> transactions = new ArrayList<Transaction>(); Long maxTxnCommitTime = null; Long maxTxnIdOnServer = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException("GetTransactions return status is " + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); JsonParser parser = jsonFactory.createJsonParser(reader); JsonToken token = parser.nextValue(); while (token != null) { if ("transactions".equals(parser.getCurrentName())) { token = parser.nextToken(); //START_ARRAY while (token == JsonToken.START_OBJECT) { token = parser.nextValue(); long id = parser.getLongValue(); token = parser.nextValue(); long commitTime = parser.getLongValue(); token = parser.nextValue(); long updates = parser.getLongValue(); token = parser.nextValue(); long deletes = parser.getLongValue(); Transaction txn = new Transaction(); txn.setCommitTimeMs(commitTime); txn.setDeletes(deletes); txn.setId(id); txn.setUpdates(updates); transactions.add(txn); token = parser.nextToken(); //END_OBJECT token = parser.nextToken(); // START_OBJECT or END_ARRAY; } } else if ("maxTxnCommitTime".equals(parser.getCurrentName())) { maxTxnCommitTime = parser.getLongValue(); } else if ("maxTxnId".equals(parser.getCurrentName())) { maxTxnIdOnServer = parser.getLongValue(); } token = parser.nextValue(); } parser.close(); reader.close(); } finally { if (response != null) { response.release(); } } return new Transactions(transactions, maxTxnCommitTime, maxTxnIdOnServer); } public List<Node> getNodes(GetNodesParameters parameters, int maxResults) throws AuthenticationException, IOException, JSONException { StringBuilder url = new StringBuilder(GET_NODES_URL); JSONObject body = new JSONObject(); if (parameters.getTransactionIds() != null) { JSONArray jsonTxnIds = new JSONArray(); for (Long txnId : parameters.getTransactionIds()) { jsonTxnIds.put(txnId); } body.put("txnIds", jsonTxnIds); } if (parameters.getFromNodeId() != null) { body.put("fromNodeId", parameters.getFromNodeId()); } if (parameters.getToNodeId() != null) { body.put("toNodeId", parameters.getToNodeId()); } if (parameters.getExcludeAspects() != null) { JSONArray jsonExcludeAspects = new JSONArray(); for (QName excludeAspect : parameters.getExcludeAspects()) { jsonExcludeAspects.put(excludeAspect.toString()); } body.put("excludeAspects", jsonExcludeAspects); } if (parameters.getIncludeAspects() != null) { JSONArray jsonIncludeAspects = new JSONArray(); for (QName includeAspect : parameters.getIncludeAspects()) { jsonIncludeAspects.put(includeAspect.toString()); } body.put("includeAspects", jsonIncludeAspects); } if (parameters.getStoreProtocol() != null) { body.put("storeProtocol", parameters.getStoreProtocol()); } if (parameters.getStoreIdentifier() != null) { body.put("storeIdentifier", parameters.getStoreIdentifier()); } body.put("maxResults", maxResults); if (parameters.getShardProperty() != null) { body.put("shardProperty", parameters.getShardProperty().toString()); } PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json"); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException("GetNodes return status is " + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString()); } JSONArray jsonNodes = json.getJSONArray("nodes"); List<Node> nodes = new ArrayList<Node>(jsonNodes.length()); for (int i = 0; i < jsonNodes.length(); i++) { JSONObject jsonNodeInfo = jsonNodes.getJSONObject(i); Node nodeInfo = new Node(); if (jsonNodeInfo.has("id")) { nodeInfo.setId(jsonNodeInfo.getLong("id")); } if (jsonNodeInfo.has("nodeRef")) { nodeInfo.setNodeRef(jsonNodeInfo.getString("nodeRef")); } if (jsonNodeInfo.has("txnId")) { nodeInfo.setTxnId(jsonNodeInfo.getLong("txnId")); } if (jsonNodeInfo.has("aclId")) { nodeInfo.setAclId(jsonNodeInfo.getLong("aclId")); } if (jsonNodeInfo.has("shardPropertyValue")) { nodeInfo.setShardPropertyValue(jsonNodeInfo.getString("shardPropertyValue")); } if (jsonNodeInfo.has("tenant")) { nodeInfo.setTenant(jsonNodeInfo.getString("tenant")); } if (jsonNodeInfo.has("status")) { Node.SolrApiNodeStatus status; String statusStr = jsonNodeInfo.getString("status"); if (statusStr.equals("u")) { status = Node.SolrApiNodeStatus.UPDATED; } else if (statusStr.equals("d")) { status = Node.SolrApiNodeStatus.DELETED; } else { status = Node.SolrApiNodeStatus.UNKNOWN; } nodeInfo.setStatus(status); } nodes.add(nodeInfo); } return nodes; } private PropertyValue getSinglePropertyValue(DataTypeDefinition dataType, Object value) throws JSONException { PropertyValue ret = null; QName dataTypeName = dataType.getName(); if (value == null || value == JSONObject.NULL) { ret = null; } else if (dataTypeName.equals(DataTypeDefinition.MLTEXT)) { JSONArray a = (JSONArray) value; Map<Locale, String> mlValues = new HashMap<Locale, String>(a.length()); for (int k = 0; k < a.length(); k++) { JSONObject pair = a.getJSONObject(k); Locale locale = deserializer.deserializeValue(Locale.class, pair.getString("locale")); String mlValue = pair.has("value") && !pair.isNull("value") ? pair.getString("value") : null; mlValues.put(locale, mlValue); } ret = new MLTextPropertyValue(mlValues); } else if (dataTypeName.equals(DataTypeDefinition.CONTENT)) { JSONObject o = (JSONObject) value; String localeStr = o.has("locale") && !o.isNull("locale") ? o.getString("locale") : null; Locale locale = (o.has("locale") && !o.isNull("locale") ? deserializer.deserializeValue(Locale.class, localeStr) : null); Long size = o.has("size") && !o.isNull("size") ? o.getLong("size") : null; String encoding = o.has("encoding") && !o.isNull("encoding") ? o.getString("encoding") : null; String mimetype = o.has("mimetype") && !o.isNull("mimetype") ? o.getString("mimetype") : null; Long id = o.has("contentId") && !o.isNull("contentId") ? o.getLong("contentId") : null; ret = new ContentPropertyValue(locale, size, encoding, mimetype, id); } else { ret = new StringPropertyValue((String) value); } return ret; } private PropertyValue getPropertyValue(PropertyDefinition propertyDef, Object value) throws JSONException { PropertyValue ret = null; if (value == null || value == JSONObject.NULL) { ret = null; } else if (propertyDef == null) { // assume a string ret = new StringPropertyValue((String) value); } else { DataTypeDefinition dataType = propertyDef.getDataType(); boolean isMulti = propertyDef.isMultiValued(); if (isMulti) { if (!(value instanceof JSONArray)) { throw new IllegalArgumentException("Expected json array, got " + value.getClass().getName()); } MultiPropertyValue multi = new MultiPropertyValue(); JSONArray array = (JSONArray) value; for (int j = 0; j < array.length(); j++) { multi.addValue(getSinglePropertyValue(dataType, array.get(j))); } ret = multi; } else { ret = getSinglePropertyValue(dataType, value); } } return ret; } public List<NodeMetaData> getNodesMetaData(NodeMetaDataParameters params, int maxResults) throws AuthenticationException, IOException, JSONException { List<Long> nodeIds = params.getNodeIds(); StringBuilder url = new StringBuilder(GET_METADATA_URL); JSONObject body = new JSONObject(); if (nodeIds != null && nodeIds.size() > 0) { JSONArray jsonNodeIds = new JSONArray(); for (Long nodeId : nodeIds) { jsonNodeIds.put(nodeId); } body.put("nodeIds", jsonNodeIds); } if (params.getFromNodeId() != null) { body.put("fromNodeId", params.getFromNodeId()); } if (params.getToNodeId() != null) { body.put("toNodeId", params.getToNodeId()); } // only need to set in cases where we don't want them in the response // because they default to true if (!params.isIncludeAclId()) { body.put("includeAclId", params.isIncludeAclId()); } if (!params.isIncludeAspects()) { body.put("includeAspects", params.isIncludeAspects()); } if (!params.isIncludeProperties()) { body.put("includeProperties", params.isIncludeProperties()); } if (!params.isIncludeChildAssociations()) { body.put("includeChildAssociations", params.isIncludeChildAssociations()); } if (!params.isIncludeParentAssociations()) { body.put("includeParentAssociations", params.isIncludeParentAssociations()); } if (!params.isIncludeChildIds()) { body.put("includeChildIds", params.isIncludeChildIds()); } if (!params.isIncludePaths()) { body.put("includePaths", params.isIncludePaths()); } if (!params.isIncludeOwner()) { body.put("includeOwner", params.isIncludeOwner()); } if (!params.isIncludeNodeRef()) { body.put("includeNodeRef", params.isIncludeNodeRef()); } if (!params.isIncludeTxnId()) { body.put("includeTxnId", params.isIncludeTxnId()); } body.put("maxResults", maxResults); PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json"); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException("GetNodeMetaData return status is " + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString(3)); } JSONArray jsonNodes = json.getJSONArray("nodes"); List<NodeMetaData> nodes = new ArrayList<NodeMetaData>(jsonNodes.length()); for (int i = 0; i < jsonNodes.length(); i++) { JSONObject jsonNodeInfo = jsonNodes.getJSONObject(i); NodeMetaData metaData = new NodeMetaData(); if (jsonNodeInfo.has("id")) { metaData.setId(jsonNodeInfo.getLong("id")); } if (jsonNodeInfo.has("tenantDomain")) { metaData.setTenantDomain(jsonNodeInfo.getString("tenantDomain")); } if (jsonNodeInfo.has("txnId")) { metaData.setTxnId(jsonNodeInfo.getLong("txnId")); } if (jsonNodeInfo.has("aclId")) { metaData.setAclId(jsonNodeInfo.getLong("aclId")); } if (jsonNodeInfo.has("nodeRef")) { metaData.setNodeRef(new NodeRef(jsonNodeInfo.getString("nodeRef"))); } if (jsonNodeInfo.has("type")) { metaData.setType(deserializer.deserializeValue(QName.class, jsonNodeInfo.getString("type"))); } if (jsonNodeInfo.has("aspects")) { JSONArray jsonAspects = jsonNodeInfo.getJSONArray("aspects"); Set<QName> aspects = new HashSet<QName>(jsonAspects.length()); for (int j = 0; j < jsonAspects.length(); j++) { String jsonAspect = (String) jsonAspects.get(j); aspects.add(deserializer.deserializeValue(QName.class, jsonAspect)); } metaData.setAspects(aspects); } if (jsonNodeInfo.has("paths")) { JSONArray jsonPaths = jsonNodeInfo.getJSONArray("paths"); List<Pair<String, QName>> paths = new ArrayList<Pair<String, QName>>(jsonPaths.length()); for (int j = 0; j < jsonPaths.length(); j++) { JSONObject path = jsonPaths.getJSONObject(j); String pathValue = path.getString("path"); QName qname = path.has("qname") ? deserializer.deserializeValue(QName.class, path.getString("qname")) : null; paths.add(new Pair<String, QName>(pathValue, qname)); } metaData.setPaths(paths); } if (jsonNodeInfo.has("namePaths")) { JSONArray jsonNamePaths = jsonNodeInfo.getJSONArray("namePaths"); List<List<String>> namePaths = new ArrayList<List<String>>(jsonNamePaths.length()); for (int j = 0; j < jsonNamePaths.length(); j++) { JSONObject jsonNamePath = jsonNamePaths.getJSONObject(j); JSONArray jsonNameElements = jsonNamePath.getJSONArray("namePath"); List<String> namePath = new ArrayList<String>(jsonNameElements.length()); for (int k = 0; k < jsonNameElements.length(); k++) { String namePathElement = jsonNameElements.getString(k); namePath.add(namePathElement); } namePaths.add(namePath); } metaData.setNamePaths(namePaths); } if (jsonNodeInfo.has("ancestors")) { JSONArray jsonAncestors = jsonNodeInfo.getJSONArray("ancestors"); HashSet<NodeRef> ancestors = new HashSet<NodeRef>(jsonAncestors.length()); for (int j = 0; j < jsonAncestors.length(); j++) { String ancestorNodeRefString = jsonAncestors.getString(j); NodeRef ancestorNodeRef = new NodeRef(ancestorNodeRefString); ancestors.add(ancestorNodeRef); } metaData.setAncestors(ancestors); } if (jsonNodeInfo.has("properties")) { JSONObject jsonProperties = jsonNodeInfo.getJSONObject("properties"); Map<QName, PropertyValue> properties = new HashMap<QName, PropertyValue>(jsonProperties.length()); @SuppressWarnings("rawtypes") Iterator propKeysIterator = jsonProperties.keys(); while (propKeysIterator.hasNext()) { String propName = (String) propKeysIterator.next(); QName propQName = deserializer.deserializeValue(QName.class, propName); Object propValueObj = jsonProperties.opt(propName); // check the expected property type to determine how to process the value PropertyDefinition propertyDef = dictionaryService.getProperty(propQName); // if(propertyDef == null) // { // // TODO which exception here? // throw new IllegalArgumentException("Could not find property definition for property " + propName); // } properties.put(propQName, getPropertyValue(propertyDef, propValueObj)); } metaData.setProperties(properties); } if (jsonNodeInfo.has("parentAssocsCrc")) { metaData.setParentAssocsCrc(jsonNodeInfo.getLong("parentAssocsCrc")); } if (jsonNodeInfo.has("parentAssocs")) { JSONArray jsonParentAssocs = jsonNodeInfo.getJSONArray("parentAssocs"); List<ChildAssociationRef> assocs = new ArrayList<ChildAssociationRef>(jsonParentAssocs.length()); for (int j = 0; j < jsonParentAssocs.length(); j++) { String childAssocRefStr = jsonParentAssocs.getString(j); ChildAssociationRef childAssociationRef = new ChildAssociationRef(childAssocRefStr); assocs.add(childAssociationRef); } metaData.setParentAssocs(assocs); } if (jsonNodeInfo.has("childAssocs")) { JSONArray jsonParentAssocs = jsonNodeInfo.getJSONArray("childAssocs"); List<ChildAssociationRef> assocs = new ArrayList<ChildAssociationRef>(jsonParentAssocs.length()); for (int j = 0; j < jsonParentAssocs.length(); j++) { String childAssocRefStr = jsonParentAssocs.getString(j); ChildAssociationRef childAssociationRef = new ChildAssociationRef(childAssocRefStr); assocs.add(childAssociationRef); } metaData.setChildAssocs(assocs); } if (jsonNodeInfo.has("childIds")) { JSONArray jsonChildIds = jsonNodeInfo.getJSONArray("childIds"); List<Long> childIds = new ArrayList<Long>(jsonChildIds.length()); for (int j = 0; j < jsonChildIds.length(); j++) { Long childId = jsonChildIds.getLong(j); childIds.add(childId); } metaData.setChildIds(childIds); } if (jsonNodeInfo.has("owner")) { metaData.setOwner(jsonNodeInfo.getString("owner")); } nodes.add(metaData); } return nodes; } public GetTextContentResponse getTextContent(Long nodeId, QName propertyQName, Long modifiedSince) throws AuthenticationException, IOException { StringBuilder url = new StringBuilder(128); url.append(GET_CONTENT); StringBuilder args = new StringBuilder(128); if (nodeId != null) { args.append("?"); args.append("nodeId"); args.append("="); args.append(nodeId); } else { throw new NullPointerException("getTextContent(): nodeId cannot be null."); } if (propertyQName != null) { if (args.length() == 0) { args.append("?"); } else { args.append("&"); } args.append("propertyQName"); args.append("="); args.append(URLEncoder.encode(propertyQName.toString())); } url.append(args); GetRequest req = new GetRequest(url.toString()); if (modifiedSince != null) { Map<String, String> headers = new HashMap<String, String>(1, 1.0f); headers.put("If-Modified-Since", String.valueOf(DateUtil.formatDate(new Date(modifiedSince)))); req.setHeaders(headers); } Response response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != Status.STATUS_NOT_MODIFIED && response.getStatus() != Status.STATUS_NO_CONTENT && response.getStatus() != Status.STATUS_OK) { throw new AlfrescoRuntimeException("GetTextContentResponse return status is " + response.getStatus()); } return new GetTextContentResponse(response); } public AlfrescoModel getModel(QName modelName) throws AuthenticationException, IOException, JSONException { // If the model is new to the SOLR side the prefix will be unknown so we can not generate prefixes for the request! // Always use the full QName with explicit URI StringBuilder url = new StringBuilder(GET_MODEL); URLCodec encoder = new URLCodec(); // must send the long name as we may not have the prefix registered url.append("?modelQName=").append(encoder.encode(modelName.toString(), "UTF-8")); GetRequest req = new GetRequest(url.toString()); Response response = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException("GetModel return status is " + response.getStatus()); } return new AlfrescoModel(M2Model.createModel(response.getContentAsStream()), Long.valueOf(response.getHeader(CHECKSUM_HEADER))); } finally { if (response != null) { response.release(); } } } public List<AlfrescoModelDiff> getModelsDiff(List<AlfrescoModel> currentModels) throws AuthenticationException, IOException, JSONException { StringBuilder url = new StringBuilder(GET_MODELS_DIFF); JSONObject body = new JSONObject(); JSONArray jsonModels = new JSONArray(); for (AlfrescoModel model : currentModels) { JSONObject jsonModel = new JSONObject(); QName modelQName = QName.createQName(model.getModel().getName(), namespaceDAO); jsonModel.put("name", modelQName.toString()); jsonModel.put("checksum", model.getChecksum()); jsonModels.put(jsonModel); } body.put("models", jsonModels); PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json"); Response response = null; JSONObject json = null; try { response = repositoryHttpClient.sendRequest(req); if (response.getStatus() != HttpStatus.SC_OK) { throw new AlfrescoRuntimeException("GetModelsDiff return status is " + response.getStatus()); } Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8")); json = new JSONObject(new JSONTokener(reader)); } finally { if (response != null) { response.release(); } } if (log.isDebugEnabled()) { log.debug(json.toString()); } JSONArray jsonDiffs = json.getJSONArray("diffs"); if (jsonDiffs == null) { throw new AlfrescoRuntimeException("GetModelsDiff badly formatted response"); } List<AlfrescoModelDiff> diffs = new ArrayList<AlfrescoModelDiff>(jsonDiffs.length()); for (int i = 0; i < jsonDiffs.length(); i++) { JSONObject jsonDiff = jsonDiffs.getJSONObject(i); diffs.add(new AlfrescoModelDiff(QName.createQName(jsonDiff.getString("name")), AlfrescoModelDiff.TYPE.valueOf(jsonDiff.getString("type")), (jsonDiff.isNull("oldChecksum") ? null : jsonDiff.getLong("oldChecksum")), (jsonDiff.isNull("newChecksum") ? null : jsonDiff.getLong("newChecksum")))); } return diffs; } /* * type conversions from serialized JSON values to SOLR-consumable objects */ @SuppressWarnings("rawtypes") private class SOLRTypeConverter { /** * Default Type Converter */ private TypeConverter instance = new TypeConverter(); private NamespaceDAO namespaceDAO; @SuppressWarnings("unchecked") SOLRTypeConverter(NamespaceDAO namespaceDAO) { this.namespaceDAO = namespaceDAO; // add all default converters to this converter // TODO find a better way of doing this Map<Class<?>, Map<Class<?>, Converter<?, ?>>> converters = DefaultTypeConverter.INSTANCE .getConverters(); for (Class source : converters.keySet()) { Map<Class<?>, Converter<?, ?>> converters1 = converters.get(source); for (Class dest : converters1.keySet()) { Converter<?, ?> converter = converters1.get(dest); instance.addConverter(source, dest, converter); } } // dates instance.addConverter(String.class, Date.class, new TypeConverter.Converter<String, Date>() { public Date convert(String source) { try { return ISO8601DateFormat.parse(source); } catch (Exception e) { throw new TypeConversionException("Failed to convert date " + source + " to string", e); } } }); // node refs instance.addConverter(String.class, NodeRef.class, new TypeConverter.Converter<String, NodeRef>() { public NodeRef convert(String source) { return new NodeRef(source); } }); // paths instance.addConverter(String.class, AttributeElement.class, new TypeConverter.Converter<String, AttributeElement>() { public AttributeElement convert(String source) { return new Path.AttributeElement(source); } }); instance.addConverter(String.class, ChildAssocElement.class, new TypeConverter.Converter<String, ChildAssocElement>() { public ChildAssocElement convert(String source) { return new Path.ChildAssocElement(instance.convert(ChildAssociationRef.class, source)); } }); instance.addConverter(String.class, Path.DescendentOrSelfElement.class, new TypeConverter.Converter<String, Path.DescendentOrSelfElement>() { public Path.DescendentOrSelfElement convert(String source) { return new Path.DescendentOrSelfElement(); } }); instance.addConverter(String.class, Path.ParentElement.class, new TypeConverter.Converter<String, Path.ParentElement>() { public Path.ParentElement convert(String source) { return new Path.ParentElement(); } }); instance.addConverter(String.class, Path.SelfElement.class, new TypeConverter.Converter<String, Path.SelfElement>() { public Path.SelfElement convert(String source) { return new Path.SelfElement(); } }); // associations instance.addConverter(String.class, ChildAssociationRef.class, new TypeConverter.Converter<String, ChildAssociationRef>() { public ChildAssociationRef convert(String source) { return new ChildAssociationRef(source); } }); instance.addConverter(String.class, AssociationRef.class, new TypeConverter.Converter<String, AssociationRef>() { public AssociationRef convert(String source) { return new AssociationRef(source); } }); // qnames instance.addConverter(String.class, QName.class, new TypeConverter.Converter<String, QName>() { public QName convert(String source) { return QName.resolveToQName(SOLRTypeConverter.this.namespaceDAO, source); } }); instance.addConverter(String.class, MLText.class, new TypeConverter.Converter<String, MLText>() { public MLText convert(String source) { return new MLText(source); } }); } public final <T> T convert(Class<T> c, Object value) { return instance.convert(c, value); } } /* * Deserializes JSON values from the remote API into objects consumable by SOLR */ private class SOLRDeserializer { private SOLRTypeConverter typeConverter; public SOLRDeserializer(NamespaceDAO namespaceDAO) { typeConverter = new SOLRTypeConverter(namespaceDAO); } public <T> T deserializeValue(Class<T> targetClass, Object value) throws JSONException { return typeConverter.convert(targetClass, value); } } private static class SOLRResponse { protected Response response; public SOLRResponse(Response response) { super(); this.response = response; } public Response getResponse() { return response; } } public static class GetTransactionsResponse extends SOLRResponse { private List<Transaction> txns; public GetTransactionsResponse(Response response, List<Transaction> txns) { super(response); this.txns = txns; } public List<Transaction> getTransaction() { return txns; } } public static class GetNodesResponse extends SOLRResponse { private List<Node> nodes; public GetNodesResponse(Response response, List<Node> nodes) { super(response); this.nodes = nodes; } public List<Node> getNodes() { return nodes; } } public static class GetNodesMetaDataResponse extends SOLRResponse { private List<NodeMetaData> nodes; public GetNodesMetaDataResponse(Response response, List<NodeMetaData> nodes) { super(response); this.nodes = nodes; } public List<NodeMetaData> getNodes() { return nodes; } } public static enum SolrApiContentStatus { NOT_MODIFIED, OK, NO_TRANSFORM, NO_CONTENT, UNKNOWN, TRANSFORM_FAILED, GENERAL_FAILURE; public static SolrApiContentStatus getStatus(String statusStr) { if (statusStr.equals("ok")) { return OK; } else if (statusStr.equals("transformFailed")) { return TRANSFORM_FAILED; } else if (statusStr.equals("noTransform")) { return NO_TRANSFORM; } else if (statusStr.equals("noContent")) { return NO_CONTENT; } else { return UNKNOWN; } } } // TODO register a stream close listener that release the response when the response has been read public static class GetTextContentResponse extends SOLRResponse { private InputStream content; private SolrApiContentStatus status; private String transformException; private String transformStatusStr; private Long transformDuration; public GetTextContentResponse(Response response) throws IOException { super(response); this.content = response.getContentAsStream(); this.transformStatusStr = response.getHeader("X-Alfresco-transformStatus"); this.transformException = response.getHeader("X-Alfresco-transformException"); String tmp = response.getHeader("X-Alfresco-transformDuration"); this.transformDuration = (tmp != null ? Long.valueOf(tmp) : null); setStatus(); } public InputStream getContent() { return content; } public SolrApiContentStatus getStatus() { return status; } private void setStatus() { int status = response.getStatus(); if (status == HttpStatus.SC_NOT_MODIFIED) { this.status = SolrApiContentStatus.NOT_MODIFIED; } else if (status == HttpStatus.SC_INTERNAL_SERVER_ERROR) { this.status = SolrApiContentStatus.GENERAL_FAILURE; } else if (status == HttpStatus.SC_OK) { this.status = SolrApiContentStatus.OK; } else if (status == HttpStatus.SC_NO_CONTENT) { if (transformStatusStr == null) { this.status = SolrApiContentStatus.UNKNOWN; } else { if (transformStatusStr.equals("noTransform")) { this.status = SolrApiContentStatus.NO_TRANSFORM; } else if (transformStatusStr.equals("transformFailed")) { this.status = SolrApiContentStatus.TRANSFORM_FAILED; } else { this.status = SolrApiContentStatus.UNKNOWN; } } } } public String getTransformException() { return transformException; } public void release() { response.release(); } public Long getTransformDuration() { return transformDuration; } } public void close() { repositoryHttpClient.close(); } }