]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
08637d0682081433178f92a5f72f6db7229113c3
[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.List;
27 import java.util.Map;
28
29 import javax.ws.rs.core.Response;
30
31 import org.collectionspace.services.client.AuthorityClient;
32 import org.collectionspace.services.client.CollectionSpaceClient;
33 import org.collectionspace.services.client.PayloadInputPart;
34 import org.collectionspace.services.client.VocabularyClient;
35 import org.collectionspace.services.client.PoxPayloadIn;
36 import org.collectionspace.services.client.PoxPayloadOut;
37 import org.collectionspace.services.common.ResourceMap;
38 import org.collectionspace.services.common.XmlTools;
39 import org.collectionspace.services.common.api.RefName;
40 import org.collectionspace.services.common.api.RefName.Authority;
41 import org.collectionspace.services.common.api.RefNameUtils;
42 import org.collectionspace.services.common.api.RefNameUtils.AuthorityInfo;
43 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
44 import org.collectionspace.services.common.api.Tools;
45 import org.collectionspace.services.common.context.ServiceContext;
46 import org.collectionspace.services.common.document.DocumentException;
47 import org.collectionspace.services.common.document.DocumentNotFoundException;
48 import org.collectionspace.services.common.document.DocumentWrapper;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
51 import org.collectionspace.services.common.vocabulary.AuthorityResource;
52 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
53 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
54 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
55 import org.collectionspace.services.config.service.ObjectPartType;
56 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
57 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
58 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
59 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
60 import org.dom4j.Document;
61 import org.dom4j.Element;
62 import org.nuxeo.ecm.core.api.ClientException;
63 import org.nuxeo.ecm.core.api.DocumentModel;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 /**
68  * AuthorityDocumentModelHandler
69  *
70  * $LastChangedRevision: $
71  * $LastChangedDate: $
72  */
73 public abstract class AuthorityDocumentModelHandler<AuthCommon>
74         extends NuxeoDocumentModelHandler<AuthCommon> {
75     
76         private final Logger logger = LoggerFactory.getLogger(AuthorityDocumentModelHandler.class);     
77     
78         protected String authorityCommonSchemaName;
79     protected String authorityItemCommonSchemaName;
80     protected boolean shouldUpdateRevNumber;
81
82     public AuthorityDocumentModelHandler(String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
83         this.authorityCommonSchemaName = authorityCommonSchemaName;
84         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
85     }
86     
87     public void setShouldUpdateRevNumber(boolean flag) {
88         this.shouldUpdateRevNumber = flag;
89     }
90     
91     public boolean getShouldUpdateRevNumber() {
92         return this.shouldUpdateRevNumber;
93     }
94     
95     /**
96      * The entity type expected from the JAX-RS Response object
97      */
98     public Class<String> getEntityResponseType() {
99         return String.class;
100     }
101         
102     protected PayloadInputPart extractPart(Response res, String partLabel)
103             throws Exception {
104             PoxPayloadIn input = new PoxPayloadIn((String)res.readEntity(getEntityResponseType()));
105             PayloadInputPart payloadInputPart = input.getPart(partLabel);
106             if (payloadInputPart == null) {
107                 logger.error("Part " + partLabel + " was unexpectedly null.");
108             }
109             return payloadInputPart;
110     }
111     
112     @Override
113     public boolean handleSync(DocumentWrapper<Object> wrapDoc) throws Exception {
114         boolean result = false;
115         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
116         Specifier specifier = (Specifier) wrapDoc.getWrappedObject();
117         //
118         // Get the rev number of the authority so we can compare with rev number of shared authority
119         //
120         DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, specifier);
121         Long rev = (Long) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REV);
122         String shortId = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.SHORT_IDENTIFIER);
123         String refName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REF_NAME);
124         //
125         // Using the short ID of the local authority, create a URN specifier to retrieve the SAS authority
126         //
127         Specifier sasSpecifier = new Specifier(SpecifierForm.URN_NAME, RefNameUtils.createShortIdRefName(shortId));
128         PoxPayloadIn sasPayloadIn = getPayloadIn(ctx, sasSpecifier);
129
130         Long sasRev = getRevision(sasPayloadIn);
131         if (sasRev > rev) {
132                 //
133                 // First, sync all the authority items
134                 //
135                 syncAllItems(ctx, sasSpecifier);
136                 //
137                 // Next, sync the authority resource/record itself
138                 //
139                 ResourceMap resourceMap = ctx.getResourceMap();
140                 String resourceName = ctx.getClient().getServiceName();
141                 AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
142                 ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, // Since it is a sync, don't update the rev.  Instead use the rev from the SAS
143                                 new Boolean(AuthorityServiceUtils.DONT_UPDATE_REV));
144                 PoxPayloadOut payloadOut = authorityResource.update(ctx, resourceMap, ctx.getUriInfo(), docModel.getName(), 
145                                 sasPayloadIn);
146                 if (payloadOut != null) {
147                         ctx.setOutput(payloadOut);
148                         result = true;
149                 }
150         }
151         
152         return result;
153     }
154     
155     /*
156      * Get the list of authority items from the remote shared authority server (SAS) and try
157      * to synchronize them with the local items.  If items exist on the remote but not the local, we'll create them.
158      */
159     protected int syncAllItems(ServiceContext ctx, Specifier sasSpecifier) throws Exception {
160         int result = -1;
161         int created = 0;
162         int synched = 0;
163         int alreadySynched = 0;
164         int totalItemsProcessed = 0;
165         
166         PoxPayloadIn sasPayloadInItemList = getPayloadInItemList(ctx, sasSpecifier);
167         List<Element> itemList = getItemList(sasPayloadInItemList);
168         if (itemList != null) {
169                 for (Element e:itemList) {
170                         String remoteRefName = XmlTools.getElementValue(e, "refName");
171                         long status = syncRemoteItemWithLocalItem(ctx, remoteRefName);
172                         if (status == 1) {
173                                 created++;
174                         } else if (status == 0) {
175                                 synched++;
176                         } else {
177                                 alreadySynched++;
178                         }
179                         totalItemsProcessed++;
180                 }
181         }
182         
183         logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed));
184         logger.info(String.format("Number of items synchronized: %d", synched));
185         logger.info(String.format("Number of items created during sync: %d", created));
186         logger.info(String.format("Number not needing synchronization: %d", alreadySynched));
187
188         return result;
189     }
190     
191     /**
192      * 
193      * @param ctx
194      * @param parentIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
195      * @param itemIdentifier   - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
196      * @throws Exception 
197      */
198     protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier) throws Exception {
199         //
200         // Create a URN short ID specifier for the getting to the remote item payload
201         Specifier authoritySpecifier = new Specifier(SpecifierForm.URN_NAME, parentIdentifier);
202         Specifier itemSpecifier = new Specifier(SpecifierForm.URN_NAME, itemIdentifier);
203         AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier);
204         //
205         // Get the remote payload
206         //
207         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.getPayloadIn(sasAuthorityItemSpecifier, 
208                         ctx.getServiceName(), getEntityResponseType());
209         //
210         // Using the payload from the remote server, create a local copy of the item
211         //
212         ResourceMap resourceMap = ctx.getResourceMap();
213         String resourceName = ctx.getClient().getServiceName();
214         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
215         Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.value,
216                         sasPayloadIn, AuthorityServiceUtils.DONT_UPDATE_REV);
217         if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
218                 throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
219                                 itemIdentifier, parentIdentifier));
220         }
221     }
222     
223     /**
224      * Try to synchronize a remote item (using its refName) with a local item.  If the local doesn't yet
225      * exist, we'll create it.
226      * Result values:
227      *  -1 = sync not needed; i.e., already in sync
228      *   0 = sync succeeded
229      *   1 = local item was missing so we created it
230      * @param ctx
231      * @param refName
232      * @return
233      * @throws Exception
234      */
235     protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String remoteRefName) throws Exception {
236         long result = -1;
237         
238         AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(remoteRefName);
239         String parentIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.inAuthority.name);
240         String itemIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.name);
241
242         ResourceMap resourceMap = ctx.getResourceMap();
243         String resourceName = ctx.getClient().getServiceName();
244         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
245         
246         PoxPayloadOut localItemPayloadOut;
247         try {
248                 localItemPayloadOut = authorityResource.getAuthorityItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
249         } catch (DocumentNotFoundException dnf) {
250                 logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", remoteRefName));
251                 createLocalItem(ctx, parentIdentifier, itemIdentifier);
252                 return 1; // exit with status of 1 means we created a new authority item
253         }
254         //
255         // If we get here, we know the item exists both locally and remotely, so we need to synchronize them
256         //
257         PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
258         if (theUpdate != null) {
259                 result = 0; // mean we neeed to sync this item with SAS
260                 logger.debug(String.format("Sync'd authority item parent='%s' id='%s with SAS.  Updated payload is: \n%s",
261                                 parentIdentifier, itemIdentifier, theUpdate.getXmlPayload()));
262         }
263         
264         return result; // -1 = no sync needed, 0 = sync'd, 1 = created new item
265     }
266         
267     private PoxPayloadIn getPayloadInItemList(ServiceContext ctx, Specifier specifier) throws Exception {
268         PoxPayloadIn result = null;
269         
270         AuthorityClient client = (AuthorityClient) ctx.getClient();
271         Response res = client.readItemList(specifier.value,
272                         null,   // partial term string
273                         null    // keyword string
274                         );
275         try {
276                 int statusCode = res.getStatus();
277         
278                 // Check the status code of the response: does it match
279                 // the expected response(s)?
280                 if (logger.isDebugEnabled()) {
281                     logger.debug(client.getClass().getCanonicalName() + ": status = " + statusCode);
282                 }
283                 
284             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response!             
285         } finally {
286                 res.close();
287         }
288         
289         return result;
290     }
291     
292     private PoxPayloadIn getPayloadIn(ServiceContext ctx, Specifier specifier) throws Exception {
293         PoxPayloadIn result = null;
294         
295         AuthorityClient client = (AuthorityClient) ctx.getClient();
296         Response res = client.read(specifier.value);
297         try {
298                 int statusCode = res.getStatus();
299         
300                 // Check the status code of the response: does it match
301                 // the expected response(s)?
302                 if (logger.isDebugEnabled()) {
303                     logger.debug(client.getClass().getCanonicalName() + ": status = " + statusCode);
304                 }
305                 
306             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response!             
307         } finally {
308                 res.close();
309         }
310         
311         return result;
312     }
313
314     /*
315      * Non standard injection of CSID into common part, since caller may access through
316      * shortId, and not know the CSID.
317      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
318      */
319     @Override
320     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
321             throws Exception {
322         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
323
324         // Add the CSID to the common part
325         if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
326             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
327             unQObjectProperties.put("csid", csid);
328         }
329
330         return unQObjectProperties;
331     }
332     
333     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
334         super.fillAllParts(wrapDoc, action);
335         //
336         // Update the record's revision number on both CREATE and UPDATE actions, but not on SYNC
337         //
338         if (this.getShouldUpdateRevNumber() == true) { // We won't update rev numbers on synchronization with SAS
339                 updateRevNumbers(wrapDoc);
340         }
341     }
342     
343     protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
344         DocumentModel documentModel = wrapDoc.getWrappedObject();
345         Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
346         if (rev == null) {
347                 rev = (long)0;
348         } else {
349                 rev++;
350         }
351         documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
352     }
353     
354     @Override
355     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
356         super.handleCreate(wrapDoc);
357         // CSPACE-3178:
358         // Uncomment once debugged and App layer is read to integrate
359         // Experimenting with this uncommented now ...
360         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
361         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
362     }
363     
364     protected String buildWhereForShortId(String name) {
365         return authorityCommonSchemaName
366                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
367                 + "='" + name + "'";
368     }
369     
370     private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
371         return true;
372     }
373     
374     private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
375         boolean result = true;
376         
377         ServiceContext ctx = this.getServiceContext();
378         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
379         String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
380         try {
381                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
382                         if (searchResultWrapper != null) {
383                                 result = false;
384                                 if (logger.isInfoEnabled() == true) {
385                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
386                                         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'",
387                                                         shortIdentifier, searchResult.getName());
388                                         logger.trace(debugMsg);
389                                 }
390                         }
391                 } catch (DocumentNotFoundException e) {
392                         // Not a problem, just means we couldn't find another authority with that short ID
393                 }
394         
395         return result;
396     }
397
398     /**
399      * If no short identifier was provided in the input payload,
400      * generate a short identifier from the display name. Either way though,
401      * the short identifier needs to be unique.
402      */
403     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
404         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
405         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
406         String shortDisplayName = "";
407         String generateShortIdentifier = null;
408         if (Tools.isEmpty(shortIdentifier)) {
409                 generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
410             docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
411         }
412         
413         if (isUnique(docModel, schemaName) == false) {
414                 String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
415                 String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
416                 String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
417                                 errMsgVerb, shortId);
418                 throw new DocumentException(errMsg);
419         }
420     }
421  
422     /**
423      * Generate a refName for the authority from the short identifier
424      * and display name.
425      * 
426      * All refNames for authorities are generated.  If a client supplies
427      * a refName, it will be overwritten during create (per this method) 
428      * or discarded during update (per filterReadOnlyPropertiesForPart).
429      * 
430      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
431      * 
432      */
433     protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
434         DocumentModel docModel = wrapDoc.getWrappedObject();
435         RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
436         String refName = authority.toString();
437         docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
438     }
439     
440     @Override
441     public RefName.RefNameInterface getRefName(ServiceContext ctx,
442                 DocumentModel docModel) {
443         RefName.RefNameInterface refname = null;
444
445         try {
446                 String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
447                 String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
448                 RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
449                         ctx.getServiceName(),
450                         null,   // Only use shortId form!!!
451                         shortIdentifier,
452                         displayName);
453                 refname = authority;
454         } catch (Exception e) {
455                 logger.error(e.getMessage(), e);
456         }
457         
458         return refname;
459     }
460     
461     @Override
462     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
463         String result = null;
464         
465         DocumentModel docModel = docWrapper.getWrappedObject();
466         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
467         RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
468         result = refname.getDisplayName();
469         
470         return result;
471     }    
472     
473     public String getShortIdentifier(ServiceContext ctx, String authCSID, String schemaName) throws Exception {
474         String shortIdentifier = null;
475         CoreSessionInterface repoSession = null;
476         boolean releaseSession = false;
477
478         RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
479         try {
480                 repoSession = nuxeoRepoClient.getRepositorySession(ctx);
481             DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
482             DocumentModel docModel = wrapDoc.getWrappedObject();
483             if (docModel == null) {
484                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
485             }
486             shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
487         } catch (ClientException ce) {
488             throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
489         } finally {
490                 if (repoSession != null) {
491                         nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
492                 }
493         }
494         
495         return shortIdentifier;
496     }
497
498     /**
499      * Filters out selected values supplied in an update request.
500      * 
501      * @param objectProps the properties filtered out from the update payload
502      * @param partMeta metadata for the object to fill
503      */
504     @Override
505     public void filterReadOnlyPropertiesForPart(
506             Map<String, Object> objectProps, ObjectPartType partMeta) {
507         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
508         String commonPartLabel = getServiceContext().getCommonPartLabel();
509         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
510             objectProps.remove(AuthorityJAXBSchema.CSID);
511             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
512             objectProps.remove(AuthorityJAXBSchema.REF_NAME);
513         }
514     }    
515 }