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