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    
023    package org.jboss.dna.repository.util;
024    
025    import java.util.Map;
026    import java.util.concurrent.ConcurrentHashMap;
027    import javax.jcr.Credentials;
028    import javax.jcr.Repository;
029    import javax.jcr.RepositoryException;
030    import javax.jcr.Session;
031    import javax.jcr.SimpleCredentials;
032    import org.jboss.dna.common.SystemFailureException;
033    import org.jboss.dna.common.util.CheckArg;
034    
035    /**
036     * A SessionFactory implementation that creates {@link Session} instances using {@link Repository} instances.
037     * <p>
038     * This factory using a naming convention where the name supplied to the {@link #createSession(String)} contains both the name of
039     * the repository and the name of the workspace. Typically, this is <i><code>repositoryName/workspaceName</code></i>, where
040     * <code>repositoryName</code> is the name under which the Repository instance was bound, and <code>workspaceName</code> is
041     * the name of the workspace. Note that this method looks for the last delimiter in the whole name to distinguish between the
042     * repository and workspace names.
043     * </p>
044     * <p>
045     * For example, if "<code>java:comp/env/repository/dataRepository/myWorkspace</code>" is passed to the
046     * {@link #createSession(String)} method, this factory will look for a {@link Repository} instance registered with the name "<code>java:comp/env/repository/dataRepository</code>"
047     * and use it to {@link Repository#login(String) create a session} to the workspace named "<code>myWorkspace</code>".
048     * </p>
049     * <p>
050     * By default, this factory creates an anonymous JCR session. To use sessions with specific {@link Credentials}, simply
051     * {@link #registerCredentials(String, Credentials) register} credentials for the appropriate repository/workspace name. For
052     * security reasons, it is not possible to retrieve the Credentials once registered with this factory.
053     * </p>
054     * @author Randall Hauch
055     */
056    public abstract class AbstractSessionFactory implements SessionFactory {
057    
058        protected static char[] DEFAULT_DELIMITERS = new char[] {'/'};
059    
060        private final char[] workspaceDelims;
061        private final String workspaceDelimsRegexCharacterSet;
062        private final Map<String, Credentials> credentials = new ConcurrentHashMap<String, Credentials>();
063    
064        /**
065         * Create an instance of the factory with the default delimiters.
066         */
067        public AbstractSessionFactory() {
068            this(null);
069        }
070    
071        /**
072         * Create an instance of the factory by supplying naming context and the characters that may be used to delimit the workspace
073         * name from the repository name.
074         * @param workspaceDelimiters the delimiters, or null/empty if the default delimiter of '/' should be used.
075         * @throws IllegalArgumentException if the context parameter is null
076         */
077        public AbstractSessionFactory( char... workspaceDelimiters ) {
078            this.workspaceDelims = (workspaceDelimiters == null || workspaceDelimiters.length == 0) ? DEFAULT_DELIMITERS : workspaceDelimiters;
079            StringBuilder sb = new StringBuilder();
080            for (char delim : this.workspaceDelims) {
081                switch (delim) {
082                    case '\\':
083                        sb.append("\\");
084                        break;
085                    // case '[' : sb.append("\\["); break;
086                    case ']':
087                        sb.append("\\]");
088                        break;
089                    case '-':
090                        sb.append("\\-");
091                        break;
092                    case '^':
093                        sb.append("\\^");
094                        break;
095                    default:
096                        sb.append(delim);
097                }
098            }
099            this.workspaceDelimsRegexCharacterSet = sb.toString();
100        }
101    
102        /**
103         * Convenience method to bind a repository in JNDI. Repository instances can be bound into JNDI using any technique, so this
104         * method need not be used. <i>Note that the name should not contain the workspace part.</i>
105         * @param name the name of the repository, without the workspace name component.
106         * @param repository the repository to be bound, or null if an existing repository should be unbound.
107         */
108        public void registerRepository( String name, Repository repository ) {
109            assert name != null;
110            // Remove all trailing delimiters ...
111            name = name.replaceAll("[" + this.workspaceDelimsRegexCharacterSet + "]+$", "");
112            if (repository != null) {
113                this.doRegisterRepository(name, repository);
114            } else {
115                this.doUnregisterRepository(name);
116            }
117        }
118    
119        protected abstract void doRegisterRepository( String name, Repository repository ) throws SystemFailureException;
120    
121        protected abstract void doUnregisterRepository( String name ) throws SystemFailureException;
122    
123        protected abstract Repository findRegisteredRepository( String name ) throws SystemFailureException;
124    
125        /**
126         * Register the credentials for the repository and workspace given by the supplied name, username and password. This is
127         * equivalent to calling <code>registerCredentials(name, new SimpleCredentials(username,password))</code>, although if
128         * <code>username</code> is null then this is equivalent to <code>registerCredentials(name,null)</code>.
129         * @param name the name of the repository and workspace
130         * @param username the username to use, or null if the existing credentials for the named workspace should be removed
131         * @param password the password, may be null or empty
132         * @return true if this overwrote existing credentials
133         * @see #registerCredentials(String, Credentials)
134         * @see #removeCredentials(String)
135         */
136        public boolean registerCredentials( String name, String username, char[] password ) {
137            if (password == null && username != null) password = new char[] {};
138            Credentials creds = username == null ? null : new SimpleCredentials(username, password);
139            return registerCredentials(name, creds);
140        }
141    
142        /**
143         * Register the credentials to be used for the named repository and workspace. Use the same name as used to
144         * {@link #createSession(String) create sessions}.
145         * @param name the name of the repository and workspace
146         * @param credentials the credentials to use, or null if the existing credentials for the named workspace should be removed
147         * @return true if this overwrote existing credentials
148         * @see #registerCredentials(String, String, char[])
149         * @see #removeCredentials(String)
150         */
151        public boolean registerCredentials( String name, Credentials credentials ) {
152            boolean foundExisting = false;
153            name = name != null ? name.trim() : null;
154            if (credentials == null) {
155                foundExisting = this.credentials.remove(name) != null;
156            } else {
157                foundExisting = this.credentials.put(name, credentials) != null;
158            }
159            return foundExisting;
160        }
161    
162        /**
163         * Remove any credentials associated with the named repository and workspace. This is equivalent to calling
164         * <code>registerCredentials(name,null)</code>.
165         * @param name the name of the repository and workspace
166         * @return true if existing credentials were found and removed, or false if no such credentials existed
167         * @see #registerCredentials(String, Credentials)
168         */
169        public boolean removeCredentials( String name ) {
170            return registerCredentials(name, null);
171        }
172    
173        /**
174         * {@inheritDoc}
175         */
176        public Session createSession( String name ) throws RepositoryException {
177            CheckArg.isNotNull(name, "session name");
178            name = name.trim();
179            // Look up the Repository object in JNDI ...
180            String repositoryName = getRepositoryName(name);
181            Repository repository = findRegisteredRepository(repositoryName);
182    
183            // Determine the name of the workspace ...
184            String workspaceName = getWorkspaceName(name);
185    
186            // Look up any credentials, which may be null ...
187            Credentials creds = this.credentials.get(name);
188    
189            // Create a session to the specified workspace and using the credentials (either or both may be null) ...
190            return repository.login(creds, workspaceName);
191        }
192    
193        protected String getWorkspaceName( String name ) {
194            assert name != null;
195            int index = getIndexOfLastWorkspaceDelimiter(name);
196            if (index == -1) return null;
197            if ((index + 1) == name.length()) return null; // delim is the last character
198            return name.substring(index + 1);
199        }
200    
201        protected String getRepositoryName( String name ) {
202            assert name != null;
203            int index = getIndexOfLastWorkspaceDelimiter(name);
204            if (index == -1) return name; // no delim
205            if ((index + 1) == name.length()) return name.substring(0, index); // delim as last character
206            return name.substring(0, index);
207        }
208    
209        protected int getIndexOfLastWorkspaceDelimiter( String name ) {
210            int index = -1;
211            for (char delim : this.workspaceDelims) {
212                int i = name.lastIndexOf(delim);
213                index = Math.max(index, i);
214            }
215            return index;
216        }
217    
218    }