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