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 }