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 }