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