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