1 /* 2 * ModeShape (http://www.modeshape.org) 3 * See the COPYRIGHT.txt file distributed with this work for information 4 * regarding copyright ownership. Some portions may be licensed 5 * to Red Hat, Inc. under one or more contributor license agreements. 6 * See the AUTHORS.txt file in the distribution for a full listing of 7 * individual contributors. 8 * 9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape 10 * is licensed to you under the terms of the GNU Lesser General Public License as 11 * published by the Free Software Foundation; either version 2.1 of 12 * the License, or (at your option) any later version. 13 * 14 * ModeShape is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this software; if not, write to the Free 21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 23 */ 24 package org.modeshape.jdbc; 25 26 import java.sql.Connection; 27 import java.sql.DriverManager; 28 import java.sql.DriverPropertyInfo; 29 import java.sql.SQLException; 30 import java.util.Properties; 31 import java.util.Set; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 35 import javax.jcr.Repository; 36 import javax.naming.Context; 37 import javax.naming.NamingException; 38 39 import org.modeshape.jdbc.delegate.ConnectionInfo; 40 import org.modeshape.jdbc.delegate.RepositoryDelegateFactory; 41 import org.modeshape.jdbc.util.Collections; 42 43 /** 44 * A JDBC driver implementation that is able to access a JCR repository to query its contents using JCR-SQL2. <h3>Connection URLs</h3> 45 * <p> 46 * The driver accepts several URL formats based on how the repository is configured: 47 * 48 * <ol> 49 * <li>configured for <i>local</i> access using JNDI 50 * <pre> 51 * jdbc:jcr:jndi:{jndiName} 52 * </pre> 53 * 54 * or 55 * 56 * <pre> 57 * jdbc:jcr:jndi:{jndiName}?{firstProperty}&{secondProperty}&... 58 * </pre> 59 * 60 * where 61 * <ul> 62 * <li><strong>{jndiName}</strong> is the JNDI name where the {@link Repository} or {@literal org.modeshape.jcr.api.Repositories} instance can be found;</li> 63 * <li><strong>{firstProperty}</strong> consists of the first property name followed by '=' followed by the property's value;</li> 64 * <li><strong>{secondProperty}</strong> consists of the second property name followed by '=' followed by the property's value;</li> 65 * </ul> 66 * Note that any use of URL encoding ('%' followed by a two-digit hexadecimal value) will be decoded before being used. 67 * </p> 68 * <p> 69 * Here's an example of a URL that defines a {@link Repository} instance located at "<code>jcr/local</code>" with a 70 * repository name of "repository" and a user, password of "secret", and workspace name of "My Workspace": 71 * 72 * <pre> 73 * jdbc:jcr:jndi:jcr/local?repositoryName=repository&user=jsmith&password=secret&workspace=My%20Workspace 74 * </pre> 75 * 76 * The "repository" property is required only if the object in JNDI is a {@literal org.modeshape.jcr.api.Repositories} object. 77 * <br /><br /> 78 * <li>configured for <i>remote</i> access using REST interface. 79 * <pre> 80 * jdbc:jcr:http://{hostname}:{port}?{firstProperty}&{secondProperty}&... 81 * </pre> 82 * where 83 * <ul> 84 * <li><strong>{hostname}</strong> is the host name where the {@link Repository} or {@literal org.modeshape.jcr.api.Repositories} instance can be found;</li> 85 * <li><strong>{port}</strong> is the port to access the {@link Repository} or {@literal org.modeshape.jcr.api.Repositories} on the specified <i>hostname</i>;</li> 86 * <li><strong>{firstProperty}</strong> consists of the first property name followed by '=' followed by the property's value;</li> 87 * <li><strong>{secondProperty}</strong> consists of the second property name followed by '=' followed by the property's value;</li> 88 * </ul> 89 * Note that any use of URL encoding ('%' followed by a two-digit hexadecimal value) will be decoded before being used. 90 * </p> 91 * Note that any use of URL encoding ('%' followed by a two-digit hexadecimal value) will be decoded before being used. 92 * 93 */ 94 95 public class JcrDriver implements java.sql.Driver { 96 protected static Logger logger = Logger.getLogger("org.modeshape.jdbc"); //$NON-NLS-1$ 97 98 public static final String WORKSPACE_PROPERTY_NAME = "workspace"; 99 public static final String REPOSITORY_PROPERTY_NAME = "repositoryName"; 100 public static final String USERNAME_PROPERTY_NAME = "user"; 101 public static final String PASSWORD_PROPERTY_NAME = "password"; 102 103 protected static final Set<String> ALL_PROPERTY_NAMES = Collections.unmodifiableSet(WORKSPACE_PROPERTY_NAME, 104 REPOSITORY_PROPERTY_NAME, 105 USERNAME_PROPERTY_NAME, 106 PASSWORD_PROPERTY_NAME); 107 108 /* URL Prefix used for JNDI access */ 109 public static final String JNDI_URL_PREFIX = "jdbc:jcr:jndi:"; 110 /* URL Prefix used for remote access */ 111 public static final String HTTP_URL_PREFIX = "jdbc:jcr:http://"; 112 113 114 private static DriverMetadata driverMetadata; 115 116 public JcrContextFactory contextFactory = null; 117 118 private static JcrDriver INSTANCE = new JcrDriver(); 119 120 static { 121 try { 122 DriverManager.registerDriver(INSTANCE); 123 } catch(SQLException e) { 124 // Logging 125 String logMsg = JdbcI18n.driverErrorRegistering.text(e.getMessage()); //$NON-NLS-1$ 126 logger.log(Level.SEVERE, logMsg); 127 } 128 } 129 130 public static JcrDriver getInstance() { 131 return INSTANCE; 132 } 133 134 135 /** 136 * No-arg constructor, required by the {@link DriverManager}. 137 */ 138 public JcrDriver() { 139 } 140 141 /** 142 * {@inheritDoc} 143 * 144 * @see java.sql.Driver#acceptsURL(java.lang.String) 145 */ 146 @Override 147 public boolean acceptsURL( String url ) { 148 return RepositoryDelegateFactory.acceptUrl(url); 149 } 150 151 /** 152 * {@inheritDoc} 153 * 154 * @see java.sql.Driver#getPropertyInfo(java.lang.String, java.util.Properties) 155 */ 156 @Override 157 public DriverPropertyInfo[] getPropertyInfo( String url, 158 Properties info ) throws SQLException{ 159 // Get the connection information ... 160 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).getConnectionInfo().getPropertyInfos(); 161 } 162 163 /** 164 * Get the information describing the connection. This method can be overridden to return a subclass of {@link ConnectionInfo} 165 * that implements {@link ConnectionInfo#getCredentials()} for a specific JCR implementation. 166 * 167 * @param url the JDBC URL 168 * @param info the JDBC connection properties 169 * @return the connection information, or null if the URL is null or not of the proper format 170 * @throws SQLException 171 */ 172 protected ConnectionInfo createConnectionInfo( String url, 173 Properties info ) throws SQLException { 174 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).getConnectionInfo(); 175 } 176 177 /** 178 * {@inheritDoc} 179 * <p> 180 * Note that if the supplied properties and URL contain properties with the same name, the value from the supplied Properties 181 * object will take precedence. 182 * </p> 183 * 184 * @see java.sql.Driver#connect(java.lang.String, java.util.Properties) 185 */ 186 @Override 187 public Connection connect( String url, 188 Properties info ) throws SQLException { 189 190 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).createConnection(); 191 } 192 193 /** 194 * {@inheritDoc} 195 * 196 * @see java.sql.Driver#getMajorVersion() 197 */ 198 @Override 199 public int getMajorVersion() { 200 return getDriverMetadata().getMajorVersion(); 201 } 202 203 /** 204 * {@inheritDoc} 205 * 206 * @see java.sql.Driver#getMinorVersion() 207 */ 208 @Override 209 public int getMinorVersion() { 210 return getDriverMetadata().getMinorVersion(); 211 } 212 213 public String getVendorName() { 214 return getDriverMetadata().getVendorName(); 215 } 216 217 public String getVendorUrl() { 218 return getDriverMetadata().getVendorUrl(); 219 } 220 221 public String getVersion() { 222 return getDriverMetadata().getVersion(); 223 } 224 225 /** 226 * {@inheritDoc} 227 * 228 * @see java.sql.Driver#jdbcCompliant() 229 */ 230 @Override 231 public boolean jdbcCompliant() { 232 return false; 233 } 234 235 236 static DriverMetadata getDriverMetadata() { 237 if (driverMetadata == null) { 238 driverMetadata = new DriverMetadata(); 239 } 240 return driverMetadata; 241 } 242 243 static class DriverMetadata { 244 private final int major; 245 private final int minor; 246 247 protected DriverMetadata() { 248 String[] coords = getVersion().split("[.-]"); 249 this.major = Integer.parseInt(coords[0]); 250 this.minor = Integer.parseInt(coords[1]); 251 } 252 253 public String getVendorName() { 254 return JdbcI18n.driverVendor.text(); 255 } 256 257 public String getVendorUrl() { 258 return JdbcI18n.driverVendorUrl.text(); 259 } 260 261 public String getVersion() { 262 return JdbcI18n.driverVersion.text(); 263 } 264 265 public int getMajorVersion() { 266 return major; 267 } 268 269 public int getMinorVersion() { 270 return minor; 271 } 272 273 public String getName() { 274 return JdbcI18n.driverName.text(); 275 } 276 } 277 278 void setContextFactory( JcrContextFactory factory ) { 279 assert factory != null; 280 this.contextFactory = factory; 281 } 282 283 public interface JcrContextFactory { 284 Context createContext( Properties properties ) throws NamingException; 285 } 286 287 288 289 }