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.delegate;
017
018import java.sql.DriverPropertyInfo;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Properties;
022import javax.jcr.Credentials;
023import javax.jcr.SimpleCredentials;
024import org.modeshape.common.text.TextDecoder;
025import org.modeshape.common.text.UrlEncoder;
026import org.modeshape.common.util.StringUtil;
027import org.modeshape.jcr.api.Repositories;
028import org.modeshape.jdbc.JdbcLocalI18n;
029import org.modeshape.jdbc.LocalJcrDriver;
030
031/**
032 * The ConnectionInfo contains the information used to connect to the Jcr Repository.
033 */
034public abstract class ConnectionInfo {
035    public static final TextDecoder URL_DECODER = new UrlEncoder();
036
037    protected String url;
038    protected String repositoryPath;
039    protected Properties properties;
040    private char propertyDelimiter = '?';
041
042    protected ConnectionInfo( String url,
043                              Properties properties ) {
044        this.url = url;
045        this.properties = properties;
046    }
047
048    protected void init() {
049        Properties props = getProperties() != null ? (Properties)getProperties().clone() : new Properties();
050        repositoryPath = getUrl().substring(this.getUrlPrefix().length());
051
052        // Find any URL parameters ...
053        int questionMarkIndex = repositoryPath.indexOf('?');
054        if (questionMarkIndex != -1) {
055            if (repositoryPath.length() > questionMarkIndex + 1) {
056                String paramStr = repositoryPath.substring(questionMarkIndex + 1);
057                for (String param : paramStr.split("&")) {
058                    String[] pair = param.split("=");
059                    if (pair.length > 1) {
060                        String key = URL_DECODER.decode(pair[0] != null ? pair[0].trim() : null);
061                        String value = URL_DECODER.decode(pair[1] != null ? pair[1].trim() : null);
062                        if (!props.containsKey(key)) {
063                            props.put(key, value);
064                        }
065                    }
066                }
067            }
068            repositoryPath = repositoryPath.substring(0, questionMarkIndex).trim();
069        }
070
071        Properties newprops = new Properties();
072        newprops.putAll(props);
073        this.setProperties(newprops);
074        String url = getUrl();
075        this.setUrl(url != null ? url.trim() : null);
076    }
077
078    /**
079     * Get the original URL of the connection.
080     * 
081     * @return the URL; never null
082     */
083    public String getUrl() {
084        return url;
085    }
086
087    /**
088     * Get the part of the {@link #getUrl()} that indicates the path to connect to the Repository. This value should be prefixed
089     * by the {@link #getUrlPrefix()} in the {@link #getUrl()}.
090     * 
091     * @return String
092     */
093    public String getRepositoryPath() {
094        return this.repositoryPath;
095    }
096
097    /**
098     * Get the immutable properties for the connection.
099     * 
100     * @return the properties; never null
101     */
102    public Properties getProperties() {
103        return properties;
104    }
105
106    /**
107     * Get the name of the repository. This is required only if the {@link Repositories} instance is being used to obtain the
108     * Repository.
109     * 
110     * @return the name of the repository, or null if no repository name was specified
111     */
112    public String getRepositoryName() {
113        return properties.getProperty(LocalJcrDriver.REPOSITORY_PROPERTY_NAME);
114    }
115
116    /**
117     * Call to set the repository name. This is called when no repository name is set on the URL, but there is only one repository
118     * in the list.
119     * 
120     * @param repositoryName
121     */
122    void setRepositoryName( String repositoryName ) {
123        this.properties.setProperty(LocalJcrDriver.REPOSITORY_PROPERTY_NAME, repositoryName);
124    }
125
126    /**
127     * Get the name of the workspace. This is not required, and if abscent implies obtaining the JCR Repository's default
128     * workspace.
129     * 
130     * @return the name of the workspace, or null if no workspace name was specified
131     */
132    public String getWorkspaceName() {
133        return properties.getProperty(LocalJcrDriver.WORKSPACE_PROPERTY_NAME);
134    }
135
136    /**
137     * Call to set the workspace name. This is not required, and if abscent implies obtaining the JCR Repository's default
138     * workspace.
139     * 
140     * @param workSpaceName
141     */
142    public void setWorkspaceName( String workSpaceName ) {
143        properties.setProperty(LocalJcrDriver.WORKSPACE_PROPERTY_NAME, workSpaceName);
144    }
145
146    /**
147     * Get the JCR user name. This is not required, and if abscent implies that no credentials should be used when obtaining a JCR
148     * Session.
149     * 
150     * @return the JCR user name, or null if no user name was specified
151     */
152    public String getUsername() {
153        return properties.getProperty(LocalJcrDriver.USERNAME_PROPERTY_NAME);
154    }
155
156    /**
157     * Get the JCR password. This is not required.
158     * 
159     * @return the JCR password, or null if no password was specified
160     */
161    public char[] getPassword() {
162        String result = properties.getProperty(LocalJcrDriver.PASSWORD_PROPERTY_NAME);
163        return result != null ? result.toCharArray() : null;
164    }
165
166    /**
167     * Return true of Teiid support is required for this connection.
168     * 
169     * @return true if Teiid support is required.
170     */
171    public boolean isTeiidSupport() {
172        String result = properties.getProperty(LocalJcrDriver.TEIID_SUPPORT_PROPERTY_NAME);
173        if (result == null) {
174            return false;
175        }
176        return result.equalsIgnoreCase(Boolean.TRUE.toString());
177    }
178
179    void setUrl( String url ) {
180        this.url = url;
181    }
182
183    void setProperties( Properties properties ) {
184        this.properties = properties;
185    }
186
187    /**
188     * Get the effective URL of this connection, which places all properties on the URL (with a '*' for each character in the
189     * password property)
190     * 
191     * @return the effective URL; never null
192     */
193    public String getEffectiveUrl() {
194        StringBuilder url = new StringBuilder(this.getUrlPrefix());
195        url.append(this.getRepositoryPath());
196        char propertyDelim = getPropertyDelimiter();
197        for (String propertyName : getProperties().stringPropertyNames()) {
198            String value = getProperties().getProperty(propertyName);
199            if (value == null) {
200                continue;
201            }
202            if (LocalJcrDriver.PASSWORD_PROPERTY_NAME.equals(propertyName)) {
203                value = StringUtil.createString('*', value.length());
204            }
205            url.append(propertyDelim).append(propertyName).append('=').append(value);
206            propertyDelim = '&';
207        }
208        return url.toString();
209    }
210
211    /**
212     * Return the starting property delimiter
213     * 
214     * @return char property delimiter
215     */
216    protected char getPropertyDelimiter() {
217        return propertyDelimiter;
218    }
219
220    protected void setPropertyDelimiter( char delimiter ) {
221        this.propertyDelimiter = delimiter;
222    }
223
224    /**
225     * Obtain the array of {@link DriverPropertyInfo} objects that describe the missing properties.
226     * 
227     * @return DriverPropertyInfo the property infos; never null but possibly empty
228     */
229    public DriverPropertyInfo[] getPropertyInfos() {
230        List<DriverPropertyInfo> results = new ArrayList<DriverPropertyInfo>();
231
232        addUrlPropertyInfo(results);
233        addUserNamePropertyInfo(results);
234        addPasswordPropertyInfo(results);
235        addWorkspacePropertyInfo(results);
236        addRepositoryNamePropertyInfo(results);
237
238        return results.toArray(new DriverPropertyInfo[results.size()]);
239    }
240
241    protected void addUrlPropertyInfo( List<DriverPropertyInfo> results ) {
242        if (getUrl() == null) {
243            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.urlPropertyName.text(), null);
244            info.description = JdbcLocalI18n.urlPropertyDescription.text(this.getEffectiveUrl(), getUrlExample());
245            info.required = true;
246            info.choices = new String[] {getUrlExample()};
247            results.add(info);
248        }
249    }
250
251    protected void addUserNamePropertyInfo( List<DriverPropertyInfo> results ) {
252        if (getUsername() == null) {
253            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.usernamePropertyName.text(), null);
254            info.description = JdbcLocalI18n.usernamePropertyDescription.text();
255            info.required = false;
256            info.choices = null;
257            results.add(info);
258        }
259    }
260
261    protected void addPasswordPropertyInfo( List<DriverPropertyInfo> results ) {
262        if (getPassword() == null) {
263            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.passwordPropertyName.text(), null);
264            info.description = JdbcLocalI18n.passwordPropertyDescription.text();
265            info.required = false;
266            info.choices = null;
267            results.add(info);
268        }
269    }
270
271    protected void addWorkspacePropertyInfo( List<DriverPropertyInfo> results ) {
272        if (getWorkspaceName() == null) {
273            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.workspaceNamePropertyName.text(), null);
274            info.description = JdbcLocalI18n.workspaceNamePropertyDescription.text();
275            info.required = false;
276            info.choices = null;
277            results.add(info);
278        }
279    }
280
281    protected void addRepositoryNamePropertyInfo( List<DriverPropertyInfo> results ) {
282        if (getRepositoryName() == null) {
283            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.repositoryNamePropertyName.text(), null);
284            info.description = JdbcLocalI18n.repositoryNamePropertyDescription.text();
285            info.required = true;
286            info.choices = null;
287            results.add(info);
288        }
289    }
290
291    /**
292     * The delegate should provide an example of the URL to be used
293     * 
294     * @return String url example
295     */
296    public abstract String getUrlExample();
297
298    /**
299     * The delegate should provide the prefix defined by the {@link LocalJcrDriver}
300     * 
301     * @return String url prefix
302     */
303    public abstract String getUrlPrefix();
304
305    /**
306     * Return the credentials based on the user name and password.
307     * 
308     * @return Credentials
309     */
310    public Credentials getCredentials() {
311        String username = getUsername();
312        char[] password = getPassword();
313        if (username != null) {
314            return new SimpleCredentials(username, password);
315        }
316        return null;
317    }
318
319}