/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jdbc.delegate; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicReference; import javax.jcr.RepositoryException; import javax.jcr.nodetype.NodeType; import javax.jcr.query.QueryResult; import org.modeshape.jdbc.JcrDriver; import org.modeshape.jdbc.JdbcLocalI18n; import org.modeshape.jdbc.LocalJcrDriver.JcrContextFactory; import org.modeshape.jdbc.rest.ModeShapeRestClient; import org.modeshape.jdbc.rest.NodeTypes; import org.modeshape.jdbc.rest.Repositories; /** * The HTTPRepositoryDelegate provides remote Repository implementation to access the Jcr layer via HTTP lookup. */ public class HttpRepositoryDelegate extends AbstractRepositoryDelegate { protected static final int PROTOCOL_HTTP = 2; public static final RepositoryDelegateFactory FACTORY = new RepositoryDelegateFactory() { @Override protected int determineProtocol( String url ) { if (url.startsWith(JcrDriver.HTTP_URL_PREFIX) && url.length() > JcrDriver.HTTP_URL_PREFIX.length()) { // This fits the pattern so far ... return PROTOCOL_HTTP; } return super.determineProtocol(url); } @Override protected RepositoryDelegate create( int protocol, String url, Properties info, JcrContextFactory contextFactory ) { if (protocol == PROTOCOL_HTTP) { return new HttpRepositoryDelegate(url, info); } return super.create(protocol, url, info, contextFactory); } }; private static final String HTTP_EXAMPLE_URL = JcrDriver.HTTP_URL_PREFIX + "{hostname}:{port}/{context root}"; private AtomicReference<Map<String, NodeType>> nodeTypes = new AtomicReference<>(); private AtomicReference<Repositories.Repository> repository = new AtomicReference<>(); private ModeShapeRestClient restClient; protected HttpRepositoryDelegate( String url, Properties info ) { super(url, info); } @Override protected ConnectionInfo createConnectionInfo( String url, Properties info ) { return new HttpConnectionInfo(url, info); } protected Repositories.Repository repository() { return this.repository.get(); } @Override public QueryResult execute( String query, String language ) throws RepositoryException { logger.trace("Executing query: {0}", query); try { org.modeshape.jdbc.rest.QueryResult result = this.restClient.query(query, language); return new HttpQueryResult(result); } catch (Exception e) { throw new RepositoryException(e.getMessage(), e); } } @Override public String explain( String query, String language ) throws RepositoryException { logger.trace("Explaining query: {0}", query); try { return this.restClient.queryPlan(query, language); } catch (Exception e) { throw new RepositoryException(e.getMessage(), e); } } @Override public String getDescriptor( String descriptorKey ) { return repository() != null ? repository().getMetadata().get(descriptorKey).toString() : ""; } @Override public NodeType nodeType( String name ) throws RepositoryException { if (nodeTypes.get() == null) { // load the node types nodeTypes(); } NodeType nodetype = nodeTypes.get().get(name); if (nodetype == null) { throw new RepositoryException(JdbcLocalI18n.unableToGetNodeType.text(name)); } return nodetype; } @Override public Collection<NodeType> nodeTypes() throws RepositoryException { Map<String, NodeType> nodeTypes = this.nodeTypes.get(); if (nodeTypes == null) { NodeTypes restNodeTypes = this.restClient.getNodeTypes(); if (restNodeTypes.isEmpty()) { throw new RepositoryException(JdbcLocalI18n.noNodeTypesReturned.text(restClient.serverUrl())); } nodeTypes = new HashMap<>(); for (org.modeshape.jdbc.rest.NodeType nodeType : restNodeTypes) { nodeTypes.put(nodeType.getName(), nodeType); } this.nodeTypes.compareAndSet(null, nodeTypes); } return this.nodeTypes.get().values(); } @Override protected void initRepository() throws SQLException { if (repository() != null) { return; } logger.debug("Creating repository for HttpRepositoryDelegate"); ConnectionInfo info = getConnectionInfo(); assert info != null; String path = info.getRepositoryPath(); if (path == null) { throw new SQLException("Missing repo path from " + info.getUrl()); } String username = info.getUsername(); if (username == null) { throw new SQLException("Missing username from " + info.getUrl()); } char[] password = info.getPassword(); if (password == null) { throw new SQLException("Missing password path from " + info.getUrl()); } String repositoryName = info.getRepositoryName(); if (repositoryName == null) { throw new SQLException("Missing repository name from " + info.getUrl()); } String serverUrl = "http://" + path + "/" + repositoryName; String workspaceName = info.getWorkspaceName(); if (workspaceName == null) { // there is no WS info, so try to figure out a default one... ModeShapeRestClient client = new ModeShapeRestClient(serverUrl, username, String.valueOf(password)); List<String> allWorkspaces = client.getWorkspaces(repositoryName).getWorkspaces(); if (allWorkspaces.isEmpty()) { throw new SQLException("No workspaces found for the " + repositoryName + " repository"); } // TODO author=Horia Chiorean date=19-Aug-14 description=There is no way to get the "default" ws so we'll choose one workspaceName = allWorkspaces.get(0); } serverUrl = serverUrl + "/" + workspaceName; logger.debug("Using server url: {0}", serverUrl); // this is only a connection test to confirm a connection can be made and results can be obtained. try { this.restClient = new ModeShapeRestClient(serverUrl, username, String.valueOf(password)); Repositories repositories = this.restClient.getRepositories(); this.setRepositoryNames(repositories.getRepositoryNames()); Repositories.Repository repository = repositories.getRepository(repositoryName); if (repository == null) { throw new SQLException(JdbcLocalI18n.unableToFindNamedRepository.text(path, repositoryName)); } this.repository.compareAndSet(null, repository); } catch (Exception e) { throw new SQLException(JdbcLocalI18n.noRepositoryNamesFound.text(), e); } } @Override public boolean isValid( final int timeout ) { try { this.restClient.getWorkspaces(getConnectionInfo().getRepositoryName()); return true; } catch (Throwable e) { return false; } } @Override public void close() { super.close(); restClient = null; nodeTypes.set(null); repository.set(null); } private class HttpConnectionInfo extends ConnectionInfo { protected HttpConnectionInfo( String url, Properties properties ) { super(url, properties); } @Override protected void init() { // parsing 2 ways of specifying the repository and workspace // 1) defined using ?repositoryName // 2) defined in the path server:8080/modeshape-rest/respositoryName/workspaceName super.init(); // if the workspace and/or repository name is not specified as a property on the url, // then parse the url to obtain the values from the path, the url must be in the format: // {hostname}:{port} / {context root} + / respositoryName / workspaceName StringBuilder url = new StringBuilder(); String[] urlsections = repositoryPath.split("/"); // if there are only 2 sections, then the url can have the workspace or repository name specified in the path if (urlsections.length < 3) { return; } // the assignment of url section is working back to front, this is so in cases where // the {context} is changed to be made up of multiple sections, instead of the default (modeshape-rest), the // workspace should be the last section (if exist) and the repository should be before the // workspace. int workspacePos = -1; int repositoryPos = -1; int repoPos = 1; if (this.getWorkspaceName() == null && urlsections.length > 3) { workspacePos = urlsections.length - 1; String workspaceName = urlsections[workspacePos]; this.setWorkspaceName(workspaceName); // if workspace is found, then repository is assume in the prior section repoPos = 2; } if (this.getRepositoryName() == null && urlsections.length > 2) { repositoryPos = urlsections.length - repoPos; String repositoryName = urlsections[repositoryPos]; this.setRepositoryName(repositoryName); } // rebuild the url without the repositoryName or WorkspaceName because // the createConnection() needs these separated. for (int i = 0; i < repositoryPos; i++) { url.append(urlsections[i]); if (i < repositoryPos - 1) { url.append("/"); } } this.repositoryPath = url.toString(); } @Override public String getUrlExample() { return HTTP_EXAMPLE_URL; } @Override public String getUrlPrefix() { return JcrDriver.HTTP_URL_PREFIX; } @Override protected void addUrlPropertyInfo( List<DriverPropertyInfo> results ) { // if the repository path doesn't have at least the {context} // example: server:8080/modeshape-rest where modeshape-rest is the context, // then the URL is considered invalid. if (!repositoryPath.contains("/")) { setUrl(null); } super.addUrlPropertyInfo(results); } } }