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 }