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 }