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 }