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    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed 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.example.repository;
25  
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.concurrent.TimeUnit;
34  import javax.jcr.Credentials;
35  import javax.jcr.Node;
36  import javax.jcr.NodeIterator;
37  import javax.jcr.PropertyIterator;
38  import javax.jcr.Session;
39  import javax.jcr.Value;
40  import javax.security.auth.login.LoginContext;
41  import javax.security.auth.login.LoginException;
42  import net.jcip.annotations.Immutable;
43  import org.jboss.security.config.IDTrustConfiguration;
44  import org.modeshape.common.collection.Problem;
45  import org.modeshape.common.text.NoOpEncoder;
46  import org.modeshape.common.util.CheckArg;
47  import org.modeshape.graph.ExecutionContext;
48  import org.modeshape.graph.Graph;
49  import org.modeshape.graph.JaasSecurityContext;
50  import org.modeshape.graph.Location;
51  import org.modeshape.graph.property.Path;
52  import org.modeshape.graph.property.PathFactory;
53  import org.modeshape.graph.property.PathNotFoundException;
54  import org.modeshape.graph.property.Property;
55  import org.modeshape.jcr.JcrConfiguration;
56  import org.modeshape.jcr.JcrEngine;
57  import org.modeshape.jcr.JcrRepository;
58  import org.xml.sax.SAXException;
59  
60  /**
61   * The repository client, with the main application.
62   */
63  public class RepositoryClient {
64  
65      public static final String INMEMORY_REPOSITORY_SOURCE_CLASSNAME = "org.modeshape.connector.inmemory.InMemoryRepositorySource";
66      public static final String JAAS_LOGIN_CONTEXT_NAME = "modeshape-jcr";
67  
68      /**
69       * @param args
70       */
71      public static void main( String[] args ) {
72          // Set up the JAAS provider (IDTrust) and a policy file (which defines the "modeshape-jcr" login config name)
73          IDTrustConfiguration idtrustConfig = new IDTrustConfiguration();
74          try {
75              idtrustConfig.config("security/jaas.conf.xml");
76          } catch (Exception ex) {
77              throw new IllegalStateException(ex);
78          }
79  
80          // Now configure the repository client component ...
81          RepositoryClient client = new RepositoryClient();
82          for (String arg : args) {
83              arg = arg.trim();
84              if (arg.equals("--api=jcr")) client.setApi(Api.JCR);
85              if (arg.equals("--api=dna")) client.setApi(Api.ModeShape);
86              if (arg.equals("--jaas")) client.setJaasContextName(JAAS_LOGIN_CONTEXT_NAME);
87              if (arg.startsWith("--jaas=") && arg.length() > 7) client.setJaasContextName(arg.substring(7).trim());
88          }
89  
90          // And have it use a ConsoleInput user interface ...
91          client.setUserInterface(new ConsoleInput(client, args));
92      }
93  
94      public enum Api {
95          JCR,
96          ModeShape;
97      }
98  
99      private Api api = Api.JCR;
100     private String jaasContextName = JAAS_LOGIN_CONTEXT_NAME;
101     private UserInterface userInterface;
102     private LoginContext loginContext;
103     private JcrEngine engine;
104 
105     /**
106      * @param userInterface Sets userInterface to the specified value.
107      */
108     public void setUserInterface( UserInterface userInterface ) {
109         this.userInterface = userInterface;
110     }
111 
112     /**
113      * Set the API that this client should use to interact with the repositories.
114      * 
115      * @param api The API that should be used
116      */
117     public void setApi( Api api ) {
118         this.api = api != null ? api : Api.ModeShape;
119     }
120 
121     /**
122      * Set the JAAS context name that should be used. If null (which is the default), then no authentication will be used.
123      * 
124      * @param jaasContextName the JAAS context name, or null if no authentication should be performed
125      */
126     public void setJaasContextName( String jaasContextName ) {
127         this.jaasContextName = jaasContextName;
128     }
129 
130     /**
131      * Start up the repositories. This method loads the configuration, then creates the engine and starts it.
132      * 
133      * @throws IOException if there is a problem initializing the repositories from the files.
134      * @throws SAXException if there is a problem with the SAX Parser
135      */
136     public void startRepositories() throws IOException, SAXException {
137         if (engine != null) return; // already started
138 
139         // Load the configuration from a file, as provided by the user interface ...
140         JcrConfiguration configuration = new JcrConfiguration();
141         configuration.loadFrom(userInterface.getRepositoryConfiguration());
142 
143         // Now create the JCR engine ...
144         engine = configuration.build();
145         engine.start();
146 
147         if (engine.getProblems().hasProblems()) {
148             for (Problem problem : engine.getProblems()) {
149                 System.err.println(problem.getMessageString());
150             }
151             throw new RuntimeException("Could not start due to problems");
152         }
153 
154         // For this example, we're using a couple of in-memory repositories (including one for the configuration repository).
155         // Normally, these would exist already and would simply be accessed. But in this example, we're going to
156         // populate these repositories here by importing from files. First do the configuration repository ...
157         String location = this.userInterface.getLocationOfRepositoryFiles();
158 
159         // Now import the content for the two in-memory repository sources ...
160         engine.getGraph("Cars").importXmlFrom(location + "/cars.xml").into("/");
161         engine.getGraph("Aircraft").importXmlFrom(location + "/aircraft.xml").into("/");
162     }
163 
164     /**
165      * Get the names of the repositories.
166      * 
167      * @return the sorted but immutable list of repository names; never null
168      */
169     public List<String> getNamesOfRepositories() {
170         List<String> names = new ArrayList<String>(engine.getRepositoryNames());
171         Collections.sort(names);
172         return Collections.unmodifiableList(names);
173     }
174 
175     /**
176      * Shut down the components and services and blocking until all resources have been released.
177      * 
178      * @throws InterruptedException if the thread was interrupted before completing the shutdown.
179      * @throws LoginException
180      */
181     public void shutdown() throws InterruptedException, LoginException {
182         logout();
183         if (engine == null) return;
184         try {
185             // Tell the engine to shut down, and then wait up to 5 seconds for it to complete...
186             engine.shutdown();
187             engine.awaitTermination(5, TimeUnit.SECONDS);
188         } finally {
189             engine = null;
190         }
191     }
192 
193     /**
194      * Get the current JAAS LoginContext (if there is one).
195      * 
196      * @return the current login context, or null if no JAAS authentication is to be used.
197      * @throws LoginException if authentication was attempted but failed
198      */
199     protected LoginContext getLoginContext() throws LoginException {
200         if (loginContext == null) {
201             if (jaasContextName != null) {
202                 loginContext = new LoginContext(jaasContextName, this.userInterface.getCallbackHandler());
203                 loginContext.login(); // This authenticates the user
204             }
205         }
206         return loginContext;
207     }
208 
209     /**
210      * Calling this will lose the context
211      * 
212      * @throws LoginException
213      */
214     public void logout() throws LoginException {
215         if (loginContext != null) {
216             try {
217                 loginContext.logout();
218             } finally {
219                 loginContext = null;
220             }
221         }
222     }
223 
224     /**
225      * Get the information about a node, using the {@link #setApi(Api) API} method.
226      * 
227      * @param sourceName the name of the repository source
228      * @param pathToNode the path to the node in the repository that is to be retrieved
229      * @param properties the map into which the property values will be placed; may be null if the properties are not to be
230      *        retrieved
231      * @param children the collection into which the child names should be placed; may be null if the children are not to be
232      *        retrieved
233      * @return true if the node was found, or false if it was not
234      * @throws Throwable
235      */
236     public boolean getNodeInfo( String sourceName,
237                                 String pathToNode,
238                                 Map<String, Object[]> properties,
239                                 List<String> children ) throws Throwable {
240         LoginContext loginContext = getLoginContext(); // will ask user to authenticate if needed
241         switch (api) {
242             case JCR: {
243                 JcrRepository jcrRepository = engine.getRepository(sourceName);
244                 Session session = null;
245                 if (loginContext != null) {
246                     // Could also use SimpleCredentials(username,password) too
247                     Credentials credentials = new JaasCredentials(loginContext);
248                     session = jcrRepository.login(credentials);
249                 } else {
250                     session = jcrRepository.login();
251                 }
252                 try {
253                     // Make the path relative to the root by removing the leading slash(es) ...
254                     pathToNode = pathToNode.replaceAll("^/+", "");
255                     // Get the node by path ...
256                     Node root = session.getRootNode();
257                     Node node = root;
258                     if (pathToNode.length() != 0) {
259                         if (!pathToNode.endsWith("]")) pathToNode = pathToNode + "[1]";
260                         node = pathToNode.equals("") ? root : root.getNode(pathToNode);
261                     }
262 
263                     // Now populate the properties and children ...
264                     if (properties != null) {
265                         for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
266                             javax.jcr.Property property = iter.nextProperty();
267                             Object[] values = null;
268                             // Must call either 'getValue()' or 'getValues()' depending upon # of values
269                             if (property.getDefinition().isMultiple()) {
270                                 Value[] jcrValues = property.getValues();
271                                 values = new String[jcrValues.length];
272                                 for (int i = 0; i < jcrValues.length; i++) {
273                                     values[i] = jcrValues[i].getString();
274                                 }
275                             } else {
276                                 values = new Object[] {property.getValue().getString()};
277                             }
278                             properties.put(property.getName(), values);
279                         }
280                     }
281                     if (children != null) {
282                         // Figure out which children need same-name sibling indexes ...
283                         Set<String> sameNameSiblings = new HashSet<String>();
284                         for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
285                             javax.jcr.Node child = iter.nextNode();
286                             if (child.getIndex() > 1) sameNameSiblings.add(child.getName());
287                         }
288                         for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
289                             javax.jcr.Node child = iter.nextNode();
290                             String name = child.getName();
291                             if (sameNameSiblings.contains(name)) name = name + "[" + child.getIndex() + "]";
292                             children.add(name);
293                         }
294                     }
295                 } catch (javax.jcr.ItemNotFoundException e) {
296                     return false;
297                 } catch (javax.jcr.PathNotFoundException e) {
298                     return false;
299                 } finally {
300                     if (session != null) session.logout();
301                 }
302                 break;
303             }
304             case ModeShape: {
305                 try {
306                     // Use the ModeShape Graph API to read the properties and children of the node ...
307                     ExecutionContext context = this.engine.getExecutionContext();
308                     if (loginContext != null) {
309                         JaasSecurityContext security = new JaasSecurityContext(loginContext);
310                         context = context.with(security);
311                     }
312                     Graph graph = engine.getGraph(context, sourceName);
313                     org.modeshape.graph.Node node = graph.getNodeAt(pathToNode);
314 
315                     if (properties != null) {
316                         // Now copy the properties into the map provided as a method parameter ...
317                         for (Property property : node.getProperties()) {
318                             String name = property.getName().getString(context.getNamespaceRegistry());
319                             properties.put(name, property.getValuesAsArray());
320                         }
321                     }
322                     if (children != null) {
323                         // And copy the names of the children into the list provided as a method parameter ...
324                         for (Location child : node.getChildren()) {
325                             String name = child.getPath().getLastSegment().getString(context.getNamespaceRegistry());
326                             children.add(name);
327                         }
328                     }
329                 } catch (PathNotFoundException e) {
330                     return false;
331                 }
332                 break;
333             }
334         }
335         return true;
336     }
337 
338     /**
339      * Utility to build a path given the current path and the input path as string, where the input path could be an absolute path
340      * or relative to the current and where the input may use "." and "..".
341      * 
342      * @param current the current path
343      * @param input the input path
344      * @return the resulting full and normalized path
345      */
346     public String buildPath( String current,
347                              String input ) {
348         if (current == null) current = "/";
349         if (input == null || input.length() == 0) return current;
350         ExecutionContext context = this.engine.getExecutionContext();
351         PathFactory factory = context.getValueFactories().getPathFactory();
352         Path inputPath = factory.create(input);
353         if (inputPath.isAbsolute()) {
354             return inputPath.getNormalizedPath().getString(context.getNamespaceRegistry(), NoOpEncoder.getInstance());
355         }
356         Path currentPath = factory.create(current);
357         currentPath = factory.create(currentPath, inputPath);
358         currentPath = currentPath.getNormalizedPath();
359         return currentPath.getString(context.getNamespaceRegistry(), NoOpEncoder.getInstance());
360     }
361 
362     /**
363      * A class that represents JCR Credentials containing the JAAS LoginContext.
364      * 
365      * @author Randall Hauch
366      */
367     @Immutable
368     protected static class JaasCredentials implements Credentials {
369         private static final long serialVersionUID = 1L;
370         private final LoginContext context;
371 
372         public JaasCredentials( LoginContext context ) {
373             CheckArg.isNotNull(context, "context");
374             this.context = context;
375         }
376 
377         /**
378          * ModeShape's JCR implementation will reflectively look for and call this method to get the JAAS LoginContext.
379          * 
380          * @return the current LoginContext
381          */
382         public LoginContext getLoginContext() {
383             return context;
384         }
385 
386     }
387 }