001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.jdbc; 017 018import java.sql.Connection; 019import java.sql.DriverManager; 020import java.sql.DriverPropertyInfo; 021import java.sql.SQLException; 022import java.sql.SQLFeatureNotSupportedException; 023import java.util.Properties; 024import java.util.Set; 025import javax.jcr.Repository; 026import javax.naming.Context; 027import javax.naming.NamingException; 028import org.modeshape.common.collection.Collections; 029import org.modeshape.common.logging.Logger; 030import org.modeshape.jdbc.delegate.ConnectionInfo; 031import org.modeshape.jdbc.delegate.LocalRepositoryDelegate; 032import org.modeshape.jdbc.delegate.RepositoryDelegate; 033import org.modeshape.jdbc.delegate.RepositoryDelegateFactory; 034 035/** 036 * A JDBC driver implementation that is able to access a JCR repository to query its contents using JCR-SQL2. <h3>Connection URLs</h3> 037 * <p> 038 * The driver accepts the following URL format for accessing a <i>local</i> JCR repository using JNDI: 039 * 040 * <pre> 041 * jdbc:jcr:jndi:{jndiName} 042 * </pre> 043 * 044 * or 045 * 046 * <pre> 047 * jdbc:jcr:jndi:{jndiName}?{firstProperty}&{secondProperty}&... 048 * </pre> 049 * 050 * where 051 * <ul> 052 * <li><strong>{jndiName}</strong> is the JNDI name where the {@link Repository} or {@literal org.modeshape.jcr.api.Repositories} 053 * instance can be found;</li> 054 * <li><strong>{firstProperty}</strong> consists of the first property name followed by '=' followed by the property's value;</li> 055 * <li><strong>{secondProperty}</strong> consists of the second property name followed by '=' followed by the property's value;</li> 056 * </ul> 057 * Note that any use of URL encoding ('%' followed by a two-digit hexadecimal value) will be decoded before being used. 058 * </p> 059 * <p> 060 * Here's an example of a URL that defines a {@link Repository} instance located at "<code>jcr/local</code>" with a repository 061 * name of "repository" and a user, password of "secret", and workspace name of "My Workspace": 062 * 063 * <pre> 064 * jdbc:jcr:jndi:jcr/local?repositoryName=repository&user=jsmith&password=secret&workspace=My%20Workspace 065 * </pre> 066 * 067 * The "repository" property is required only if the object in JNDI is a {@literal org.modeshape.jcr.api.Repositories} object. 068 * </p> 069 * <p> 070 * Note that any use of URL encoding ('%' followed by a two-digit hexadecimal value) will be decoded before being used. 071 * </p> 072 */ 073 074public class LocalJcrDriver implements java.sql.Driver { 075 protected static Logger logger = Logger.getLogger("org.modeshape.jdbc"); //$NON-NLS-1$ 076 077 public static final String WORKSPACE_PROPERTY_NAME = "workspace"; 078 public static final String REPOSITORY_PROPERTY_NAME = "repositoryName"; 079 public static final String USERNAME_PROPERTY_NAME = "user"; 080 public static final String PASSWORD_PROPERTY_NAME = "password"; 081 public static final String TEIID_SUPPORT_PROPERTY_NAME = "teiidsupport"; 082 083 protected static final Set<String> ALL_PROPERTY_NAMES = Collections.unmodifiableSet(WORKSPACE_PROPERTY_NAME, 084 REPOSITORY_PROPERTY_NAME, 085 USERNAME_PROPERTY_NAME, 086 PASSWORD_PROPERTY_NAME, 087 TEIID_SUPPORT_PROPERTY_NAME); 088 089 /* URL Prefix used for JNDI access */ 090 public static final String JNDI_URL_PREFIX = "jdbc:jcr:jndi:"; 091 092 private static LocalJcrDriver INSTANCE = new LocalJcrDriver(); 093 094 static { 095 try { 096 DriverManager.registerDriver(INSTANCE); 097 } catch (SQLException e) { 098 // Logging 099 logger.error(JdbcLocalI18n.driverErrorRegistering, e.getMessage()); 100 } 101 } 102 103 private final JcrContextFactory contextFactory; 104 private final RepositoryDelegateFactory delegateFactory; 105 private final DriverInfo driverInfo; 106 107 /** 108 * No-arg constructor, required by the {@link DriverManager}. 109 */ 110 public LocalJcrDriver() { 111 this(null); 112 } 113 114 /** 115 * Create an instance of this driver using the supplied JNDI naming context factory. This is useful for testing, but is 116 * otherwise not generally recommended. 117 * 118 * @param namingContextFactory the naming context factory; may be null if one should be created automatically 119 */ 120 protected LocalJcrDriver( JcrContextFactory namingContextFactory ) { 121 this(LocalRepositoryDelegate.FACTORY, new DriverInfo(JdbcLocalI18n.driverName.text(), JdbcLocalI18n.driverVendor.text(), 122 JdbcLocalI18n.driverVendorUrl.text(), 123 JdbcLocalI18n.driverVersion.text()), namingContextFactory); 124 } 125 126 /** 127 * Constructor for subclasses, that should be called by subclasses no-arg constructor. 128 * 129 * @param delegateFactory the factory that should be used to create {@link RepositoryDelegate} instances; may not be null 130 * @param driverInfo the information about the driver; may not be null 131 * @param namingContextFactory the naming context factory; may be null if one should be created automatically 132 */ 133 protected LocalJcrDriver( RepositoryDelegateFactory delegateFactory, 134 DriverInfo driverInfo, 135 JcrContextFactory namingContextFactory ) { 136 assert delegateFactory != null; 137 assert driverInfo != null; 138 this.delegateFactory = delegateFactory; 139 this.driverInfo = driverInfo; 140 this.contextFactory = namingContextFactory; 141 } 142 143 @Override 144 public boolean acceptsURL( String url ) { 145 return delegateFactory.acceptUrl(url); 146 } 147 148 @Override 149 public DriverPropertyInfo[] getPropertyInfo( String url, 150 Properties info ) throws SQLException { 151 // Get the connection information ... 152 RepositoryDelegate repositoryDelegate = delegateFactory.createRepositoryDelegate(url, info, this.contextFactory); 153 ConnectionInfo connectionInfo = repositoryDelegate.getConnectionInfo(); 154 return connectionInfo.getPropertyInfos(); 155 } 156 157 /** 158 * Get the information describing the connection. This method can be overridden to return a subclass of {@link ConnectionInfo} 159 * that implements {@link ConnectionInfo#getCredentials()} for a specific JCR implementation. 160 * 161 * @param url the JDBC URL 162 * @param info the JDBC connection properties 163 * @return the connection information, or null if the URL is null or not of the proper format 164 * @throws SQLException 165 */ 166 protected ConnectionInfo createConnectionInfo( String url, 167 Properties info ) throws SQLException { 168 RepositoryDelegate repositoryDelegate = delegateFactory.createRepositoryDelegate(url, info, this.contextFactory); 169 return repositoryDelegate.getConnectionInfo(); 170 } 171 172 /** 173 * {@inheritDoc} 174 * <p> 175 * Note that if the supplied properties and URL contain properties with the same name, the value from the supplied Properties 176 * object will take precedence. 177 * </p> 178 * 179 * @see java.sql.Driver#connect(java.lang.String, java.util.Properties) 180 */ 181 @Override 182 public Connection connect( String url, 183 Properties info ) throws SQLException { 184 if (!acceptsURL(url)) { 185 return null; 186 } 187 RepositoryDelegate repositoryDelegate = delegateFactory.createRepositoryDelegate(url, info, this.contextFactory); 188 return repositoryDelegate.createConnection(getDriverInfo()); 189 } 190 191 @Override 192 public int getMajorVersion() { 193 return getDriverInfo().getMajorVersion(); 194 } 195 196 @Override 197 public int getMinorVersion() { 198 return getDriverInfo().getMinorVersion(); 199 } 200 201 public String getVendorName() { 202 return getDriverInfo().getVendorName(); 203 } 204 205 public String getVendorUrl() { 206 return getDriverInfo().getVendorUrl(); 207 } 208 209 public String getVersion() { 210 return getDriverInfo().getVersion(); 211 } 212 213 @Override 214 public boolean jdbcCompliant() { 215 return false; 216 } 217 218 protected final DriverInfo getDriverInfo() { 219 return driverInfo; 220 } 221 222 /** 223 * This method always throws {@link SQLFeatureNotSupportedException}. 224 * <p> 225 * <em>Note:</em> This method is part of the JDBC API in JDK 1.7. 226 * </p> 227 * 228 * @return the parent logger 229 * @throws SQLFeatureNotSupportedException 230 */ 231 @Override 232 public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { 233 throw new SQLFeatureNotSupportedException(); 234 } 235 236 /** 237 * And interface that can be passed to this driver's constructor to create the JNDI naming context given the set of connection 238 * properties. 239 */ 240 public interface JcrContextFactory { 241 /** 242 * Create a JNDI naming context from the supplied connection properties. 243 * 244 * @param properties the connection properties; may be null or empty 245 * @return the naming context; may not be null 246 * @throws NamingException if there is a problem creating or obtaining the naming context 247 */ 248 Context createContext( Properties properties ) throws NamingException; 249 } 250 251}