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 public static final String TEIID_SUPPORT_PROPERTY_NAME = "teiidsupport";
103
104 protected static final Set<String> ALL_PROPERTY_NAMES = Collections.unmodifiableSet(WORKSPACE_PROPERTY_NAME,
105 REPOSITORY_PROPERTY_NAME,
106 USERNAME_PROPERTY_NAME,
107 PASSWORD_PROPERTY_NAME,
108 TEIID_SUPPORT_PROPERTY_NAME);
109
110 /* URL Prefix used for JNDI access */
111 public static final String JNDI_URL_PREFIX = "jdbc:jcr:jndi:";
112 /* URL Prefix used for remote access */
113 public static final String HTTP_URL_PREFIX = "jdbc:jcr:http://";
114
115
116 private static DriverMetadata driverMetadata;
117
118 public JcrContextFactory contextFactory = null;
119
120 private static JcrDriver INSTANCE = new JcrDriver();
121
122 static {
123 try {
124 DriverManager.registerDriver(INSTANCE);
125 } catch(SQLException e) {
126 // Logging
127 String logMsg = JdbcI18n.driverErrorRegistering.text(e.getMessage()); //$NON-NLS-1$
128 logger.log(Level.SEVERE, logMsg);
129 }
130 }
131
132 public static JcrDriver getInstance() {
133 return INSTANCE;
134 }
135
136
137 /**
138 * No-arg constructor, required by the {@link DriverManager}.
139 */
140 public JcrDriver() {
141 }
142
143 /**
144 * {@inheritDoc}
145 *
146 * @see java.sql.Driver#acceptsURL(java.lang.String)
147 */
148 @Override
149 public boolean acceptsURL( String url ) {
150 return RepositoryDelegateFactory.acceptUrl(url);
151 }
152
153 /**
154 * {@inheritDoc}
155 *
156 * @see java.sql.Driver#getPropertyInfo(java.lang.String, java.util.Properties)
157 */
158 @Override
159 public DriverPropertyInfo[] getPropertyInfo( String url,
160 Properties info ) throws SQLException{
161 // Get the connection information ...
162 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).getConnectionInfo().getPropertyInfos();
163 }
164
165 /**
166 * Get the information describing the connection. This method can be overridden to return a subclass of {@link ConnectionInfo}
167 * that implements {@link ConnectionInfo#getCredentials()} for a specific JCR implementation.
168 *
169 * @param url the JDBC URL
170 * @param info the JDBC connection properties
171 * @return the connection information, or null if the URL is null or not of the proper format
172 * @throws SQLException
173 */
174 protected ConnectionInfo createConnectionInfo( String url,
175 Properties info ) throws SQLException {
176 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).getConnectionInfo();
177 }
178
179 /**
180 * {@inheritDoc}
181 * <p>
182 * Note that if the supplied properties and URL contain properties with the same name, the value from the supplied Properties
183 * object will take precedence.
184 * </p>
185 *
186 * @see java.sql.Driver#connect(java.lang.String, java.util.Properties)
187 */
188 @Override
189 public Connection connect( String url,
190 Properties info ) throws SQLException {
191
192 return RepositoryDelegateFactory.createRepositoryDelegate(url, info, this.contextFactory).createConnection();
193 }
194
195 /**
196 * {@inheritDoc}
197 *
198 * @see java.sql.Driver#getMajorVersion()
199 */
200 @Override
201 public int getMajorVersion() {
202 return getDriverMetadata().getMajorVersion();
203 }
204
205 /**
206 * {@inheritDoc}
207 *
208 * @see java.sql.Driver#getMinorVersion()
209 */
210 @Override
211 public int getMinorVersion() {
212 return getDriverMetadata().getMinorVersion();
213 }
214
215 public String getVendorName() {
216 return getDriverMetadata().getVendorName();
217 }
218
219 public String getVendorUrl() {
220 return getDriverMetadata().getVendorUrl();
221 }
222
223 public String getVersion() {
224 return getDriverMetadata().getVersion();
225 }
226
227 /**
228 * {@inheritDoc}
229 *
230 * @see java.sql.Driver#jdbcCompliant()
231 */
232 @Override
233 public boolean jdbcCompliant() {
234 return false;
235 }
236
237
238 static DriverMetadata getDriverMetadata() {
239 if (driverMetadata == null) {
240 driverMetadata = new DriverMetadata();
241 }
242 return driverMetadata;
243 }
244
245 static class DriverMetadata {
246 private final int major;
247 private final int minor;
248
249 protected DriverMetadata() {
250 String[] coords = getVersion().split("[.-]");
251 this.major = Integer.parseInt(coords[0]);
252 this.minor = Integer.parseInt(coords[1]);
253 }
254
255 public String getVendorName() {
256 return JdbcI18n.driverVendor.text();
257 }
258
259 public String getVendorUrl() {
260 return JdbcI18n.driverVendorUrl.text();
261 }
262
263 public String getVersion() {
264 return JdbcI18n.driverVersion.text();
265 }
266
267 public int getMajorVersion() {
268 return major;
269 }
270
271 public int getMinorVersion() {
272 return minor;
273 }
274
275 public String getName() {
276 return JdbcI18n.driverName.text();
277 }
278 }
279
280 public void setContextFactory( JcrContextFactory factory ) {
281 assert factory != null;
282 this.contextFactory = factory;
283 }
284
285 public interface JcrContextFactory {
286 Context createContext( Properties properties ) throws NamingException;
287 }
288
289
290
291 }