1 /* 2 * $Id$ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 package org.apache.tiles.request.locale; 23 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.FileNotFoundException; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.net.JarURLConnection; 30 import java.net.URI; 31 import java.net.URISyntaxException; 32 import java.net.URL; 33 import java.net.URLConnection; 34 import java.util.HashSet; 35 import java.util.Locale; 36 import java.util.Set; 37 38 import org.slf4j.Logger; 39 import org.slf4j.LoggerFactory; 40 41 import static java.lang.System.getProperty; 42 import static java.util.Collections.unmodifiableSet; 43 44 /** 45 * A {@link PostfixedApplicationResource} that can be accessed through a URL. 46 * 47 * @version $Rev$ $Date$ 48 */ 49 50 public class URLApplicationResource extends PostfixedApplicationResource { 51 /** 52 * System parameter to specify additional remote protocols. If a url has a remote protocol, then any 53 * {@link IOException} will be thrown directly. If a url has a local protocol, then any {@link IOException} 54 * will be caught and transformed into a {@link FileNotFoundException}. 55 */ 56 static final String REMOTE_PROTOCOLS_PROPERTY = "tiles.remoteProtocols"; 57 private static final Logger LOG = LoggerFactory.getLogger(URLApplicationResource.class); 58 private static final Set<String> REMOTE_PROTOCOLS; 59 60 static { 61 REMOTE_PROTOCOLS = initRemoteProtocols(); 62 } 63 64 /** 65 * Creates an unmodifiable set of <em>remote</em> protocols which are used in {@link URL} objects, see {@link URL#getProtocol()}. 66 * A url with a remote protocol establishes a network connection when its {@link URL#openConnection()} is being called. 67 * The set will always contain the built-in remote protocols below: 68 * <ul> 69 * <li><a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/net/www/protocol/ftp">ftp</a></li> 70 * <li><a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/net/www/protocol/http">http</a></li> 71 * <li><a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/net/www/protocol/https">https</a></li> 72 * <li><a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/net/www/protocol/mailto">mailto</a></li> 73 * <li><a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/net/www/protocol/netdoc">netdoc</a></li> 74 * </ul> 75 * It's possible, that your environment provides additional remote protocols because of following reasons: 76 * <ul> 77 * <li>your application server adds more remote protocols, see its documentation for further details.</li> 78 * <li>your application supplies custom remote protocols trough its own {@link java.net.URLStreamHandlerFactory} 79 * (see following excellent <a href="https://stackoverflow.com/questions/26363573/registering-and-using-a-custom-java-net-url-protocol">explanation</a> 80 * for getting an idea how to do this)</li> 81 * </ul> 82 * If you need to use such extra remote protocols in Tiles, you may enhance the set via system property {@code tiles.remoteProtocols}. Suppose 83 * you need to add your custom remote protocols "foo" and "bar". To do so, add following parameter to the command line (use ";" as separator): 84 * <pre> 85 * -Dtiles.remoteProtocols=foo;bar 86 * </pre> 87 * The resulting set will then contain the built-in protocols plus "foo" and "bar". 88 * 89 * @return Unmodifiable set of remote protocols, never {@code null} 90 */ 91 static Set<String> initRemoteProtocols() { 92 Set<String> remoteProtocols = new HashSet<String>(); 93 remoteProtocols.add("ftp"); 94 remoteProtocols.add("http"); 95 remoteProtocols.add("https"); 96 remoteProtocols.add("mailto"); 97 remoteProtocols.add("netdoc"); 98 99 String protocolsProp = getProperty(REMOTE_PROTOCOLS_PROPERTY); 100 if (protocolsProp != null) { 101 for (String protocol : protocolsProp.split(";")) { 102 remoteProtocols.add(protocol.trim()); 103 } 104 } 105 return unmodifiableSet(remoteProtocols); 106 } 107 108 private static boolean isLocal(URL url) { 109 return !REMOTE_PROTOCOLS.contains(url.getProtocol()); 110 } 111 112 /** the URL where the contents can be found. */ 113 private final URL url; 114 /** if the URL matches a file, this is the file. */ 115 private File file; 116 /** if the URL points to a local resource */ 117 private final boolean local; 118 119 /** 120 * Creates a URLApplicationResource for the specified path that can be accessed through the specified URL. 121 * 122 * @param localePath the path including localization. 123 * @param url the URL where the contents can be found. 124 */ 125 public URLApplicationResource(String localePath, URL url) { 126 super(localePath); 127 this.url = url; 128 if ("file".equals(url.getProtocol())) { 129 file = getFile(url); 130 } 131 local = isLocal(url); 132 } 133 134 /** 135 * Creates a URLApplicationResource for the specified path that can be accessed through the specified URL. 136 * 137 * @param path the path excluding localization. 138 * @param locale the Locale. 139 * @param url the URL where the contents can be found. 140 */ 141 public URLApplicationResource(String path, Locale locale, URL url) { 142 super(path, locale); 143 this.url = url; 144 if ("file".equals(url.getProtocol())) { 145 file = getFile(url); 146 } 147 local = isLocal(url); 148 } 149 150 private URLConnection openConnection() throws IOException { 151 try { 152 return url.openConnection(); 153 } catch (IOException e) { 154 // If the url points to a local resource but it cannot be 155 // opened, then the resource actually does not exist. In this 156 // case throw a FileNotFoundException 157 if (local) { 158 FileNotFoundException fne = new FileNotFoundException(url.toString()); 159 fne.initCause(e); 160 throw fne; 161 } 162 throw e; 163 } 164 } 165 166 private static File getFile(URL url) { 167 try { 168 return new File(new URI(url.toExternalForm()).getSchemeSpecificPart()); 169 } catch (URISyntaxException e) { 170 LOG.debug("Cannot translate URL to file name, expect a performance impact", e); 171 return null; 172 } 173 } 174 175 /** {@inheritDoc} */ 176 @Override 177 public InputStream getInputStream() throws IOException { 178 if (file != null) { 179 return new FileInputStream(file); 180 } else { 181 return openConnection().getInputStream(); 182 } 183 } 184 185 /** {@inheritDoc} */ 186 @Override 187 public long getLastModified() throws IOException { 188 if (file != null) { 189 return file.lastModified(); 190 } else { 191 URLConnection connection = openConnection(); 192 if (connection instanceof JarURLConnection) { 193 return ((JarURLConnection) connection).getJarEntry().getTime(); 194 } else { 195 long result = connection.getLastModified(); 196 return result; 197 } 198 } 199 } 200 201 /** {@inheritDoc} */ 202 @Override 203 public String toString() { 204 return "Resource " + getLocalePath() + " at " + url.toString(); 205 } 206 207 protected URL getURL(){ 208 return url; 209 } 210 211 protected File getFile(){ 212 return file; 213 } 214 }