]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
6c0232f189e0df6d0ca841f59e3efe0effe2ba16
[tmp/jakarta-migration.git] /
1 /**
2  *  This document is a part of the source code and related artifacts
3  *  for CollectionSpace, an open source collections management system
4  *  for museums and related institutions:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
18  *  Unless required by applicable law or agreed to in writing, software
19  *  distributed under the License is distributed on an "AS IS" BASIS,
20  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21  *  See the License for the specific language governing permissions and
22  *  limitations under the License.
23  */
24 package org.collectionspace.services.common.vocabulary.nuxeo;
25
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.ws.rs.core.MultivaluedMap;
31 import javax.ws.rs.core.Response;
32
33 import org.apache.commons.httpclient.HttpStatus;
34 import org.collectionspace.services.client.AbstractCommonListUtils;
35 import org.collectionspace.services.client.AuthorityClient;
36 import org.collectionspace.services.client.PayloadInputPart;
37 import org.collectionspace.services.client.PoxPayloadIn;
38 import org.collectionspace.services.client.PoxPayloadOut;
39 import org.collectionspace.services.client.XmlTools;
40 import org.collectionspace.services.client.workflow.WorkflowClient;
41 import org.collectionspace.services.common.ResourceMap;
42 import org.collectionspace.services.common.api.RefName;
43 import org.collectionspace.services.common.api.RefName.Authority;
44 import org.collectionspace.services.common.api.RefNameUtils;
45 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
46 import org.collectionspace.services.common.api.Tools;
47 import org.collectionspace.services.common.context.ServiceContext;
48 import org.collectionspace.services.common.document.DocumentException;
49 import org.collectionspace.services.common.document.DocumentNotFoundException;
50 import org.collectionspace.services.common.document.DocumentReferenceException;
51 import org.collectionspace.services.common.document.DocumentWrapper;
52 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
53 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
54 import org.collectionspace.services.common.vocabulary.AuthorityResource;
55 import org.collectionspace.services.common.vocabulary.AuthorityServiceUtils;
56 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
57 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
58 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
59 import org.collectionspace.services.config.service.ListResultField;
60 import org.collectionspace.services.config.service.ObjectPartType;
61 import org.collectionspace.services.jaxb.AbstractCommonList;
62 import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
63 import org.collectionspace.services.lifecycle.TransitionDef;
64 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
65 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
66 import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl;
67 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
68 import org.dom4j.Element;
69 import org.nuxeo.ecm.core.api.ClientException;
70 import org.nuxeo.ecm.core.api.DocumentModel;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 /**
75  * AuthorityDocumentModelHandler
76  *
77  * $LastChangedRevision: $
78  * $LastChangedDate: $
79  */
80 @SuppressWarnings("rawtypes")
81 public abstract class AuthorityDocumentModelHandler<AuthCommon>
82                 extends NuxeoDocumentModelHandler<AuthCommon> {
83
84         private final Logger logger = LoggerFactory.getLogger(AuthorityDocumentModelHandler.class);
85
86         protected String authorityCommonSchemaName;
87         protected String authorityItemCommonSchemaName;
88         protected boolean shouldUpdateRevNumber = true; // default to updating the revision number
89
90         public AuthorityDocumentModelHandler(String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
91                 this.authorityCommonSchemaName = authorityCommonSchemaName;
92                 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
93         }
94
95         public void setShouldUpdateRevNumber(boolean flag) {
96                 this.shouldUpdateRevNumber = flag;
97         }
98
99         public boolean getShouldUpdateRevNumber() {
100                 return this.shouldUpdateRevNumber;
101         }
102
103         /**
104          * The entity type expected from the JAX-RS Response object
105          */
106         public Class<String> getEntityResponseType() {
107                 return String.class;
108         }
109
110         @Override
111         public void prepareSync() throws Exception {
112                 this.setShouldUpdateRevNumber(AuthorityServiceUtils.DONT_UPDATE_REV);  // Never update rev nums on sync operations
113         }
114
115         protected PayloadInputPart extractPart(Response res, String partLabel)
116                         throws Exception {
117                         PoxPayloadIn input = new PoxPayloadIn((String)res.readEntity(getEntityResponseType()));
118                         PayloadInputPart payloadInputPart = input.getPart(partLabel);
119                         if (payloadInputPart == null) {
120                                 logger.error("Part " + partLabel + " was unexpectedly null.");
121                         }
122                         return payloadInputPart;
123         }
124
125         @Override
126         public boolean handleSync(DocumentWrapper<Object> wrapDoc) throws Exception {
127                 boolean result = false;
128                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
129                 Specifier specifier = (Specifier) wrapDoc.getWrappedObject();
130                 //
131                 // Get the rev number of the authority so we can compare with rev number of shared authority
132                 //
133                 DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, specifier);
134                 if (docModel != null) {
135                         Long localRev = (Long) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REV);
136                         String shortId = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.SHORT_IDENTIFIER);
137                         String remoteClientConfigName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REMOTECLIENT_CONFIG_NAME); // If set, contains the name of the remote client configuration (remoteClientConfigName) from the tenant bindings
138                         //
139                         // Using the short ID of the local authority, create a URN specifier to retrieve the SAS authority
140                         //
141                         Specifier sasSpecifier = new Specifier(SpecifierForm.URN_NAME, shortId);
142                         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(ctx, remoteClientConfigName, sasSpecifier, getEntityResponseType());
143                         //
144                         // If the authority on the SAS is newer, synch all the items and then the authority record as well
145                         //
146                         //
147                         Long sasRev = getRevision(sasPayloadIn);
148                         if (sasRev > localRev || true) { // FIXME: Along with the revision number, we need to use other meta information to determine if a sync should happen -for now, alway sync
149                                 //
150                                 // First, sync all the authority items
151                                 //
152                                 syncAllItems(ctx, sasSpecifier);  // FIXME: We probably want to consider "paging" this instead of handling the entire set of items.
153                                 //
154                                 // Next, sync the authority resource/record itself
155                                 //
156                                 AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
157                                 ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, AuthorityServiceUtils.DONT_UPDATE_REV); // Don't update the rev number, use the rev number for the SAS instance instead
158                                 PoxPayloadOut payloadOut = authorityResource.update(ctx, ctx.getResourceMap(), ctx.getUriInfo(), docModel.getName(),
159                                                 sasPayloadIn);
160                                 if (payloadOut != null) {
161                                         ctx.setOutput(payloadOut);
162                                         result = true;
163                                 }
164                                 //
165                                 // We may need to transition the authority into a replicated state the first time we sync it.
166                                 //
167                                 String workflowState = docModel.getCurrentLifeCycleState();
168                                 if (workflowState.contains(WorkflowClient.WORKFLOWSTATE_REPLICATED) == false) {
169                                         String authorityCsid = docModel.getName();
170                                         authorityResource.updateWorkflowWithTransition(ctx, ctx.getUriInfo(), authorityCsid, WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
171                                 }
172                         }
173                 } else {
174                         String errMsg = String.format("Authority of type '%s' with identifier '%s' does not exist.",
175                                         getServiceContext().getServiceName(), specifier.getURNValue());
176                         logger.debug(errMsg);
177                         throw new DocumentException(errMsg);
178                 }
179
180                 return result;
181         }
182
183         /*
184          * Get the list of authority items from the remote shared authority server (SAS) and try
185          * to synchronize them with the local items.  If items exist on the remote but not the local, we'll create them.
186          */
187         protected int syncAllItems(ServiceContext ctx, Specifier sasAuthoritySpecifier) throws Exception {
188                 int result = -1;
189                 int created = 0;
190                 int synched = 0;
191                 int alreadySynched = 0;
192                 int deprecated = 0;
193                 int totalItemsProcessed = 0;
194                 ArrayList<String> itemsInRemoteAuthority = new ArrayList<String>();
195                 //
196                 // Iterate over the list of items/terms in the remote authority
197                 //
198                 PoxPayloadIn itemListPayload = requestItemList(ctx, sasAuthoritySpecifier);
199                 List<Element> itemList = getItemList(itemListPayload);
200                 if (itemList != null) {
201                         for (Element e:itemList) {
202                                 String remoteRefName = XmlTools.getElementValue(e, AuthorityItemJAXBSchema.REF_NAME);
203                                 itemsInRemoteAuthority.add(XmlTools.getElementValue(e, AuthorityItemJAXBSchema.SHORT_IDENTIFIER));
204                                 long status = syncRemoteItemWithLocalItem(ctx, remoteRefName);
205                                 if (status == 1) {
206                                         created++;
207                                 } else if (status == 0) {
208                                         synched++;
209                                 } else {
210                                         alreadySynched++;
211                                 }
212                                 totalItemsProcessed++;
213                         }
214                 }
215                 //
216                 // Now see if we need to deprecate or delete items that have been hard-deleted from the SAS but still exist
217                 // locally.  Subtract (remove) the list of remote items from the list of local items to determine which
218                 // of the remote items have been hard deleted.
219                 //
220                 ArrayList<String> itemsInLocalAuthority = getItemsInLocalAuthority(ctx, sasAuthoritySpecifier);
221                 itemsInLocalAuthority.removeAll(itemsInRemoteAuthority);
222                 if (itemsInLocalAuthority.size() > 0) {
223                         ArrayList<String> remainingItems = itemsInLocalAuthority; // now a subset of local items that no longer exist on the SAS, so we need to try to delete them (or mark them as deprecated if they still have records referencing them)
224                         //
225                         // We now need to either hard-delete or deprecate the remaining authorities
226                         //
227                         long processed = deleteOrDeprecateItems(ctx, sasAuthoritySpecifier, remainingItems);
228                         if (processed != remainingItems.size()) {
229                                 throw new Exception("Encountered unexpected exception trying to delete or deprecated authority items during synchronization.");
230                         }
231                 }
232                 //
233                 // Now that we've sync'd all the items, we need to synchronize the hierarchy relationships
234                 //
235                 for (String itemShortId:itemsInRemoteAuthority) {
236                         long status = syncRemoteItemRelationshipsWithLocalItem(ctx, sasAuthoritySpecifier, itemShortId);
237                         if (status == 1) {
238                                 created++;
239                         } else if (status == 0) {
240                                 synched++;
241                         } else {
242                                 alreadySynched++;
243                         }
244                         totalItemsProcessed++;
245                 }
246
247                 logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed));
248                 logger.info(String.format("Number of items synchronized: %d", synched));
249                 logger.info(String.format("Number of items created during sync: %d", created));
250                 logger.info(String.format("Number not needing synchronization: %d", alreadySynched));
251
252                 return result;
253         }
254
255         /**
256          * This method should ***only*** be used as part of a SAS synch operation.
257          *
258          * @param ctx
259          * @param refNameList
260          * @return
261          * @throws Exception
262          */
263         private long deleteOrDeprecateItems(ServiceContext ctx, Specifier authoritySpecifier, ArrayList<String> itemShortIdList) throws Exception {
264                 long result = 0;
265                 AuthorityItemSpecifier authorityItemSpecificer = null;
266
267                 ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, false); // Don't update the revision number when we delete or deprecate the item
268                 for (String itemShortId:itemShortIdList) {
269                         AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
270                         try {
271                                 authorityItemSpecificer = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authoritySpecifier.value,
272                                                 itemShortId);
273                                 authorityResource.deleteAuthorityItem(ctx,
274                                                 authorityItemSpecificer.getParentSpecifier().getURNValue(),
275                                                 authorityItemSpecificer.getItemSpecifier().getURNValue(),
276                                                 AuthorityServiceUtils.DONT_UPDATE_REV); // Since we're sync'ing, we shouldn't update the revision number (obviously this only applies to soft-deletes since hard-deletes destroy the record)
277                                 result++;
278                         } catch (DocumentReferenceException de) {
279                                 logger.info(String.format("Authority item with '%s' has existing references and cannot be removed during sync.",
280                                                 authorityItemSpecificer), de);
281                                 boolean marked = AuthorityServiceUtils.markAuthorityItemAsDeprecated(ctx, authorityItemCommonSchemaName,
282                                                 authorityItemSpecificer);
283                                 if (marked == true) {
284                                         result++;
285                                 }
286                         } catch (Exception e) {
287                                 logger.warn(String.format("Unable to delete authority item '%s'", authorityItemSpecificer), e);
288                                 throw e;
289                         }
290                 }
291
292                 if (logger.isWarnEnabled() == true) {
293                         if (result != itemShortIdList.size()) {
294                                 logger.warn(String.format("Unable to delete or deprecate some authority items during synchronization with SAS.  Deleted or deprecated %d of %d.  See the services log file for details.",
295                                                 result, itemShortIdList.size()));
296                         }
297                 }
298
299                 return result;
300         }
301
302         /**
303          * Gets the list of SAS related items in the local authority.  We exlude items with the "proposed" flags because
304          * we want a list with only SAS created items.
305          *
306          * We need to add pagination support to this call!!!
307          *
308          * @param ctx
309          * @param authoritySpecifier
310          * @return
311          * @throws Exception
312          */
313         private ArrayList<String> getItemsInLocalAuthority(ServiceContext ctx, Specifier authoritySpecifier) throws Exception {
314                 ArrayList<String> result = new ArrayList<String>();
315
316                 ResourceMap resourceMap = ctx.getResourceMap();
317                 String resourceName = ctx.getClient().getServiceName();
318                 AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
319                 AbstractCommonList acl = authorityResource.getAuthorityItemList(ctx, authoritySpecifier.getURNValue(), ctx.getUriInfo());
320
321                 List<ListItem> listItemList = acl.getListItem();
322                 for (ListItem listItem:listItemList) {
323                         Boolean proposed = getBooleanValue(listItem, AuthorityItemJAXBSchema.PROPOSED);
324                         if (proposed == false) { // exclude "proposed" (i.e., local-only items)
325                                 result.add(AbstractCommonListUtils.ListItemGetElementValue(listItem, AuthorityItemJAXBSchema.SHORT_IDENTIFIER));
326                         }
327                 }
328
329                 return result;
330         }
331
332         private Boolean getBooleanValue(ListItem listItem, String name) {
333                 Boolean result = null;
334
335                 String value = AbstractCommonListUtils.ListItemGetElementValue(listItem, name);
336                 if (value != null) {
337                         result = Boolean.valueOf(value);
338                 }
339
340                 return result;
341         }
342
343         private String getStringValue(ListItem listItem, String name) {
344                 return AbstractCommonListUtils.ListItemGetElementValue(listItem, AuthorityItemJAXBSchema.REF_NAME);
345         }
346
347         /**
348          * This method should only be used during a SAS synchronization request.
349          *
350          * @param ctx
351          * @param parentIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
352          * @param itemIdentifier   - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
353          * @throws Exception
354          */
355         protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier, Boolean syncHierarchicalRelationships) throws Exception {
356                 //
357                 // Create a URN short ID specifier for the getting a copy of the remote authority item
358                 //
359                 Specifier authoritySpecifier = Specifier.getSpecifier(parentIdentifier);
360                 Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier);
361                 AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier);
362                 //
363                 // Get the remote client configuration name
364                 //
365                 DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, authoritySpecifier);
366                 String remoteClientConfigName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REMOTECLIENT_CONFIG_NAME); // If set, contains the name of the remote client configuration (remoteClientConfigName) from the tenant bindings
367                 //
368                 // Get the remote payload
369                 //
370                 PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(sasAuthorityItemSpecifier, remoteClientConfigName,
371                                 ctx.getServiceName(), getEntityResponseType(), syncHierarchicalRelationships);
372                 sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); // We need to filter domain name part of any and all refnames in the payload
373                 //
374                 // Using the payload from the remote server, create a local copy of the item
375                 //
376                 AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
377                 Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.getURNValue(),
378                                 sasPayloadIn, AuthorityServiceUtils.DONT_UPDATE_REV, AuthorityServiceUtils.NOT_PROPOSED, AuthorityServiceUtils.SAS_ITEM);
379                 //
380                 // Check the response for successful POST result
381                 //
382                 if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
383                         throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
384                                         itemIdentifier, parentIdentifier));
385                 }
386                 //
387                 // Since we're creating an item that was sourced from the replication server, we need to replicate it locally.
388                 //
389                 authorityResource.updateItemWorkflowWithTransition(ctx, parentIdentifier, itemIdentifier,
390                                 WorkflowClient.WORKFLOWTRANSITION_REPLICATE, AuthorityServiceUtils.DONT_UPDATE_REV); // don't update the rev number of the new replicated item (use the rev number of the sourced item)
391                 }
392
393         /**
394          * Try to synchronize a remote item (using its refName) with a local item.  If the local doesn't yet
395          * exist, we'll create it.
396          * Result values:
397          *      -1 = sync not needed; i.e., already in sync
398          *   0 = sync succeeded
399          *   1 = local item was missing so we created it
400          * @param ctx
401          * @param refName
402          * @return
403          * @throws Exception
404          */
405         protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String itemRefName) throws Exception {
406                 long result = -1;
407                 //
408                 // Using the item refname (with no local CSID), create specifiers that we'll use to find the local versions
409                 //
410                 AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(itemRefName);
411                 String parentIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.inAuthority.name);
412                 String itemIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.name);
413                 //
414                 // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates)
415                 //
416                 AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
417                 PoxPayloadOut localItemPayloadOut;
418                 try {
419                         localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier);
420                 } catch (DocumentNotFoundException dnf) {
421                         //
422                         // Document not found, means we need to create an item/term that exists only on the SAS
423                         //
424                         logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", itemRefName));
425                         createLocalItem(ctx, parentIdentifier, itemIdentifier, AuthorityClient.DONT_INCLUDE_RELATIONS);
426                         return 1; // exit with status of 1 means we created a new authority item
427                 }
428                 //
429                 // If we get here, we know the item exists both locally and remotely, so we need to synchronize them.
430                 //
431                 //
432                 try {
433                         PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, false);
434                         if (theUpdate != null) {
435                                 result = 0; // means we needed to sync this item with SAS
436                                 logger.debug(String.format("Synced authority item %s in authority %s",
437                                                 itemIdentifier, parentIdentifier));
438                         }
439                 } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them.
440                         result = -1;
441                         logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.",
442                                         itemIdentifier));
443                 }
444
445                 return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item
446         }
447
448         /**
449          * Ensure the local items relationships look the same as the remote items' by synchronizing the hierarchy relationship records
450          * of the SAS item with the local item.
451          *
452          * @param ctx
453          * @param refName
454          * @return
455          * @throws Exception
456          */
457         protected long syncRemoteItemRelationshipsWithLocalItem(ServiceContext ctx, Specifier authoritySpecifier, String itemShortId) throws Exception {
458                 long result = -1;
459
460                 String parentIdentifier = authoritySpecifier.getURNValue();
461                 String itemIdentifier = Specifier.createShortIdURNValue(itemShortId);
462                 //
463                 // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates)
464                 //
465                 AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
466                 PoxPayloadOut localItemPayloadOut;
467                 try {
468                         MultivaluedMap queryParams = ctx.getQueryParams();
469                         localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier);
470                 } catch (DocumentNotFoundException dnf) {
471                         //
472                         // Document not found, means we need to create an item/term that exists only on the SAS
473                         //
474                         logger.info(String.format("Remote item with short ID ='%s' doesn't exist locally, so we can't synchronize its relationships.", itemShortId));
475                         return result;
476                 }
477                 //
478                 // If we get here, we know the item exists both locally and remotely, so we need to synchronize the hierarchy relationships.
479                 //
480                 //
481                 try {
482                         PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, AuthorityClient.INCLUDE_RELATIONS);
483                         if (theUpdate != null) {
484                                 result = 0; // means we needed to sync this item with SAS
485                                 logger.debug(String.format("Synced authority item %s in authority %s",
486                                                         itemIdentifier, parentIdentifier));
487                                 }
488                 } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them.
489                         result = -1;
490                         logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.",
491                                         itemIdentifier));
492                 }
493
494                 return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item
495
496
497         }
498
499         private void assertStatusCode(Response res, Specifier specifier, AuthorityClient client) throws Exception {
500                 int statusCode = res.getStatus();
501
502                 if (statusCode != HttpStatus.SC_OK) {
503                         String errMsg = String.format("Could not retrieve authority information for '%s' on remote server '%s'.  Server returned status code %d",
504                                         specifier.getURNValue(), client.getBaseURL(), statusCode);
505                         throw new DocumentException(statusCode, errMsg);
506                 }
507         }
508
509         /**
510          * Request an authority item list payload from the SAS server. This is a non-paging solution.
511          * If the authority has a very large number of items/terms, we might not be able to handle them
512          * all.
513          *
514          * @param ctx
515          * @param specifier
516          * @return
517          * @throws Exception
518          */
519         private PoxPayloadIn requestItemList(ServiceContext ctx, Specifier specifier) throws Exception {
520                 PoxPayloadIn result = null;
521                 AuthorityClient client = (AuthorityClient) ctx.getClient();
522
523                 // Request the item list using the max page size (0).
524
525                 Response res = client.readItemList(
526                         specifier.getURNValue(),
527                         null, // partial term string
528                         null, // keyword string
529                         0,    // page size
530                         0     // page number
531                 );
532
533                 assertStatusCode(res, specifier, client);
534
535                 try {
536                         result = new PoxPayloadIn((String) res.readEntity(getEntityResponseType())); // Get the entire response.
537                 } finally {
538                         res.close();
539                 }
540
541                 return result;
542         }
543
544         /*
545          * Non standard injection of CSID into common part, since caller may access through
546          * shortId, and not know the CSID.
547          * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
548          */
549         @Override
550         protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
551                         throws Exception {
552                 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
553
554                 // Add the CSID to the common part
555                 if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
556                         String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
557                         unQObjectProperties.put("csid", csid);
558                 }
559
560                 return unQObjectProperties;
561         }
562
563         public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
564                 super.fillAllParts(wrapDoc, action);
565                 //
566                 // Update the record's revision number on both CREATE and UPDATE actions, but not on SYNC
567                 //
568                 if (this.getShouldUpdateRevNumber() == true) { // We won't update rev numbers on synchronization with SAS
569                         updateRevNumbers(wrapDoc);
570                 }
571         }
572
573         protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
574                 DocumentModel documentModel = wrapDoc.getWrappedObject();
575                 Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
576                 if (rev == null) {
577                         rev = (long)0;
578                 } else {
579                         rev++;
580                 }
581                 documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
582         }
583
584         /*
585          * We consider workflow state changes as changes that should bump the revision number
586          * (non-Javadoc)
587          * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
588          */
589         @Override
590         public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
591                 boolean updateRevNumber = this.getShouldUpdateRevNumber();
592                 Boolean contextProperty = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
593                 if (contextProperty != null) {
594                         updateRevNumber = contextProperty;
595                 }
596
597                 if (updateRevNumber == true) { // We don't update the rev number of synchronization requests
598                         updateRevNumbers(wrapDoc);
599                 }
600         }
601
602         @Override
603         public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
604                 super.handleCreate(wrapDoc);
605                 // CSPACE-3178:
606                 // Uncomment once debugged and App layer is read to integrate
607                 // Experimenting with this uncommented now ...
608                 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
609                 updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
610         }
611
612         protected String buildWhereForShortId(String name) {
613                 return authorityCommonSchemaName
614                                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
615                                 + "='" + name + "'";
616         }
617
618         private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
619                 return true;
620         }
621
622         private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
623                 boolean result = true;
624
625                 ServiceContext ctx = this.getServiceContext();
626                 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
627                 String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
628                 try {
629                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
630                         if (searchResultWrapper != null) {
631                                 result = false;
632                                 if (logger.isInfoEnabled() == true) {
633                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
634                                         String debugMsg = String.format("Could not create a new authority with a short identifier of '%s', because one already exists with the same short identifer: CSID = '%s'",
635                                                         shortIdentifier, searchResult.getName());
636                                         logger.trace(debugMsg);
637                                 }
638                         }
639                 } catch (DocumentNotFoundException e) {
640                         // Not a problem, just means we couldn't find another authority with that short ID
641                 }
642
643                 return result;
644         }
645
646         /**
647          * If no short identifier was provided in the input payload,
648          * generate a short identifier from the display name. Either way though,
649          * the short identifier needs to be unique.
650          */
651         private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
652                 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
653                 String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
654                 String shortDisplayName = "";
655                 String generateShortIdentifier = null;
656                 if (Tools.isEmpty(shortIdentifier)) {
657                         generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
658                         docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
659                 }
660
661                 if (isUnique(docModel, schemaName) == false) {
662                         String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
663                         String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
664                         String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
665                                         errMsgVerb, shortId);
666                         throw new DocumentException(errMsg);
667                 }
668         }
669
670         /**
671          * Generate a refName for the authority from the short identifier
672          * and display name.
673          *
674          * All refNames for authorities are generated.  If a client supplies
675          * a refName, it will be overwritten during create (per this method)
676          * or discarded during update (per filterReadOnlyPropertiesForPart).
677          *
678          * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
679          *
680          */
681         protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
682                 DocumentModel docModel = wrapDoc.getWrappedObject();
683                 RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
684                 String refName = authority.toString();
685                 docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
686         }
687
688         @Override
689         public RefName.RefNameInterface getRefName(ServiceContext ctx,
690                         DocumentModel docModel) {
691                 RefName.RefNameInterface refname = null;
692
693                 try {
694                         String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
695                         String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
696                         RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
697                                         ctx.getServiceName(),
698                                         null,   // Only use shortId form!!!
699                                         shortIdentifier,
700                                         displayName);
701                         refname = authority;
702                 } catch (Exception e) {
703                         logger.error(e.getMessage(), e);
704                 }
705
706                 return refname;
707         }
708
709         @Override
710         protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
711                 String result = null;
712
713                 DocumentModel docModel = docWrapper.getWrappedObject();
714                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
715                 RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
716                 result = refname.getDisplayName();
717
718                 return result;
719         }
720
721         public String getShortIdentifier(ServiceContext ctx, String authCSID, String schemaName) throws Exception {
722                 String shortIdentifier = null;
723                 CoreSessionInterface repoSession = null;
724                 boolean releaseSession = false;
725
726                 NuxeoRepositoryClientImpl nuxeoRepoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
727                 try {
728                         repoSession = nuxeoRepoClient.getRepositorySession(ctx);
729                         DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
730                         DocumentModel docModel = wrapDoc.getWrappedObject();
731                         if (docModel == null) {
732                                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
733                         }
734                         shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
735                 } catch (ClientException ce) {
736                         throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
737                 } finally {
738                         if (repoSession != null) {
739                                 nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
740                         }
741                 }
742
743                 return shortIdentifier;
744         }
745
746         /**
747          * Filters out selected values supplied in an update request.
748          *
749          * @param objectProps the properties filtered out from the update payload
750          * @param partMeta metadata for the object to fill
751          */
752         @Override
753         public void filterReadOnlyPropertiesForPart(
754                         Map<String, Object> objectProps, ObjectPartType partMeta) {
755                 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
756                 String commonPartLabel = getServiceContext().getCommonPartLabel();
757                 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
758                         objectProps.remove(AuthorityJAXBSchema.CSID);
759                         objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
760                         objectProps.remove(AuthorityJAXBSchema.REF_NAME);
761                 }
762         }
763
764         @Override
765         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
766                         String schema, ListResultField field) throws DocumentException {
767                 Object result = null;
768
769                 result = super.getListResultValue(docModel, schema, field);
770
771                 return result;
772         }
773 }