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    }