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 }