001: package org.drools.repository;
002:
003: import java.util.Calendar;
004:
005: import javax.jcr.Node;
006: import javax.jcr.PathNotFoundException;
007: import javax.jcr.Property;
008: import javax.jcr.RepositoryException;
009: import javax.jcr.UnsupportedRepositoryOperationException;
010: import javax.jcr.Value;
011:
012: /**
013: * This is the parent class for versionable assets.
014: * Contains standard fields based on Dublin Core, and
015: * stuff required for versioning.
016: * For dublin core, refer to <a href="http://dublincore.org/documents/dces/">Here</a>
017: *
018: * @see CategorisableItem for more attributes to do with BRMS resources.
019: * @author Ben Truitt, Michael Neale
020: *
021: */
022: public abstract class VersionableItem extends Item {
023:
024: /**
025: * Property names for this node type.
026: */
027: public static final String TITLE_PROPERTY_NAME = "drools:title";
028: public static final String DESCRIPTION_PROPERTY_NAME = "drools:description";
029: public static final String LAST_MODIFIED_PROPERTY_NAME = "drools:lastModified";
030: public static final String FORMAT_PROPERTY_NAME = "drools:format";
031: public static final String CHECKIN_COMMENT = "drools:checkinComment";
032: public static final String VERSION_NUMBER_PROPERTY_NAME = "drools:versionNumber";
033: public static final String CONTENT_PROPERTY_ARCHIVE_FLAG = "drools:archive";
034:
035: /** Dublin core based fields. */
036: public static final String LAST_CONTRIBUTOR_PROPERTY_NAME = "drools:lastContributor";
037: public static final String CREATOR_PROPERTY_NAME = "drools:creator";
038: public static final String TYPE_PROPERTY_NAME = "drools:type";
039: public static final String SOURCE_PROPERTY_NAME = "drools:source";
040: public static final String SUBJECT_PROPERTY_NAME = "drools:subject";
041: public static final String RELATION_PROPERTY_NAME = "drools:relation";
042: public static final String RIGHTS_PROPERTY_NAME = "drools:rights";
043: public static final String COVERAGE_PROPERTY_NAME = "drools:coverage";
044: public static final String PUBLISHER_PROPERTY_NAME = "drools:publisher";
045:
046: /**
047: * The name of the state property on the rule node type
048: */
049: public static final String STATE_PROPERTY_NAME = "drools:stateReference";
050:
051: /**
052: * The name of the tag property on the rule node type
053: */
054: public static final String CATEGORY_PROPERTY_NAME = "drools:categoryReference";
055:
056: /**
057: * The possible formats for the format property of the node
058: */
059: public static final String DEFAULT_CONTENT_FORMAT = "txt";
060:
061: /** this is what is referred to when reading content from a versioned node */
062: private Node contentNode = null;
063:
064: /**
065: * Sets this object's node attribute to the specified node
066: *
067: * @param rulesRepository the RulesRepository object that this object is being created from
068: * @param node the node in the repository that this item corresponds to
069: */
070: public VersionableItem(RulesRepository rulesRepository, Node node) {
071: super (rulesRepository, node);
072: }
073:
074: /**
075: * @return A unique identifier for this items content node.
076: * This UUID is constant even with new versions, it represents the asset, and
077: * ALL its historical versions.
078: */
079: public String getUUID() {
080: try {
081: return this .getVersionContentNode().getUUID();
082: } catch (RepositoryException e) {
083: throw new RulesRepositoryException(e);
084: }
085: }
086:
087: /**
088: * This will return true if the current entity is actually a
089: * historical version (which means is effectively read only).
090: */
091: public boolean isHistoricalVersion() throws RepositoryException {
092: return this .node.getPrimaryNodeType().getName().equals(
093: "nt:version")
094: || node.getPrimaryNodeType().getName().equals(
095: "nt:frozenNode");
096: }
097:
098: /**
099: * @return the predessor node of this node in the version history, or null if no predecessor version exists
100: * @throws RulesRepositoryException
101: */
102: protected Node getPrecedingVersionNode()
103: throws RulesRepositoryException {
104: try {
105: Node versionNode;
106: if (this .node.getPrimaryNodeType().getName().equals(
107: "nt:version")) {
108: versionNode = this .node;
109: } else {
110: versionNode = this .node.getBaseVersion();
111: }
112:
113: Property predecessorsProperty = versionNode
114: .getProperty("jcr:predecessors");
115: Value[] predecessorValues = predecessorsProperty
116: .getValues();
117:
118: if (predecessorValues.length > 0) {
119: Node predecessorNode = this .node
120: .getSession()
121: .getNodeByUUID(predecessorValues[0].getString());
122:
123: //we don't want to return the root node - it isn't a true predecessor
124: if (predecessorNode.getName().equals("jcr:rootVersion")) {
125: return null;
126: }
127:
128: return predecessorNode;
129: }
130: } catch (PathNotFoundException e) {
131: //do nothing - this will happen if no predecessors exits
132: } catch (Exception e) {
133: log.error("Caught exception", e);
134: throw new RulesRepositoryException(e);
135: }
136: return null;
137: }
138:
139: /**
140: * @return the successor node of this node in the version history
141: * @throws RulesRepositoryException
142: */
143: protected Node getSucceedingVersionNode()
144: throws RulesRepositoryException {
145: try {
146: Property successorsProperty = this .node
147: .getProperty("jcr:successors");
148: Value[] successorValues = successorsProperty.getValues();
149:
150: if (successorValues.length > 0) {
151: Node successorNode = this .node.getSession()
152: .getNodeByUUID(successorValues[0].getString());
153: return successorNode;
154: }
155: } catch (PathNotFoundException e) {
156: //do nothing - this will happen if no successors exist
157: } catch (Exception e) {
158: log.error("Caught exception", e);
159: throw new RulesRepositoryException(e);
160: }
161: return null;
162: }
163:
164: /**
165: * @return an Iterator over VersionableItem objects encapsulating each successor node of this
166: * Item's node
167: * @throws RulesRepositoryException
168: * @Deprecated Until I can work out why it isn't quite kosher.
169: */
170: ItemVersionIterator getSuccessorVersionsIterator()
171: throws RulesRepositoryException {
172: return new ItemVersionIterator(this ,
173: ItemVersionIterator.ITERATION_TYPE_SUCCESSOR);
174: }
175:
176: /**
177: * @return an Iterator over VersionableItem objects encapsulating each predecessor node of this
178: * Item's node
179: * @throws RulesRepositoryException
180: * @Deprecated Until I can work out why it isn't quite kosher.
181: */
182: ItemVersionIterator getPredecessorVersionsIterator()
183: throws RulesRepositoryException {
184: return new ItemVersionIterator(this ,
185: ItemVersionIterator.ITERATION_TYPE_PREDECESSOR);
186: }
187:
188: /**
189: * Clients of this method can cast the resulting object to the type of object they are
190: * calling the method on (e.g.
191: * <pre>
192: * RuleItem item;
193: * ...
194: * RuleItem predcessor = (RuleItem) item.getPrecedingVersion();
195: * </pre>
196: * @return a VersionableItem object encapsulating the predessor node of this node in the
197: * version history, or null if no predecessor version exists
198: * @throws RulesRepositoryException
199: */
200: public abstract VersionableItem getPrecedingVersion()
201: throws RulesRepositoryException;
202:
203: /**
204: * Clients of this method can cast the resulting object to the type of object they are
205: * calling the method on (e.g.
206: * <pre>
207: * RuleItem item;
208: * ...
209: * RuleItem successor = (RuleItem) item.getSucceedingVersion();
210: * </pre>
211: *
212: * @return a VersionableItem object encapsulating the successor node of this node in the
213: * version history.
214: * @throws RulesRepositoryException
215: */
216: public abstract VersionableItem getSucceedingVersion()
217: throws RulesRepositoryException;
218:
219: /**
220: * Gets the Title of the versionable node. See the Dublin Core documentation for more
221: * explanation: http://dublincore.org/documents/dces/
222: *
223: * @return the title of the node this object encapsulates
224: * @throws RulesRepositoryException
225: */
226: public String getTitle() throws RulesRepositoryException {
227: try {
228: Node theNode = getVersionContentNode();
229:
230: Property data = theNode.getProperty(TITLE_PROPERTY_NAME);
231: return data.getValue().getString();
232: } catch (Exception e) {
233: log.error("Caught Exception", e);
234: throw new RulesRepositoryException(e);
235: }
236: }
237:
238: /**
239: * See the Dublin Core documentation for more
240: * explanation: http://dublincore.org/documents/dces/
241: *
242: * @param title the new title for the node
243: * @throws RulesRepositoryException
244: */
245: public void updateTitle(String title)
246: throws RulesRepositoryException {
247: updateStringProperty(title, TITLE_PROPERTY_NAME);
248: }
249:
250: public void updateType(String type) {
251: updateStringProperty(type, TYPE_PROPERTY_NAME);
252: }
253:
254: public void updateExternalSource(String source) {
255: updateStringProperty(source, SOURCE_PROPERTY_NAME);
256: }
257:
258: public void updateSubject(String sub) {
259: updateStringProperty(sub, SUBJECT_PROPERTY_NAME);
260: }
261:
262: public void updateExternalRelation(String rel) {
263: updateStringProperty(rel, RELATION_PROPERTY_NAME);
264: }
265:
266: public void updateRights(String rights) {
267: updateStringProperty(rights, RIGHTS_PROPERTY_NAME);
268: }
269:
270: public void updateCoverage(String cov) {
271: updateStringProperty(cov, COVERAGE_PROPERTY_NAME);
272: }
273:
274: public void updatePublisher(String pub) {
275: updateStringProperty(pub, PUBLISHER_PROPERTY_NAME);
276: }
277:
278: /**
279: * update a text field. This is a convenience method that just
280: * uses the JCR node to set a property.
281: * This will also update the timestamp.
282: */
283: protected void updateStringProperty(String value, String prop) {
284: try {
285: checkIsUpdateable();
286:
287: if (value == null) {
288: return;
289: }
290:
291: node.checkout();
292: node.setProperty(prop, value);
293: Calendar lastModified = Calendar.getInstance();
294: this .node.setProperty(LAST_MODIFIED_PROPERTY_NAME,
295: lastModified);
296:
297: } catch (Exception e) {
298: if (e instanceof RuntimeException) {
299: throw (RuntimeException) e;
300: }
301: throw new RulesRepositoryException(e);
302: }
303: }
304:
305: /**
306: * See the Dublin Core documentation for more
307: * explanation: http://dublincore.org/documents/dces/
308: *
309: * @return the description of this object's node.
310: * @throws RulesRepositoryException
311: */
312: public String getDescription() throws RulesRepositoryException {
313: return getStringProperty(DESCRIPTION_PROPERTY_NAME);
314: }
315:
316: /**
317: * get this version number (default is incrementing integer, but you
318: * can provide an implementation of VersionNumberGenerator if needed).
319: */
320: public long getVersionNumber() {
321: // try {
322: // if ( getVersionContentNode().hasProperty( VERSION_NUMBER_PROPERTY_NAME ) ) {
323: // return getVersionContentNode().getProperty( VERSION_NUMBER_PROPERTY_NAME ).getString();
324: // } else {
325: // return null;
326: // }
327: // } catch ( RepositoryException e ) {
328: // throw new RulesRepositoryException( e );
329: // }
330:
331: return getLongProperty(VERSION_NUMBER_PROPERTY_NAME);
332: }
333:
334: /**
335: * This will return the checkin comment for the latest revision.
336: */
337: public String getCheckinComment() throws RulesRepositoryException {
338: return getStringProperty(CHECKIN_COMMENT);
339: }
340:
341: /**
342: * @return the date the function node (this version) was last modified
343: * @throws RulesRepositoryException
344: */
345: public Calendar getLastModified() throws RulesRepositoryException {
346: try {
347: Property lastModifiedProperty = getVersionContentNode()
348: .getProperty(LAST_MODIFIED_PROPERTY_NAME);
349: return lastModifiedProperty.getDate();
350: } catch (Exception e) {
351: log.error("Caught Exception", e);
352: throw new RulesRepositoryException(e);
353: }
354: }
355:
356: /**
357: * Creates a new version of this object's node, updating the description content
358: * for the node.
359: * <br>
360: * See the Dublin Core documentation for more
361: * explanation: http://dublincore.org/documents/dces/
362: *
363: * @param newDescriptionContent the new description content for the rule
364: * @throws RulesRepositoryException
365: */
366: public void updateDescription(String newDescriptionContent)
367: throws RulesRepositoryException {
368: try {
369: this .node.checkout();
370:
371: this .node.setProperty(DESCRIPTION_PROPERTY_NAME,
372: newDescriptionContent);
373:
374: Calendar lastModified = Calendar.getInstance();
375: this .node.setProperty(LAST_MODIFIED_PROPERTY_NAME,
376: lastModified);
377:
378: } catch (Exception e) {
379: log.error("Caught Exception", e);
380: throw new RulesRepositoryException(e);
381: }
382: }
383:
384: /**
385: * This returns the format of an item.
386: * This is analagous to a file extension
387: * if the resource was a file (it may contain more information
388: * then a pure file extension could, however).
389: *
390: * See the Dublin Core documentation for more
391: * explanation: http://dublincore.org/documents/dces/
392: *
393: * @return the format of this object's node
394: * @throws RulesRepositoryException
395: */
396: public String getFormat() throws RulesRepositoryException {
397: try {
398: Node theNode = getVersionContentNode();
399:
400: Property data = theNode.getProperty(FORMAT_PROPERTY_NAME);
401: return data.getValue().getString();
402: } catch (Exception e) {
403: log.error("Caught Exception", e);
404: throw new RulesRepositoryException(e);
405: }
406: }
407:
408: /**
409: * This sets the format (or "file extension" of the resource).
410: * In some cases this is critical, and generally should not be changed
411: * after the initial version is checked in.
412: *
413: * @param newFormat
414: */
415: public void updateFormat(String newFormat) {
416: this .updateStringProperty(newFormat, FORMAT_PROPERTY_NAME);
417: }
418:
419: /**
420: * When retrieving content, if we are dealing with a version in the history,
421: * we need to get the actual content node to retrieve values.
422: *
423: */
424: public Node getVersionContentNode() throws RepositoryException,
425: PathNotFoundException {
426: if (this .contentNode == null) {
427: this .contentNode = getRealContentFromVersion(this .node);
428: }
429: return contentNode;
430: }
431:
432: /**
433: * This deals with a node which *may* be a version, if it is, it grabs the frozen copy.
434: */
435: protected Node getRealContentFromVersion(Node node)
436: throws RepositoryException, PathNotFoundException {
437: if (node.getPrimaryNodeType().getName().equals("nt:version")) {
438: return node.getNode("jcr:frozenNode");
439: } else {
440: return node;
441: }
442: }
443:
444: /**
445: * Need to get the name from the content node, not the version node
446: * if it is in fact a version !
447: */
448: public String getName() {
449: try {
450: return getVersionContentNode().getName();
451: } catch (RepositoryException e) {
452: throw new RulesRepositoryException(e);
453: }
454: }
455:
456: /**
457: * This will check out the node prior to editing.
458: */
459: public void checkout() {
460:
461: try {
462: this .node.checkout();
463: } catch (UnsupportedRepositoryOperationException e) {
464: String message = "";
465: try {
466: message = "Error: Caught UnsupportedRepositoryOperationException when attempting to checkout rule: "
467: + this .node.getName()
468: + ". Are you sure your JCR repository supports versioning? ";
469: log.error(message, e);
470: } catch (RepositoryException e1) {
471: log.error("Caught Exception", e);
472: throw new RulesRepositoryException(e1);
473: }
474: throw new RulesRepositoryException(message, e);
475: } catch (Exception e) {
476: log.error("Caught Exception", e);
477: throw new RulesRepositoryException(e);
478: }
479: }
480:
481: /**
482: * This will save the content (if it hasn't been already) and
483: * then check it in to create a new version.
484: * It will also set the last modified property.
485: */
486: public void checkin(String comment) {
487: checkIsUpdateable();
488: try {
489: this .node.setProperty(LAST_MODIFIED_PROPERTY_NAME, Calendar
490: .getInstance());
491: this .node.setProperty(CHECKIN_COMMENT, comment);
492: this .node.setProperty(LAST_CONTRIBUTOR_PROPERTY_NAME,
493: this .node.getSession().getUserID());
494: long nextVersion = getVersionNumber() + 1;
495: this .node.setProperty(VERSION_NUMBER_PROPERTY_NAME,
496: nextVersion);
497: this .node.getSession().save();
498: this .node.checkin();
499: } catch (RepositoryException e) {
500: throw new RulesRepositoryException("Unable to checkin.", e);
501: }
502: }
503:
504: /**
505: * This will check to see if the node is the "head" and
506: * so can be updated (you can't update historical nodes ).
507: * @throws RulesRepositoryException if it is not allowed
508: * (means a programming error !).
509: */
510: protected void checkIsUpdateable() {
511: try {
512: if (this .node.getPrimaryNodeType().getName().equals(
513: "nt:version")) {
514: String message = "Error. Tags can only be added to the head version of a rule node";
515: log.error(message);
516: throw new RulesRepositoryException(message);
517: }
518: } catch (RepositoryException e) {
519: throw new RulesRepositoryException(e);
520: }
521: }
522:
523: /**
524: * Sets this object's rule node's state property to refer to the specified state node
525: *
526: * @param stateName the name of the state to set the rule node to
527: * @throws RulesRepositoryException
528: */
529: public void updateState(String stateName)
530: throws RulesRepositoryException {
531: try {
532:
533: //now set the state property of the rule
534: checkout();
535:
536: StateItem stateItem = this .rulesRepository
537: .getState(stateName);
538: this .updateState(stateItem);
539: } catch (Exception e) {
540: log.error("Caught exception", e);
541: throw new RulesRepositoryException(e);
542: }
543: }
544:
545: /**
546: * Sets this object's rule node's state property to refer to the specified StateItem's node
547: *
548: * @param stateItem the StateItem encapsulating the node to refer to from this object's node's state
549: * property
550: * @throws RulesRepositoryException
551: */
552: public void updateState(StateItem stateItem)
553: throws RulesRepositoryException {
554: checkIsUpdateable();
555: try {
556:
557: //now set the state property of the rule
558: checkout();
559: this .node.setProperty(STATE_PROPERTY_NAME, stateItem
560: .getNode());
561: } catch (Exception e) {
562: log.error("Caught exception", e);
563: throw new RulesRepositoryException(e);
564: }
565: }
566:
567: /**
568: * Gets StateItem object corresponding to the state property of this object's node
569: *
570: * @return a StateItem object corresponding to the state property of this object's node, or null
571: * if the state property is not set
572: * @throws RulesRepositoryException
573: */
574: public StateItem getState() throws RulesRepositoryException {
575: try {
576: Node content = getVersionContentNode();
577: Property stateProperty = content
578: .getProperty(STATE_PROPERTY_NAME);
579: Node stateNode = this .rulesRepository.getSession()
580: .getNodeByUUID(stateProperty.getString());
581: return new StateItem(this .rulesRepository, stateNode);
582: } catch (PathNotFoundException e) {
583: //not set
584: return null;
585: } catch (Exception e) {
586: log.error("Caught exception", e);
587: throw new RulesRepositoryException(e);
588: }
589: }
590:
591: /**
592: * This will return the current state item as a displayable thing.
593: * If there is no state, it will be an empty string.
594: */
595: public String getStateDescription() {
596: StateItem state = this .getState();
597: if (state == null) {
598: return "";
599: } else {
600: return state.getName();
601: }
602: }
603:
604: /** Compare this rules state with some other state */
605: public boolean sameState(StateItem other) {
606: StateItem this State = getState();
607: if (this State == other) {
608: return true;
609: } else if (this State != null) {
610: return this State.equals(other);
611: } else {
612: return false;
613: }
614: }
615:
616: /**
617: * Returns the last contributors name.
618: */
619: public String getLastContributor() {
620: return getStringProperty(LAST_CONTRIBUTOR_PROPERTY_NAME);
621: }
622:
623: /**
624: * This is the person who initially created the resource.
625: */
626: public String getCreator() {
627: return getStringProperty(CREATOR_PROPERTY_NAME);
628: }
629:
630: /**
631: * This is the Dublin Core field of type (a broad classification of resource type).
632: */
633: public String getType() {
634: return getStringProperty(TYPE_PROPERTY_NAME);
635: }
636:
637: /**
638: * This is the source of the asset/rule. Ie a human description of where it came from.
639: */
640: public String getExternalSource() {
641: return getStringProperty(SOURCE_PROPERTY_NAME);
642: }
643:
644: /**
645: * Typically,
646: * Subject will be expressed as keywords,
647: * key phrases or classification codes that describe a topic of the resource.
648: */
649: public String getSubject() {
650: return getStringProperty(SUBJECT_PROPERTY_NAME);
651: }
652:
653: /**
654: * A reference to a EXTERNAL related resource.
655: */
656: public String getExternalRelation() {
657: return getStringProperty(RELATION_PROPERTY_NAME);
658: }
659:
660: /**
661: * Optionally contains any copyright/ownership rights for the asset.
662: */
663: public String getRights() {
664: return getStringProperty(RIGHTS_PROPERTY_NAME);
665: }
666:
667: /**
668: * Typically, Coverage will include spatial location
669: * (a place name or geographic coordinates), temporal period (a period label, date, or date range) or jurisdiction (such as a named administrative entity). Recommended best practice is to select a value from a controlled vocabulary (for example, the Thesaurus of Geographic Names [TGN]) and to use, where appropriate, named places or time periods in preference to numeric identifiers such as sets of coordinates or date ranges.
670: */
671: public String getCoverage() {
672: return getStringProperty(COVERAGE_PROPERTY_NAME);
673: }
674:
675: /**
676: * Examples of Publisher include a person, an organization, or a service.
677: * Typically, the name of a Publisher should be used to indicate the entity.
678: */
679: public String getPublisher() {
680: return getStringProperty(PUBLISHER_PROPERTY_NAME);
681: }
682:
683: /**
684: * This returns the date/time on which the asset was "ORIGINALLY CREATED".
685: * Kinda handy if you want to know how old something is.
686: */
687: public Calendar getCreatedDate() {
688: Property prop;
689: try {
690: prop = this .node.getProperty("jcr:created");
691: return prop.getDate();
692: } catch (RepositoryException e) {
693: throw new RulesRepositoryException(e);
694: }
695:
696: }
697:
698: protected String getStringProperty(String property) {
699: try {
700: Node theNode = getVersionContentNode();
701: if (theNode.hasProperty(property)) {
702: Property data = theNode.getProperty(property);
703: return data.getValue().getString();
704: } else {
705: return "";
706: }
707: } catch (RepositoryException e) {
708: throw new RulesRepositoryException(e);
709: }
710: }
711:
712: protected long getLongProperty(String property) {
713: try {
714: Node theNode = getVersionContentNode();
715: if (theNode.hasProperty(property)) {
716: Property data = theNode.getProperty(property);
717: return data.getValue().getLong();
718: } else {
719: return 0;
720: }
721: } catch (RepositoryException e) {
722: throw new RulesRepositoryException(e);
723: }
724: }
725:
726: /**
727: * This returns the id of the exact version node (as opposed to the "main" node).
728: * Note that each asset has only one UUID the whole time, but there are also UUIDs
729: * for each item in the history.
730: * So while the main UUID version remains constant, the version UUIDs change on each
731: * checkin, which is what this method provides.
732: */
733: public String getVersionSnapshotUUID() {
734: try {
735: if (isHistoricalVersion()) {
736: return this .node.getUUID();
737: } else {
738: throw new RulesRepositoryException(
739: "This is the current version of the asset.");
740: }
741: } catch (RepositoryException e) {
742: throw new RulesRepositoryException(e);
743: }
744:
745: }
746:
747: public VersionableItem archiveItem(boolean data) {
748: checkout();
749:
750: try {
751: this .node.setProperty(CONTENT_PROPERTY_ARCHIVE_FLAG, data);
752: return this ;
753: } catch (RepositoryException e) {
754: log
755: .error("Unable to update this VersionableItem binary archive flag");
756: throw new RulesRepositoryException(e);
757: }
758: }
759:
760: /**
761: * Test if the VersionableItem is archived
762: */
763: public boolean isArchived() {
764: try {
765: return this .node.getProperty(CONTENT_PROPERTY_ARCHIVE_FLAG)
766: .getBoolean();
767: } catch (RepositoryException e) {
768: log.error("Unable to check this asset");
769: throw new RulesRepositoryException(e);
770: }
771: }
772:
773: }
|