]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
5798be7063e7dfba253f79f1a6b601c9b0abfd6f
[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);  // 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      * @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                 throw new DocumentException(statusCode, errMsg);
504         }
505     }
506     
507     /**
508      * Request an authority item list payload from the SAS server.  This is a non-paging solution.  If the authority
509      * has a very large number of items/terms, we might not be able to handle them all.
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         //
521         // First find out how many items exist
522         Response res = client.readItemList(specifier.getURNValue(),
523                         null,   // partial term string
524                         null,   // keyword string
525                         0,              // page size
526                         0               // page number
527                         );
528         assertStatusCode(res, specifier, client);
529         AbstractCommonList commonList;
530         try {
531                 commonList = res.readEntity(AbstractCommonList.class);
532         } finally {
533                 res.close();
534         }
535         long numOfItems = commonList.getTotalItems();        
536         
537         //
538         // Next, request a payload list with all the items
539         res = client.readItemList(specifier.getURNValue(),
540                         null,           // partial term string
541                         null,           // keyword string
542                         numOfItems,     // page size
543                         0                       // page number
544                         );        
545         assertStatusCode(res, specifier, client);
546         try {
547             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response.
548         } finally {
549                 res.close();
550         }
551         
552         return result;
553     }
554     
555
556     /*
557      * Non standard injection of CSID into common part, since caller may access through
558      * shortId, and not know the CSID.
559      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
560      */
561     @Override
562     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
563             throws Exception {
564         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
565
566         // Add the CSID to the common part
567         if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
568             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
569             unQObjectProperties.put("csid", csid);
570         }
571
572         return unQObjectProperties;
573     }
574     
575     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
576         super.fillAllParts(wrapDoc, action);
577         //
578         // Update the record's revision number on both CREATE and UPDATE actions, but not on SYNC
579         //
580         if (this.getShouldUpdateRevNumber() == true) { // We won't update rev numbers on synchronization with SAS
581                 updateRevNumbers(wrapDoc);
582         }
583     }
584     
585     protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
586         DocumentModel documentModel = wrapDoc.getWrappedObject();
587         Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
588         if (rev == null) {
589                 rev = (long)0;
590         } else {
591                 rev++;
592         }
593         documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
594     }
595     
596     /*
597      * We consider workflow state changes as changes that should bump the revision number
598      * (non-Javadoc)
599      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
600      */
601     @Override
602     public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
603         boolean updateRevNumber = this.getShouldUpdateRevNumber();
604         Boolean contextProperty = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
605         if (contextProperty != null) {
606                 updateRevNumber = contextProperty;
607         }
608
609         if (updateRevNumber == true) { // We don't update the rev number of synchronization requests
610                 updateRevNumbers(wrapDoc);
611         }
612     }
613     
614     @Override
615     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
616         super.handleCreate(wrapDoc);
617         // CSPACE-3178:
618         // Uncomment once debugged and App layer is read to integrate
619         // Experimenting with this uncommented now ...
620         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
621         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
622     }
623     
624     protected String buildWhereForShortId(String name) {
625         return authorityCommonSchemaName
626                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
627                 + "='" + name + "'";
628     }
629     
630     private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
631         return true;
632     }
633     
634     private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
635         boolean result = true;
636         
637         ServiceContext ctx = this.getServiceContext();
638         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
639         String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
640         try {
641                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
642                         if (searchResultWrapper != null) {
643                                 result = false;
644                                 if (logger.isInfoEnabled() == true) {
645                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
646                                         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'",
647                                                         shortIdentifier, searchResult.getName());
648                                         logger.trace(debugMsg);
649                                 }
650                         }
651                 } catch (DocumentNotFoundException e) {
652                         // Not a problem, just means we couldn't find another authority with that short ID
653                 }
654         
655         return result;
656     }
657
658     /**
659      * If no short identifier was provided in the input payload,
660      * generate a short identifier from the display name. Either way though,
661      * the short identifier needs to be unique.
662      */
663     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
664         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
665         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
666         String shortDisplayName = "";
667         String generateShortIdentifier = null;
668         if (Tools.isEmpty(shortIdentifier)) {
669                 generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
670             docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
671         }
672         
673         if (isUnique(docModel, schemaName) == false) {
674                 String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
675                 String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
676                 String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
677                                 errMsgVerb, shortId);
678                 throw new DocumentException(errMsg);
679         }
680     }
681  
682     /**
683      * Generate a refName for the authority from the short identifier
684      * and display name.
685      * 
686      * All refNames for authorities are generated.  If a client supplies
687      * a refName, it will be overwritten during create (per this method) 
688      * or discarded during update (per filterReadOnlyPropertiesForPart).
689      * 
690      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
691      * 
692      */
693     protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
694         DocumentModel docModel = wrapDoc.getWrappedObject();
695         RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
696         String refName = authority.toString();
697         docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
698     }
699     
700     @Override
701     public RefName.RefNameInterface getRefName(ServiceContext ctx,
702                 DocumentModel docModel) {
703         RefName.RefNameInterface refname = null;
704
705         try {
706                 String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
707                 String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
708                 RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
709                         ctx.getServiceName(),
710                         null,   // Only use shortId form!!!
711                         shortIdentifier,
712                         displayName);
713                 refname = authority;
714         } catch (Exception e) {
715                 logger.error(e.getMessage(), e);
716         }
717         
718         return refname;
719     }
720     
721     @Override
722     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
723         String result = null;
724         
725         DocumentModel docModel = docWrapper.getWrappedObject();
726         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
727         RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
728         result = refname.getDisplayName();
729         
730         return result;
731     }    
732     
733     public String getShortIdentifier(ServiceContext ctx, String authCSID, String schemaName) throws Exception {
734         String shortIdentifier = null;
735         CoreSessionInterface repoSession = null;
736         boolean releaseSession = false;
737
738         RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
739         try {
740                 repoSession = nuxeoRepoClient.getRepositorySession(ctx);
741             DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
742             DocumentModel docModel = wrapDoc.getWrappedObject();
743             if (docModel == null) {
744                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
745             }
746             shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
747         } catch (ClientException ce) {
748             throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
749         } finally {
750                 if (repoSession != null) {
751                         nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
752                 }
753         }
754         
755         return shortIdentifier;
756     }
757
758     /**
759      * Filters out selected values supplied in an update request.
760      * 
761      * @param objectProps the properties filtered out from the update payload
762      * @param partMeta metadata for the object to fill
763      */
764     @Override
765     public void filterReadOnlyPropertiesForPart(
766             Map<String, Object> objectProps, ObjectPartType partMeta) {
767         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
768         String commonPartLabel = getServiceContext().getCommonPartLabel();
769         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
770             objectProps.remove(AuthorityJAXBSchema.CSID);
771             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
772             objectProps.remove(AuthorityJAXBSchema.REF_NAME);
773         }
774     }    
775 }