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