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 }