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.Connection;
019import java.sql.DriverPropertyInfo;
020import java.sql.SQLException;
021import java.util.ArrayList;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Properties;
025import java.util.Set;
026import javax.jcr.LoginException;
027import javax.jcr.NoSuchWorkspaceException;
028import javax.jcr.Repository;
029import javax.jcr.RepositoryException;
030import javax.jcr.Workspace;
031import javax.jcr.nodetype.NodeType;
032import javax.jcr.nodetype.NodeTypeIterator;
033import javax.jcr.query.Query;
034import javax.jcr.query.QueryResult;
035import javax.naming.Context;
036import javax.naming.InitialContext;
037import javax.naming.NamingException;
038import org.modeshape.jcr.api.Repositories;
039import org.modeshape.jdbc.JdbcLocalI18n;
040import org.modeshape.jdbc.LocalJcrDriver;
041import org.modeshape.jdbc.LocalJcrDriver.JcrContextFactory;
042
043/**
044 * The LocalRepositoryDelegate provides a local Repository implementation to access the Jcr layer via JNDI Lookup.
045 */
046public class LocalRepositoryDelegate extends AbstractRepositoryDelegate {
047    public static final RepositoryDelegateFactory FACTORY = new RepositoryDelegateFactory() {};
048
049    private static final String JNDI_EXAMPLE_URL = LocalJcrDriver.JNDI_URL_PREFIX + "{jndiName}";
050
051    protected static final Set<LocalSession> TRANSACTION_IDS = java.util.Collections.synchronizedSet(new HashSet<LocalSession>());
052
053    private JcrContextFactory jcrContext = null;
054    private Repository repository = null;
055
056    public LocalRepositoryDelegate( String url,
057                                    Properties info,
058                                    JcrContextFactory contextFactory ) {
059        super(url, info);
060
061        if (contextFactory == null) {
062            jcrContext = new JcrContextFactory() {
063                @Override
064                public Context createContext( Properties properties ) throws NamingException {
065                    return ((properties == null || properties.isEmpty()) ? new InitialContext() : new InitialContext(properties));
066                }
067            };
068        } else {
069            this.jcrContext = contextFactory;
070        }
071    }
072
073    @Override
074    protected ConnectionInfo createConnectionInfo( String url,
075                                                   Properties info ) {
076        return new JNDIConnectionInfo(url, info);
077    }
078
079    protected JcrContextFactory getJcrContext() {
080        return jcrContext;
081    }
082
083    private LocalSession getLocalSession() throws LoginException, NoSuchWorkspaceException, RepositoryException {
084        return LocalSession.getLocalSessionInstance().getLocalSession(repository, getConnectionInfo());
085    }
086
087    private LocalSession getCurrentLocalSession() {
088        return LocalSession.getLocalSessionInstance().getLocalSession();
089    }
090
091    @Override
092    public String getDescriptor( String descriptorKey ) {
093        return repository.getDescriptor(descriptorKey);
094    }
095
096    @Override
097    public NodeType nodeType( String name ) throws RepositoryException {
098        LocalSession localSession = getLocalSession();
099        return localSession.getSession().getWorkspace().getNodeTypeManager().getNodeType(name);
100    }
101
102    @Override
103    public List<NodeType> nodeTypes() throws RepositoryException {
104
105        LocalSession localSession = getLocalSession();
106        List<NodeType> types = new ArrayList<NodeType>();
107        NodeTypeIterator its = localSession.getSession().getWorkspace().getNodeTypeManager().getAllNodeTypes();
108        while (its.hasNext()) {
109            types.add((NodeType)its.next());
110        }
111        return types;
112
113    }
114
115    /**
116     * This execute method is used for redirection so that the JNDI implementation can control calling execute.
117     * 
118     * @see java.sql.Statement#execute(java.lang.String)
119     */
120    @Override
121    public QueryResult execute( String query,
122                                String language ) throws RepositoryException {
123        logger.trace("Executing query: {0}", query);
124
125        // Create the query ...
126
127        final Query jcrQuery = getLocalSession().getSession().getWorkspace().getQueryManager().createQuery(query, language);
128        return jcrQuery.execute();
129    }
130
131    @Override
132    public String explain( String query,
133                           String language ) throws RepositoryException {
134        logger.trace("Explaining query: {0}", query);
135
136        // Create the query ...
137
138        final org.modeshape.jcr.api.query.Query jcrQuery = (org.modeshape.jcr.api.query.Query)getLocalSession().getSession().getWorkspace().getQueryManager().createQuery(query,
139                                                                                                                                                                          language);
140        return jcrQuery.explain().getPlan();
141    }
142
143    @Override
144    protected void initRepository() throws SQLException {
145        if (repository != null) {
146            return;
147        }
148        logger.debug("Creating repository for LocalRepositoryDelegate");
149
150        Set<String> repositoryNames = null;
151        ConnectionInfo connInfo = this.getConnectionInfo();
152        assert connInfo != null;
153
154        // Look up the object in JNDI and find the JCR Repository object ...
155        String jndiName = connInfo.getRepositoryPath();
156        if (jndiName == null) {
157            String msg = JdbcLocalI18n.urlMustContainJndiNameOfRepositoryOrRepositoriesObject.text();
158            throw new SQLException(msg);
159        }
160
161        Context context = null;
162        try {
163            context = this.jcrContext.createContext(connInfo.getProperties());
164        } catch (NamingException e) {
165            throw new SQLException(JdbcLocalI18n.unableToGetJndiContext.text(e.getLocalizedMessage()));
166        }
167        if (context == null) {
168            throw new SQLException(JdbcLocalI18n.unableToFindObjectInJndi.text(jndiName));
169
170        }
171        String repositoryName = "NotAssigned";
172        try {
173
174            Object target = context.lookup(jndiName);
175            repositoryName = connInfo.getRepositoryName();
176
177            if (target instanceof Repositories) {
178                logger.trace("JNDI Lookup found Repositories ");
179                Repositories repositories = (Repositories)target;
180
181                if (repositoryName == null) {
182                    repositoryNames = repositories.getRepositoryNames();
183                    if (repositoryNames == null || repositoryNames.isEmpty()) {
184                        throw new SQLException(JdbcLocalI18n.noRepositoryNamesFound.text());
185                    }
186                    if (repositoryNames.size() == 1) {
187                        repositoryName = repositoryNames.iterator().next();
188                        connInfo.setRepositoryName(repositoryName);
189                        logger.trace("Setting Repository {0} as default", repositoryName);
190
191                    } else {
192                        throw new SQLException(JdbcLocalI18n.objectInJndiIsRepositories.text(jndiName));
193                    }
194                }
195                try {
196                    repository = repositories.getRepository(repositoryName);
197                } catch (RepositoryException e) {
198                    throw new SQLException(JdbcLocalI18n.unableToFindNamedRepository.text(jndiName, repositoryName));
199                }
200            } else if (target instanceof Repository) {
201                logger.trace("JNDI Lookup found a Repository");
202                repository = (Repository)target;
203                repositoryNames = new HashSet<String>(1);
204
205                if (repositoryName == null) {
206                    repositoryName = ("DefaultRepository");
207                    connInfo.setRepositoryName(repositoryName);
208                }
209
210                repositoryNames.add(repositoryName);
211            } else {
212                throw new SQLException(JdbcLocalI18n.objectInJndiMustBeRepositoryOrRepositories.text(jndiName));
213            }
214            assert repository != null;
215        } catch (NamingException e) {
216            throw new SQLException(JdbcLocalI18n.unableToFindObjectInJndi.text(jndiName), e);
217        }
218        this.setRepositoryName(repositoryName);
219        this.setRepositoryNames(repositoryNames);
220    }
221
222    @Override
223    public boolean isValid( final int timeout ) throws RepositoryException {
224
225        LocalSession ls = getLocalSession();
226        if (!ls.getSession().isLive()) {
227            ls.remove();
228            return false;
229        }
230
231        return true;
232    }
233
234    @Override
235    public void closeStatement() {
236        LocalSession session = getCurrentLocalSession();
237        try {
238            if (session != null) {
239                session.remove();
240            }
241        } catch (Exception e) {
242            // do nothing
243        }
244    }
245
246    @Override
247    public void close() {
248        for (LocalSession id : TRANSACTION_IDS) {
249            id.remove();
250        }
251        this.repository = null;
252    }
253
254    @SuppressWarnings( "unused" )
255    @Override
256    public void rollback() throws RepositoryException {
257        closeStatement();
258    }
259
260    @Override
261    public <T> T unwrap( Class<T> iface ) throws SQLException {
262
263        try {
264            if (iface.isInstance(this)) {
265                return iface.cast(this);
266            }
267
268            if (iface.isInstance(Workspace.class)) {
269                Workspace workspace = getLocalSession().getSession().getWorkspace();
270                return iface.cast(workspace);
271            }
272        } catch (RepositoryException re) {
273            throw new SQLException(re.getLocalizedMessage());
274        }
275
276        throw new SQLException(JdbcLocalI18n.classDoesNotImplementInterface.text(Connection.class.getSimpleName(),
277                                                                                 iface.getName()));
278    }
279
280    class JNDIConnectionInfo extends ConnectionInfo {
281
282        protected JNDIConnectionInfo( String url,
283                                      Properties properties ) {
284            super(url, properties);
285        }
286
287        @Override
288        public String getUrlExample() {
289            return JNDI_EXAMPLE_URL;
290        }
291
292        @Override
293        public String getUrlPrefix() {
294            return LocalJcrDriver.JNDI_URL_PREFIX;
295        }
296
297        @Override
298        protected void addRepositoryNamePropertyInfo( List<DriverPropertyInfo> results ) {
299            boolean no_errors = results.size() == 0;
300            boolean nameRequired = false;
301            if (getRepositoryName() == null) {
302                boolean found = false;
303                if (no_errors) {
304                    try {
305                        Context context = getJcrContext().createContext(getProperties());
306                        Object obj = context.lookup(getRepositoryPath());
307                        if (obj instanceof Repositories) {
308                            nameRequired = true;
309                            found = true;
310                        } else if (obj instanceof Repository) {
311                            found = true;
312                        }
313                    } catch (NamingException e) {
314                        // do nothing about it ...
315                    }
316                }
317                if (nameRequired || !found) {
318                    DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.repositoryNamePropertyName.text(), null);
319                    info.description = JdbcLocalI18n.repositoryNamePropertyDescription.text();
320                    info.required = nameRequired;
321                    info.choices = null;
322                    results.add(info);
323                }
324            }
325        }
326    }
327
328}