]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
87f2cdd03c8242d82e7a1e024b8a4c4196deaf7d
[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 }