001 /* 002 * JBoss, Home of Professional Open Source. 003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors 004 * as indicated by the @author tags. See the copyright.txt file in the 005 * distribution for a full listing of individual contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 package org.jboss.dna.maven; 023 024 import java.net.MalformedURLException; 025 import java.net.URL; 026 import java.net.URLStreamHandler; 027 import java.util.regex.Matcher; 028 import java.util.regex.Pattern; 029 import org.jboss.dna.common.text.TextDecoder; 030 import org.jboss.dna.common.text.TextEncoder; 031 import org.jboss.dna.common.text.UrlEncoder; 032 033 /** 034 * Wrapper for a URL that uses a format for referencing JCR nodes and content. 035 * 036 * @author Randall Hauch 037 */ 038 public class MavenUrl { 039 040 public static final int NO_PORT = -1; 041 public static final String JCR_PROTOCOL = "jcr"; 042 protected static final String URL_PATH_DELIMITER = "/"; 043 044 private String hostname = ""; 045 private int port = NO_PORT; 046 private String workspaceName = ""; 047 private String path = URL_PATH_DELIMITER; 048 049 /** 050 * Get the host name 051 * 052 * @return the host name 053 */ 054 public String getHostname() { 055 return this.hostname; 056 } 057 058 /** 059 * @param hostname the new host name 060 */ 061 public void setHostname( String hostname ) { 062 this.hostname = hostname != null ? hostname.trim() : ""; 063 this.hostname = trimDelimiters(this.hostname, true, true); 064 } 065 066 /** 067 * Get the port. This method returns {@link #NO_PORT} if the port has not been specified. 068 * 069 * @return the port 070 */ 071 public int getPort() { 072 return this.port; 073 } 074 075 /** 076 * @param port the new port, or {@link #NO_PORT} if there is no port 077 */ 078 public void setPort( int port ) { 079 this.port = port; 080 } 081 082 public String getHostnameAndPort() { 083 if (this.port == NO_PORT) return this.hostname; 084 if (this.hostname.length() == 0) return ""; 085 return this.hostname + ":" + this.port; 086 } 087 088 /** 089 * @return workspaceName 090 */ 091 public String getWorkspaceName() { 092 return this.workspaceName; 093 } 094 095 /** 096 * Set the name of the workspace. 097 * 098 * @param workspaceName the name of the workspace 099 */ 100 public void setWorkspaceName( String workspaceName ) { 101 this.workspaceName = workspaceName != null ? workspaceName.trim() : ""; 102 this.workspaceName = trimDelimiters(this.workspaceName, true, true); 103 } 104 105 protected String trimDelimiters( String string, boolean removeLeading, boolean removeTrailing ) { 106 if (string == null || string.length() == 0) return ""; 107 if (removeLeading) string = string.replaceAll("^/+", ""); 108 if (removeTrailing) string = string.replaceAll("/+$", ""); 109 return string; 110 } 111 112 /** 113 * @return path 114 */ 115 public String getPath() { 116 return this.path; 117 } 118 119 /** 120 * @param path Sets path to the specified value. 121 */ 122 public void setPath( String path ) { 123 // Make sure the path starts with a '/' ... 124 this.path = path != null ? URL_PATH_DELIMITER + path.trim() : URL_PATH_DELIMITER; 125 this.path = this.path.replaceAll("^/{2,}", "/"); 126 assert this.path.startsWith(URL_PATH_DELIMITER); 127 } 128 129 /** 130 * Get a URL that corresponds to the information in this object. 131 * 132 * @param handler the URL stream handler that will be used to handle obtaining an input stream or an output stream on the 133 * resulting URL 134 * @param encoder an encoder that will be used to escape any characters that are not allowed in URLs; {@link UrlEncoder} will 135 * be used if no encoder is specified 136 * @return the URL 137 * @throws MalformedURLException if the resulting URL would be malformed 138 */ 139 public URL getUrl( URLStreamHandler handler, TextEncoder encoder ) throws MalformedURLException { 140 if (encoder == null) { 141 encoder = new UrlEncoder().setSlashEncoded(false); 142 } 143 final boolean hasWorkspaceName = this.workspaceName.length() > 0; 144 final boolean hasPath = this.path.length() > 1; // path includes leading delim 145 String filePart = null; 146 if (hasWorkspaceName && hasPath) { 147 filePart = URL_PATH_DELIMITER + encoder.encode(this.workspaceName) + encoder.encode(this.path); 148 } else if (hasWorkspaceName) { 149 filePart = URL_PATH_DELIMITER + encoder.encode(this.workspaceName) + URL_PATH_DELIMITER; 150 } else if (hasPath) { 151 filePart = URL_PATH_DELIMITER + encoder.encode(this.path); 152 } else { 153 filePart = URL_PATH_DELIMITER; 154 } 155 int actualPort = this.hostname.length() != 0 ? this.port : NO_PORT; 156 return new URL(JCR_PROTOCOL, this.hostname, actualPort, filePart, handler); 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public String toString() { 164 UrlEncoder encoder = new UrlEncoder().setSlashEncoded(false); 165 String encodedWorkspace = encoder.encode(this.workspaceName); 166 String encodedPath = encoder.encode(this.path); 167 final boolean hasHostname = this.hostname.length() > 0; 168 final boolean hasPath = encodedPath.length() > 1; // path includes leading delim 169 StringBuilder sb = new StringBuilder(); 170 sb.append(JCR_PROTOCOL).append(":"); 171 if (hasHostname) { 172 sb.append("//").append(this.hostname); 173 if (this.port != NO_PORT) { 174 sb.append(":").append(this.port); 175 } 176 } 177 sb.append(URL_PATH_DELIMITER).append(encodedWorkspace); 178 if (hasPath) { 179 sb.append(encodedPath); 180 } else { 181 sb.append(URL_PATH_DELIMITER); 182 } 183 return sb.toString(); 184 } 185 186 /** 187 * Parse the supplied URL and determine if the URL fits the JCR URL format. If it does, return a {@link MavenUrl} instance; 188 * otherwise return null. If the URL is malformed or otherwise invalid, this method also returns null. 189 * <p> 190 * The URL format is expected to fit the following pattern: 191 * 192 * <pre> 193 * jcr://hostname:port/workspaceName/path/to/node 194 * </pre> 195 * 196 * where 197 * <ul> 198 * <li><b>hostname</b> is the name of the repository's host; typically, this is unspecified to refer to a repository in the 199 * same VM</li> 200 * <li><b>port</b> is the port on the host. If the hostname is unspecified, the port should be excluded.</li> 201 * <li><b>workspaceName</b> is the name of the workspace in the repository</li> 202 * <li><b>path/to/node</b> is the path of the node or property that is to be referenced</li> 203 * </ul> 204 * </p> 205 * 206 * @param url the URL to be parsed 207 * @param decoder the text encoder that should be used to decode the URL; may be null if no decoding should be done 208 * @return the object representing the JCR information contained in the URL 209 * @see #parse(URL, TextDecoder) 210 */ 211 public static MavenUrl parse( String url, TextDecoder decoder ) { 212 if (decoder == null) decoder = new UrlEncoder(); 213 // This regular expression has the following groups: 214 // 1) //hostname:port 215 // 2) hostname:port 216 // 3) hostname 217 // 4) :port 218 // 5) port 219 // 6) workspaceName 220 // 7) path, including leading '/' 221 Pattern urlPattern = Pattern.compile("jcr:(//(([^/:]*)(:([^/]*))?))?/([^/]*)(/?.*)"); 222 Matcher matcher = urlPattern.matcher(url); 223 MavenUrl result = null; 224 if (matcher.find()) { 225 result = new MavenUrl(); 226 result.setHostname(matcher.group(3)); 227 String portStr = matcher.group(5); 228 if (portStr != null && portStr.trim().length() != 0) { 229 result.setPort(Integer.parseInt(portStr)); 230 } 231 String workspaceName = decoder.decode(matcher.group(6)); 232 String path = decoder.decode(matcher.group(7)); 233 result.setWorkspaceName(workspaceName); 234 result.setPath(path); 235 } 236 return result; 237 } 238 239 /** 240 * Parse the supplied URL and determine if the URL fits the JCR URL format. If it does, return a {@link MavenUrl} instance; 241 * otherwise return null. If the URL is malformed or otherwise invalid, this method also returns null. 242 * 243 * @param url the URL to be parsed 244 * @param decoder the text encoder that should be used to decode the URL; may be null if no decoding should be done 245 * @return the object representing the JCR information contained in the URL 246 * @see #parse(String,TextDecoder) 247 */ 248 public static MavenUrl parse( URL url, TextDecoder decoder ) { 249 if (url == null) return null; 250 return parse(url.toExternalForm(), decoder); 251 } 252 }