1 package org.collectionspace.services.common.vocabulary;
3 import java.util.HashMap;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
9 import javax.ws.rs.core.Response;
11 import org.collectionspace.services.client.AuthorityClient;
12 import org.collectionspace.services.client.PoxPayloadIn;
13 import org.collectionspace.services.common.ServiceMain;
14 import org.collectionspace.services.common.api.RefNameUtils;
15 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
16 import org.collectionspace.services.common.api.Tools;
17 import org.collectionspace.services.common.context.MultipartServiceContextImpl;
18 import org.collectionspace.services.common.context.ServiceContext;
19 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
20 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
21 import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityIdentifierUtils;
22 import org.collectionspace.services.config.service.ServiceBindingType;
23 import org.collectionspace.services.config.tenant.RemoteClientConfig;
24 import org.collectionspace.services.config.tenant.RemoteClientConfigurations;
25 import org.collectionspace.services.config.tenant.TenantBindingType;
26 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
27 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
28 import org.dom4j.Document;
29 import org.dom4j.DocumentHelper;
30 import org.dom4j.Node;
31 import org.dom4j.XPath;
32 import org.collectionspace.services.common.document.DocumentException;
33 import org.collectionspace.services.common.document.DocumentNotFoundException;
34 import org.nuxeo.ecm.core.api.DocumentModel;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 @SuppressWarnings("rawtypes")
39 public class AuthorityServiceUtils {
40 private static final Logger logger = LoggerFactory.getLogger(AuthorityServiceUtils.class);
42 // Used to keep track if an authority item's is deprecated
43 public static final String DEFAULT_REMOTECLIENT_CONFIG_NAME = "default";
44 public static final String IS_DEPRECATED_PROPERTY = "IS_DEPRECATED_PROPERTY";
45 public static final Boolean DEPRECATED = true;
46 public static final Boolean NOT_DEPRECATED = !DEPRECATED;
48 // Used to keep track if an authority item's rev number should be updated
49 public static final String SHOULD_UPDATE_REV_PROPERTY = "SHOULD_UPDATE_REV_PROPERTY";
50 public static final boolean UPDATE_REV = true;
51 public static final boolean DONT_UPDATE_REV = !UPDATE_REV;
53 // Used to keep track if an authority item is a locally proposed member of a SAS authority
54 public static final String IS_PROPOSED_PROPERTY = "IS_PROPOSED";
55 public static final Boolean PROPOSED = true;
56 public static final Boolean NOT_PROPOSED = !PROPOSED;
57 public static final Boolean SAS_ITEM = true;
58 public static final Boolean NOT_SAS_ITEM = !SAS_ITEM;
60 public static final Boolean NO_CHANGE = null;
62 // Matches the domain name part of a refname. For example, "core.collectionspace.org" of
63 // urn:cspace:core.collectionspace.org:personauthorities:name(person):item:name(BigBird1461101206103)'Big Bird'.
64 public static final Pattern REFNAME_DOMAIN_PATTERN = Pattern.compile("(urn:cspace:)(([a-z]{1,}\\.?)*)");
67 * Try to find a named remote client configuration in the current tenant bindings. If the value of the incoming param 'remoteClientConfigName' is
68 * blank or null, we'll try to find a name in the authority service's bindings. If we can't find a name there, we'll try using the default name.
70 * If the incoming param 'remoteClientConfigName' is not null, we'll look through all the named remote client configurations in the tenant's binding
71 * to find the configuration. If we can't find the named configuration, we'll throw an exception.
73 * If there are no remote client configurations in the tenant's bindings, we'll throw an exception.
75 public static final RemoteClientConfig getRemoteClientConfig(ServiceContext ctx, String remoteClientConfigName) throws Exception {
76 RemoteClientConfig result = null;
78 TenantBindingType tenantBinding = ServiceMain.getInstance().getTenantBindingConfigReader().getTenantBinding(ctx.getTenantId());
79 RemoteClientConfigurations remoteClientConfigurations = tenantBinding.getRemoteClientConfigurations();
80 if (remoteClientConfigurations != null) {
81 if (Tools.isEmpty(remoteClientConfigName) == true) {
82 // Since the authority instance didn't specify a remote client config name, let's see if the authority type's service bindings specifies one
83 ServiceBindingType serviceBindingType =
84 ServiceMain.getInstance().getTenantBindingConfigReader().getServiceBinding(ctx.getTenantId(), ctx.getServiceName());
85 remoteClientConfigName = serviceBindingType.getRemoteClientConfigName();
88 // If we still don't have a remote client config name, let's use the default value.
90 if (Tools.isEmpty(remoteClientConfigName) == true) {
91 remoteClientConfigName = DEFAULT_REMOTECLIENT_CONFIG_NAME;
94 List<RemoteClientConfig> remoteClientConfigList = remoteClientConfigurations.getRemoteClientConfig();
95 for (RemoteClientConfig config : remoteClientConfigList) {
96 if (config.getName().equalsIgnoreCase(remoteClientConfigName)) {
102 String errMsg = String.format("No remote client configurations could be found in the tenant bindings for tenant named '%s'.",
103 ctx.getTenantName());
104 logger.error(errMsg);
105 throw new Exception(errMsg);
108 if (result == null) {
109 String errMsg = String.format("Could not find a remote client configuration named '%s' in the tenant bindings for tenant named '%s'",
110 remoteClientConfigName, ctx.getTenantName());
111 logger.error(errMsg);
112 throw new Exception(errMsg);
119 * Make a request to the SAS Server for an authority payload.
123 * @param responseType
127 public static PoxPayloadIn requestPayloadInFromRemoteServer(ServiceContext ctx, String remoteClientConfigName, Specifier specifier, Class responseType) throws Exception {
128 PoxPayloadIn result = null;
130 RemoteClientConfig remoteClientConfig = getRemoteClientConfig(ctx, remoteClientConfigName);
131 AuthorityClient client = (AuthorityClient) ctx.getClient(remoteClientConfig);
133 Response res = client.read(specifier.getURNValue());
135 int statusCode = res.getStatus();
136 if (statusCode == org.apache.commons.httpclient.HttpStatus.SC_OK) {
137 result = new PoxPayloadIn((String)res.readEntity(responseType)); // Get the entire response!
139 String errMsg = String.format("Could not retrieve authority information for '%s' on remote server '%s'. Server returned status code %d",
140 specifier.getURNValue(), remoteClientConfig.getUrl(), statusCode);
141 if (logger.isDebugEnabled()) {
142 logger.debug(errMsg);
144 throw new DocumentException(statusCode, errMsg);
154 // Makes a call to the remote SAS server for a authority item payload
156 public static PoxPayloadIn requestPayloadInFromRemoteServer(
157 AuthorityItemSpecifier specifier,
158 String remoteClientConfigName,
161 boolean syncHierarchicalRelationships) throws Exception {
162 PoxPayloadIn result = null;
164 ServiceContext authorityCtx = new MultipartServiceContextImpl(serviceName);
165 RemoteClientConfig remoteClientConfig = getRemoteClientConfig(authorityCtx, remoteClientConfigName);
166 AuthorityClient client = (AuthorityClient) authorityCtx.getClient(remoteClientConfig);
167 Response res = client.readItem(specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(),
168 AuthorityClient.INCLUDE_DELETED_ITEMS, syncHierarchicalRelationships);
171 int statusCode = res.getStatus();
172 if (statusCode == org.apache.commons.httpclient.HttpStatus.SC_OK) {
173 result = new PoxPayloadIn((String)res.readEntity(responseType)); // Get the entire response.
175 String errMsg = String.format("Could not retrieve authority item information for '%s:%s' on remote server '%s'. Server returned status code %d",
176 specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(), remoteClientConfig.getUrl(), statusCode);
177 if (logger.isDebugEnabled()) {
178 logger.debug(errMsg);
180 throw new DocumentException(statusCode, errMsg);
189 public static boolean setAuthorityItemDeprecated(ServiceContext ctx,
190 DocumentModel docModel, String authorityItemCommonSchemaName, Boolean flag) throws Exception {
191 boolean result = false;
193 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.DEPRECATED,
195 CoreSessionInterface repoSession = (CoreSessionInterface) ctx.getCurrentRepositorySession();
196 repoSession.saveDocument(docModel);
203 * The domain name part of refnames on a remote SAS may not match that of local refnames.
204 * Update all the payload's refnames with the local domain name.
206 public static PoxPayloadIn localizeRefNameDomains(ServiceContext ctx, PoxPayloadIn payload) throws org.dom4j.DocumentException {
207 String localDomain = ctx.getTenantName();
208 Matcher matcher = REFNAME_DOMAIN_PATTERN.matcher(payload.getXmlPayload());
209 StringBuffer localizedXmlBuffer = new StringBuffer();
211 while (matcher.find() == true) {
212 String remoteDomain = matcher.group(2);
214 if (logger.isDebugEnabled()) {
215 logger.debug("Replacing " + remoteDomain + " with " + localDomain);
218 matcher.appendReplacement(localizedXmlBuffer, matcher.group(1) + localDomain);
221 matcher.appendTail(localizedXmlBuffer);
223 if (logger.isTraceEnabled()) {
224 logger.trace(String.format("Updated payload:\n%s", localizedXmlBuffer));
227 return new PoxPayloadIn(localizedXmlBuffer.toString());
231 * Localizes the relations list in an authority item payload from a remote server during SAS sync.
233 * Relations to items that do not exist in the local authority are removed. If the related item
234 * doesn't exist locally because it is new on the remote and hasn't been synced yet, the relations
235 * will be created when the new item is synced, because the new item's relations list will include
238 * The following elements are removed from each relation: uri, csid, subjectCsid, objectCsid,
239 * object/csid, object/uri, subject/csid, subject/uri. These apply only to the remote. By removing
240 * them, the relation will be created locally if necessary.
243 * @param authorityResource
245 * @param itemSpecifier
250 public static PoxPayloadIn localizeRelations(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, Specifier itemSpecifier, PoxPayloadIn payload) throws Exception {
251 // TODO: Relations to items that don't exist need to be removed, because a create/update will fail
252 // if the subject/object of any supplied relation can't be found. Consider changing the create/update
253 // code to ignore any relations to items that don't exist, but still save the record and any other
254 // relations. This will speed up sync when many items have relations, since the checks to see if
255 // all related items exist locally can be skipped.
257 String itemShortId = itemSpecifier.value;
258 Document document = payload.getDOMDocument();
260 Map<String, String> namespaceUris = new HashMap<String, String>();
261 namespaceUris.put("rel", "http://collectionspace.org/services/relation");
263 XPath xPath = DocumentHelper.createXPath("//rel:relations-common-list/relation-list-item");
264 xPath.setNamespaceURIs(namespaceUris);
266 List<Node> listItemNodes = xPath.selectNodes(document);
268 if (logger.isDebugEnabled()) {
269 logger.debug(String.format("Found %d relation list items", listItemNodes.size()));
272 for (Node listItemNode : listItemNodes) {
273 String objectRefName = listItemNode.selectSingleNode("object/refName").getText();
274 AuthorityTermInfo objectTermInfo = RefNameUtils.parseAuthorityTermInfo(objectRefName);
275 String objectShortId = objectTermInfo.name;
278 !objectShortId.equals(itemShortId)
279 && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(objectShortId))
281 if (logger.isDebugEnabled()) {
282 logger.debug(String.format("Omitting remote relation: object with short id %s does does not exist locally", objectShortId));
285 listItemNode.detach();
289 String subjectRefName = listItemNode.selectSingleNode("subject/refName").getText();
290 AuthorityTermInfo subjectTermInfo = RefNameUtils.parseAuthorityTermInfo(subjectRefName);
291 String subjectShortId = subjectTermInfo.name;
294 !subjectShortId.equals(itemShortId)
295 && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(subjectShortId))
297 if (logger.isDebugEnabled()) {
298 logger.debug(String.format("Omitting remote relation: subject with short id %s does does not exist locally", subjectShortId));
301 listItemNode.detach();
305 listItemNode.selectSingleNode("csid").detach();
306 listItemNode.selectSingleNode("objectCsid").detach();
307 listItemNode.selectSingleNode("subjectCsid").detach();
308 listItemNode.selectSingleNode("uri").detach();
309 listItemNode.selectSingleNode("object/csid").detach();
310 listItemNode.selectSingleNode("object/uri").detach();
311 listItemNode.selectSingleNode("subject/csid").detach();
312 listItemNode.selectSingleNode("subject/uri").detach();
315 String xml = document.asXML();
317 if (logger.isTraceEnabled()) {
318 logger.trace("Prepared remote relations:\n" + xml);
321 return new PoxPayloadIn(xml);
325 * Check if an item with a given short ID exists in a parent.
328 * @param authorityResource
330 * @param itemSpecifier
331 * @return true if the item exists, false otherwise.
334 public static boolean checkItemExists(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, String itemSpecifier) throws Exception {
335 String itemCsid = null;
338 itemCsid = authorityResource.lookupItemCSID(ctx, itemSpecifier, parentCsid, "checkItemExists()", "CHECK_ITEM_EXISTS");
339 } catch (DocumentNotFoundException e) {
343 return (itemCsid != null);
347 * Mark the authority item as deprecated.
353 public static boolean markAuthorityItemAsDeprecated(ServiceContext ctx, String authorityItemCommonSchemaName, AuthorityItemSpecifier authorityItemSpecifier) throws Exception {
354 boolean result = false;
357 DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, (CoreSessionInterface)ctx.getCurrentRepositorySession(),
358 authorityItemCommonSchemaName, authorityItemSpecifier);
359 result = setAuthorityItemDeprecated(ctx, docModel, authorityItemCommonSchemaName, AuthorityServiceUtils.DEPRECATED);
360 } catch (Exception e) {
361 logger.warn(String.format("Could not mark item '%s' as deprecated.", authorityItemSpecifier.getItemSpecifier().getURNValue()), e);