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: package org.apache.cocoon.jcr.source;
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.ByteArrayOutputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.util.ArrayList;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.GregorianCalendar;
028:
029: import javax.jcr.Item;
030: import javax.jcr.Node;
031: import javax.jcr.NodeIterator;
032: import javax.jcr.PathNotFoundException;
033: import javax.jcr.Property;
034: import javax.jcr.RepositoryException;
035: import javax.jcr.Session;
036:
037: import org.apache.cocoon.CascadingIOException;
038: import org.apache.excalibur.source.ModifiableTraversableSource;
039: import org.apache.excalibur.source.Source;
040: import org.apache.excalibur.source.SourceException;
041: import org.apache.excalibur.source.SourceNotFoundException;
042: import org.apache.excalibur.source.SourceValidity;
043: import org.apache.excalibur.source.TraversableSource;
044:
045: /**
046: * A Source for a JCR node.
047: *
048: * @version $Id: JCRNodeSource.java 449153 2006-09-23 04:27:50Z crossley $
049: */
050: public class JCRNodeSource implements Source, TraversableSource,
051: ModifiableTraversableSource {
052:
053: /** The full URI */
054: protected String computedURI;
055:
056: /** The node path */
057: protected final String path;
058:
059: /** The factory that created this Source */
060: protected final JCRSourceFactory factory;
061:
062: /** The session this source is bound to */
063: protected final Session session;
064:
065: /** The node pointed to by this source (can be null) */
066: protected Node node;
067:
068: public JCRNodeSource(JCRSourceFactory factory, Session session,
069: String path) throws SourceException {
070: this .factory = factory;
071: this .session = session;
072: this .path = path;
073:
074: try {
075: Item item = session.getItem(path);
076: if (!item.isNode()) {
077: throw new SourceException("Path '" + path
078: + "' is a property (should be a node)");
079: } else {
080: this .node = (Node) item;
081: }
082: } catch (PathNotFoundException e) {
083: // Not found
084: this .node = null;
085: } catch (RepositoryException e) {
086: throw new SourceException("Cannot lookup repository path "
087: + path, e);
088: }
089: }
090:
091: public JCRNodeSource(JCRSourceFactory factory, Node node)
092: throws SourceException {
093: this .factory = factory;
094: this .node = node;
095:
096: try {
097: this .session = node.getSession();
098: this .path = node.getPath();
099: } catch (RepositoryException e) {
100: throw new SourceException("Cannot get node's informations",
101: e);
102: }
103: }
104:
105: public JCRNodeSource(JCRNodeSource parent, Node node)
106: throws SourceException {
107: this .factory = parent.factory;
108: this .session = parent.session;
109: this .node = node;
110:
111: try {
112: this .path = getChildPath(parent.path, node.getName());
113:
114: } catch (RepositoryException e) {
115: throw new SourceException("Cannot get name of child of "
116: + parent.getURI(), e);
117: }
118: }
119:
120: private String getChildPath(String path, String name) {
121: StringBuffer pathBuf = new StringBuffer(path);
122: // Append '/' only if the parent isn't the root (it's path is "/" in
123: // that case)
124: if (pathBuf.length() > 1)
125: pathBuf.append('/');
126: pathBuf.append(name);
127: return pathBuf.toString();
128: }
129:
130: /**
131: * Returns the JCR <code>Node</code> this source points to, or
132: * <code>null</code> if it denotes a non-existing path.
133: *
134: * @return the JCR node.
135: */
136: public Node getNode() {
137: return this .node;
138: }
139:
140: /**
141: * Returns the path within the repository this source points to.
142: *
143: * @return the path
144: */
145: public String getPath() {
146: return this .path;
147: }
148:
149: /**
150: * Returns the JCR <code>Session</code> used by this source.
151: *
152: * @return the JCR session.
153: */
154: public Session getSession() {
155: return this .session;
156: }
157:
158: /**
159: * Returns the JCR <code>Node</code> used to store the content of this
160: * source.
161: *
162: * @return the JCR content node, or <code>null</code> if no such node
163: * exist, either because the source is a collection or doesn't
164: * currently contain data.
165: */
166: public Node getContentNode() {
167: if (this .node == null) {
168: return null;
169: }
170:
171: if (isCollection()) {
172: return null;
173: }
174:
175: try {
176: return this .factory.getContentNode(this .node);
177: } catch (RepositoryException e) {
178: return null;
179: }
180: }
181:
182: // =============================================================================================
183: // Source interface
184: // =============================================================================================
185:
186: /*
187: * (non-Javadoc)
188: *
189: * @see org.apache.excalibur.source.Source#exists()
190: */
191: public boolean exists() {
192: return this .node != null;
193: }
194:
195: /*
196: * (non-Javadoc)
197: *
198: * @see org.apache.excalibur.source.Source#getInputStream()
199: */
200: public InputStream getInputStream() throws IOException,
201: SourceNotFoundException {
202: if (this .node == null) {
203: throw new SourceNotFoundException("Path '" + this .getURI()
204: + "' does not exist");
205: }
206:
207: if (this .isCollection()) {
208: throw new SourceException("Path '" + this .getURI()
209: + "' is a collection");
210: }
211:
212: try {
213: Property contentProp = this .factory
214: .getContentProperty(this .node);
215: return contentProp.getStream();
216: } catch (Exception e) {
217: throw new SourceException("Error opening stream for '"
218: + this .getURI() + "'", e);
219: }
220: }
221:
222: /*
223: * (non-Javadoc)
224: *
225: * @see org.apache.excalibur.source.Source#getURI()
226: */
227: public String getURI() {
228: if (this .computedURI == null) {
229: this .computedURI = this .factory.getScheme() + ":/"
230: + this .path;
231: }
232: return this .computedURI;
233: }
234:
235: /*
236: * (non-Javadoc)
237: *
238: * @see org.apache.excalibur.source.Source#getScheme()
239: */
240: public String getScheme() {
241: return this .factory.getScheme();
242: }
243:
244: /*
245: * (non-Javadoc)
246: *
247: * @see org.apache.excalibur.source.Source#getValidity()
248: */
249: public SourceValidity getValidity() {
250: if (!exists()) {
251: return null;
252: }
253: try {
254: Property prop = this .factory.getValidityProperty(this .node);
255: return prop == null ? null : new JCRNodeSourceValidity(prop
256: .getValue());
257: } catch (RepositoryException re) {
258: return null;
259: }
260: }
261:
262: /*
263: * (non-Javadoc)
264: *
265: * @see org.apache.excalibur.source.Source#refresh()
266: */
267: public void refresh() {
268: // nothing to do here
269: }
270:
271: /*
272: * (non-Javadoc)
273: *
274: * @see org.apache.excalibur.source.Source#getMimeType()
275: */
276: public String getMimeType() {
277: if (!exists()) {
278: return null;
279: }
280: try {
281: Property prop = this .factory.getMimeTypeProperty(this .node);
282: if (prop == null) {
283: return null;
284: } else {
285: String value = prop.getString();
286: return value.length() == 0 ? null : value;
287: }
288: } catch (RepositoryException re) {
289: return null;
290: }
291: }
292:
293: /*
294: * (non-Javadoc)
295: *
296: * @see org.apache.excalibur.source.Source#getContentLength()
297: */
298: public long getContentLength() {
299: if (!exists()) {
300: return -1;
301: }
302: try {
303: Property prop = this .factory.getContentProperty(this .node);
304: return prop == null ? -1 : prop.getLength();
305: } catch (RepositoryException re) {
306: return -1;
307: }
308: }
309:
310: /*
311: * (non-Javadoc)
312: *
313: * @see org.apache.excalibur.source.Source#getLastModified()
314: */
315: public long getLastModified() {
316: if (!exists()) {
317: return 0;
318: }
319: try {
320: Property prop = this .factory
321: .getLastModifiedDateProperty(this .node);
322: return prop == null ? 0 : prop.getDate().getTime()
323: .getTime();
324: } catch (RepositoryException re) {
325: return 0;
326: }
327: }
328:
329: // =============================================================================================
330: // TraversableSource interface
331: // =============================================================================================
332:
333: public boolean isCollection() {
334: if (!exists())
335: return false;
336:
337: try {
338: return this .factory.isCollection(this .node);
339: } catch (RepositoryException e) {
340: return false;
341: }
342: }
343:
344: public Collection getChildren() throws SourceException {
345: if (!isCollection()) {
346: return Collections.EMPTY_LIST;
347: } else {
348: ArrayList children = new ArrayList();
349:
350: NodeIterator nodes;
351: try {
352: nodes = this .node.getNodes();
353: } catch (RepositoryException e) {
354: throw new SourceException("Cannot get child nodes for "
355: + getURI(), e);
356: }
357:
358: while (nodes.hasNext()) {
359: children.add(this .factory.createSource(this , nodes
360: .nextNode()));
361: }
362: return children;
363: }
364: }
365:
366: public Source getChild(String name) throws SourceException {
367: if (this .isCollection()) {
368: return this .factory.createSource(this .session,
369: getChildPath(this .path, name));
370: } else {
371: throw new SourceException("Not a collection: " + getURI());
372: }
373: }
374:
375: public String getName() {
376: return this .path.substring(this .path.lastIndexOf('/') + 1);
377: }
378:
379: public Source getParent() throws SourceException {
380: if (this .path.length() == 1) {
381: // Root
382: return null;
383: }
384:
385: int lastPos = this .path.lastIndexOf('/');
386: String parentPath = lastPos == 0 ? "/" : this .path.substring(0,
387: lastPos);
388: return this .factory.createSource(this .session, parentPath);
389: }
390:
391: // =============================================================================================
392: // ModifiableTraversableSource interface
393: // =============================================================================================
394:
395: public OutputStream getOutputStream() throws IOException {
396: if (isCollection()) {
397: throw new SourceException("Cannot write to collection "
398: + this .getURI());
399: }
400:
401: try {
402: Node contentNode;
403: if (!exists()) {
404: JCRNodeSource parent = (JCRNodeSource) getParent();
405:
406: // Create the path if it doesn't exist
407: parent.makeCollection();
408:
409: // Create our node
410: this .node = this .factory.createFileNode(parent.node,
411: getName());
412: contentNode = this .factory.createContentNode(this .node);
413: } else {
414: contentNode = this .factory.getContentNode(this .node);
415: }
416:
417: return new JCRSourceOutputStream(contentNode);
418: } catch (RepositoryException e) {
419: throw new SourceException("Cannot create content node for "
420: + getURI(), e);
421: }
422: }
423:
424: public void delete() throws SourceException {
425: if (exists()) {
426: try {
427: this .node.remove();
428: this .node = null;
429: this .session.save();
430: } catch (RepositoryException e) {
431: throw new SourceException("Cannot delete " + getURI(),
432: e);
433: }
434: }
435: }
436:
437: public boolean canCancel(OutputStream os) {
438: if (os instanceof JCRSourceOutputStream) {
439: return ((JCRSourceOutputStream) os).canCancel();
440: } else {
441: return false;
442: }
443: }
444:
445: public void cancel(OutputStream os) throws IOException {
446: if (canCancel(os)) {
447: ((JCRSourceOutputStream) os).cancel();
448: } else {
449: throw new IllegalArgumentException(
450: "Stream cannot be cancelled");
451: }
452: }
453:
454: public void makeCollection() throws SourceException {
455: if (exists()) {
456: if (!isCollection()) {
457: throw new SourceException(
458: "Cannot make a collection with existing node at "
459: + getURI());
460: }
461: } else {
462: try {
463: // Ensure parent exists
464: JCRNodeSource parent = (JCRNodeSource) getParent();
465: if (parent == null) {
466: throw new RuntimeException(
467: "Problem: root node does not exist!!");
468: }
469: parent.makeCollection();
470: Node parentNode = parent.node;
471:
472: String typeName = this .factory
473: .getFolderNodeType(parentNode);
474:
475: this .node = parentNode.addNode(getName(), typeName);
476: this .session.save();
477:
478: } catch (RepositoryException e) {
479: throw new SourceException("Cannot make collection "
480: + this .getURI(), e);
481: }
482: }
483: }
484:
485: // ----------------------------------------------------------------------------------
486: // Private helper class for ModifiableSource implementation
487: // ----------------------------------------------------------------------------------
488:
489: /**
490: * An outputStream that will save the session upon close, and discard it
491: * upon cancel.
492: */
493: private class JCRSourceOutputStream extends ByteArrayOutputStream {
494: private boolean isClosed = false;
495:
496: private final Node contentNode;
497:
498: public JCRSourceOutputStream(Node contentNode) {
499: this .contentNode = contentNode;
500: }
501:
502: public void close() throws IOException {
503: if (!isClosed) {
504: super .close();
505: this .isClosed = true;
506: try {
507: JCRSourceFactory.ContentTypeInfo info = (JCRSourceFactory.ContentTypeInfo) factory
508: .getTypeInfo(contentNode);
509: contentNode
510: .setProperty(info.contentProp,
511: new ByteArrayInputStream(this
512: .toByteArray()));
513: if (info.lastModifiedProp != null) {
514: contentNode.setProperty(info.lastModifiedProp,
515: new GregorianCalendar());
516: }
517: if (info.mimeTypeProp != null) {
518: // FIXME: define mime type
519: contentNode.setProperty(info.mimeTypeProp, "");
520: }
521:
522: JCRNodeSource.this .session.save();
523: } catch (RepositoryException e) {
524: throw new CascadingIOException(
525: "Cannot save content to " + getURI(), e);
526: }
527: }
528: }
529:
530: public boolean canCancel() {
531: return !isClosed;
532: }
533:
534: public void cancel() throws IOException {
535: if (isClosed) {
536: throw new IllegalStateException(
537: "Cannot cancel : outputstrem is already closed");
538: }
539: }
540: }
541: }
|