KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > jcr > source > JCRSourceFactory


1 /*
2  * Copyright 2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.jcr.source;
17
18 import java.io.IOException JavaDoc;
19 import java.net.MalformedURLException JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.Map JavaDoc;
22
23 import javax.jcr.LoginException;
24 import javax.jcr.Node;
25 import javax.jcr.Property;
26 import javax.jcr.Repository;
27 import javax.jcr.RepositoryException;
28 import javax.jcr.Session;
29
30 import org.apache.avalon.framework.CascadingRuntimeException;
31 import org.apache.avalon.framework.configuration.Configurable;
32 import org.apache.avalon.framework.configuration.Configuration;
33 import org.apache.avalon.framework.configuration.ConfigurationException;
34 import org.apache.avalon.framework.service.ServiceException;
35 import org.apache.avalon.framework.service.ServiceManager;
36 import org.apache.avalon.framework.service.Serviceable;
37 import org.apache.avalon.framework.thread.ThreadSafe;
38 import org.apache.excalibur.source.Source;
39 import org.apache.excalibur.source.SourceException;
40 import org.apache.excalibur.source.SourceFactory;
41 import org.apache.excalibur.source.SourceUtil;
42
43 /**
44  * JCRSourceFactory is an implementation of
45  * <code>ModifiableTraversableSource</code> on top of a JCR (aka <a
46  * HREF="http://www.jcp.org/en/jsr/detail?id=170">JSR-170</a>) repository.
47  * <p>
48  * Since JCR allows a repository to define its own node types, it is necessary
49  * to configure this source factory with a description of what node types map to
50  * "files" and "folders" and the properties used to store source-related data.
51  * <p>
52  * A typical configuration for a naked Jackrabbit repository is as follows:
53  *
54  * <pre>
55  *
56  * &lt;source-factories&gt;
57  * &lt;component-instance class=&quot;org.apache.cocoon.jcr.source.JCRSourceFactory&quot; name=&quot;jcr&quot;&gt;
58  * &lt;folder-node type=&quot;rep:root&quot; new-file=&quot;nt:file&quot; new-folder=&quot;nt:folder&quot;/&gt;
59  * &lt;folder-node type=&quot;nt:folder&quot; new-file=&quot;nt:file&quot;/&gt;
60  * &lt;file-node type=&quot;nt:file&quot; content-path=&quot;jcr:content&quot; content-type=&quot;nt:resource&quot;/&gt;
61  * &lt;file-node type=&quot;nt:linkedFile&quot; content-ref=&quot;jcr:content&quot;/&gt;
62  * &lt;content-node type=&quot;nt:resource&quot;
63  * content-prop=&quot;jcr:data&quot;
64  * mimetype-prop=&quot;jcr:mimeType&quot;
65  * lastmodified-prop=&quot;jcr:lastModified&quot;
66  * validity-prop=&quot;jcr:lastModified&quot;/&gt;
67  * &lt;/component-instance&gt;
68  * &lt;/source-factories&gt;
69  *
70  * </pre>
71  *
72  * A <code>&lt;folder-node&gt;</code> defines a node type that is mapped to a
73  * non-terminal source (i.e. that can have children). The <code>new-file</code>
74  * and <code>new-folder</code> attributes respectively define what node types
75  * should be used to create a new terminal and non-terminal source.
76  * <p>
77  * A <code>&lt;file-node&gt;</code> defines a note type that is mapped to a
78  * terminal source (i.e. that can have some content). The
79  * <code>content-path</code> attribute defines the path to the node's child
80  * that actually holds the content, and <code>content-type</code> defines the
81  * type of this content node.
82  * <p>
83  * The <code>content-ref</code> attribute is used to comply with JCR's
84  * <code>nt:linkedFile</code> definition where the content node is not a
85  * direct child of the file node, but is referenced by a property of this file
86  * node. Such node types are read-only as there's no way to indicate where the
87  * referenced content node should be created.
88  * <p>
89  * A <code>&lt;content-node&gt;</code> defines a node type that actually holds
90  * the content of a <code>file-node</code>. The <code>content-prop</code>
91  * attribute must be present and gives the name of the node's binary property
92  * that will hold the actual content. Other attributes are optional:
93  * <ul>
94  * <li><code>mimetype-prop</code> defines a string property holding the
95  * content's MIME type, </li>
96  * <li><code>lastmodified-prop</code> defines a date property holding the
97  * node's last modification date. It is automatically updated when content is
98  * written to the <code>content-node</code>. </li>
99  * <li><code>validity-prop</code> defines a property that gives the validity
100  * of the content, used by Cocoon's cache. If not specified,
101  * <code>lastmodified-prop</code> is used, if present. Otherwise the source
102  * has no validity and won't be cacheable. </li>
103  * </ul>
104  * <p>
105  * The format of URIs for this source is a path in the repository, and it is
106  * therefore currently limited to repository traversal. Further work will add
107  * the ability to specify query strings.
108  *
109  * @version $Id: JCRSourceFactory.java 240329 2005-08-26 20:04:15Z vgritsenko $
110  */

111 public class JCRSourceFactory implements ThreadSafe, SourceFactory, Configurable, Serviceable {
112
113     protected static class NodeTypeInfo {
114         // Empty base class
115
}
116
117     protected static class FolderTypeInfo extends NodeTypeInfo {
118         public String JavaDoc newFileType;
119
120         public String JavaDoc newFolderType;
121     }
122
123     protected static class FileTypeInfo extends NodeTypeInfo {
124         public String JavaDoc contentPath;
125
126         public String JavaDoc contentType;
127
128         public String JavaDoc contentRef;
129     }
130
131     protected static class ContentTypeInfo extends NodeTypeInfo {
132         public String JavaDoc contentProp;
133
134         public String JavaDoc mimeTypeProp;
135
136         public String JavaDoc lastModifiedProp;
137
138         public String JavaDoc validityProp;
139     }
140
141     /**
142      * The repository we use
143      */

144     protected Repository repo;
145
146     /**
147      * Scheme, lazily computed at the first call to getSource()
148      */

149     protected String JavaDoc scheme;
150
151     /**
152      * The NodeTypeInfo for each of the types described in the configuration
153      */

154     protected Map JavaDoc typeInfos;
155
156     protected ServiceManager manager;
157
158     public void service(ServiceManager manager) throws ServiceException {
159         this.manager = manager;
160         // this.repo is lazily initialized to avoid a circular dependency between SourceResolver
161
// and JackrabbitRepository that leads to a StackOverflowError at initialization time
162
}
163
164     public void configure(Configuration config) throws ConfigurationException {
165         this.typeInfos = new HashMap JavaDoc();
166
167         Configuration[] children = config.getChildren();
168
169         for (int i = 0; i < children.length; i++) {
170             Configuration child = children[i];
171             String JavaDoc name = child.getName();
172
173             if ("folder-node".equals(name)) {
174                 FolderTypeInfo info = new FolderTypeInfo();
175                 String JavaDoc type = child.getAttribute("type");
176                 info.newFileType = child.getAttribute("new-file");
177                 info.newFolderType = child.getAttribute("new-folder", type);
178
179                 this.typeInfos.put(type, info);
180
181             } else if ("file-node".equals(name)) {
182                 FileTypeInfo info = new FileTypeInfo();
183                 info.contentPath = child.getAttribute("content-path", null);
184                 info.contentType = child.getAttribute("content-type", null);
185                 info.contentRef = child.getAttribute("content-ref", null);
186                 if (info.contentPath == null && info.contentRef == null) {
187                     throw new ConfigurationException("One of content-path or content-ref is required at " + child.getLocation());
188                 }
189                 if (info.contentPath != null && info.contentType == null) {
190                     throw new ConfigurationException("content-type must be present with content-path at " + child.getLocation());
191                 }
192                 this.typeInfos.put(child.getAttribute("type"), info);
193
194             } else if ("content-node".equals(name)) {
195                 ContentTypeInfo info = new ContentTypeInfo();
196                 info.contentProp = child.getAttribute("content-prop");
197                 info.lastModifiedProp = child.getAttribute("lastmodified-prop", null);
198                 info.mimeTypeProp = child.getAttribute("mimetype-prop", null);
199                 info.validityProp = child.getAttribute("validity-prop", info.lastModifiedProp);
200                 this.typeInfos.put(child.getAttribute("type"), info);
201
202             } else {
203                 throw new ConfigurationException("Unknown configuration " + name + " at " + child.getLocation());
204             }
205         }
206
207     }
208
209     protected void lazyInit() {
210         if (this.repo == null) {
211             try {
212                 this.repo = (Repository)manager.lookup(Repository.class.getName());
213             } catch (Exception JavaDoc e) {
214                 throw new CascadingRuntimeException("Cannot lookup repository", e);
215             }
216         }
217     }
218
219     /*
220      * (non-Javadoc)
221      *
222      * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String,
223      * java.util.Map)
224      */

225     public Source getSource(String JavaDoc uri, Map JavaDoc parameters) throws IOException JavaDoc, MalformedURLException JavaDoc {
226         lazyInit();
227
228         if (this.scheme == null) {
229             this.scheme = SourceUtil.getScheme(uri);
230         }
231
232         Session session;
233         try {
234             // TODO: accept a different workspace?
235
session = repo.login();
236         } catch (LoginException e) {
237             throw new SourceException("Login to repository failed", e);
238         } catch (RepositoryException e) {
239             throw new SourceException("Cannot access repository", e);
240         }
241
242         // Compute the path
243
String JavaDoc path = SourceUtil.getSpecificPart(uri);
244         if (!path.startsWith("//")) {
245             throw new MalformedURLException JavaDoc("Expecting " + this.scheme + "://path and got " + uri);
246         }
247         // Remove first '/'
248
path = path.substring(1);
249         int pathLen = path.length();
250         if (pathLen > 1) {
251             // Not root: ensure there's no trailing '/'
252
if (path.charAt(pathLen - 1) == '/') {
253                 path = path.substring(0, pathLen - 1);
254             }
255         }
256
257         return createSource(session, path);
258     }
259
260     /*
261      * (non-Javadoc)
262      *
263      * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
264      */

265     public void release(Source source) {
266         // nothing
267
}
268
269     public String JavaDoc getScheme() {
270         return this.scheme;
271     }
272
273     /**
274      * Get the type info for a node.
275      *
276      * @param node the node
277      * @return the type info
278      * @throws RepositoryException if node type couldn't be accessed or if no type info is found
279      */

280     public NodeTypeInfo getTypeInfo(Node node) throws RepositoryException {
281         String JavaDoc typeName = node.getPrimaryNodeType().getName();
282         NodeTypeInfo result = (NodeTypeInfo) this.typeInfos.get(typeName);
283         if (result == null) {
284             // TODO: build a NodeTypeInfo using introspection
285
throw new RepositoryException("No type info found for node type '" + typeName + "' at " + node.getPath());
286         }
287
288         return result;
289     }
290
291     /**
292      * Get the type info for a given node type name.
293      * @param typeName the type name
294      * @return the type info
295      * @throws RepositoryException if no type info is found
296      */

297     public NodeTypeInfo getTypeInfo(String JavaDoc typeName) throws RepositoryException {
298         NodeTypeInfo result = (NodeTypeInfo) this.typeInfos.get(typeName);
299         if (result == null) {
300             // TODO: build a NodeTypeInfo using introspection
301
throw new RepositoryException("No type info found for node type '" + typeName + "'");
302         }
303
304         return result;
305     }
306
307     /**
308      * Get the content node for a given node
309      *
310      * @param node the node for which we want the content node
311      * @return the content node
312      * @throws RepositoryException if some error occurs, or if the given node isn't a file node or a content node
313      */

314     public Node getContentNode(Node node) throws RepositoryException {
315         NodeTypeInfo info = getTypeInfo(node);
316
317         if (info instanceof ContentTypeInfo) {
318             return node;
319
320         } else if (info instanceof FileTypeInfo) {
321             FileTypeInfo finfo = (FileTypeInfo) info;
322             if (".".equals(finfo.contentPath)) {
323                 return node;
324             } else if (finfo.contentPath != null) {
325                 return node.getNode(finfo.contentPath);
326             } else {
327                 Property ref = node.getProperty(finfo.contentRef);
328                 return getContentNode(ref.getNode());
329             }
330         } else {
331             // A folder
332
throw new RepositoryException("Can't get content node for folder node at " + node.getPath());
333         }
334     }
335
336     /**
337      * Creates a new source given its parent and its node
338      *
339      * @param parent the parent
340      * @param node the node
341      * @return a new source
342      * @throws SourceException
343      */

344     public JCRNodeSource createSource(JCRNodeSource parent, Node node) throws SourceException {
345         return new JCRNodeSource(parent, node);
346     }
347
348     /**
349      * Creates a new source given a session and a path
350      *
351      * @param session the session
352      * @param path the absolute path
353      * @return a new source
354      * @throws SourceException
355      */

356     public JCRNodeSource createSource(Session session, String JavaDoc path) throws SourceException {
357         return new JCRNodeSource(this, session, path);
358     }
359
360     /**
361      * Create a child file node in a folder node.
362      *
363      * @param folderNode the folder node
364      * @param name the child's name
365      * @return the newly created child node
366      * @throws RepositoryException if some error occurs
367      */

368     public Node createFileNode(Node folderNode, String JavaDoc name) throws RepositoryException {
369         NodeTypeInfo info = getTypeInfo(folderNode);
370         if (!(info instanceof FolderTypeInfo)) {
371             throw new RepositoryException("Node type " + folderNode.getPrimaryNodeType().getName() + " is not a folder type");
372         }
373
374         FolderTypeInfo folderInfo = (FolderTypeInfo) info;
375         return folderNode.addNode(name, folderInfo.newFileType);
376     }
377
378     /**
379      * Create the content node for a file node.
380      *
381      * @param fileNode the file node
382      * @return the content node for this file node
383      * @throws RepositoryException if some error occurs
384      */

385     public Node createContentNode(Node fileNode) throws RepositoryException {
386
387         NodeTypeInfo info = getTypeInfo(fileNode);
388         if (!(info instanceof FileTypeInfo)) {
389             throw new RepositoryException("Node type " + fileNode.getPrimaryNodeType().getName() + " is not a file type");
390         }
391
392         FileTypeInfo fileInfo = (FileTypeInfo) info;
393         Node contentNode = fileNode.addNode(fileInfo.contentPath, fileInfo.contentType);
394
395         return contentNode;
396     }
397
398     /**
399      * Get the content property for a given node
400      *
401      * @param node a file or content node
402      * @return the content property
403      * @throws RepositoryException if some error occurs
404      */

405     public Property getContentProperty(Node node) throws RepositoryException {
406         Node contentNode = getContentNode(node);
407         ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
408         return contentNode.getProperty(info.contentProp);
409     }
410
411     /**
412      * Get the mime-type property for a given node
413      *
414      * @param node a file or content node
415      * @return the mime-type property, or <code>null</code> if no such property exists
416      * @throws RepositoryException if some error occurs
417      */

418     public Property getMimeTypeProperty(Node node) throws RepositoryException {
419         Node contentNode = getContentNode(node);
420         ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
421
422         String JavaDoc propName = info.mimeTypeProp;
423         if (propName != null && contentNode.hasProperty(propName)) {
424             return contentNode.getProperty(propName);
425         } else {
426             return null;
427         }
428     }
429
430     /**
431      * Get the lastmodified property for a given node
432      *
433      * @param node a file or content node
434      * @return the lastmodified property, or <code>null</code> if no such property exists
435      * @throws RepositoryException if some error occurs
436      */

437     public Property getLastModifiedDateProperty(Node node) throws RepositoryException {
438         Node contentNode = getContentNode(node);
439         ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
440
441         String JavaDoc propName = info.lastModifiedProp;
442         if (propName != null && contentNode.hasProperty(propName)) {
443             return contentNode.getProperty(propName);
444         } else {
445             return null;
446         }
447     }
448
449     /**
450      * Get the validity property for a given node
451      *
452      * @param node a file or content node
453      * @return the validity property, or <code>null</code> if no such property exists
454      * @throws RepositoryException if some error occurs
455      */

456     public Property getValidityProperty(Node node) throws RepositoryException {
457         Node contentNode = getContentNode(node);
458         ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
459
460         String JavaDoc propName = info.validityProp;
461         if (propName != null && contentNode.hasProperty(propName)) {
462             return contentNode.getProperty(propName);
463         } else {
464             return null;
465         }
466     }
467
468     /**
469      * Does a node represent a collection (i.e. folder-node)?
470      *
471      * @param node the node
472      * @return <code>true</code> if it's a collection
473      * @throws RepositoryException if some error occurs
474      */

475     public boolean isCollection(Node node) throws RepositoryException {
476         return getTypeInfo(node) instanceof FolderTypeInfo;
477     }
478
479     /**
480      * Get the node type to create a new subfolder of a given folder node.
481      *
482      * @param folderNode
483      * @return the child folder node type
484      * @throws RepositoryException if some error occurs
485      */

486     public String JavaDoc getFolderNodeType(Node folderNode) throws RepositoryException {
487         FolderTypeInfo info = (FolderTypeInfo) getTypeInfo(folderNode);
488         return info.newFolderType;
489     }
490 }
491
Popular Tags