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    }