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