View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * Unless otherwise indicated, all code in ModeShape is licensed
10   * to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.concurrent.ScheduledExecutorService;
36  import java.util.concurrent.ScheduledThreadPoolExecutor;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.locks.Lock;
39  import java.util.concurrent.locks.ReentrantLock;
40  import javax.jcr.Repository;
41  import javax.jcr.RepositoryException;
42  import net.jcip.annotations.ThreadSafe;
43  import org.modeshape.cnd.CndImporter;
44  import org.modeshape.common.util.CheckArg;
45  import org.modeshape.common.util.Logger;
46  import org.modeshape.graph.ExecutionContext;
47  import org.modeshape.graph.Graph;
48  import org.modeshape.graph.Location;
49  import org.modeshape.graph.Node;
50  import org.modeshape.graph.Subgraph;
51  import org.modeshape.graph.connector.RepositoryConnectionFactory;
52  import org.modeshape.graph.connector.RepositorySource;
53  import org.modeshape.graph.connector.RepositorySourceCapabilities;
54  import org.modeshape.graph.io.GraphBatchDestination;
55  import org.modeshape.graph.property.Name;
56  import org.modeshape.graph.property.Path;
57  import org.modeshape.graph.property.PathFactory;
58  import org.modeshape.graph.property.PathNotFoundException;
59  import org.modeshape.graph.property.Property;
60  import org.modeshape.graph.property.basic.GraphNamespaceRegistry;
61  import org.modeshape.jcr.JcrRepository.Option;
62  import org.modeshape.repository.ModeShapeConfiguration;
63  import org.modeshape.repository.ModeShapeEngine;
64  
65  /**
66   * The basic component that encapsulates the ModeShape services, including the {@link Repository} instances.
67   */
68  @ThreadSafe
69  public class JcrEngine extends ModeShapeEngine {
70  
71      final static int LOCK_SWEEP_INTERVAL_IN_MILLIS = 30000;
72      final static int LOCK_EXTENSION_INTERVAL_IN_MILLIS = LOCK_SWEEP_INTERVAL_IN_MILLIS * 2;
73  
74      private static final Logger log = Logger.getLogger(ModeShapeEngine.class);
75  
76      private final Map<String, JcrRepository> repositories;
77      private final Lock repositoriesLock;
78  
79      /**
80       * Provides the ability to schedule lock clean-up
81       */
82      private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(2);
83  
84      JcrEngine( ExecutionContext context,
85                 ModeShapeConfiguration.ConfigurationDefinition configuration ) {
86          super(context, configuration);
87          this.repositories = new HashMap<String, JcrRepository>();
88          this.repositoriesLock = new ReentrantLock();
89      }
90  
91      /**
92       * Clean up session-scoped locks created by session that are no longer active by iterating over the {@link JcrRepository
93       * repositories} and calling their {@link JcrRepository#cleanUpLocks() clean-up method}.
94       * <p>
95       * It should not be possible for a session to be terminated without cleaning up its locks, but this method will help clean-up
96       * dangling locks should a session terminate abnormally.
97       * </p>
98       */
99      void cleanUpLocks() {
100         Collection<JcrRepository> repos;
101 
102         try {
103             // Make a copy of the repositories to minimize the time that the lock needs to be held
104             repositoriesLock.lock();
105             repos = new ArrayList<JcrRepository>(repositories.values());
106         } finally {
107             repositoriesLock.unlock();
108         }
109 
110         for (JcrRepository repository : repos) {
111             try {
112                 repository.cleanUpLocks();
113             } catch (Throwable t) {
114                 log.error(t, JcrI18n.errorCleaningUpLocks, repository.getRepositorySourceName());
115             }
116         }
117     }
118 
119     @Override
120     public void shutdown() {
121         scheduler.shutdown();
122         super.shutdown();
123 
124         try {
125             this.repositoriesLock.lock();
126             // Shut down all of the repositories ...
127             for (JcrRepository repository : repositories.values()) {
128                 repository.close();
129             }
130             this.repositories.clear();
131         } finally {
132             this.repositoriesLock.unlock();
133         }
134     }
135 
136     @Override
137     public boolean awaitTermination( long timeout,
138                                      TimeUnit unit ) throws InterruptedException {
139         if (!scheduler.awaitTermination(timeout, unit)) return false;
140 
141         return super.awaitTermination(timeout, unit);
142     }
143 
144     @Override
145     public void start() {
146         super.start();
147 
148         final JcrEngine engine = this;
149         Runnable cleanUpTask = new Runnable() {
150 
151             public void run() {
152                 engine.cleanUpLocks();
153             }
154 
155         };
156         scheduler.scheduleAtFixedRate(cleanUpTask,
157                                       LOCK_SWEEP_INTERVAL_IN_MILLIS,
158                                       LOCK_SWEEP_INTERVAL_IN_MILLIS,
159                                       TimeUnit.MILLISECONDS);
160     }
161 
162     /**
163      * Get the {@link Repository} implementation for the named repository.
164      * 
165      * @param repositoryName the name of the repository, which corresponds to the name of a configured {@link RepositorySource}
166      * @return the named repository instance
167      * @throws IllegalArgumentException if the repository name is null, blank or invalid
168      * @throws RepositoryException if there is no repository with the specified name
169      * @throws IllegalStateException if this engine was not {@link #start() started}
170      */
171     public final JcrRepository getRepository( String repositoryName ) throws RepositoryException {
172         CheckArg.isNotEmpty(repositoryName, "repositoryName");
173         checkRunning();
174         try {
175             repositoriesLock.lock();
176             JcrRepository repository = repositories.get(repositoryName);
177             if (repository == null) {
178                 try {
179                     repository = doCreateJcrRepository(repositoryName);
180                 } catch (PathNotFoundException e) {
181                     // The repository name is not a valid repository ...
182                     String msg = JcrI18n.repositoryDoesNotExist.text(repositoryName);
183                     throw new RepositoryException(msg);
184                 }
185                 repositories.put(repositoryName, repository);
186             }
187             return repository;
188         } finally {
189             repositoriesLock.unlock();
190         }
191     }
192 
193     /**
194      * Get the names of each of the JCR repositories.
195      * 
196      * @return the immutable names of the repositories that exist at the time this method is called
197      */
198     public Set<String> getRepositoryNames() {
199         checkRunning();
200         Set<String> results = new HashSet<String>();
201         // Read the names of the JCR repositories from the configuration (not from the Repository objects used so far) ...
202         PathFactory pathFactory = getExecutionContext().getValueFactories().getPathFactory();
203         Path repositoriesPath = pathFactory.create(configuration.getPath(), ModeShapeLexicon.REPOSITORIES);
204         Graph configuration = getConfigurationGraph();
205         for (Location child : configuration.getChildren().of(repositoriesPath)) {
206             Name repositoryName = child.getPath().getLastSegment().getName();
207             results.add(readable(repositoryName));
208         }
209         return Collections.unmodifiableSet(results);
210     }
211 
212     protected JcrRepository doCreateJcrRepository( String repositoryName ) throws RepositoryException, PathNotFoundException {
213         RepositoryConnectionFactory connectionFactory = getRepositoryConnectionFactory();
214         Map<String, String> descriptors = new HashMap<String, String>();
215         Map<Option, String> options = new HashMap<Option, String>();
216 
217         // Read the subgraph that represents the repository ...
218         PathFactory pathFactory = getExecutionContext().getValueFactories().getPathFactory();
219         Path repositoriesPath = pathFactory.create(configuration.getPath(), ModeShapeLexicon.REPOSITORIES);
220         Path repositoryPath = pathFactory.create(repositoriesPath, repositoryName);
221         Graph configuration = getConfigurationGraph();
222         Subgraph subgraph = configuration.getSubgraphOfDepth(6).at(repositoryPath);
223 
224         // Read the options ...
225         Node optionsNode = subgraph.getNode(ModeShapeLexicon.OPTIONS);
226         if (optionsNode != null) {
227             for (Location optionLocation : optionsNode.getChildren()) {
228                 Node optionNode = configuration.getNodeAt(optionLocation);
229                 Path.Segment segment = optionLocation.getPath().getLastSegment();
230                 Property valueProperty = optionNode.getProperty(ModeShapeLexicon.VALUE);
231                 if (valueProperty == null) continue;
232                 Option option = Option.findOption(segment.getName().getLocalName());
233                 if (option == null) continue;
234                 options.put(option, valueProperty.getFirstValue().toString());
235             }
236         }
237 
238         // Read the descriptors ...
239         Node descriptorsNode = subgraph.getNode(ModeShapeLexicon.DESCRIPTORS);
240         if (descriptorsNode != null) {
241             for (Location descriptorLocation : descriptorsNode.getChildren()) {
242                 Node optionNode = configuration.getNodeAt(descriptorLocation);
243                 Path.Segment segment = descriptorLocation.getPath().getLastSegment();
244                 Property valueProperty = optionNode.getProperty(ModeShapeLexicon.VALUE);
245                 if (valueProperty == null) continue;
246                 descriptors.put(segment.getName().getLocalName(), valueProperty.getFirstValue().toString());
247             }
248         }
249 
250         // Read the namespaces ...
251         ExecutionContext context = getExecutionContext();
252         Node namespacesNode = subgraph.getNode(ModeShapeLexicon.NAMESPACES);
253         if (namespacesNode != null) {
254             GraphNamespaceRegistry registry = new GraphNamespaceRegistry(configuration, namespacesNode.getLocation().getPath(),
255                                                                          ModeShapeLexicon.NAMESPACE_URI);
256             context = context.with(registry);
257         }
258 
259         // Get the name of the source ...
260         Property property = subgraph.getRoot().getProperty(ModeShapeLexicon.SOURCE_NAME);
261         if (property == null || property.isEmpty()) {
262             String readableName = readable(ModeShapeLexicon.SOURCE_NAME);
263             String readablePath = readable(subgraph.getLocation());
264             String msg = JcrI18n.propertyNotFoundOnNode.text(readableName, readablePath, configuration.getCurrentWorkspaceName());
265             throw new RepositoryException(msg);
266         }
267         String sourceName = context.getValueFactories().getStringFactory().create(property.getFirstValue());
268 
269         // Find the capabilities ...
270         RepositorySource source = getRepositorySource(sourceName);
271         RepositorySourceCapabilities capabilities = source != null ? source.getCapabilities() : null;
272         // Create the repository ...
273         JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName,
274                                                      getRepositoryService().getRepositoryLibrary(), capabilities, descriptors,
275                                                      options);
276 
277         // Register all the the node types ...
278         Node nodeTypesNode = subgraph.getNode(JcrLexicon.NODE_TYPES);
279         if (nodeTypesNode != null) {
280             boolean needToRefreshSubgraph = false;
281 
282             // Expand any references to a CND file
283             Property resourceProperty = nodeTypesNode.getProperty(ModeShapeLexicon.RESOURCE);
284             if (resourceProperty != null) {
285                 String resources = this.context.getValueFactories().getStringFactory().create(resourceProperty.getFirstValue());
286 
287                 for (String resource : resources.split("\\s*,\\s*")) {
288                     Graph.Batch batch = configuration.batch();
289                     GraphBatchDestination destination = new GraphBatchDestination(batch);
290 
291                     Path nodeTypesPath = pathFactory.create(repositoryPath, JcrLexicon.NODE_TYPES);
292                     CndImporter importer = new CndImporter(destination, nodeTypesPath, false);
293                     InputStream is = getClass().getResourceAsStream(resource);
294                     try {
295                         if (is != null) {
296                             importer.importFrom(is, this.getProblems(), resource);
297                             batch.execute();
298                             needToRefreshSubgraph = true;
299                         }
300                     } catch (IOException ioe) {
301                         ioe.printStackTrace();
302                     }
303                 }
304 
305             }
306 
307             // Re-read the subgraph, in case any new nodes were added
308             Subgraph nodeTypesSubgraph = subgraph;
309             if (needToRefreshSubgraph) {
310                 nodeTypesSubgraph = configuration.getSubgraphOfDepth(4).at(nodeTypesNode.getLocation().getPath());
311             }
312 
313             repository.getRepositoryTypeManager().registerNodeTypes(nodeTypesSubgraph, nodeTypesNode.getLocation());// throws
314                                                                                                                     // exception
315         }
316 
317         return repository;
318     }
319 
320     protected final String readable( Name name ) {
321         return name.getString(context.getNamespaceRegistry());
322     }
323 
324     protected final String readable( Path path ) {
325         return path.getString(context.getNamespaceRegistry());
326     }
327 
328     protected final String readable( Location location ) {
329         return location.getString(context.getNamespaceRegistry());
330     }
331 }