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.jcr.api.Repositories;
63  import org.modeshape.repository.ModeShapeConfiguration;
64  import org.modeshape.repository.ModeShapeEngine;
65  
66  /**
67   * The basic component that encapsulates the ModeShape services, including the {@link Repository} instances.
68   */
69  @ThreadSafe
70  public class JcrEngine extends ModeShapeEngine implements Repositories {
71  
72      final static int LOCK_SWEEP_INTERVAL_IN_MILLIS = 30000;
73      final static int LOCK_EXTENSION_INTERVAL_IN_MILLIS = LOCK_SWEEP_INTERVAL_IN_MILLIS * 2;
74  
75      private static final Logger log = Logger.getLogger(ModeShapeEngine.class);
76  
77      private final Map<String, JcrRepository> repositories;
78      private final Lock repositoriesLock;
79  
80      /**
81       * Provides the ability to schedule lock clean-up
82       */
83      private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(2);
84  
85      JcrEngine( ExecutionContext context,
86                 ModeShapeConfiguration.ConfigurationDefinition configuration ) {
87          super(context, configuration);
88          this.repositories = new HashMap<String, JcrRepository>();
89          this.repositoriesLock = new ReentrantLock();
90      }
91  
92      /**
93       * Clean up session-scoped locks created by session that are no longer active by iterating over the {@link JcrRepository
94       * repositories} and calling their {@link JcrRepository#cleanUpLocks() clean-up method}.
95       * <p>
96       * It should not be possible for a session to be terminated without cleaning up its locks, but this method will help clean-up
97       * dangling locks should a session terminate abnormally.
98       * </p>
99       */
100     void cleanUpLocks() {
101         Collection<JcrRepository> repos;
102 
103         try {
104             // Make a copy of the repositories to minimize the time that the lock needs to be held
105             repositoriesLock.lock();
106             repos = new ArrayList<JcrRepository>(repositories.values());
107         } finally {
108             repositoriesLock.unlock();
109         }
110 
111         for (JcrRepository repository : repos) {
112             try {
113                 repository.cleanUpLocks();
114             } catch (Throwable t) {
115                 log.error(t, JcrI18n.errorCleaningUpLocks, repository.getRepositorySourceName());
116             }
117         }
118     }
119 
120     @Override
121     public void shutdown() {
122         scheduler.shutdown();
123         super.shutdown();
124 
125         try {
126             this.repositoriesLock.lock();
127             // Shut down all of the repositories ...
128             for (JcrRepository repository : repositories.values()) {
129                 repository.close();
130             }
131             this.repositories.clear();
132         } finally {
133             this.repositoriesLock.unlock();
134         }
135     }
136 
137     @Override
138     public boolean awaitTermination( long timeout,
139                                      TimeUnit unit ) throws InterruptedException {
140         if (!scheduler.awaitTermination(timeout, unit)) return false;
141 
142         return super.awaitTermination(timeout, unit);
143     }
144 
145     @Override
146     public void start() {
147         super.start();
148 
149         final JcrEngine engine = this;
150         Runnable cleanUpTask = new Runnable() {
151 
152             public void run() {
153                 engine.cleanUpLocks();
154             }
155 
156         };
157         scheduler.scheduleAtFixedRate(cleanUpTask,
158                                       LOCK_SWEEP_INTERVAL_IN_MILLIS,
159                                       LOCK_SWEEP_INTERVAL_IN_MILLIS,
160                                       TimeUnit.MILLISECONDS);
161     }
162 
163     /**
164      * Get the {@link Repository} implementation for the named repository.
165      * 
166      * @param repositoryName the name of the repository, which corresponds to the name of a configured {@link RepositorySource}
167      * @return the named repository instance
168      * @throws IllegalArgumentException if the repository name is null, blank or invalid
169      * @throws RepositoryException if there is no repository with the specified name
170      * @throws IllegalStateException if this engine was not {@link #start() started}
171      */
172     public final JcrRepository getRepository( String repositoryName ) throws RepositoryException {
173         CheckArg.isNotEmpty(repositoryName, "repositoryName");
174         checkRunning();
175         try {
176             repositoriesLock.lock();
177             JcrRepository repository = repositories.get(repositoryName);
178             if (repository == null) {
179                 try {
180                     repository = doCreateJcrRepository(repositoryName);
181                 } catch (PathNotFoundException e) {
182                     // The repository name is not a valid repository ...
183                     String msg = JcrI18n.repositoryDoesNotExist.text(repositoryName);
184                     throw new RepositoryException(msg);
185                 }
186                 repositories.put(repositoryName, repository);
187             }
188             return repository;
189         } finally {
190             repositoriesLock.unlock();
191         }
192     }
193 
194     /**
195      * Get the names of each of the JCR repositories.
196      * 
197      * @return the immutable names of the repositories that exist at the time this method is called
198      */
199     public Set<String> getRepositoryNames() {
200         checkRunning();
201         Set<String> results = new HashSet<String>();
202         // Read the names of the JCR repositories from the configuration (not from the Repository objects used so far) ...
203         PathFactory pathFactory = getExecutionContext().getValueFactories().getPathFactory();
204         Path repositoriesPath = pathFactory.create(configuration.getPath(), ModeShapeLexicon.REPOSITORIES);
205         Graph configuration = getConfigurationGraph();
206         for (Location child : configuration.getChildren().of(repositoriesPath)) {
207             Name repositoryName = child.getPath().getLastSegment().getName();
208             results.add(readable(repositoryName));
209         }
210         return Collections.unmodifiableSet(results);
211     }
212 
213     protected JcrRepository doCreateJcrRepository( String repositoryName ) throws RepositoryException, PathNotFoundException {
214         RepositoryConnectionFactory connectionFactory = getRepositoryConnectionFactory();
215         Map<String, String> descriptors = new HashMap<String, String>();
216         Map<Option, String> options = new HashMap<Option, String>();
217 
218         // Read the subgraph that represents the repository ...
219         PathFactory pathFactory = getExecutionContext().getValueFactories().getPathFactory();
220         Path repositoriesPath = pathFactory.create(configuration.getPath(), ModeShapeLexicon.REPOSITORIES);
221         Path repositoryPath = pathFactory.create(repositoriesPath, repositoryName);
222         Graph configuration = getConfigurationGraph();
223         Subgraph subgraph = configuration.getSubgraphOfDepth(6).at(repositoryPath);
224 
225         // Read the options ...
226         Node optionsNode = subgraph.getNode(ModeShapeLexicon.OPTIONS);
227         if (optionsNode != null) {
228             for (Location optionLocation : optionsNode.getChildren()) {
229                 Node optionNode = configuration.getNodeAt(optionLocation);
230                 Path.Segment segment = optionLocation.getPath().getLastSegment();
231                 Property valueProperty = optionNode.getProperty(ModeShapeLexicon.VALUE);
232                 if (valueProperty == null) continue;
233                 Option option = Option.findOption(segment.getName().getLocalName());
234                 if (option == null) continue;
235                 options.put(option, valueProperty.getFirstValue().toString());
236             }
237         }
238 
239         // Read the descriptors ...
240         Node descriptorsNode = subgraph.getNode(ModeShapeLexicon.DESCRIPTORS);
241         if (descriptorsNode != null) {
242             for (Location descriptorLocation : descriptorsNode.getChildren()) {
243                 Node optionNode = configuration.getNodeAt(descriptorLocation);
244                 Path.Segment segment = descriptorLocation.getPath().getLastSegment();
245                 Property valueProperty = optionNode.getProperty(ModeShapeLexicon.VALUE);
246                 if (valueProperty == null) continue;
247                 descriptors.put(segment.getName().getLocalName(), valueProperty.getFirstValue().toString());
248             }
249         }
250 
251         // Read the namespaces ...
252         ExecutionContext context = getExecutionContext();
253         Node namespacesNode = subgraph.getNode(ModeShapeLexicon.NAMESPACES);
254         if (namespacesNode != null) {
255             GraphNamespaceRegistry registry = new GraphNamespaceRegistry(configuration, namespacesNode.getLocation().getPath(),
256                                                                          ModeShapeLexicon.NAMESPACE_URI);
257             context = context.with(registry);
258         }
259 
260         // Get the name of the source ...
261         Property property = subgraph.getRoot().getProperty(ModeShapeLexicon.SOURCE_NAME);
262         if (property == null || property.isEmpty()) {
263             String readableName = readable(ModeShapeLexicon.SOURCE_NAME);
264             String readablePath = readable(subgraph.getLocation());
265             String msg = JcrI18n.propertyNotFoundOnNode.text(readableName, readablePath, configuration.getCurrentWorkspaceName());
266             throw new RepositoryException(msg);
267         }
268         String sourceName = context.getValueFactories().getStringFactory().create(property.getFirstValue());
269 
270         // Find the capabilities ...
271         RepositorySource source = getRepositorySource(sourceName);
272         RepositorySourceCapabilities capabilities = source != null ? source.getCapabilities() : null;
273         // Create the repository ...
274         JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName,
275                                                      getRepositoryService().getRepositoryLibrary(), capabilities, descriptors,
276                                                      options);
277 
278         // Register all the the node types ...
279         Node nodeTypesNode = subgraph.getNode(JcrLexicon.NODE_TYPES);
280         if (nodeTypesNode != null) {
281             boolean needToRefreshSubgraph = false;
282 
283             // Expand any references to a CND file
284             Property resourceProperty = nodeTypesNode.getProperty(ModeShapeLexicon.RESOURCE);
285             if (resourceProperty != null) {
286                 String resources = this.context.getValueFactories().getStringFactory().create(resourceProperty.getFirstValue());
287 
288                 for (String resource : resources.split("\\s*,\\s*")) {
289                     Graph.Batch batch = configuration.batch();
290                     GraphBatchDestination destination = new GraphBatchDestination(batch);
291 
292                     Path nodeTypesPath = pathFactory.create(repositoryPath, JcrLexicon.NODE_TYPES);
293                     CndImporter importer = new CndImporter(destination, nodeTypesPath, false);
294                     InputStream is = getClass().getResourceAsStream(resource);
295                     try {
296                         if (is != null) {
297                             importer.importFrom(is, this.getProblems(), resource);
298                             batch.execute();
299                             needToRefreshSubgraph = true;
300                         }
301                     } catch (IOException ioe) {
302                         ioe.printStackTrace();
303                     }
304                 }
305 
306             }
307 
308             // Re-read the subgraph, in case any new nodes were added
309             Subgraph nodeTypesSubgraph = subgraph;
310             if (needToRefreshSubgraph) {
311                 nodeTypesSubgraph = configuration.getSubgraphOfDepth(4).at(nodeTypesNode.getLocation().getPath());
312             }
313 
314             repository.getRepositoryTypeManager().registerNodeTypes(nodeTypesSubgraph, nodeTypesNode.getLocation());// throws
315             // exception
316         }
317 
318         return repository;
319     }
320 
321     protected final String readable( Name name ) {
322         return name.getString(context.getNamespaceRegistry());
323     }
324 
325     protected final String readable( Path path ) {
326         return path.getString(context.getNamespaceRegistry());
327     }
328 
329     protected final String readable( Location location ) {
330         return location.getString(context.getNamespaceRegistry());
331     }
332 }