1 package org.modeshape.jcr;
2
3 import java.io.FileInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.Hashtable;
7 import java.util.concurrent.TimeUnit;
8 import javax.jcr.Repository;
9 import javax.jcr.RepositoryException;
10 import javax.naming.Context;
11 import javax.naming.Name;
12 import javax.naming.NamingException;
13 import javax.naming.RefAddr;
14 import javax.naming.Reference;
15 import javax.naming.event.EventContext;
16 import javax.naming.event.NamespaceChangeListener;
17 import javax.naming.event.NamingEvent;
18 import javax.naming.event.NamingExceptionEvent;
19 import javax.naming.spi.ObjectFactory;
20 import org.modeshape.common.collection.Problem;
21 import org.modeshape.common.collection.Problems;
22 import org.modeshape.common.util.Logger;
23 import org.xml.sax.SAXException;
24
25 /**
26 * The {@code JndiRepositoryFactory} class provides a means of initializing and accessing {@link Repository repositories} in a
27 * JNDI tree. <h2>Example JNDI Configurations</h2> <h3>Tomcat 5.5</h3>
28 * <p>
29 * The following configuration can be added to added to server.xml in Tomcat 5.5 to initialize a {@code JcrRepository} and add it
30 * to the JNDI tree.
31 *
32 * <pre>
33 * <GlobalNamingResources>
34 * <!-- Other configuration omitted -->
35 * <Resource name="jcr/local" auth="Container"
36 * type="javax.jcr.Repository"
37 * factory="org.modeshape.jcr.JndiRepositoryFactory"
38 * configFile="/tck/default/configRepository.xml"
39 * repositoryName="Test Repository Source"
40 * />
41 * </GlobalNamingResources>
42 * </pre>
43 *
44 * This will create a repository loaded from the or file "/tck/default/configRepository.xml" and return the JCR
45 * repository named "Test Repository Source". The name of the repository is important as a single configuration file may
46 * contain configuration information for many JCR repositories.
47 * </p>
48 */
49 public class JndiRepositoryFactory implements ObjectFactory {
50
51 private static final String CONFIG_FILE = "configFile";
52 private static final String REPOSITORY_NAME = "repositoryName";
53
54 private static JcrEngine engine;
55 protected static final Logger log = Logger.getLogger(JndiRepositoryFactory.class);
56
57 /**
58 * {@link JcrConfiguration#loadFrom(java.io.InputStream) Initializes} and {@link JcrEngine#start() starts} the {@code
59 * JcrEngine} managed by this factory.
60 *
61 * @param configFileName the name of the file containing the configuration information for the {@code JcrEngine}; may not be
62 * null. This method will first attempt to load this file as a resource from the classpath. If no resource with the
63 * given name exists, the name will be treated as a file name and loaded from the file system.
64 * @throws IOException if there is an error or problem reading the configuration resource at the supplied path
65 * @throws SAXException if the contents of the configuration resource are not valid XML
66 * @throws RepositoryException if the {@link JcrEngine#start() JcrEngine could not be started}
67 * @see JcrConfiguration#loadFrom(java.io.InputStream)
68 * @see Class#getResourceAsStream(String)
69 */
70 private static synchronized void initializeEngine( String configFileName )
71 throws IOException, SAXException, RepositoryException {
72 if (engine != null) return;
73
74 log.info(JcrI18n.engineStarting);
75 long start = System.currentTimeMillis();
76
77 JcrConfiguration config = new JcrConfiguration();
78 InputStream configStream = JndiRepositoryFactory.class.getResourceAsStream(configFileName);
79
80 if (configStream == null) {
81 try {
82 configStream = new FileInputStream(configFileName);
83 } catch (IOException ioe) {
84 throw new RepositoryException(ioe);
85 }
86 }
87
88 engine = config.loadFrom(configStream).build();
89 engine.start();
90
91 Problems problems = engine.getProblems();
92 for (Problem problem : problems) {
93 switch (problem.getStatus()) {
94 case ERROR:
95 log.error(problem.getThrowable(), problem.getMessage(), problem.getParameters());
96 break;
97 case WARNING:
98 log.warn(problem.getThrowable(), problem.getMessage(), problem.getParameters());
99 break;
100 case INFO:
101 log.info(problem.getThrowable(), problem.getMessage(), problem.getParameters());
102 break;
103 }
104 }
105
106 if (problems.hasErrors()) {
107 throw new RepositoryException(JcrI18n.couldNotStartEngine.text());
108 }
109 log.info(JcrI18n.engineStarted, (System.currentTimeMillis() - start));
110 }
111
112 /**
113 * Creates an {@code JcrRepository} using the reference information specified.
114 * <p>
115 * This method first attempts to convert the {@code obj} parameter into a {@link Reference reference to JNDI configuration
116 * information}. If that is successful, a {@link JcrEngine} will be created (if not previously created by a call to this
117 * method) and it will be configured from the resource or file at the location specified by the {@code configFile} key in the
118 * reference. After the configuration is successful, the {@link JcrEngine#getRepository(String) JcrEngine will be queried} for
119 * the repository with the name specified by the value of the @{code repositoryName} key in the reference.
120 * </p>
121 *
122 * @param obj the reference to the JNDI configuration information; must be a non-null instance of {@link Reference}
123 * @param name ignored
124 * @param nameCtx ignored
125 * @param environment ignored
126 * @return the repository; never null
127 * @throws IOException if there is an error or problem reading the configuration resource at the supplied path
128 * @throws SAXException if the contents of the configuration resource are not valid XML
129 * @throws NamingException if there is an error registering the namespace listener
130 * @throws RepositoryException if the {@link JcrEngine#start() JcrEngine could not be started}, the named repository does not
131 * exist in the given configuration resource, or the named repository could not be created
132 */
133 public JcrRepository getObjectInstance( Object obj,
134 Name name,
135 Context nameCtx,
136 Hashtable<?, ?> environment )
137 throws IOException, SAXException, RepositoryException, NamingException {
138 if (!(obj instanceof Reference)) return null;
139
140 Reference ref = (Reference)obj;
141
142 if (engine == null) {
143 RefAddr configFile = ref.get(CONFIG_FILE);
144 assert configFile != null;
145
146 initializeEngine(configFile.getContent().toString());
147
148 if (nameCtx instanceof EventContext) {
149 EventContext evtCtx = (EventContext)nameCtx;
150
151 NamespaceChangeListener listener = new NamespaceChangeListener() {
152
153 public void namingExceptionThrown( NamingExceptionEvent evt ) {
154 evt.getException().printStackTrace();
155 }
156
157 public void objectAdded( NamingEvent evt ) {
158 }
159
160 public void objectRemoved( NamingEvent evt ) {
161 Object oldObject = evt.getOldBinding().getObject();
162 if (!(oldObject instanceof JcrEngine)) return;
163
164 JcrEngine engine = (JcrEngine)oldObject;
165
166 log.info(JcrI18n.engineStopping);
167 long start = System.currentTimeMillis();
168 engine.shutdown();
169 try {
170 engine.awaitTermination(30, TimeUnit.SECONDS);
171 log.info(JcrI18n.engineStopped, (System.currentTimeMillis() - start));
172 } catch (InterruptedException ie) {
173 // Thread.interrupted();
174 }
175 }
176
177 public void objectRenamed( NamingEvent evt ) {
178 }
179
180 };
181
182 evtCtx.addNamingListener(name, EventContext.OBJECT_SCOPE, listener);
183 }
184 }
185
186 assert engine != null;
187
188 RefAddr repositoryName = ref.get(REPOSITORY_NAME);
189 assert repositoryName != null;
190
191 return engine.getRepository(repositoryName.getContent().toString());
192 }
193
194 }