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 package org.jboss.dna.jcr;
023
024 import java.lang.reflect.Method;
025 import java.security.AccessControlContext;
026 import java.util.Collections;
027 import java.util.HashMap;
028 import java.util.Map;
029 import java.util.UUID;
030 import javax.jcr.Credentials;
031 import javax.jcr.LoginException;
032 import javax.jcr.Node;
033 import javax.jcr.Repository;
034 import javax.jcr.RepositoryException;
035 import javax.jcr.Session;
036 import javax.jcr.SimpleCredentials;
037 import javax.security.auth.login.LoginContext;
038 import net.jcip.annotations.ThreadSafe;
039 import org.jboss.dna.common.util.CheckArg;
040 import org.jboss.dna.graph.ExecutionContext;
041 import org.jboss.dna.graph.ExecutionContextFactory;
042 import org.jboss.dna.graph.Graph;
043 import org.jboss.dna.graph.connectors.RepositoryConnectionFactory;
044 import com.google.common.base.ReferenceType;
045 import com.google.common.collect.ReferenceMap;
046
047 /**
048 * Creates JCR {@link Session sessions} to an underlying repository (which may be a federated repository).
049 * <p>
050 * This JCR repository must be configured with the ability to connect to a repository via a supplied
051 * {@link RepositoryConnectionFactory repository connection factory} and repository source name. An
052 * {@link ExecutionContextFactory execution context factory} must also be supplied to enable working with the underlying DNA graph
053 * implementation to which this JCR implementation delegates.
054 * </p>
055 * <p>
056 * If {@link Credentials credentials} are used to login, implementations <em>must</em> also implement one of the following
057 * methods:
058 *
059 * <pre>
060 * public {@link AccessControlContext} getAccessControlContext();
061 * public {@link LoginContext} getLoginContext();
062 * </pre>
063 *
064 * Note, {@link Session#getAttributeNames() attributes} on credentials are not supported. JCR {@link SimpleCredentials} are also
065 * not supported.
066 * </p>
067 *
068 * @author John Verhaeg
069 * @author Randall Hauch
070 */
071 @ThreadSafe
072 public class JcrRepository implements Repository {
073
074 private final Map<String, String> descriptors;
075 private final ExecutionContextFactory executionContextFactory;
076 private final RepositoryConnectionFactory connectionFactory;
077
078 /**
079 * Creates a JCR repository that uses the supplied {@link RepositoryConnectionFactory repository connection factory} to
080 * establish {@link Session sessions} to the underlying repository source upon {@link #login() login}.
081 *
082 * @param executionContextFactory An execution context factory.
083 * @param connectionFactory A repository connection factory.
084 * @throws IllegalArgumentException If <code>executionContextFactory</code> or <code>connectionFactory</code> is
085 * <code>null</code>.
086 */
087 public JcrRepository( ExecutionContextFactory executionContextFactory,
088 RepositoryConnectionFactory connectionFactory ) {
089 this(null, executionContextFactory, connectionFactory);
090 }
091
092 /**
093 * Creates a JCR repository that uses the supplied {@link RepositoryConnectionFactory repository connection factory} to
094 * establish {@link Session sessions} to the underlying repository source upon {@link #login() login}.
095 *
096 * @param descriptors The {@link #getDescriptorKeys() descriptors} for this repository; may be <code>null</code>.
097 * @param executionContextFactory An execution context factory.
098 * @param connectionFactory A repository connection factory.
099 * @throws IllegalArgumentException If <code>executionContextFactory</code> or <code>connectionFactory</code> is
100 * <code>null</code>.
101 */
102 public JcrRepository( Map<String, String> descriptors,
103 ExecutionContextFactory executionContextFactory,
104 RepositoryConnectionFactory connectionFactory ) {
105 CheckArg.isNotNull(executionContextFactory, "executionContextFactory");
106 CheckArg.isNotNull(connectionFactory, "connectionFactory");
107 this.executionContextFactory = executionContextFactory;
108 this.connectionFactory = connectionFactory;
109 Map<String, String> modifiableDescriptors;
110 if (descriptors == null) {
111 modifiableDescriptors = new HashMap<String, String>();
112 } else {
113 modifiableDescriptors = new HashMap<String, String>(descriptors);
114 }
115 // Initialize required JCR descriptors.
116 modifiableDescriptors.put(Repository.LEVEL_1_SUPPORTED, "true");
117 modifiableDescriptors.put(Repository.LEVEL_2_SUPPORTED, "false");
118 modifiableDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, "false");
119 modifiableDescriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, "false");
120 modifiableDescriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, "false");
121 modifiableDescriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, "false");
122 modifiableDescriptors.put(Repository.OPTION_VERSIONING_SUPPORTED, "false");
123 modifiableDescriptors.put(Repository.QUERY_XPATH_DOC_ORDER, "true");
124 modifiableDescriptors.put(Repository.QUERY_XPATH_POS_INDEX, "true");
125 // Vendor-specific descriptors (REP_XXX) will only be initialized if not already present, allowing for customer branding.
126 if (!modifiableDescriptors.containsKey(Repository.REP_NAME_DESC)) {
127 modifiableDescriptors.put(Repository.REP_NAME_DESC, JcrI18n.REP_NAME_DESC.text());
128 }
129 if (!modifiableDescriptors.containsKey(Repository.REP_VENDOR_DESC)) {
130 modifiableDescriptors.put(Repository.REP_VENDOR_DESC, JcrI18n.REP_VENDOR_DESC.text());
131 }
132 if (!modifiableDescriptors.containsKey(Repository.REP_VENDOR_URL_DESC)) {
133 modifiableDescriptors.put(Repository.REP_VENDOR_URL_DESC, "http://www.jboss.org/dna");
134 }
135 if (!modifiableDescriptors.containsKey(Repository.REP_VERSION_DESC)) {
136 modifiableDescriptors.put(Repository.REP_VERSION_DESC, "0.2");
137 }
138 modifiableDescriptors.put(Repository.SPEC_NAME_DESC, JcrI18n.SPEC_NAME_DESC.text());
139 modifiableDescriptors.put(Repository.SPEC_VERSION_DESC, "1.0");
140 this.descriptors = Collections.unmodifiableMap(modifiableDescriptors);
141 }
142
143 /**
144 * {@inheritDoc}
145 *
146 * @throws IllegalArgumentException if <code>key</code> is <code>null</code>.
147 * @see javax.jcr.Repository#getDescriptor(java.lang.String)
148 */
149 public String getDescriptor( String key ) {
150 CheckArg.isNotEmpty(key, "key");
151 return descriptors.get(key);
152 }
153
154 /**
155 * {@inheritDoc}
156 *
157 * @see javax.jcr.Repository#getDescriptorKeys()
158 */
159 public String[] getDescriptorKeys() {
160 return descriptors.keySet().toArray(new String[descriptors.size()]);
161 }
162
163 /**
164 * {@inheritDoc}
165 *
166 * @see javax.jcr.Repository#login()
167 */
168 public synchronized Session login() throws RepositoryException {
169 return login(null, null);
170 }
171
172 /**
173 * {@inheritDoc}
174 *
175 * @see javax.jcr.Repository#login(javax.jcr.Credentials)
176 */
177 public synchronized Session login( Credentials credentials ) throws RepositoryException {
178 return login(credentials, null);
179 }
180
181 /**
182 * {@inheritDoc}
183 *
184 * @see javax.jcr.Repository#login(java.lang.String)
185 */
186 public synchronized Session login( String workspaceName ) throws RepositoryException {
187 return login(null, workspaceName);
188 }
189
190 /**
191 * {@inheritDoc}
192 *
193 * @throws IllegalArgumentException if <code>credentials</code> is not <code>null</code> but:
194 * <ul>
195 * <li>provides neither a <code>getLoginContext()</code> nor a <code>getAccessControlContext()</code> method.</li>
196 * <li>provides a <code>getLoginContext()</code> method that doesn't return a {@link LoginContext}.
197 * <li>provides a <code>getLoginContext()</code> method that returns a <code>null</code> {@link LoginContext}.
198 * <li>does not provide a <code>getLoginContext()</code> method, but provides a <code>getAccessControlContext()</code>
199 * method that doesn't return an {@link AccessControlContext}.
200 * <li>does not provide a <code>getLoginContext()</code> method, but provides a <code>getAccessControlContext()</code>
201 * method that returns a <code>null</code> {@link AccessControlContext}.
202 * </ul>
203 * @see javax.jcr.Repository#login(javax.jcr.Credentials, java.lang.String)
204 */
205 public synchronized Session login( Credentials credentials,
206 String workspaceName ) throws RepositoryException {
207 // Ensure credentials are either null or provide a JAAS method
208 ExecutionContext execContext;
209 if (credentials == null) {
210 execContext = executionContextFactory.create();
211 } else {
212 try {
213 // Check if credentials provide a login context
214 try {
215 Method method = credentials.getClass().getMethod("getLoginContext");
216 if (method.getReturnType() != LoginContext.class) {
217 throw new IllegalArgumentException(JcrI18n.credentialsMustReturnLoginContext.text(credentials.getClass()));
218 }
219 LoginContext loginContext = (LoginContext)method.invoke(credentials);
220 if (loginContext == null) {
221 throw new IllegalArgumentException(JcrI18n.credentialsMustReturnLoginContext.text(credentials.getClass()));
222 }
223 execContext = executionContextFactory.create(loginContext);
224 } catch (NoSuchMethodException error) {
225 // Check if credentials provide an access control context
226 try {
227 Method method = credentials.getClass().getMethod("getAccessControlContext");
228 if (method.getReturnType() != AccessControlContext.class) {
229 throw new IllegalArgumentException(
230 JcrI18n.credentialsMustReturnAccessControlContext.text(credentials.getClass()));
231 }
232 AccessControlContext accessControlContext = (AccessControlContext)method.invoke(credentials);
233 if (accessControlContext == null) {
234 throw new IllegalArgumentException(
235 JcrI18n.credentialsMustReturnAccessControlContext.text(credentials.getClass()));
236 }
237 execContext = executionContextFactory.create(accessControlContext);
238 } catch (NoSuchMethodException error2) {
239 throw new IllegalArgumentException(JcrI18n.credentialsMustProvideJaasMethod.text(credentials.getClass()),
240 error2);
241 }
242 }
243 } catch (RuntimeException error) {
244 throw error;
245 } catch (Exception error) {
246 throw new RepositoryException(error);
247 }
248 }
249 // Authenticate if possible
250 assert execContext != null;
251 LoginContext loginContext = execContext.getLoginContext();
252 if (loginContext != null) {
253 try {
254 loginContext.login();
255 } catch (javax.security.auth.login.LoginException error) {
256 throw new LoginException(error);
257 }
258 }
259 // Ensure valid workspace name
260 if (workspaceName == null) workspaceName = JcrI18n.defaultWorkspaceName.text();
261 // Create session
262 Graph graph = Graph.create(workspaceName, connectionFactory, execContext);
263 return new JcrSession(this, workspaceName, graph, new ReferenceMap<UUID, Node>(ReferenceType.STRONG, ReferenceType.SOFT));
264 }
265 }