001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.example.dna.repository;
023
024 import java.io.IOException;
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.HashSet;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.concurrent.TimeUnit;
032 import javax.jcr.Credentials;
033 import javax.jcr.Node;
034 import javax.jcr.NodeIterator;
035 import javax.jcr.PropertyIterator;
036 import javax.jcr.Session;
037 import javax.jcr.Value;
038 import javax.naming.NamingException;
039 import javax.security.auth.login.LoginContext;
040 import javax.security.auth.login.LoginException;
041 import net.jcip.annotations.Immutable;
042 import org.jboss.dna.common.text.NoOpEncoder;
043 import org.jboss.dna.common.util.CheckArg;
044 import org.jboss.dna.connector.inmemory.InMemoryRepositorySource;
045 import org.jboss.dna.graph.BasicExecutionContextFactory;
046 import org.jboss.dna.graph.ExecutionContext;
047 import org.jboss.dna.graph.ExecutionContextFactory;
048 import org.jboss.dna.graph.Graph;
049 import org.jboss.dna.graph.Location;
050 import org.jboss.dna.graph.properties.Path;
051 import org.jboss.dna.graph.properties.PathFactory;
052 import org.jboss.dna.graph.properties.PathNotFoundException;
053 import org.jboss.dna.graph.properties.Property;
054 import org.jboss.dna.jcr.JcrRepository;
055 import org.jboss.dna.repository.RepositoryLibrary;
056 import org.jboss.dna.repository.RepositoryService;
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-repository-example";
066
067 /**
068 * @param args
069 */
070 public static void main( String[] args ) {
071 RepositoryClient client = new RepositoryClient();
072 for (String arg : args) {
073 arg = arg.trim();
074 if (arg.equals("--api=jcr")) client.setApi(Api.JCR);
075 if (arg.equals("--api=dna")) client.setApi(Api.DNA);
076 if (arg.equals("--jaas")) client.setJaasContextName(JAAS_LOGIN_CONTEXT_NAME);
077 if (arg.startsWith("--jaas=") && arg.length() > 7) client.setJaasContextName(arg.substring(7).trim());
078 }
079 client.setUserInterface(new ConsoleInput(client, args));
080 }
081
082 public enum Api {
083 JCR,
084 DNA;
085 }
086
087 private RepositoryLibrary sources;
088 private ExecutionContextFactory contextFactory;
089 private RepositoryService repositoryService;
090 private Api api = Api.JCR;
091 private String jaasContextName;
092 private UserInterface userInterface;
093 private LoginContext loginContext;
094 private ExecutionContext context;
095
096 /**
097 * @param userInterface Sets userInterface to the specified value.
098 */
099 public void setUserInterface( UserInterface userInterface ) {
100 this.userInterface = userInterface;
101 }
102
103 /**
104 * Set the API that this client should use to interact with the repositories.
105 *
106 * @param api The API that should be used
107 */
108 public void setApi( Api api ) {
109 this.api = api != null ? api : Api.DNA;
110 }
111
112 /**
113 * Set the JAAS context name that should be used. If null (which is the default), then no authentication will be used.
114 *
115 * @param jaasContextName the JAAS context name, or null if no authentication should be performed
116 */
117 public void setJaasContextName( String jaasContextName ) {
118 this.jaasContextName = jaasContextName;
119 }
120
121 /**
122 * Start up the repositories. This method creates the necessary components and services, and initializes the in-memory
123 * repositories.
124 *
125 * @throws IOException if there is a problem initializing the repositories from the files.
126 * @throws SAXException if there is a problem with the SAX Parser
127 * @throws NamingException if there is a problem registering or looking up objects in JNDI
128 */
129 public void startRepositories() throws IOException, SAXException, NamingException {
130 if (repositoryService != null) return; // already started
131
132 // Create the factory for execution contexts.
133 contextFactory = new BasicExecutionContextFactory();
134
135 // Create the execution context that we'll use for the services. If we'd want to use JAAS, we'd create the context
136 // by supplying LoginContext, AccessControlContext, or even Subject with CallbackHandlers. But no JAAS in this example.
137 context = contextFactory.create();
138
139 // Create the library for the RepositorySource instances ...
140 sources = new RepositoryLibrary(contextFactory);
141
142 // Load into the source manager the repository source for the configuration repository ...
143 InMemoryRepositorySource configSource = new InMemoryRepositorySource();
144 configSource.setName("Configuration");
145 sources.addSource(configSource);
146
147 // For this example, we're using a couple of in-memory repositories (including one for the configuration repository).
148 // Normally, these would exist already and would simply be accessed. But in this example, we're going to
149 // populate these repositories here by importing from files. First do the configuration repository ...
150 String location = this.userInterface.getLocationOfRepositoryFiles();
151 Graph.create("Configuration", sources, context).importXmlFrom(location + "/configRepository.xml").into("/");
152
153 // Now instantiate the Repository Service ...
154 repositoryService = new RepositoryService(sources, configSource.getName(), context);
155 repositoryService.getAdministrator().start();
156
157 // Now import the conten for two of the other in-memory repositories ...
158 Graph.create("Cars", sources, context).importXmlFrom(location + "/cars.xml").into("/");
159 Graph.create("Aircraft", sources, context).importXmlFrom(location + "/aircraft.xml").into("/");
160 }
161
162 /**
163 * Get the names of the repositories.
164 *
165 * @return the repository names
166 */
167 public List<String> getNamesOfRepositories() {
168 List<String> names = new ArrayList<String>(sources.getSourceNames());
169 Collections.sort(names);
170 return names;
171 }
172
173 /**
174 * Shut down the components and services and blocking until all resources have been released.
175 *
176 * @throws InterruptedException if the thread was interrupted before completing the shutdown.
177 * @throws LoginException
178 */
179 public void shutdown() throws InterruptedException, LoginException {
180 logout();
181 if (repositoryService == null) return;
182 try {
183 // Shut down the various services ...
184 repositoryService.getAdministrator().shutdown();
185
186 // Shut down the manager of the RepositorySource instances, waiting until all connections are closed
187 sources.getAdministrator().shutdown();
188 sources.getAdministrator().awaitTermination(1, TimeUnit.SECONDS);
189 } finally {
190 repositoryService = null;
191 sources = null;
192 }
193 }
194
195 /**
196 * Get the current JAAS LoginContext (if there is one).
197 *
198 * @return the current login context, or null if no JAAS authentication is to be used.
199 * @throws LoginException if authentication was attempted but failed
200 */
201 protected LoginContext getLoginContext() throws LoginException {
202 if (loginContext == null) {
203 if (jaasContextName != null) {
204 loginContext = new LoginContext(jaasContextName, this.userInterface.getCallbackHandler());
205 loginContext.login();
206 }
207 }
208 return loginContext;
209 }
210
211 /**
212 * Calling this will lose the context
213 *
214 * @throws LoginException
215 */
216 public void logout() throws LoginException {
217 if (loginContext != null) {
218 try {
219 loginContext.logout();
220 } finally {
221 loginContext = null;
222 }
223 }
224 }
225
226 /**
227 * Get the information about a node, using the {@link #setApi(Api) API} method.
228 *
229 * @param sourceName the name of the repository source
230 * @param pathToNode the path to the node in the repository that is to be retrieved
231 * @param properties the map into which the property values will be placed; may be null if the properties are not to be
232 * retrieved
233 * @param children the collection into which the child names should be placed; may be null if the children are not to be
234 * retrieved
235 * @return true if the node was found, or false if it was not
236 * @throws Throwable
237 */
238 public boolean getNodeInfo( String sourceName,
239 String pathToNode,
240 Map<String, Object[]> properties,
241 List<String> children ) throws Throwable {
242 LoginContext loginContext = getLoginContext(); // will ask user to authenticate if needed
243 switch (api) {
244 case JCR: {
245 JcrRepository jcrRepository = new JcrRepository(contextFactory, sources);
246 Session session = null;
247 if (loginContext != null) {
248 Credentials credentials = new JaasCredentials(loginContext);
249 session = jcrRepository.login(credentials, sourceName);
250 } else {
251 session = jcrRepository.login(sourceName);
252 }
253 try {
254 // Make the path relative to the root by removing the leading slash(es) ...
255 pathToNode = pathToNode.replaceAll("^/+", "");
256 // Get the node by path ...
257 Node root = session.getRootNode();
258 Node node = root;
259 if (pathToNode.length() != 0) {
260 if (!pathToNode.endsWith("]")) pathToNode = pathToNode + "[1]";
261 node = pathToNode.equals("") ? root : root.getNode(pathToNode);
262 }
263
264 // Now populate the properties and children ...
265 if (properties != null) {
266 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
267 javax.jcr.Property property = iter.nextProperty();
268 Object[] values = null;
269 // Must call either 'getValue()' or 'getValues()' depending upon # of values
270 if (property.getDefinition().isMultiple()) {
271 Value[] jcrValues = property.getValues();
272 values = new String[jcrValues.length];
273 for (int i = 0; i < jcrValues.length; i++) {
274 values[i] = jcrValues[i].getString();
275 }
276 } else {
277 values = new Object[] {property.getValue().getString()};
278 }
279 properties.put(property.getName(), values);
280 }
281 }
282 if (children != null) {
283 // Figure out which children need same-name sibling indexes ...
284 Set<String> sameNameSiblings = new HashSet<String>();
285 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
286 javax.jcr.Node child = iter.nextNode();
287 if (child.getIndex() > 1) sameNameSiblings.add(child.getName());
288 }
289 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
290 javax.jcr.Node child = iter.nextNode();
291 String name = child.getName();
292 if (sameNameSiblings.contains(name)) name = name + "[" + child.getIndex() + "]";
293 children.add(name);
294 }
295 }
296 } catch (javax.jcr.PathNotFoundException e) {
297 return false;
298 } finally {
299 if (session != null) session.logout();
300 }
301 break;
302 }
303 case DNA: {
304 try {
305 // Use the DNA Graph API to read the properties and children of the node ...
306 ExecutionContext context = loginContext != null ? contextFactory.create(loginContext) : contextFactory.create();
307 Graph graph = Graph.create(sourceName, sources, context);
308 org.jboss.dna.graph.Node node = graph.getNodeAt(pathToNode);
309
310 if (properties != null) {
311 // Now copy the properties into the map provided as a method parameter ...
312 for (Property property : node.getProperties()) {
313 String name = property.getName().getString(context.getNamespaceRegistry());
314 properties.put(name, property.getValuesAsArray());
315 }
316 }
317 if (children != null) {
318 // And copy the names of the children into the list provided as a method parameter ...
319 for (Location child : node.getChildren()) {
320 String name = child.getPath().getLastSegment().getString(context.getNamespaceRegistry());
321 children.add(name);
322 }
323 }
324 } catch (PathNotFoundException e) {
325 return false;
326 }
327 break;
328 }
329 }
330 return true;
331 }
332
333 /**
334 * Utility to build a path given the current path and the input path as string, where the input path could be an absolute path
335 * or relative to the current and where the input may use "." and "..".
336 *
337 * @param current the current path
338 * @param input the input path
339 * @return the resulting full and normalized path
340 */
341 protected String buildPath( String current,
342 String input ) {
343 if (current == null) current = "/";
344 if (input == null || input.length() == 0) return current;
345 PathFactory factory = context.getValueFactories().getPathFactory();
346 Path inputPath = factory.create(input);
347 if (inputPath.isAbsolute()) {
348 return inputPath.getNormalizedPath().getString(context.getNamespaceRegistry(), NoOpEncoder.getInstance());
349 }
350 Path currentPath = factory.create(current);
351 currentPath = factory.create(currentPath, inputPath);
352 currentPath = currentPath.getNormalizedPath();
353 return currentPath.getString(context.getNamespaceRegistry(), NoOpEncoder.getInstance());
354 }
355
356 /**
357 * A class that represents JCR Credentials containing the JAAS LoginContext.
358 *
359 * @author Randall Hauch
360 */
361 @Immutable
362 protected static class JaasCredentials implements Credentials {
363 private static final long serialVersionUID = 1L;
364 private final LoginContext context;
365
366 public JaasCredentials( LoginContext context ) {
367 CheckArg.isNotNull(context, "context");
368 this.context = context;
369 }
370
371 /**
372 * JBoss DNA's JCR implementation will reflectively look for and call this method to get the JAAS LoginContext.
373 *
374 * @return the current LoginContext
375 */
376 public LoginContext getLoginContext() {
377 return context;
378 }
379
380 }
381 }