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