]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
30016a37983a5328a600c5d96c97c31e6ff1b22e
[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.collectionspace.services.client.AbstractCommonListUtils;
34 import org.collectionspace.services.client.AuthorityClient;
35 import org.collectionspace.services.client.PayloadInputPart;
36 import org.collectionspace.services.client.PoxPayloadIn;
37 import org.collectionspace.services.client.PoxPayloadOut;
38 import org.collectionspace.services.client.XmlTools;
39 import org.collectionspace.services.client.workflow.WorkflowClient;
40 import org.collectionspace.services.common.ResourceMap;
41 import org.collectionspace.services.common.api.RefName;
42 import org.collectionspace.services.common.api.RefName.Authority;
43 import org.collectionspace.services.common.api.RefNameUtils;
44 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
45 import org.collectionspace.services.common.api.Tools;
46 import org.collectionspace.services.common.context.ServiceContext;
47 import org.collectionspace.services.common.document.DocumentException;
48 import org.collectionspace.services.common.document.DocumentNotFoundException;
49 import org.collectionspace.services.common.document.DocumentReferenceException;
50 import org.collectionspace.services.common.document.DocumentWrapper;
51 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
52 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
53 import org.collectionspace.services.common.vocabulary.AuthorityResource;
54 import org.collectionspace.services.common.vocabulary.AuthorityServiceUtils;
55 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
56 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
57 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
58 import org.collectionspace.services.config.service.ObjectPartType;
59 import org.collectionspace.services.jaxb.AbstractCommonList;
60 import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
61 import org.collectionspace.services.lifecycle.TransitionDef;
62 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
63 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
64 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
65 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
66 import org.dom4j.Element;
67 import org.eclipse.jetty.http.HttpStatus;
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);
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      * @param ctx
257      * @param refNameList
258      * @return
259      * @throws Exception
260      */
261         private long deleteOrDeprecateItems(ServiceContext ctx, Specifier authoritySpecifier, ArrayList<String> itemShortIdList) throws Exception {
262         long result = 0;
263         AuthorityItemSpecifier authorityItemSpecificer = null;
264
265         ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, false); // Don't update the revision number when we delete or deprecate the item
266         for (String itemShortId:itemShortIdList) {
267                 AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
268                 try {
269                 authorityItemSpecificer = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authoritySpecifier.value,
270                                 itemShortId);
271                         authorityResource.deleteAuthorityItem(ctx,
272                                         authorityItemSpecificer.getParentSpecifier().getURNValue(),
273                                         authorityItemSpecificer.getItemSpecifier().getURNValue(),
274                                         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)
275                         result++;
276                 } catch (DocumentReferenceException de) {
277                         logger.info(String.format("Authority item with '%s' has existing references and cannot be removed during sync.",
278                                         authorityItemSpecificer), de);
279                         boolean marked = AuthorityServiceUtils.markAuthorityItemAsDeprecated(ctx, authorityItemCommonSchemaName,
280                                         authorityItemSpecificer);
281                         if (marked == true) {
282                                 result++;
283                         }
284                 } catch (Exception e) {
285                         logger.warn(String.format("Unable to delete authority item '%s'", authorityItemSpecificer), e);
286                         throw e;
287                 }
288         }
289
290         if (logger.isWarnEnabled() == true) {
291                 if (result != itemShortIdList.size()) {
292                         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.",
293                                         result, itemShortIdList.size()));
294                 }
295         }
296         
297         return result;
298     }
299     
300     /**
301      * Gets the list of SAS related items in the local authority.  We exlude items with the "proposed" flags because
302      * we want a list with only SAS created items.
303      * 
304      * We need to add pagination support to this call!!!
305      * 
306      * @param ctx
307      * @param authoritySpecifier
308      * @return
309      * @throws Exception
310      */
311     private ArrayList<String> getItemsInLocalAuthority(ServiceContext ctx, Specifier authoritySpecifier) throws Exception {
312         ArrayList<String> result = new ArrayList<String>();
313         
314         ResourceMap resourceMap = ctx.getResourceMap();
315         String resourceName = ctx.getClient().getServiceName();
316         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
317         AbstractCommonList acl = authorityResource.getAuthorityItemList(ctx, authoritySpecifier.getURNValue(), ctx.getUriInfo());
318         
319         List<ListItem> listItemList = acl.getListItem();
320         for (ListItem listItem:listItemList) {
321                 Boolean proposed = getBooleanValue(listItem, AuthorityItemJAXBSchema.PROPOSED);
322                 if (proposed == false) { // exclude "proposed" (i.e., local-only items)
323                         result.add(AbstractCommonListUtils.ListItemGetElementValue(listItem, AuthorityItemJAXBSchema.SHORT_IDENTIFIER));
324                 }
325         }
326         
327         return result;
328     }
329     
330     private Boolean getBooleanValue(ListItem listItem, String name) {
331         Boolean result = null;
332         
333                 String value = AbstractCommonListUtils.ListItemGetElementValue(listItem, name);
334                 if (value != null) {
335                         result = Boolean.valueOf(value);
336                 }
337                 
338                 return result;
339     }
340     
341     private String getStringValue(ListItem listItem, String name) {
342         return AbstractCommonListUtils.ListItemGetElementValue(listItem, AuthorityItemJAXBSchema.REF_NAME);
343     }
344     
345     /**
346      * This method should only be used during a SAS synchronization request.
347      * 
348      * @param ctx
349      * @param parentIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
350      * @param itemIdentifier   - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
351      * @throws Exception 
352      */
353     protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier, Boolean syncHierarchicalRelationships) throws Exception {
354         //
355         // Create a URN short ID specifier for the getting a copy of the remote authority item
356         //
357         Specifier authoritySpecifier = Specifier.getSpecifier(parentIdentifier);
358         Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier);
359         AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier);
360         //
361         // Get the remote client configuration name
362         //
363         DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, authoritySpecifier);
364         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
365         //
366         // Get the remote payload
367         //
368         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(sasAuthorityItemSpecifier, remoteClientConfigName,
369                         ctx.getServiceName(), getEntityResponseType(), syncHierarchicalRelationships);
370         sasPayloadIn = AuthorityServiceUtils.filterRefnameDomains(ctx, sasPayloadIn); // We need to filter domain name part of any and all refnames in the payload
371         //
372         // Using the payload from the remote server, create a local copy of the item
373         //
374         AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
375         Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.getURNValue(),
376                         sasPayloadIn, AuthorityServiceUtils.DONT_UPDATE_REV, AuthorityServiceUtils.NOT_PROPOSED, AuthorityServiceUtils.SAS_ITEM);
377         //
378         // Check the response for successful POST result
379         //
380         if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
381                 throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
382                                 itemIdentifier, parentIdentifier));
383         }
384         //
385         // Since we're creating an item that was sourced from the replication server, we need to replicate it locally.
386         //
387         authorityResource.updateItemWorkflowWithTransition(ctx, parentIdentifier, itemIdentifier, 
388                         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)
389         }
390     
391     /**
392      * Try to synchronize a remote item (using its refName) with a local item.  If the local doesn't yet
393      * exist, we'll create it.
394      * Result values:
395      *  -1 = sync not needed; i.e., already in sync
396      *   0 = sync succeeded
397      *   1 = local item was missing so we created it
398      * @param ctx
399      * @param refName
400      * @return
401      * @throws Exception
402      */
403     protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String itemRefName) throws Exception {
404         long result = -1;
405         //
406         // Using the item refname (with no local CSID), create specifiers that we'll use to find the local versions
407         //
408         AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(itemRefName);
409         String parentIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.inAuthority.name);
410         String itemIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.name);
411         //
412         // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates)
413         //
414         AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();            
415         PoxPayloadOut localItemPayloadOut;
416         try {
417                 localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier);
418         } catch (DocumentNotFoundException dnf) {
419                 //
420                 // Document not found, means we need to create an item/term that exists only on the SAS
421                 //
422                 logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", itemRefName));
423                 createLocalItem(ctx, parentIdentifier, itemIdentifier, AuthorityClient.DONT_INCLUDE_RELATIONS);
424                 return 1; // exit with status of 1 means we created a new authority item
425         }
426         //
427         // If we get here, we know the item exists both locally and remotely, so we need to synchronize them.
428         //
429         //
430         try {
431                 PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, false);
432                 if (theUpdate != null) {
433                         result = 0; // means we needed to sync this item with SAS
434                         logger.debug(String.format("Sync'd authority item parent='%s' id='%s with SAS.  Updated payload is: \n%s",
435                                         parentIdentifier, itemIdentifier, theUpdate.getXmlPayload()));
436                 }
437         } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them.
438                 result = -1;
439                 logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.",
440                                 itemIdentifier));
441         }
442         
443         return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item
444     }
445     
446     /**
447      * Ensure the local items relationships look the same as the remote items' by synchronizing the hierarchy relationship records
448      * of the SAS item with the local item.
449      * 
450      * @param ctx
451      * @param refName
452      * @return
453      * @throws Exception
454      */
455     protected long syncRemoteItemRelationshipsWithLocalItem(ServiceContext ctx, Specifier authoritySpecifier, String itemShortId) throws Exception {
456         long result = -1;
457         
458         String parentIdentifier = authoritySpecifier.getURNValue();
459         String itemIdentifier = Specifier.createShortIdURNValue(itemShortId);
460         //
461         // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates)
462         //
463         AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();            
464         PoxPayloadOut localItemPayloadOut;
465         try {
466                 MultivaluedMap queryParams = ctx.getQueryParams();
467                 localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier);
468         } catch (DocumentNotFoundException dnf) {
469                 //
470                 // Document not found, means we need to create an item/term that exists only on the SAS
471                 //
472                 logger.info(String.format("Remote item with short ID ='%s' doesn't exist locally, so we can't synchronize its relationships.", itemShortId));
473                 return result;
474         }
475         //
476         // If we get here, we know the item exists both locally and remotely, so we need to synchronize the hierarchy relationships.
477         //
478         //
479         try {
480                 PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, AuthorityClient.INCLUDE_RELATIONS);
481                 if (theUpdate != null) {
482                         result = 0; // means we needed to sync this item with SAS
483                         logger.debug(String.format("Sync'd authority item parent='%s' id='%s with SAS.  Updated payload is: \n%s",
484                                         parentIdentifier, itemIdentifier, theUpdate.getXmlPayload()));
485                 }
486         } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them.
487                 result = -1;
488                 logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.",
489                                 itemIdentifier));
490         }
491         
492         return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item
493         
494         
495     }
496     
497     private void assertStatusCode(Response res, Specifier specifier, AuthorityClient client) throws Exception {
498         int statusCode = res.getStatus();
499
500         if (statusCode != HttpStatus.OK_200) {
501                 String errMsg = String.format("Could not retrieve authority information for '%s' on remote server '%s'.  Server returned status code %d",
502                                 specifier.getURNValue(), client.getBaseURL(), statusCode);
503                 res.close();
504                 throw new DocumentException(statusCode, errMsg);
505         }
506     }
507     
508     /**
509      * Request an authority item list payload from the SAS server.
510      * 
511      * @param ctx
512      * @param specifier
513      * @return
514      * @throws Exception
515      */
516     private PoxPayloadIn requestPayloadInItemList(ServiceContext ctx, Specifier specifier) throws Exception {
517         PoxPayloadIn result = null;
518         AuthorityClient client = (AuthorityClient) ctx.getClient();
519         //
520         // First find out how many items exist
521         Response res = client.readItemList(specifier.getURNValue(),
522                         null,   // partial term string
523                         null,   // keyword string
524                         0,              // page size
525                         0               // page number
526                         );
527         assertStatusCode(res, specifier, client);
528         AbstractCommonList commonList = res.readEntity(AbstractCommonList.class);
529         res.close();
530         long numOfItems = commonList.getTotalItems();        
531         
532         //
533         // Next, request a payload list with all the items
534         res = client.readItemList(specifier.getURNValue(),
535                         null,           // partial term string
536                         null,           // keyword string
537                         numOfItems,     // page size
538                         0                       // page number
539                         );        
540         assertStatusCode(res, specifier, client);
541
542         try {
543             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response.
544         } finally {
545                 res.close();
546         }
547         
548         return result;
549     }
550     
551
552     /*
553      * Non standard injection of CSID into common part, since caller may access through
554      * shortId, and not know the CSID.
555      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
556      */
557     @Override
558     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
559             throws Exception {
560         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
561
562         // Add the CSID to the common part
563         if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
564             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
565             unQObjectProperties.put("csid", csid);
566         }
567
568         return unQObjectProperties;
569     }
570     
571     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
572         super.fillAllParts(wrapDoc, action);
573         //
574         // Update the record's revision number on both CREATE and UPDATE actions, but not on SYNC
575         //
576         if (this.getShouldUpdateRevNumber() == true) { // We won't update rev numbers on synchronization with SAS
577                 updateRevNumbers(wrapDoc);
578         }
579     }
580     
581     protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
582         DocumentModel documentModel = wrapDoc.getWrappedObject();
583         Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
584         if (rev == null) {
585                 rev = (long)0;
586         } else {
587                 rev++;
588         }
589         documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
590     }
591     
592     /*
593      * We consider workflow state changes as changes that should bump the revision number
594      * (non-Javadoc)
595      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
596      */
597     @Override
598     public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
599         boolean updateRevNumber = this.getShouldUpdateRevNumber();
600         Boolean contextProperty = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
601         if (contextProperty != null) {
602                 updateRevNumber = contextProperty;
603         }
604
605         if (updateRevNumber == true) { // We don't update the rev number of synchronization requests
606                 updateRevNumbers(wrapDoc);
607         }
608     }
609     
610     @Override
611     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
612         super.handleCreate(wrapDoc);
613         // CSPACE-3178:
614         // Uncomment once debugged and App layer is read to integrate
615         // Experimenting with this uncommented now ...
616         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
617         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
618     }
619     
620     protected String buildWhereForShortId(String name) {
621         return authorityCommonSchemaName
622                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
623                 + "='" + name + "'";
624     }
625     
626     private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
627         return true;
628     }
629     
630     private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
631         boolean result = true;
632         
633         ServiceContext ctx = this.getServiceContext();
634         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
635         String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
636         try {
637                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
638                         if (searchResultWrapper != null) {
639                                 result = false;
640                                 if (logger.isInfoEnabled() == true) {
641                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
642                                         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'",
643                                                         shortIdentifier, searchResult.getName());
644                                         logger.trace(debugMsg);
645                                 }
646                         }
647                 } catch (DocumentNotFoundException e) {
648                         // Not a problem, just means we couldn't find another authority with that short ID
649                 }
650         
651         return result;
652     }
653
654     /**
655      * If no short identifier was provided in the input payload,
656      * generate a short identifier from the display name. Either way though,
657      * the short identifier needs to be unique.
658      */
659     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
660         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
661         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
662         String shortDisplayName = "";
663         String generateShortIdentifier = null;
664         if (Tools.isEmpty(shortIdentifier)) {
665                 generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
666             docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
667         }
668         
669         if (isUnique(docModel, schemaName) == false) {
670                 String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
671                 String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
672                 String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
673                                 errMsgVerb, shortId);
674                 throw new DocumentException(errMsg);
675         }
676     }
677  
678     /**
679      * Generate a refName for the authority from the short identifier
680      * and display name.
681      * 
682      * All refNames for authorities are generated.  If a client supplies
683      * a refName, it will be overwritten during create (per this method) 
684      * or discarded during update (per filterReadOnlyPropertiesForPart).
685      * 
686      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
687      * 
688      */
689     protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
690         DocumentModel docModel = wrapDoc.getWrappedObject();
691         RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
692         String refName = authority.toString();
693         docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
694     }
695     
696     @Override
697     public RefName.RefNameInterface getRefName(ServiceContext ctx,
698                 DocumentModel docModel) {
699         RefName.RefNameInterface refname = null;
700
701         try {
702                 String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
703                 String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
704                 RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
705                         ctx.getServiceName(),
706                         null,   // Only use shortId form!!!
707                         shortIdentifier,
708                         displayName);
709                 refname = authority;
710         } catch (Exception e) {
711                 logger.error(e.getMessage(), e);
712         }
713         
714         return refname;
715     }
716     
717     @Override
718     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
719         String result = null;
720         
721         DocumentModel docModel = docWrapper.getWrappedObject();
722         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
723         RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
724         result = refname.getDisplayName();
725         
726         return result;
727     }    
728     
729     public String getShortIdentifier(ServiceContext ctx, String authCSID, String schemaName) throws Exception {
730         String shortIdentifier = null;
731         CoreSessionInterface repoSession = null;
732         boolean releaseSession = false;
733
734         RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
735         try {
736                 repoSession = nuxeoRepoClient.getRepositorySession(ctx);
737             DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
738             DocumentModel docModel = wrapDoc.getWrappedObject();
739             if (docModel == null) {
740                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
741             }
742             shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
743         } catch (ClientException ce) {
744             throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
745         } finally {
746                 if (repoSession != null) {
747                         nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
748                 }
749         }
750         
751         return shortIdentifier;
752     }
753
754     /**
755      * Filters out selected values supplied in an update request.
756      * 
757      * @param objectProps the properties filtered out from the update payload
758      * @param partMeta metadata for the object to fill
759      */
760     @Override
761     public void filterReadOnlyPropertiesForPart(
762             Map<String, Object> objectProps, ObjectPartType partMeta) {
763         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
764         String commonPartLabel = getServiceContext().getCommonPartLabel();
765         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
766             objectProps.remove(AuthorityJAXBSchema.CSID);
767             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
768             objectProps.remove(AuthorityJAXBSchema.REF_NAME);
769         }
770     }    
771 }