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.sql.SQLException; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Properties; 025import java.util.concurrent.atomic.AtomicReference; 026import javax.jcr.RepositoryException; 027import javax.jcr.nodetype.NodeType; 028import javax.jcr.query.QueryResult; 029import org.modeshape.jdbc.JcrDriver; 030import org.modeshape.jdbc.JdbcLocalI18n; 031import org.modeshape.jdbc.LocalJcrDriver.JcrContextFactory; 032import org.modeshape.jdbc.rest.ModeShapeRestClient; 033import org.modeshape.jdbc.rest.NodeTypes; 034import org.modeshape.jdbc.rest.Repositories; 035 036/** 037 * The HTTPRepositoryDelegate provides remote Repository implementation to access the Jcr layer via HTTP lookup. 038 */ 039public class HttpRepositoryDelegate extends AbstractRepositoryDelegate { 040 041 protected static final int PROTOCOL_HTTP = 2; 042 043 public static final RepositoryDelegateFactory FACTORY = new RepositoryDelegateFactory() { 044 045 @Override 046 protected int determineProtocol( String url ) { 047 if (url.startsWith(JcrDriver.HTTP_URL_PREFIX) && url.length() > JcrDriver.HTTP_URL_PREFIX.length()) { 048 // This fits the pattern so far ... 049 return PROTOCOL_HTTP; 050 } 051 return super.determineProtocol(url); 052 } 053 054 @Override 055 protected RepositoryDelegate create( int protocol, 056 String url, 057 Properties info, 058 JcrContextFactory contextFactory ) { 059 if (protocol == PROTOCOL_HTTP) { 060 return new HttpRepositoryDelegate(url, info); 061 } 062 return super.create(protocol, url, info, contextFactory); 063 } 064 }; 065 066 private static final String HTTP_EXAMPLE_URL = JcrDriver.HTTP_URL_PREFIX + "{hostname}:{port}/{context root}"; 067 private AtomicReference<Map<String, NodeType>> nodeTypes = new AtomicReference<>(); 068 private AtomicReference<Repositories.Repository> repository = new AtomicReference<>(); 069 private ModeShapeRestClient restClient; 070 071 protected HttpRepositoryDelegate( String url, 072 Properties info ) { 073 super(url, info); 074 } 075 076 @Override 077 protected ConnectionInfo createConnectionInfo( String url, 078 Properties info ) { 079 return new HttpConnectionInfo(url, info); 080 } 081 082 protected Repositories.Repository repository() { 083 return this.repository.get(); 084 } 085 086 @Override 087 public QueryResult execute( String query, 088 String language ) throws RepositoryException { 089 logger.trace("Executing query: {0}", query); 090 try { 091 org.modeshape.jdbc.rest.QueryResult result = this.restClient.query(query, language); 092 return new HttpQueryResult(result); 093 } catch (Exception e) { 094 throw new RepositoryException(e.getMessage(), e); 095 } 096 } 097 098 @Override 099 public String explain( String query, 100 String language ) throws RepositoryException { 101 logger.trace("Explaining query: {0}", query); 102 try { 103 return this.restClient.queryPlan(query, language); 104 } catch (Exception e) { 105 throw new RepositoryException(e.getMessage(), e); 106 } 107 } 108 109 @Override 110 public String getDescriptor( String descriptorKey ) { 111 return repository() != null ? repository().getMetadata().get(descriptorKey).toString() : ""; 112 } 113 114 @Override 115 public NodeType nodeType( String name ) throws RepositoryException { 116 if (nodeTypes.get() == null) { 117 // load the node types 118 nodeTypes(); 119 } 120 121 NodeType nodetype = nodeTypes.get().get(name); 122 if (nodetype == null) { 123 throw new RepositoryException(JdbcLocalI18n.unableToGetNodeType.text(name)); 124 } 125 126 return nodetype; 127 } 128 129 @Override 130 public Collection<NodeType> nodeTypes() throws RepositoryException { 131 Map<String, NodeType> nodeTypes = this.nodeTypes.get(); 132 if (nodeTypes == null) { 133 NodeTypes restNodeTypes = this.restClient.getNodeTypes(); 134 if (restNodeTypes.isEmpty()) { 135 throw new RepositoryException(JdbcLocalI18n.noNodeTypesReturned.text(restClient.serverUrl())); 136 } 137 nodeTypes = new HashMap<>(); 138 for (org.modeshape.jdbc.rest.NodeType nodeType : restNodeTypes) { 139 nodeTypes.put(nodeType.getName(), nodeType); 140 } 141 this.nodeTypes.compareAndSet(null, nodeTypes); 142 } 143 return this.nodeTypes.get().values(); 144 } 145 146 @Override 147 protected void initRepository() throws SQLException { 148 if (repository() != null) { 149 return; 150 } 151 logger.debug("Creating repository for HttpRepositoryDelegate"); 152 153 ConnectionInfo info = getConnectionInfo(); 154 assert info != null; 155 156 String path = info.getRepositoryPath(); 157 if (path == null) { 158 throw new SQLException("Missing repo path from " + info.getUrl()); 159 } 160 String username = info.getUsername(); 161 if (username == null) { 162 throw new SQLException("Missing username from " + info.getUrl()); 163 } 164 char[] password = info.getPassword(); 165 if (password == null) { 166 throw new SQLException("Missing password path from " + info.getUrl()); 167 } 168 169 String repositoryName = info.getRepositoryName(); 170 if (repositoryName == null) { 171 throw new SQLException("Missing repository name from " + info.getUrl()); 172 } 173 174 String serverUrl = "http://" + path + "/" + repositoryName; 175 176 String workspaceName = info.getWorkspaceName(); 177 if (workspaceName == null) { 178 // there is no WS info, so try to figure out a default one... 179 ModeShapeRestClient client = new ModeShapeRestClient(serverUrl, username, String.valueOf(password)); 180 List<String> allWorkspaces = client.getWorkspaces(repositoryName).getWorkspaces(); 181 if (allWorkspaces.isEmpty()) { 182 throw new SQLException("No workspaces found for the " + repositoryName + " repository"); 183 } 184 // TODO author=Horia Chiorean date=19-Aug-14 description=There is no way to get the "default" ws so we'll choose one 185 workspaceName = allWorkspaces.get(0); 186 } 187 188 serverUrl = serverUrl + "/" + workspaceName; 189 logger.debug("Using server url: {0}", serverUrl); 190 // this is only a connection test to confirm a connection can be made and results can be obtained. 191 try { 192 this.restClient = new ModeShapeRestClient(serverUrl, username, String.valueOf(password)); 193 Repositories repositories = this.restClient.getRepositories(); 194 this.setRepositoryNames(repositories.getRepositoryNames()); 195 Repositories.Repository repository = repositories.getRepository(repositoryName); 196 if (repository == null) { 197 throw new SQLException(JdbcLocalI18n.unableToFindNamedRepository.text(path, repositoryName)); 198 } 199 this.repository.compareAndSet(null, repository); 200 } catch (Exception e) { 201 throw new SQLException(JdbcLocalI18n.noRepositoryNamesFound.text(), e); 202 } 203 } 204 205 @Override 206 public boolean isValid( final int timeout ) { 207 try { 208 this.restClient.getWorkspaces(getConnectionInfo().getRepositoryName()); 209 return true; 210 } catch (Throwable e) { 211 return false; 212 } 213 } 214 215 @Override 216 public void close() { 217 super.close(); 218 restClient = null; 219 nodeTypes.set(null); 220 repository.set(null); 221 } 222 223 private class HttpConnectionInfo extends ConnectionInfo { 224 225 protected HttpConnectionInfo( String url, 226 Properties properties ) { 227 super(url, properties); 228 } 229 230 @Override 231 protected void init() { 232 // parsing 2 ways of specifying the repository and workspace 233 // 1) defined using ?repositoryName 234 // 2) defined in the path server:8080/modeshape-rest/respositoryName/workspaceName 235 236 super.init(); 237 238 // if the workspace and/or repository name is not specified as a property on the url, 239 // then parse the url to obtain the values from the path, the url must be in the format: 240 // {hostname}:{port} / {context root} + / respositoryName / workspaceName 241 242 StringBuilder url = new StringBuilder(); 243 String[] urlsections = repositoryPath.split("/"); 244 // if there are only 2 sections, then the url can have the workspace or repository name specified in the path 245 if (urlsections.length < 3) { 246 return; 247 } 248 249 // the assignment of url section is working back to front, this is so in cases where 250 // the {context} is changed to be made up of multiple sections, instead of the default (modeshape-rest), the 251 // workspace should be the last section (if exist) and the repository should be before the 252 // workspace. 253 int workspacePos = -1; 254 int repositoryPos = -1; 255 int repoPos = 1; 256 if (this.getWorkspaceName() == null && urlsections.length > 3) { 257 workspacePos = urlsections.length - 1; 258 String workspaceName = urlsections[workspacePos]; 259 this.setWorkspaceName(workspaceName); 260 // if workspace is found, then repository is assume in the prior section 261 repoPos = 2; 262 263 } 264 if (this.getRepositoryName() == null && urlsections.length > 2) { 265 repositoryPos = urlsections.length - repoPos; 266 String repositoryName = urlsections[repositoryPos]; 267 this.setRepositoryName(repositoryName); 268 } 269 270 // rebuild the url without the repositoryName or WorkspaceName because 271 // the createConnection() needs these separated. 272 for (int i = 0; i < repositoryPos; i++) { 273 url.append(urlsections[i]); 274 if (i < repositoryPos - 1) { 275 url.append("/"); 276 } 277 } 278 279 this.repositoryPath = url.toString(); 280 } 281 282 @Override 283 public String getUrlExample() { 284 return HTTP_EXAMPLE_URL; 285 } 286 287 @Override 288 public String getUrlPrefix() { 289 return JcrDriver.HTTP_URL_PREFIX; 290 } 291 292 @Override 293 protected void addUrlPropertyInfo( List<DriverPropertyInfo> results ) { 294 // if the repository path doesn't have at least the {context} 295 // example: server:8080/modeshape-rest where modeshape-rest is the context, 296 // then the URL is considered invalid. 297 if (!repositoryPath.contains("/")) { 298 setUrl(null); 299 } 300 super.addUrlPropertyInfo(results); 301 } 302 } 303}