001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.example.dna.repository;
025
026 import java.io.IOException;
027 import java.util.ArrayList;
028 import java.util.Collections;
029 import java.util.HashSet;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Set;
033 import java.util.concurrent.TimeUnit;
034 import javax.jcr.Credentials;
035 import javax.jcr.Node;
036 import javax.jcr.NodeIterator;
037 import javax.jcr.PropertyIterator;
038 import javax.jcr.Session;
039 import javax.jcr.Value;
040 import javax.security.auth.login.LoginContext;
041 import javax.security.auth.login.LoginException;
042 import net.jcip.annotations.Immutable;
043 import org.jboss.dna.common.text.NoOpEncoder;
044 import org.jboss.dna.common.util.CheckArg;
045 import org.jboss.dna.graph.ExecutionContext;
046 import org.jboss.dna.graph.Graph;
047 import org.jboss.dna.graph.JaasSecurityContext;
048 import org.jboss.dna.graph.Location;
049 import org.jboss.dna.graph.property.Path;
050 import org.jboss.dna.graph.property.PathFactory;
051 import org.jboss.dna.graph.property.PathNotFoundException;
052 import org.jboss.dna.graph.property.Property;
053 import org.jboss.dna.jcr.JcrConfiguration;
054 import org.jboss.dna.jcr.JcrEngine;
055 import org.jboss.dna.jcr.JcrRepository;
056 import org.jboss.security.config.IDTrustConfiguration;
057 import org.xml.sax.SAXException;
058
059 /**
060 * @author Randall Hauch
061 */
062 public class RepositoryClient {
063
064 public static final String INMEMORY_REPOSITORY_SOURCE_CLASSNAME = "org.jboss.dna.connector.inmemory.InMemoryRepositorySource";
065 public static final String JAAS_LOGIN_CONTEXT_NAME = "dna-jcr";
066
067 /**
068 * @param args
069 */
070 public static void main( String[] args ) {
071 // Set up the JAAS provider (IDTrust) and a policy file (which defines the "dna-jcr" login config name)
072 IDTrustConfiguration idtrustConfig = new IDTrustConfiguration();
073 try {
074 idtrustConfig.config("security/jaas.conf.xml");
075 } catch (Exception ex) {
076 throw new IllegalStateException(ex);
077 }
078
079 // Now configure the repository client component ...
080 RepositoryClient client = new RepositoryClient();
081 for (String arg : args) {
082 arg = arg.trim();
083 if (arg.equals("--api=jcr")) client.setApi(Api.JCR);
084 if (arg.equals("--api=dna")) client.setApi(Api.DNA);
085 if (arg.equals("--jaas")) client.setJaasContextName(JAAS_LOGIN_CONTEXT_NAME);
086 if (arg.startsWith("--jaas=") && arg.length() > 7) client.setJaasContextName(arg.substring(7).trim());
087 }
088
089 // And have it use a ConsoleInput user interface ...
090 client.setUserInterface(new ConsoleInput(client, args));
091 }
092
093 public enum Api {
094 JCR,
095 DNA;
096 }
097
098 private Api api = Api.JCR;
099 private String jaasContextName = JAAS_LOGIN_CONTEXT_NAME;
100 private UserInterface userInterface;
101 private LoginContext loginContext;
102 private JcrEngine engine;
103
104 /**
105 * @param userInterface Sets userInterface to the specified value.
106 */
107 public void setUserInterface( UserInterface userInterface ) {
108 this.userInterface = userInterface;
109 }
110
111 /**
112 * Set the API that this client should use to interact with the repositories.
113 *
114 * @param api The API that should be used
115 */
116 public void setApi( Api api ) {
117 this.api = api != null ? api : Api.DNA;
118 }
119
120 /**
121 * Set the JAAS context name that should be used. If null (which is the default), then no authentication will be used.
122 *
123 * @param jaasContextName the JAAS context name, or null if no authentication should be performed
124 */
125 public void setJaasContextName( String jaasContextName ) {
126 this.jaasContextName = jaasContextName;
127 }
128
129 /**
130 * Start up the repositories. This method loads the configuration, then creates the engine and starts it.
131 *
132 * @throws IOException if there is a problem initializing the repositories from the files.
133 * @throws SAXException if there is a problem with the SAX Parser
134 */
135 public void startRepositories() throws IOException, SAXException {
136 if (engine != null) return; // already started
137
138 // Load the configuration from a file, as provided by the user interface ...
139 JcrConfiguration configuration = new JcrConfiguration();
140 configuration.loadFrom(userInterface.getRepositoryConfiguration());
141
142 // Load the node types for each JCR repository, via a CND file. These could have been defined
143 // in the configuration file, but this approach is easy and allows us to define the node types
144 // using the CND format in one or multiple files.
145 String locationOfCndFiles = userInterface.getLocationOfCndFiles();
146 configuration.repository("Aircraft").addNodeTypes(locationOfCndFiles + "/aircraft.cnd");
147 configuration.repository("Cars").addNodeTypes(locationOfCndFiles + "/cars.cnd");
148 configuration.repository("Vehicles").addNodeTypes(locationOfCndFiles + "/vehicles.cnd");
149
150 // Now create the JCR engine ...
151 engine = configuration.build();
152 engine.start();
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 DNA: {
305 try {
306 // Use the DNA 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.jboss.dna.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 * JBoss DNA'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 }