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.naming.NamingException;
041 import javax.security.auth.login.LoginContext;
042 import javax.security.auth.login.LoginException;
043 import net.jcip.annotations.Immutable;
044 import org.jboss.dna.common.text.NoOpEncoder;
045 import org.jboss.dna.common.util.CheckArg;
046 import org.jboss.dna.graph.ExecutionContext;
047 import org.jboss.dna.graph.Graph;
048 import org.jboss.dna.graph.Location;
049 import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
050 import org.jboss.dna.graph.property.Path;
051 import org.jboss.dna.graph.property.PathFactory;
052 import org.jboss.dna.graph.property.PathNotFoundException;
053 import org.jboss.dna.graph.property.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 RepositoryService repositoryService;
089 private Api api = Api.JCR;
090 private String jaasContextName;
091 private UserInterface userInterface;
092 private LoginContext loginContext;
093 private ExecutionContext context;
094
095 /**
096 * @param userInterface Sets userInterface to the specified value.
097 */
098 public void setUserInterface( UserInterface userInterface ) {
099 this.userInterface = userInterface;
100 }
101
102 /**
103 * Set the API that this client should use to interact with the repositories.
104 *
105 * @param api The API that should be used
106 */
107 public void setApi( Api api ) {
108 this.api = api != null ? api : Api.DNA;
109 }
110
111 /**
112 * Set the JAAS context name that should be used. If null (which is the default), then no authentication will be used.
113 *
114 * @param jaasContextName the JAAS context name, or null if no authentication should be performed
115 */
116 public void setJaasContextName( String jaasContextName ) {
117 this.jaasContextName = jaasContextName;
118 }
119
120 /**
121 * Start up the repositories. This method creates the necessary components and services, and initializes the in-memory
122 * repositories.
123 *
124 * @throws IOException if there is a problem initializing the repositories from the files.
125 * @throws SAXException if there is a problem with the SAX Parser
126 * @throws NamingException if there is a problem registering or looking up objects in JNDI
127 */
128 public void startRepositories() throws IOException, SAXException, NamingException {
129 if (repositoryService != null) return; // already started
130
131 // Create the execution context that we'll use for the services. If we'd want to use JAAS, we'd create the context
132 // by supplying LoginContext, AccessControlContext, or even Subject with CallbackHandlers. But no JAAS in this example.
133 context = new ExecutionContext();
134
135 // Create the library for the RepositorySource instances ...
136 sources = new RepositoryLibrary(context);
137
138 // Load into the source manager the repository source for the configuration repository ...
139 InMemoryRepositorySource configSource = new InMemoryRepositorySource();
140 configSource.setName("Configuration");
141 configSource.setDefaultWorkspaceName("default");
142 sources.addSource(configSource);
143
144 // For this example, we're using a couple of in-memory repositories (including one for the configuration repository).
145 // Normally, these would exist already and would simply be accessed. But in this example, we're going to
146 // populate these repositories here by importing from files. First do the configuration repository ...
147 String location = this.userInterface.getLocationOfRepositoryFiles();
148 Graph config = Graph.create("Configuration", sources, context);
149 config.useWorkspace("default");
150 config.importXmlFrom(location + "/configRepository.xml").into("/");
151
152 // Now instantiate the Repository Service ...
153 Path configRoot = context.getValueFactories().getPathFactory().create("/jcr:system");
154 repositoryService = new RepositoryService(sources, configSource.getName(), "default", configRoot, context);
155 repositoryService.getAdministrator().start();
156
157 // Now import the conten for two of the other in-memory repositories ...
158 Graph cars = Graph.create("Cars", sources, context);
159 cars.importXmlFrom(location + "/cars.xml").into("/");
160
161 Graph aircraft = Graph.create("Aircraft", sources, context);
162 aircraft.importXmlFrom(location + "/aircraft.xml").into("/");
163 }
164
165 /**
166 * Get the names of the repositories.
167 *
168 * @return the repository names
169 */
170 public List<String> getNamesOfRepositories() {
171 List<String> names = new ArrayList<String>(sources.getSourceNames());
172 Collections.sort(names);
173 return names;
174 }
175
176 /**
177 * Shut down the components and services and blocking until all resources have been released.
178 *
179 * @throws InterruptedException if the thread was interrupted before completing the shutdown.
180 * @throws LoginException
181 */
182 public void shutdown() throws InterruptedException, LoginException {
183 logout();
184 if (repositoryService == null) return;
185 try {
186 // Shut down the various services ...
187 repositoryService.getAdministrator().shutdown();
188
189 // Shut down the manager of the RepositorySource instances, waiting until all connections are closed
190 sources.getAdministrator().shutdown();
191 sources.getAdministrator().awaitTermination(1, TimeUnit.SECONDS);
192 } finally {
193 repositoryService = null;
194 sources = null;
195 }
196 }
197
198 /**
199 * Get the current JAAS LoginContext (if there is one).
200 *
201 * @return the current login context, or null if no JAAS authentication is to be used.
202 * @throws LoginException if authentication was attempted but failed
203 */
204 protected LoginContext getLoginContext() throws LoginException {
205 if (loginContext == null) {
206 if (jaasContextName != null) {
207 loginContext = new LoginContext(jaasContextName, this.userInterface.getCallbackHandler());
208 loginContext.login();
209 }
210 }
211 return loginContext;
212 }
213
214 /**
215 * Calling this will lose the context
216 *
217 * @throws LoginException
218 */
219 public void logout() throws LoginException {
220 if (loginContext != null) {
221 try {
222 loginContext.logout();
223 } finally {
224 loginContext = null;
225 }
226 }
227 }
228
229 /**
230 * Get the information about a node, using the {@link #setApi(Api) API} method.
231 *
232 * @param sourceName the name of the repository source
233 * @param pathToNode the path to the node in the repository that is to be retrieved
234 * @param properties the map into which the property values will be placed; may be null if the properties are not to be
235 * retrieved
236 * @param children the collection into which the child names should be placed; may be null if the children are not to be
237 * retrieved
238 * @return true if the node was found, or false if it was not
239 * @throws Throwable
240 */
241 public boolean getNodeInfo( String sourceName,
242 String pathToNode,
243 Map<String, Object[]> properties,
244 List<String> children ) throws Throwable {
245 LoginContext loginContext = getLoginContext(); // will ask user to authenticate if needed
246 switch (api) {
247 case JCR: {
248 JcrRepository jcrRepository = new JcrRepository(context, sources, sourceName);
249 Session session = null;
250 if (loginContext != null) {
251 Credentials credentials = new JaasCredentials(loginContext);
252 session = jcrRepository.login(credentials, "default");
253 } else {
254 session = jcrRepository.login("default");
255 }
256 try {
257 // Make the path relative to the root by removing the leading slash(es) ...
258 pathToNode = pathToNode.replaceAll("^/+", "");
259 // Get the node by path ...
260 Node root = session.getRootNode();
261 Node node = root;
262 if (pathToNode.length() != 0) {
263 if (!pathToNode.endsWith("]")) pathToNode = pathToNode + "[1]";
264 node = pathToNode.equals("") ? root : root.getNode(pathToNode);
265 }
266
267 // Now populate the properties and children ...
268 if (properties != null) {
269 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
270 javax.jcr.Property property = iter.nextProperty();
271 Object[] values = null;
272 // Must call either 'getValue()' or 'getValues()' depending upon # of values
273 if (property.getDefinition().isMultiple()) {
274 Value[] jcrValues = property.getValues();
275 values = new String[jcrValues.length];
276 for (int i = 0; i < jcrValues.length; i++) {
277 values[i] = jcrValues[i].getString();
278 }
279 } else {
280 values = new Object[] {property.getValue().getString()};
281 }
282 properties.put(property.getName(), values);
283 }
284 }
285 if (children != null) {
286 // Figure out which children need same-name sibling indexes ...
287 Set<String> sameNameSiblings = new HashSet<String>();
288 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
289 javax.jcr.Node child = iter.nextNode();
290 if (child.getIndex() > 1) sameNameSiblings.add(child.getName());
291 }
292 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
293 javax.jcr.Node child = iter.nextNode();
294 String name = child.getName();
295 if (sameNameSiblings.contains(name)) name = name + "[" + child.getIndex() + "]";
296 children.add(name);
297 }
298 }
299 } catch (javax.jcr.ItemNotFoundException e) {
300 return false;
301 } catch (javax.jcr.PathNotFoundException e) {
302 return false;
303 } finally {
304 if (session != null) session.logout();
305 }
306 break;
307 }
308 case DNA: {
309 try {
310 // Use the DNA Graph API to read the properties and children of the node ...
311 ExecutionContext context = loginContext != null ? this.context.create(loginContext) : this.context;
312 Graph graph = Graph.create(sourceName, sources, context);
313 graph.useWorkspace("default");
314 org.jboss.dna.graph.Node node = graph.getNodeAt(pathToNode);
315
316 if (properties != null) {
317 // Now copy the properties into the map provided as a method parameter ...
318 for (Property property : node.getProperties()) {
319 String name = property.getName().getString(context.getNamespaceRegistry());
320 properties.put(name, property.getValuesAsArray());
321 }
322 }
323 if (children != null) {
324 // And copy the names of the children into the list provided as a method parameter ...
325 for (Location child : node.getChildren()) {
326 String name = child.getPath().getLastSegment().getString(context.getNamespaceRegistry());
327 children.add(name);
328 }
329 }
330 } catch (PathNotFoundException e) {
331 return false;
332 }
333 break;
334 }
335 }
336 return true;
337 }
338
339 /**
340 * Utility to build a path given the current path and the input path as string, where the input path could be an absolute path
341 * or relative to the current and where the input may use "." and "..".
342 *
343 * @param current the current path
344 * @param input the input path
345 * @return the resulting full and normalized path
346 */
347 protected String buildPath( String current,
348 String input ) {
349 if (current == null) current = "/";
350 if (input == null || input.length() == 0) return current;
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 }