001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /* $Id: RCML.java 473861 2006-11-12 03:51:14Z gregor $ */
020:
021: package org.apache.lenya.cms.repository;
022:
023: import java.io.IOException;
024: import java.util.ArrayList;
025: import java.util.Date;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.Vector;
031:
032: import org.apache.avalon.framework.service.ServiceManager;
033: import org.apache.excalibur.source.SourceResolver;
034: import org.apache.lenya.cms.cocoon.source.SourceUtil;
035: import org.apache.lenya.cms.rc.CheckInEntry;
036: import org.apache.lenya.cms.rc.CheckOutEntry;
037: import org.apache.lenya.cms.rc.RCML;
038: import org.apache.lenya.cms.rc.RCMLEntry;
039: import org.apache.lenya.cms.rc.RevisionControlException;
040: import org.apache.lenya.util.Assert;
041: import org.apache.lenya.xml.DocumentHelper;
042: import org.apache.lenya.xml.NamespaceHelper;
043: import org.w3c.dom.Document;
044: import org.w3c.dom.Element;
045:
046: /**
047: * Handle with the RCML file
048: */
049: public class SourceNodeRCML implements RCML {
050:
051: protected static final String NAMESPACE = "";
052:
053: private boolean dirty = false;
054: private int maximalNumberOfEntries = 5;
055: private Vector entries;
056:
057: private ServiceManager manager;
058:
059: private String contentSourceUri;
060: private String metaSourceUri;
061:
062: private static Map ELEMENTS = new HashMap();
063: protected static final String ELEMENT_CHECKIN = "CheckIn";
064: protected static final String ELEMENT_CHECKOUT = "CheckOut";
065: protected static final String ELEMENT_BACKUP = "Backup";
066: protected static final String ELEMENT_TIME = "Time";
067: protected static final String ELEMENT_VERSION = "Version";
068: protected static final String ELEMENT_IDENTITY = "Identity";
069: protected static final String ELEMENT_XPSREVISIONCONTROL = "XPSRevisionControl";
070: protected static final String ELEMENT_SESSION = "session";
071:
072: protected static final String ATTR_BACKUP = "backup";
073: protected static final String ATTR_TIME = "time";
074: protected static final String ATTR_VERSION = "version";
075: protected static final String ATTR_IDENTITY = "identity";
076: protected static final String ATTR_SESSION = "session";
077:
078: {
079: ELEMENTS.put(new Short(ci), ELEMENT_CHECKIN);
080: ELEMENTS.put(new Short(co), ELEMENT_CHECKOUT);
081: }
082:
083: /**
084: * @param contentSourceUri The content source URI.
085: * @param metaSourceUri The meta source URI.
086: * @param manager The service manager.
087: */
088: public SourceNodeRCML(String contentSourceUri,
089: String metaSourceUri, ServiceManager manager) {
090: this .maximalNumberOfEntries = 200;
091: this .maximalNumberOfEntries = (2 * this .maximalNumberOfEntries) + 1;
092: this .manager = manager;
093: this .contentSourceUri = contentSourceUri;
094: this .metaSourceUri = metaSourceUri;
095: }
096:
097: protected static final String RCML_EXTENSION = ".rcml";
098:
099: private static final String ALL_SESSIONS = "unrestricted";
100:
101: protected String getRcmlSourceUri() {
102: return this .contentSourceUri + RCML_EXTENSION;
103: }
104:
105: /**
106: * Call the method write, if the document is dirty
107: *
108: * @throws IOException if an error occurs
109: * @throws Exception if an error occurs
110: */
111: protected void finalize() throws IOException, Exception {
112: if (this .isDirty()) {
113: write();
114: }
115: }
116:
117: /**
118: * Write the XML RCML-document in the RCML-file.
119: * @throws RevisionControlException if an error occurs
120: */
121: public synchronized void write() throws RevisionControlException {
122: NamespaceHelper helper = saveToXml();
123: Assert.notNull("XML document", helper);
124: try {
125: SourceUtil.writeDOM(helper.getDocument(),
126: getRcmlSourceUri(), this .manager);
127: } catch (Exception e) {
128: throw new RevisionControlException(e);
129: }
130: clearDirty();
131: }
132:
133: /**
134: * Write a new entry for a check out or a check in the RCML-File made by the user with identity
135: * at time
136: * @param node The node.
137: * @param type co for a check out, ci for a check in
138: * @param time
139: * @param backup Create backup element (only considered for check-in entries).
140: * @param newVersion If the revision number shall be increased (only considered for check-in
141: * entries).
142: * @param restrictedToSession If the check-out is restricted to the session (only considered for
143: * check-out entries).
144: * @throws RevisionControlException if an error occurs
145: */
146: public synchronized void checkOutIn(Node node, short type,
147: long time, boolean backup, boolean newVersion,
148: boolean restrictedToSession)
149: throws RevisionControlException {
150:
151: String identity = node.getSession().getIdentity().getUser()
152: .getId();
153:
154: Vector entries = getEntries();
155: if (entries.size() == 0) {
156: if (type == ci) {
157: throw new IllegalStateException(
158: "Can't check in - not checked out.");
159: }
160: } else {
161: RCMLEntry latestEntry = getLatestEntry();
162: if (type == latestEntry.getType()) {
163: String elementName = (String) ELEMENTS.get(new Short(
164: type));
165: throw new IllegalStateException("RCML entry type <"
166: + elementName
167: + "> not allowed twice in a row. Before: ["
168: + latestEntry.getIdentity() + "], now: ["
169: + identity + "], node: ["
170: + this .contentSourceUri + "]");
171: }
172: }
173:
174: String sessionId;
175: if (type == RCML.co && !restrictedToSession) {
176: sessionId = ALL_SESSIONS;
177: } else {
178: sessionId = node.getSession().getId();
179: }
180:
181: RCMLEntry entry;
182: switch (type) {
183: case RCML.ci:
184: int version = 0;
185: CheckInEntry latestEntry = getLatestCheckInEntry();
186: if (latestEntry != null) {
187: version = latestEntry.getVersion();
188: }
189: if (newVersion) {
190: version++;
191: }
192: entry = new CheckInEntry(sessionId, identity, time,
193: version, backup);
194: break;
195: case RCML.co:
196: entry = new CheckOutEntry(sessionId, identity, time);
197: break;
198: default:
199: throw new IllegalArgumentException("No such type: [" + type
200: + "]");
201: }
202:
203: entries.add(0, entry);
204: setDirty();
205: }
206:
207: protected Element saveToXml(NamespaceHelper helper, RCMLEntry entry)
208: throws RevisionControlException {
209: String elementName = (String) ELEMENTS.get(new Short(entry
210: .getType()));
211: Element entryElement = helper.createElement(elementName);
212:
213: entryElement.setAttribute(ATTR_IDENTITY, entry.getIdentity());
214: entryElement.setAttribute(ATTR_SESSION, entry.getSessionId());
215: entryElement.setAttribute(ATTR_TIME, Long.toString(entry
216: .getTime()));
217:
218: if (entry.getType() == ci) {
219: CheckInEntry checkInEntry = (CheckInEntry) entry;
220: entryElement.setAttribute(ATTR_VERSION, Integer
221: .toString(checkInEntry.getVersion()));
222: if (checkInEntry.hasBackup()) {
223: entryElement.setAttribute(ATTR_BACKUP, "true");
224: }
225: }
226:
227: return entryElement;
228: }
229:
230: protected NamespaceHelper saveToXml()
231: throws RevisionControlException {
232: try {
233: NamespaceHelper helper = new NamespaceHelper(NAMESPACE, "",
234: ELEMENT_XPSREVISIONCONTROL);
235: Element root = helper.getDocument().getDocumentElement();
236: Vector entries = getEntries();
237: for (Iterator i = entries.iterator(); i.hasNext();) {
238: RCMLEntry entry = (RCMLEntry) i.next();
239: Element element = saveToXml(helper, entry);
240: root.appendChild(element);
241: }
242: return helper;
243: } catch (Exception e) {
244: throw new RevisionControlException(
245: "Could create revision control XML ["
246: + getRcmlSourceUri() + "]", e);
247: }
248: }
249:
250: protected Element getLatestElement(NamespaceHelper helper,
251: String type) throws RevisionControlException {
252: Element parent = helper.getDocument().getDocumentElement();
253: return helper.getFirstChild(parent, type);
254: }
255:
256: /**
257: * get the latest check out
258: * @return CheckOutEntry The entry of the check out
259: * @throws RevisionControlException if an error occurs
260: */
261: public CheckOutEntry getLatestCheckOutEntry()
262: throws RevisionControlException {
263: return (CheckOutEntry) getLatestEntry(RCML.co);
264: }
265:
266: /**
267: * get the latest check in
268: * @return CheckInEntry The entry of the check in
269: * @throws RevisionControlException if an error occurs
270: */
271: public CheckInEntry getLatestCheckInEntry()
272: throws RevisionControlException {
273: return (CheckInEntry) getLatestEntry(RCML.ci);
274: }
275:
276: /**
277: * get the latest entry (a check out or check in)
278: * @param type The type.
279: * @return RCMLEntry The entry of the check out/in
280: * @throws RevisionControlException if an error occurs
281: */
282: public RCMLEntry getLatestEntry(short type)
283: throws RevisionControlException {
284: Vector entries = getEntries();
285: for (Iterator i = entries.iterator(); i.hasNext();) {
286: RCMLEntry entry = (RCMLEntry) i.next();
287: if (entry.getType() == type) {
288: return entry;
289: }
290: }
291: return null;
292: }
293:
294: public RCMLEntry getLatestEntry() throws RevisionControlException {
295: Vector entries = getEntries();
296: if (entries.isEmpty()) {
297: return null;
298: } else {
299: return (RCMLEntry) entries.firstElement();
300: }
301: }
302:
303: protected RCMLEntry getEntry(NamespaceHelper helper, Element element) {
304: if (element.hasAttribute(ATTR_IDENTITY)) {
305: String type = element.getLocalName();
306: String sessionId = element.getAttribute(ATTR_SESSION);
307: String identity = element.getAttribute(ATTR_IDENTITY);
308: String timeString = element.getAttribute(ATTR_TIME);
309: long time = new Long(timeString).longValue();
310: if (type.equals(ELEMENT_CHECKIN)) {
311: String versionString = element
312: .getAttribute(ATTR_VERSION);
313: int version = new Integer(versionString).intValue();
314: boolean backup = element.hasAttribute(ATTR_BACKUP);
315: return new CheckInEntry(sessionId, identity, time,
316: version, backup);
317: } else if (type.equals(ELEMENT_CHECKOUT)) {
318: return new CheckOutEntry(sessionId, identity, time);
319: } else {
320: throw new RuntimeException(
321: "Unsupported RCML entry type: [" + type + "]");
322: }
323: } else {
324: return getLegacyEntry(helper, element);
325: }
326: }
327:
328: protected RCMLEntry getLegacyEntry(NamespaceHelper helper,
329: Element element) {
330: String type = element.getLocalName();
331: String sessionId = getChildValue(helper, element,
332: ELEMENT_SESSION, "");
333: String identity = getChildValue(helper, element,
334: ELEMENT_IDENTITY);
335: String timeString = getChildValue(helper, element, ELEMENT_TIME);
336: long time = new Long(timeString).longValue();
337: if (type.equals(ELEMENT_CHECKIN)) {
338: String versionString = getChildValue(helper, element,
339: ELEMENT_VERSION);
340: int version = new Integer(versionString).intValue();
341: boolean backup = helper
342: .getChildren(element, ELEMENT_BACKUP).length > 0;
343: return new CheckInEntry(sessionId, identity, time, version,
344: backup);
345: } else if (type.equals(ELEMENT_CHECKOUT)) {
346: return new CheckOutEntry(sessionId, identity, time);
347: } else {
348: throw new RuntimeException("Unsupported RCML entry type: ["
349: + type + "]");
350: }
351: }
352:
353: protected String getChildValue(NamespaceHelper helper,
354: Element element, String childName, String defaultValue) {
355: Element child = DocumentHelper.getFirstChild(element,
356: NAMESPACE, childName);
357: if (child == null) {
358: return defaultValue;
359: } else {
360: return DocumentHelper.getSimpleElementText(child);
361: }
362: }
363:
364: protected String getChildValue(NamespaceHelper helper,
365: Element element, String childName) {
366: Element child = helper.getFirstChild(element, childName);
367: if (child == null) {
368: throw new RuntimeException("The element <"
369: + element.getNodeName()
370: + "> has no child element <" + childName
371: + ">. Source URI: [" + getRcmlSourceUri() + "[");
372: }
373: return DocumentHelper.getSimpleElementText(child);
374: }
375:
376: /**
377: * get all check in and check out
378: * @return Vector of all check out and check in entries in this RCML-file
379: * @throws RevisionControlException if an error occurs
380: */
381: public synchronized Vector getEntries()
382: throws RevisionControlException {
383: if (this .entries == null) {
384: this .entries = new Vector();
385: String uri = getRcmlSourceUri();
386: try {
387: if (SourceUtil.exists(uri, this .manager)) {
388: Document xml = SourceUtil
389: .readDOM(uri, this .manager);
390: NamespaceHelper helper = new NamespaceHelper(
391: NAMESPACE, "", xml);
392: Element parent = xml.getDocumentElement();
393: Element[] elements = helper.getChildren(parent);
394: for (int i = 0; i < elements.length; i++) {
395: RCMLEntry entry = getEntry(helper, elements[i]);
396: entries.add(entry);
397: }
398: }
399: } catch (Exception e) {
400: throw new RevisionControlException(e);
401: }
402: }
403: return this .entries;
404: }
405:
406: /**
407: * get all backup entries
408: * @return Vector of all entries in this RCML-file with a backup
409: * @throws Exception if an error occurs
410: */
411: public synchronized Vector getBackupEntries() throws Exception {
412: Vector entries = getEntries();
413: Vector backupEntries = new Vector();
414: for (Iterator i = entries.iterator(); i.hasNext();) {
415: RCMLEntry entry = (RCMLEntry) i.next();
416: if (entry.getType() == RCML.ci
417: && ((CheckInEntry) entry).hasBackup()) {
418: backupEntries.add(entry);
419: }
420: }
421: return backupEntries;
422: }
423:
424: public synchronized void makeBackup(long time)
425: throws RevisionControlException {
426: makeBackup(this .contentSourceUri, time);
427: makeBackup(this .metaSourceUri, time);
428: }
429:
430: protected synchronized void makeBackup(String sourceUri, long time)
431: throws RevisionControlException {
432: String backupSourceUri = getBackupSourceUri(sourceUri, time);
433: try {
434: if (SourceUtil.exists(sourceUri, manager)) {
435: SourceUtil.copy(this .manager, sourceUri,
436: backupSourceUri);
437: }
438: } catch (Exception e) {
439: throw new RevisionControlException(e);
440: }
441: }
442:
443: public synchronized void restoreBackup(Node node, long time)
444: throws RevisionControlException {
445: SourceNode sourceNode = (SourceNode) node;
446: restoreBackup(sourceNode.getContentSource(), time);
447: restoreBackup(sourceNode.getMetaSource(), time);
448: }
449:
450: protected synchronized void restoreBackup(SourceWrapper wrapper,
451: long time) throws RevisionControlException {
452: String backupSourceUri = getBackupSourceUri(wrapper, time);
453: SourceResolver resolver = null;
454: try {
455: resolver = (SourceResolver) this .manager
456: .lookup(SourceResolver.ROLE);
457: SourceUtil.copy(resolver, backupSourceUri, wrapper
458: .getOutputStream());
459: } catch (Exception e) {
460: throw new RevisionControlException(e);
461: } finally {
462: if (resolver != null) {
463: this .manager.release(resolver);
464: }
465: }
466: }
467:
468: protected String getBackupSourceUri(SourceWrapper wrapper, long time) {
469: String uri = wrapper.getRealSourceUri();
470: return getBackupSourceUri(uri, time);
471: }
472:
473: protected String getBackupSourceUri(String uri, long time) {
474: return uri + "." + time + ".bak";
475: }
476:
477: /**
478: * Prune the list of entries and delete the corresponding backups. Limit the number of entries
479: * to the value maximalNumberOfEntries (2maxNumberOfRollbacks(configured)+1)
480: * @throws RevisionControlException if an error occurs
481: */
482: public synchronized void pruneEntries()
483: throws RevisionControlException {
484: Vector entries = getEntries();
485: RCMLEntry[] array = (RCMLEntry[]) entries
486: .toArray(new RCMLEntry[entries.size()]);
487:
488: for (int i = this .maximalNumberOfEntries; i < entries.size(); i++) {
489: // remove the backup file associated with this entry
490: RCMLEntry entry = array[i];
491: if (entry.getType() == ci
492: && ((CheckInEntry) entry).hasBackup()) {
493: long time = entry.getTime();
494: deleteBackup(this .contentSourceUri, time);
495: deleteBackup(this .metaSourceUri, time);
496: }
497: this .entries.remove(entry);
498: }
499: setDirty();
500: }
501:
502: protected synchronized void deleteBackup(String sourceUri, long time)
503: throws RevisionControlException {
504: String uri = getBackupSourceUri(sourceUri, time);
505: try {
506: SourceUtil.delete(uri, this .manager);
507: SourceUtil.deleteEmptyCollections(uri, this .manager);
508: } catch (Exception e) {
509: throw new RevisionControlException(e);
510: }
511: }
512:
513: /**
514: * Check if the document is dirty
515: * @return boolean dirty
516: */
517: public boolean isDirty() {
518: return this .dirty;
519: }
520:
521: /**
522: * Set the value dirty to true
523: */
524: protected void setDirty() {
525: this .dirty = true;
526: }
527:
528: /**
529: * Set the value dirty to false
530: */
531: protected void clearDirty() {
532: this .dirty = false;
533: }
534:
535: /**
536: * get the time's value of the backups
537: * @return String[] the times
538: * @throws Exception if an error occurs
539: */
540: public String[] getBackupsTime() throws Exception {
541:
542: Vector entries = getEntries();
543: List times = new ArrayList();
544: for (Iterator i = entries.iterator(); i.hasNext();) {
545: RCMLEntry entry = (RCMLEntry) i.next();
546: if (entry.getType() == ci
547: && ((CheckInEntry) entry).hasBackup()) {
548: times.add(Long.toString(entry.getTime()));
549: }
550: }
551: return (String[]) times.toArray(new String[times.size()]);
552:
553: }
554:
555: /**
556: * Delete the revisions, the RCML source and the collection if the latter is empty.
557: * @return boolean true, if the file was deleted
558: */
559: public synchronized boolean delete() {
560: try {
561: deleteRevisions();
562: SourceUtil.delete(getRcmlSourceUri(), this .manager);
563: SourceUtil.deleteEmptyCollections(getRcmlSourceUri(),
564: this .manager);
565: } catch (Exception e) {
566: throw new RuntimeException(e);
567: }
568: return true;
569: }
570:
571: /**
572: * delete the revisions
573: * @throws RevisionControlException when somthing went wrong
574: */
575: public synchronized void deleteRevisions()
576: throws RevisionControlException {
577: try {
578: String[] times = getBackupsTime();
579: for (int i = 0; i < times.length; i++) {
580: long time = new Long(times[i]).longValue();
581: deleteBackup(this .contentSourceUri, time);
582: deleteBackup(this .metaSourceUri, time);
583: }
584: this .entries.clear();
585: } catch (Exception e) {
586: throw new RevisionControlException(e);
587: }
588: }
589:
590: public synchronized void copyFrom(Node node, Node otherNode)
591: throws RevisionControlException {
592:
593: SourceNode otherSourceNode = (SourceNode) otherNode;
594: SourceNode sourceNode = (SourceNode) node;
595: SourceNodeRCML otherRcml = (SourceNodeRCML) ((SourceNode) otherNode)
596: .getRcml();
597:
598: try {
599:
600: Vector backupEntries = otherRcml.getBackupEntries();
601: for (Iterator i = backupEntries.iterator(); i.hasNext();) {
602: RCMLEntry entry = (RCMLEntry) i.next();
603: long time = entry.getTime();
604: String otherContentUri = otherRcml.getBackupSourceUri(
605: otherSourceNode.getContentSource(), time);
606: String this ContentUri = this .getBackupSourceUri(
607: sourceNode.getContentSource(), time);
608: SourceUtil.copy(this .manager, otherContentUri,
609: this ContentUri);
610:
611: String otherMetaUri = otherRcml.getBackupSourceUri(
612: otherSourceNode.getMetaSource(), time);
613: String this MetaUri = this .getBackupSourceUri(sourceNode
614: .getMetaSource(), time);
615: SourceUtil
616: .copy(this .manager, otherMetaUri, this MetaUri);
617: }
618:
619: this .entries = new Vector();
620: Vector otherEntries = otherRcml.getEntries();
621: for (Iterator i = otherEntries.iterator(); i.hasNext();) {
622: RCMLEntry entry = (RCMLEntry) i.next();
623: RCMLEntry newEntry = null;
624: switch (entry.getType()) {
625: case co:
626: newEntry = new CheckOutEntry(entry.getSessionId(),
627: entry.getIdentity(), entry.getTime());
628: break;
629: case ci:
630: CheckInEntry ciEntry = (CheckInEntry) entry;
631: newEntry = new CheckInEntry(ciEntry.getSessionId(),
632: ciEntry.getIdentity(), ciEntry.getTime(),
633: ciEntry.getVersion(), ciEntry.hasBackup());
634: break;
635: }
636: this .entries.add(newEntry);
637: }
638:
639: write();
640: } catch (Exception e) {
641: throw new RevisionControlException(e);
642: }
643: }
644:
645: public synchronized boolean isCheckedOut()
646: throws RevisionControlException {
647: RCMLEntry entry = getLatestEntry();
648: return entry != null && entry.getType() == RCML.co;
649: }
650:
651: public synchronized void checkIn(Node node, boolean backup,
652: boolean newVersion) throws RevisionControlException {
653: long time = new Date().getTime();
654:
655: if (backup) {
656: makeBackup(time);
657: }
658:
659: checkOutIn(node, RCML.ci, time, backup, newVersion, false);
660: pruneEntries();
661: write();
662: }
663:
664: public synchronized void checkOut(Node node)
665: throws RevisionControlException {
666: checkOut(node, true);
667: }
668:
669: public synchronized void checkOut(Node node,
670: boolean restrictedToSession)
671: throws RevisionControlException {
672: checkOutIn(node, RCML.co, new Date().getTime(), false, false,
673: restrictedToSession);
674: write();
675: }
676:
677: public boolean isCheckedOutBySession(Session session)
678: throws RevisionControlException {
679: Vector entries = getEntries();
680: if (entries.size() > 0) {
681: RCMLEntry entry = (RCMLEntry) entries.get(0);
682: String otherSessionId = entry.getSessionId();
683: if (entry.getType() == co) {
684: // not restricted to session
685: if (otherSessionId.equals(ALL_SESSIONS)) {
686: String otherUserId = entry.getIdentity();
687: String userId = session.getIdentity().getUser()
688: .getId();
689: return userId.equals(otherUserId);
690: }
691: // restricted to session
692: if (otherSessionId.equals(session.getId())) {
693: return true;
694: }
695: }
696: }
697: return false;
698: }
699:
700: }
|