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