001: /**
002: *
003: */package org.sakaiproject.content.impl;
004:
005: import java.io.InputStream;
006: import java.util.List;
007:
008: import org.apache.commons.logging.Log;
009: import org.apache.commons.logging.LogFactory;
010: import org.sakaiproject.component.cover.ComponentManager;
011: import org.sakaiproject.content.api.ContentCollection;
012: import org.sakaiproject.content.api.ContentCollectionEdit;
013: import org.sakaiproject.content.api.ContentEntity;
014: import org.sakaiproject.content.api.ContentHostingHandler;
015: import org.sakaiproject.content.api.ContentResource;
016: import org.sakaiproject.content.api.ContentResourceEdit;
017: import org.sakaiproject.content.impl.BaseContentService.Storage;
018: import org.sakaiproject.entity.api.Edit;
019: import org.sakaiproject.entity.api.Entity;
020: import org.sakaiproject.entity.api.ResourceProperties;
021: import org.sakaiproject.exception.ServerOverloadException;
022:
023: /**
024: * <p>
025: * Implementation of the Handler Resolver. This class chains back to the storage to get local entities but then resolves the IDs through to virtual content entities based on the name ContentHostingHandlers. The primary 3 methods are getRealParent(),
026: * getVirtualEntity(), and getVirtualChild(). The remaining methods are largely plumbing, proxying the Storage mechanism that is being used. The Storage mechanims must be aware that this code will cause re-entry into the Sotrage methods and so the Storage
027: * <b>must</b> implement some sort of call stack advisor to prevent recursion.
028: * </p>
029: * <p>
030: * The getRealParent() method takes the current Id, and finds the closest ancestor that exists in the main Storage area.
031: * </p>
032: * <p>
033: * The getVirtualEntity() converts a ContentEntity into a ContentEntity managed by the ContentHostingHandler named in that entity. If no ContentHostingHandler is named, then the ContentEntity is returned unchanged.
034: * </p>
035: * <p>
036: * The getVirtualChild() method takes a ContentEntity that is a parent of the target ContentEntity and tried to find the target ContentEntity by navigating to children and resolving ContentEntity via ContentHostingHandlers where appropriate. If the target
037: * does not exist, null is returned.
038: * </p>
039: * <p>
040: * To make this navigation process efficient there needs to be some form of Cache in place, ideally this would be a cluster wide cache with event based expiry.
041: * </p>
042: *
043: * @author ieb (initial version), johnf (substantial edits)
044: */
045: public class ContentHostingHandlerResolverImpl implements
046: BaseContentHostingHandlerResolver {
047:
048: private static final Log log = LogFactory
049: .getLog(ContentHostingHandlerResolverImpl.class);
050:
051: /**
052: * Find the closest real ancestor to the requested id, this recurses into itself
053: *
054: * @param id
055: * @return the closest ancestor or null if not found (bit unlikely)
056: */
057: public ContentEntity getRealParent(Storage storage, String id) {
058: ContentEntity ce = storage.getCollection(id);
059: if (ce == null) {
060: ce = storage.getResource(id);
061: }
062: if (ce == null) {
063: if (id.equals(Entity.SEPARATOR))
064: return getRealParent(storage, Entity.SEPARATOR);
065: int lastSlash = id.lastIndexOf(Entity.SEPARATOR, id
066: .length() - 2);
067: if (lastSlash > 0) {
068: String parentId = id
069: .substring(0, lastSlash + 1 /* ian@caret.cam wanted a "- 1" here */);
070: ce = getRealParent(storage, parentId);
071: }
072: }
073: return ce;
074: }
075:
076: /**
077: * Convert the ContentEntity into its virtual shadow via its ContentHostingHandler bean. If no bean is defined for the ContentEntity, no resolution is performed. If the ContentEntity is null, no resolution is performed.
078: *
079: * @param ce
080: * @return a resolved ContentEntity where appropriate, otherwise the orginal
081: */
082: public ContentEntity getVirtualEntity(ContentEntity ce,
083: String finalId) {
084: if (ce == null) {
085: return null;
086: }
087: ResourceProperties p = ce.getProperties();
088: String chhbeanname = p.getProperty(CHH_BEAN_NAME);
089:
090: if (chhbeanname != null && chhbeanname.length() > 0) {
091: try {
092: ContentHostingHandler chh = (ContentHostingHandler) ComponentManager
093: .get(chhbeanname);
094: return chh.getVirtualContentEntity(ce, finalId);
095:
096: } catch (Exception e) {
097: // log and return null
098: log
099: .warn(
100: "Failed to find CHH Bean "
101: + chhbeanname
102: + " or bean failed to resolve virtual entity ID",
103: e);
104: return ce;
105: }
106: }
107: return ce;
108: }
109:
110: /**
111: * Locate the ContentEntity with the final Id, or null if can't be found, resolving virtual content entities as part of the resolution process. Will return a real content entity if that is what the finalId represents.
112: *
113: * @param finalId
114: * @param ce
115: * @param exact -
116: * if true, the exact match otherwise the nearest ancestor
117: * @return
118: */
119: public ContentEntity getVirtualChild(String finalId,
120: ContentEntity ce, boolean exact) {
121: if (ce == null) {
122: return null; // entirely empty resources tool
123: }
124: String this id = ce.getId();
125: // check for an exact match
126: if (finalId.equals(this id)) {
127: return ce;
128: }
129: // find the next ID in the target eg
130: // /A/B/C == thisid
131: // /A/B/C/D/E/F == finalId
132: // ^
133: int nextSlash = finalId.indexOf(Entity.SEPARATOR, this id
134: .length());
135:
136: // /A/B/C/D/E/F == finalId
137: // not found
138: ContentEntity nextce;
139:
140: if (nextSlash == -1) {
141: // hence final id found
142: nextce = getVirtualEntity(ce, finalId);
143: } else if (nextSlash == finalId.length()
144: - Entity.SEPARATOR.length()
145: && this id.length() == nextSlash) {
146: // we are looking for either:
147: // (i) the root of a virtual container, and the current position is
148: // on the membrane between the real and virtual worlds: the
149: // separator at the end of thisid is the root of the virtual world.
150: // (ii) a virtual container whose name is specified with a trailing
151: // "/" character (eg a directory in a file system) where this is OK.
152: nextce = getVirtualEntity(ce, finalId);
153: } else {
154: // found C in the middle of a long string of containers
155: // /A/B/C/D/..
156: String nextId = finalId.substring(0, nextSlash);
157: nextce = getVirtualEntity(ce.getMember(nextId), finalId);
158: }
159:
160: if (nextce == null || nextce.getId().equals(this id)) {
161: if (exact) {
162: return null;
163: } else {
164: return ce;
165: }
166: } else {
167: return getVirtualChild(finalId, nextce, exact);
168: }
169: }
170:
171: /**
172: * ********************************************************************************** Everything below merely proxies method calls onto the underlying Storage (for real Collections and Resources) or onto the underlying ContentHostingHandler (for
173: * virtual Collections and Resources). i.e. the "plumbing" starts here! **********************************************************************************
174: */
175:
176: /**
177: * Cancel collection, using storage if real, or the ContentHostingHandler if present.
178: */
179: public void cancelCollection(Storage storage,
180: ContentCollectionEdit edit) {
181: ContentHostingHandler chh = edit.getContentHandler();
182: if (chh != null) {
183: chh.cancel(edit);
184: } else {
185: storage.cancelCollection(edit);
186: }
187: }
188:
189: /**
190: * {@inheritDoc} Cancel collection, using storage if real, or the ContentHostingHandler if present
191: */
192: public void cancelResource(Storage storage, ContentResourceEdit edit) {
193: ContentHostingHandler chh = edit.getContentHandler();
194: if (chh != null) {
195: chh.cancel(edit);
196: } else {
197: storage.cancelResource(edit);
198: }
199: }
200:
201: /**
202: * {@inheritDoc}
203: */
204: public boolean checkCollection(Storage storage, String id) {
205: if (storage.checkCollection(id)) {
206: return true;
207: }
208: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
209: id), true);
210: if (ce != null) {
211: if (id.equals(ce.getId())) {
212: if (ce instanceof ContentCollection) {
213: return true;
214: }
215: }
216: }
217: return false;
218: }
219:
220: /**
221: * {@inheritDoc}
222: */
223:
224: public boolean checkResource(Storage storage, String id) {
225: if (storage.checkResource(id)) {
226: return true;
227: }
228:
229: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
230: id), true);
231: if (ce != null) {
232: if (id.equals(ce.getId())) {
233: if (ce instanceof ContentResource) {
234: return true;
235: }
236: }
237: }
238: return false;
239: }
240:
241: /**
242: * {@inheritDoc}
243: */
244:
245: public void commitCollection(Storage storage,
246: ContentCollectionEdit edit) {
247: ContentHostingHandler chh = edit.getContentHandler();
248: if (chh != null) {
249: chh.commit(edit);
250: } else {
251: storage.commitCollection(edit);
252: }
253: }
254:
255: /**
256: * {@inheritDoc}
257: */
258:
259: public void commitDeleteResource(Storage storage,
260: ContentResourceEdit edit, String uuid) {
261: ContentHostingHandler chh = edit.getContentHandler();
262: if (chh != null) {
263: chh.commitDeleted(edit, uuid);
264: } else {
265: storage.commitDeleteResource(edit, uuid);
266: }
267: }
268:
269: /**
270: * {@inheritDoc}
271: */
272:
273: public void commitResource(Storage storage, ContentResourceEdit edit)
274: throws ServerOverloadException {
275: ContentHostingHandler chh = edit.getContentHandler();
276: if (chh != null) {
277: chh.commit(edit);
278: } else {
279: storage.commitResource(edit);
280: }
281: }
282:
283: /**
284: * {@inheritDoc}
285: */
286:
287: public ContentCollectionEdit editCollection(Storage storage,
288: String id) {
289: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
290: id), false);
291: if (ce != null) {
292: ContentHostingHandler chh = ce.getContentHandler();
293: if (chh != null) {
294: return chh.getContentCollectionEdit(id);
295: }
296: }
297: return storage.editCollection(id);
298: }
299:
300: /**
301: * {@inheritDoc}
302: */
303:
304: public ContentResourceEdit editResource(Storage storage, String id) {
305: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
306: id), false);
307: if (ce != null) {
308: ContentHostingHandler chh = ce.getContentHandler();
309: if (chh != null) {
310: return chh.getContentResourceEdit(id);
311: }
312: }
313: return storage.editResource(id);
314: }
315:
316: /**
317: * {@inheritDoc}
318: */
319: public ContentCollection getCollection(Storage storage, String id) {
320: ContentCollection cc = storage.getCollection(id);
321: if (cc != null) {
322: return cc;
323: }
324: ContentEntity rp = getRealParent(storage, id);
325: if (rp == null) {
326: return null;
327: }
328:
329: ContentEntity ce = getVirtualChild(id, rp, true);
330: if (ce instanceof ContentCollection) {
331: return (ContentCollection) ce;
332: }
333: return null;
334: }
335:
336: /**
337: * {@inheritDoc}
338: */
339: public List getCollections(Storage storage,
340: ContentCollection collection) {
341: ContentHostingHandler chh = collection.getContentHandler();
342: if (chh != null) {
343: return chh.getCollections(collection);
344: } else {
345: List allCollections = storage.getCollections(collection); // the real collections
346:
347: // Find any virtual *resources* which are really *collections*
348: List l = storage.getResources(collection);
349: for (java.util.Iterator i = l.iterator(); i.hasNext();) {
350: ContentResource o = (ContentResource) i.next();
351: ContentResource cr = getResource(storage, o.getId());
352: if (cr != null) {
353: ResourceProperties p = cr.getProperties();
354: if (p != null
355: && p.getProperty(CHH_BEAN_NAME) != null
356: && p.getProperty(CHH_BEAN_NAME).length() > 0) {
357: allCollections.add(getVirtualChild(cr.getId()
358: + Entity.SEPARATOR, cr, true)); // this one is a virtual collection!
359: }
360: }
361: }
362: return allCollections;
363: }
364: }
365:
366: /**
367: * {@inheritDoc}
368: */
369: public List getFlatResources(Storage storage, String id) {
370: List l = storage.getFlatResources(id);
371: if (l != null) {
372: return l;
373: }
374: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
375: id), true);
376: if (ce != null) {
377: ContentHostingHandler chh = ce.getContentHandler();
378: if (chh != null) {
379: return chh.getFlatResources(ce);
380: }
381: }
382: return null;
383: }
384:
385: /**
386: * {@inheritDoc}
387: */
388: public ContentResource getResource(Storage storage, String id) {
389:
390: ContentResource cc = storage.getResource(id);
391: if (cc != null) {
392: return cc;
393: }
394: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
395: id), true);
396: if (ce instanceof ContentResource) {
397: return (ContentResource) ce;
398: }
399: return null;
400: }
401:
402: /**
403: * {@inheritDoc}
404: *
405: * @throws ServerOverloadException
406: */
407: public byte[] getResourceBody(Storage storage,
408: ContentResource resource) throws ServerOverloadException {
409: ContentHostingHandler chh = resource.getContentHandler();
410: if (chh != null) {
411: return chh.getResourceBody(resource);
412: } else {
413: return storage.getResourceBody(resource);
414: }
415: }
416:
417: /**
418: * {@inheritDoc}
419: */
420: public List getResources(Storage storage,
421: ContentCollection collection) {
422: ContentHostingHandler chh = collection.getContentHandler();
423: if (chh != null) {
424: return chh.getResources(collection);
425: } else {
426: List l = storage.getResources(collection);
427: for (java.util.Iterator i = l.iterator(); i.hasNext();) {
428: ContentResource o = (ContentResource) i.next();
429: ContentResource cr = getResource(storage, o.getId());
430: if (cr != null) {
431: ResourceProperties p = cr.getProperties();
432: if (p != null
433: && p.getProperty(CHH_BEAN_NAME) != null
434: && p.getProperty(CHH_BEAN_NAME).length() > 0)
435: i.remove(); // this one is a virtual collection!
436: }
437: }
438: return l;
439: }
440: }
441:
442: /**
443: * {@inheritDoc}
444: */
445: public ContentCollectionEdit putCollection(Storage storage,
446: String id) {
447: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
448: id), false);
449: if (ce != null) {
450: ContentHostingHandler chh = ce.getContentHandler();
451: if (chh != null) {
452: return chh.getContentCollectionEdit(id);
453: }
454: }
455: return storage.putCollection(id);
456: }
457:
458: /**
459: * {@inheritDoc}
460: */
461: public ContentResourceEdit putDeleteResource(Storage storage,
462: String id, String uuid, String userId) {
463: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
464: id), false);
465: if (ce != null) {
466: ContentHostingHandler chh = ce.getContentHandler();
467: if (chh != null) {
468: return chh.putDeleteResource(id, uuid, userId);
469: }
470: }
471: return storage.putDeleteResource(id, uuid, userId);
472: }
473:
474: /**
475: * {@inheritDoc}
476: */
477: public ContentResourceEdit putResource(Storage storage, String id) {
478: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
479: id), false);
480: if (ce != null) {
481: ContentHostingHandler chh = ce.getContentHandler();
482: if (chh != null) {
483: return chh.getContentResourceEdit(id);
484: }
485: }
486: return storage.putResource(id);
487: }
488:
489: /**
490: * {@inheritDoc}
491: */
492: public void removeCollection(Storage storage,
493: ContentCollectionEdit edit) {
494: ContentHostingHandler chh = edit.getContentHandler();
495: if (chh != null) {
496: chh.removeCollection(edit);
497: } else {
498: storage.removeCollection(edit);
499: }
500: }
501:
502: /**
503: * {@inheritDoc}
504: */
505: public void removeResource(Storage storage, ContentResourceEdit edit) {
506: ContentHostingHandler chh = edit.getContentHandler();
507: if (chh != null) {
508: chh.removeResource(edit);
509: } else {
510: storage.removeResource(edit);
511: }
512: }
513:
514: /**
515: * {@inheritDoc}
516: *
517: * @throws ServerOverloadException
518: */
519: public InputStream streamResourceBody(Storage storage,
520: ContentResource resource) throws ServerOverloadException {
521: ContentHostingHandler chh = resource.getContentHandler();
522: if (chh != null) {
523: return chh.streamResourceBody(resource);
524: } else {
525: return storage.streamResourceBody(resource);
526: }
527: }
528:
529: protected org.sakaiproject.util.StorageUser resourceStorageUser;
530:
531: protected org.sakaiproject.util.StorageUser collectionStorageUser;
532:
533: public void setResourceUser(org.sakaiproject.util.StorageUser rsu) {
534: resourceStorageUser = rsu;
535: }
536:
537: public void setCollectionUser(org.sakaiproject.util.StorageUser csu) {
538: collectionStorageUser = csu;
539: }
540:
541: public Edit newCollectionEdit(String id) {
542: return collectionStorageUser.newResourceEdit(null, id, null);
543: }
544:
545: public Edit newResourceEdit(String id) {
546: return resourceStorageUser.newResourceEdit(null, id, null);
547: }
548:
549: public int getMemberCount(Storage storage, String id) {
550: ContentEntity ce = getVirtualChild(id, getRealParent(storage,
551: id), false);
552: if (ce != null) {
553: ContentHostingHandler chh = ce.getContentHandler();
554: if (chh != null) {
555: return chh.getMemberCount(ce);
556: }
557: }
558: return storage.getMemberCount(id);
559: }
560: }
|