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 }