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    }