View Javadoc

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}&amp;{secondProperty}&amp;...
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&amp;password=secret&amp;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}&amp;{secondProperty}&amp;...
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 }