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