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: package org.apache.lenya.cms.repository;
019:
020: import java.io.InputStream;
021: import java.io.OutputStream;
022: import java.util.Collection;
023:
024: import org.apache.avalon.framework.logger.AbstractLogEnabled;
025: import org.apache.avalon.framework.logger.Logger;
026: import org.apache.avalon.framework.service.ServiceException;
027: import org.apache.avalon.framework.service.ServiceManager;
028: import org.apache.excalibur.source.SourceResolver;
029: import org.apache.excalibur.source.TraversableSource;
030: import org.apache.lenya.ac.Identity;
031: import org.apache.lenya.ac.User;
032: import org.apache.lenya.cms.metadata.MetaData;
033: import org.apache.lenya.cms.metadata.MetaDataException;
034: import org.apache.lenya.cms.observation.DocumentEvent;
035: import org.apache.lenya.cms.observation.RepositoryEvent;
036: import org.apache.lenya.cms.observation.RepositoryEventFactory;
037: import org.apache.lenya.cms.rc.CheckInEntry;
038: import org.apache.lenya.cms.rc.RCML;
039: import org.apache.lenya.cms.rc.RevisionControlException;
040: import org.apache.lenya.transaction.Lock;
041: import org.apache.lenya.transaction.TransactionException;
042: import org.apache.lenya.transaction.Transactionable;
043:
044: /**
045: * A repository node.
046: *
047: * @version $Id: SourceNode.java 575297 2007-09-13 12:50:13Z andreas $
048: */
049: public class SourceNode extends AbstractLogEnabled implements Node,
050: Transactionable {
051:
052: protected ServiceManager manager;
053:
054: private ContentSourceWrapper contentSource;
055: private MetaSourceWrapper metaSource;
056:
057: /**
058: * Ctor.
059: *
060: * @param session
061: * @param sourceUri
062: * @param manager
063: * @param logger
064: */
065: public SourceNode(Session session, String sourceUri,
066: ServiceManager manager, Logger logger) {
067: this .manager = manager;
068: enableLogging(logger);
069: this .session = session;
070:
071: this .contentSource = new ContentSourceWrapper(this , sourceUri,
072: manager, logger);
073: this .metaSource = new MetaSourceWrapper(this , sourceUri,
074: manager, logger);
075: }
076:
077: protected ContentSourceWrapper getContentSource() {
078: return this .contentSource;
079: }
080:
081: protected MetaSourceWrapper getMetaSource() {
082: return this .metaSource;
083: }
084:
085: /**
086: * @see org.apache.lenya.transaction.Transactionable#deleteTransactionable()
087: */
088: public void deleteTransactionable() throws RepositoryException {
089: this .contentSource.deleteTransactionable();
090: this .metaSource.deleteTransactionable();
091: getRcml().delete();
092: }
093:
094: protected String getUserId() {
095: String userId = null;
096: Identity identity = getSession().getIdentity();
097: if (identity != null) {
098: User user = identity.getUser();
099: if (user != null) {
100: userId = user.getId();
101: }
102: }
103: return userId;
104: }
105:
106: /**
107: * @see org.apache.lenya.transaction.Transactionable#checkin()
108: */
109: public void checkin() throws RepositoryException {
110: RCML rcml = getRcml();
111: synchronized (rcml) {
112: try {
113: if (!rcml.isCheckedOutBySession(getSession())) {
114: throw new RepositoryException(
115: "Cannot check in node ["
116: + getSourceURI()
117: + "]: not checked out by this session!");
118: }
119: rcml
120: .checkIn(this , exists(), getSession().isDirty(
121: this ));
122: } catch (Exception e) {
123: throw new RepositoryException(e);
124: }
125: }
126: }
127:
128: public void forceCheckIn() throws RepositoryException {
129: RCML rcml = getRcml();
130: synchronized (rcml) {
131: try {
132: if (!rcml.isCheckedOut()) {
133: throw new RepositoryException(
134: "Cannot check in node [" + getSourceURI()
135: + "]: not checked out!");
136: }
137: rcml.checkIn(this , false, false);
138: } catch (Exception e) {
139: throw new RepositoryException(e);
140: }
141: }
142: }
143:
144: /**
145: * @see org.apache.lenya.transaction.Transactionable#isCheckedOut()
146: */
147: public boolean isCheckedOut() throws RepositoryException {
148: try {
149: return getRcml().isCheckedOut();
150: } catch (Exception e) {
151: throw new RepositoryException(e);
152: }
153: }
154:
155: public boolean isCheckedOutBySession(Session session)
156: throws RepositoryException {
157: try {
158: return getRcml().isCheckedOutBySession(session);
159: } catch (RevisionControlException e) {
160: throw new RepositoryException(e);
161: }
162: }
163:
164: public void checkout(boolean restrictedToSession)
165: throws RepositoryException {
166:
167: if (getLogger().isDebugEnabled())
168: getLogger().debug(
169: "SourceNode::checkout() called, sourceURI ["
170: + getSourceURI() + "]");
171:
172: RCML rcml = getRcml();
173: synchronized (rcml) {
174: try {
175: if (rcml.isCheckedOut()
176: && !rcml.isCheckedOutBySession(getSession())) {
177: throw new RepositoryException(
178: "The node ["
179: + this
180: + "] is already checked out by another session!");
181: }
182: if (!rcml.isCheckedOut()) {
183: rcml.checkOut(this , restrictedToSession);
184: }
185: } catch (RevisionControlException e) {
186: throw new RepositoryException(e);
187: }
188: }
189: }
190:
191: public void checkout() throws RepositoryException {
192: checkout(true);
193: }
194:
195: private Lock lock;
196:
197: /**
198: * @see org.apache.lenya.transaction.Transactionable#hasChanged()
199: */
200: public boolean hasChanged() throws RepositoryException {
201: try {
202: int currentVersion = getCurrentRevisionNumber();
203: int lockVersion = getLock().getVersion();
204: return currentVersion > lockVersion;
205: } catch (Exception e) {
206: throw new RepositoryException(e);
207: }
208: }
209:
210: protected int getCurrentRevisionNumber() throws RepositoryException {
211: CheckInEntry entry;
212: try {
213: entry = getRcml().getLatestCheckInEntry();
214: } catch (RevisionControlException e) {
215: throw new RepositoryException(e);
216: }
217: if (entry == null) {
218: return 0;
219: } else {
220: return entry.getVersion();
221: }
222: }
223:
224: /**
225: * @return The document node, if this is a meta data node, or the node itself otherwise.
226: * @throws ServiceException
227: * @throws RepositoryException
228: */
229: protected Node getDocumentNode() throws ServiceException,
230: RepositoryException {
231: Node node;
232: String sourceUri = getSourceURI();
233: if (sourceUri.endsWith(".meta")) {
234: String documentSourceUri = sourceUri.substring(0, sourceUri
235: .length()
236: - ".meta".length());
237: NodeFactory factory = null;
238: try {
239: factory = (NodeFactory) this .manager
240: .lookup(NodeFactory.ROLE);
241: node = (Node) factory.buildItem(getSession(),
242: documentSourceUri);
243: } finally {
244: if (factory != null) {
245: this .manager.release(factory);
246: }
247: }
248: } else {
249: node = this ;
250: }
251: return node;
252: }
253:
254: /**
255: * @see org.apache.lenya.transaction.Transactionable#createTransactionable()
256: */
257: public void createTransactionable() throws RepositoryException {
258: }
259:
260: /**
261: * @see org.apache.lenya.transaction.Transactionable#lock()
262: */
263: public synchronized void lock() throws RepositoryException {
264: if (getLogger().isDebugEnabled()) {
265: getLogger().debug("Locking [" + this + "]");
266: }
267: try {
268: int currentRev = getCurrentRevisionNumber();
269: int contentLoadRev = getContentSource().getLoadRevision();
270: int contentRev = contentLoadRev == -1 ? currentRev
271: : contentLoadRev;
272:
273: int metaLoadRev = getMetaSource().getLoadRevision();
274: int metaRev = metaLoadRev == -1 ? currentRev : metaLoadRev;
275:
276: int lockRev = Math.min(contentRev, metaRev);
277: this .lock = getSession().createLock(this , lockRev);
278: } catch (TransactionException e) {
279: throw new RepositoryException(e);
280: }
281:
282: }
283:
284: /**
285: * @see org.apache.lenya.transaction.Transactionable#getLock()
286: */
287: public Lock getLock() {
288: return this .lock;
289: }
290:
291: /**
292: * @see org.apache.lenya.transaction.Transactionable#unlock()
293: */
294: public void unlock() throws RepositoryException {
295: this .lock = null;
296: try {
297: getSession().removeLock(this );
298: } catch (TransactionException e) {
299: throw new RepositoryException(e);
300: }
301: }
302:
303: /**
304: * @see org.apache.lenya.transaction.Transactionable#isLocked()
305: */
306: public boolean isLocked() throws RepositoryException {
307: return this .lock != null;
308: }
309:
310: /**
311: * @see java.lang.Object#toString()
312: */
313: public String toString() {
314: return "node " + getSourceURI();
315: }
316:
317: /**
318: *
319: */
320: public Collection getChildren() throws RepositoryException {
321: SourceResolver resolver = null;
322: TraversableSource source = null;
323: try {
324: resolver = (SourceResolver) this .manager
325: .lookup(SourceResolver.ROLE);
326: source = (TraversableSource) resolver
327: .resolveURI(this .contentSource.getRealSourceUri());
328: Collection children = source.getChildren();
329: java.util.Iterator iterator = children.iterator();
330: java.util.Vector newChildren = new java.util.Vector();
331: while (iterator.hasNext()) {
332: TraversableSource child = (TraversableSource) iterator
333: .next();
334: newChildren.add(new SourceNode(getSession(),
335: getSourceURI() + "/" + child.getName(),
336: this .manager, getLogger()));
337: }
338: return newChildren;
339: } catch (Exception e) {
340: throw new RepositoryException(e);
341: }
342: }
343:
344: /**
345: *
346: */
347: public boolean isCollection() throws RepositoryException {
348: SourceResolver resolver = null;
349: TraversableSource source = null;
350: try {
351: resolver = (SourceResolver) this .manager
352: .lookup(SourceResolver.ROLE);
353: source = (TraversableSource) resolver
354: .resolveURI(this .contentSource.getRealSourceUri());
355: return source.isCollection();
356: } catch (Exception e) {
357: throw new RepositoryException(e);
358: }
359: }
360:
361: /**
362: * @see org.apache.lenya.cms.repository.Node#getSourceURI()
363: */
364: public String getSourceURI() {
365: return this .contentSource.getSourceUri();
366: }
367:
368: private Session session;
369:
370: /**
371: * @see org.apache.lenya.cms.repository.Node#getSession()
372: */
373: public Session getSession() {
374: return this .session;
375: }
376:
377: public void registerDirty() throws RepositoryException {
378: try {
379: if (!getSession().isDirty(this )) {
380: getSession().registerDirty(this );
381: enqueueEvent(DocumentEvent.CHANGED);
382: }
383: } catch (TransactionException e) {
384: throw new RepositoryException(e);
385: }
386: }
387:
388: protected void enqueueEvent(Object descriptor) {
389: RepositoryEvent event = RepositoryEventFactory.createEvent(
390: this .manager, this , getLogger(), descriptor);
391: getSession().enqueueEvent(event);
392: }
393:
394: public void registerRemoved() throws RepositoryException {
395: try {
396: getSession().registerRemoved(this );
397: enqueueEvent(DocumentEvent.REMOVED);
398: } catch (Exception e) {
399: throw new RepositoryException(e);
400: }
401: }
402:
403: private RCML rcml;
404: private Persistable persistable;
405:
406: protected synchronized RCML getRcml() {
407: if (this .rcml == null) {
408: SourceNodeRcmlFactory factory = SourceNodeRcmlFactory
409: .getInstance();
410: this .rcml = factory.getRcml(this , this .manager);
411: }
412: return this .rcml;
413: }
414:
415: public History getHistory() {
416: return new SourceNodeHistory(this , this .manager, getLogger());
417: }
418:
419: public MetaData getMetaData(String namespaceUri)
420: throws MetaDataException {
421: return this .metaSource.getMetaData(namespaceUri);
422: }
423:
424: public boolean exists() throws RepositoryException {
425: return this .contentSource.exists();
426: }
427:
428: public OutputStream getOutputStream() throws RepositoryException {
429: return this .contentSource.getOutputStream();
430: }
431:
432: public long getContentLength() throws RepositoryException {
433: return this .contentSource.getContentLength();
434: }
435:
436: public InputStream getInputStream() throws RepositoryException {
437: return this .contentSource.getInputStream();
438: }
439:
440: public long getLastModified() throws RepositoryException {
441:
442: if (!exists()) {
443: throw new RepositoryException("The node [" + this
444: + "] does not exist!");
445: }
446:
447: long contentLastModified = this .contentSource.getLastModified();
448: long metaLastModified = 0;
449: if (this .metaSource.exists()) {
450: metaLastModified = this .metaSource.getLastModified();
451: }
452:
453: return Math.max(contentLastModified, metaLastModified);
454: }
455:
456: public String getMimeType() throws RepositoryException {
457: return this .contentSource.getMimeType();
458: }
459:
460: public String[] getMetaDataNamespaceUris() throws MetaDataException {
461: return this .metaSource.getMetaDataNamespaceUris();
462: }
463:
464: public synchronized void saveTransactionable()
465: throws TransactionException {
466: if (!isCheckedOut()) {
467: throw new RepositoryException("Cannot save node ["
468: + getSourceURI() + "]: not checked out!");
469: }
470: this .contentSource.saveTransactionable();
471: this .metaSource.saveTransactionable();
472: }
473:
474: public void delete() throws RepositoryException {
475: this .contentSource.delete();
476: this .metaSource.delete();
477: registerRemoved();
478: }
479:
480: public String getCheckoutUserId() throws RepositoryException {
481: RCML rcml = getRcml();
482: synchronized (rcml) {
483: try {
484: if (!rcml.isCheckedOut()) {
485: throw new RepositoryException("The node [" + this
486: + "] is not checked out!");
487: }
488: return rcml.getLatestEntry().getIdentity();
489: } catch (RevisionControlException e) {
490: throw new RepositoryException(e);
491: }
492: }
493: }
494:
495: public void copyRevisionsFrom(Node source)
496: throws RepositoryException {
497: try {
498: boolean wasLocked = isLocked();
499: if (wasLocked) {
500: unlock();
501: }
502: getRcml().copyFrom(this , source);
503: if (wasLocked) {
504: // this is a hack: update the lock revision to the latest copied revision to avoid
505: // the "node has changed" error
506: this .lock = getSession().createLock(this ,
507: getCurrentRevisionNumber());
508: }
509: } catch (RevisionControlException e) {
510: throw new RepositoryException(e);
511: } catch (TransactionException e) {
512: throw new RepositoryException(e);
513: }
514: }
515:
516: public void rollback(int revisionNumber) throws RepositoryException {
517: try {
518: long time = getHistory().getRevision(revisionNumber)
519: .getTime();
520: getRcml().restoreBackup(this , time);
521: } catch (RevisionControlException e) {
522: throw new RepositoryException(e);
523: }
524: }
525:
526: public boolean isCheckedOutBySession() throws TransactionException {
527: return isCheckedOutBySession(getSession());
528: }
529:
530: public void setPersistable(Persistable item)
531: throws RepositoryException {
532: this .persistable = item;
533: }
534:
535: public Persistable getPersistable() {
536: return this.persistable;
537: }
538:
539: }
|