001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002 *
003 * Copyright 2000-2013, Michael Meyling <mime@qedeq.org>.
004 *
005 * "Hilbert II" is free software; you can redistribute
006 * it and/or modify it under the terms of the GNU General Public
007 * License as published by the Free Software Foundation; either
008 * version 2 of the License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 */
015
016 package org.qedeq.base.io;
017
018 import java.io.File;
019 import java.io.FileNotFoundException;
020 import java.io.FileOutputStream;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.UnsupportedEncodingException;
024 import java.lang.reflect.InvocationTargetException;
025 import java.net.HttpURLConnection;
026 import java.net.MalformedURLException;
027 import java.net.URL;
028 import java.net.URLConnection;
029 import java.net.URLDecoder;
030
031 import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
032 import org.apache.commons.httpclient.HttpClient;
033 import org.apache.commons.httpclient.HttpStatus;
034 import org.apache.commons.httpclient.methods.GetMethod;
035 import org.apache.commons.httpclient.params.HttpMethodParams;
036 import org.qedeq.base.trace.Trace;
037 import org.qedeq.base.utility.YodaUtility;
038
039
040 /**
041 * A collection of useful static methods for URL s.
042 *
043 * @author Michael Meyling
044 */
045 public final class UrlUtility {
046
047 /** This class, for debugging purpose. */
048 private static final Class CLASS = UrlUtility.class;
049
050 /**
051 * Constructor, should never be called.
052 */
053 private UrlUtility() {
054 // don't call me
055 }
056
057 /**
058 * Convert file in URL.
059 *
060 * @param file File.
061 * @return URL.
062 */
063 public static URL toUrl(final File file) {
064 try {
065 return file.getAbsoluteFile().toURI().toURL();
066 } catch (MalformedURLException e) { // should only happen if there is a bug in the JDK
067 throw new RuntimeException(e);
068 }
069 }
070
071 /**
072 * Convert URL path in file path.
073 *
074 * @param url Convert this URL path.
075 * @return File path.
076 */
077 public static File transformURLPathToFilePath(final URL url) {
078 try {
079 return new File(URLDecoder.decode(url.getFile(), "UTF-8"));
080 } catch (UnsupportedEncodingException e) {
081 throw new RuntimeException(e);
082 }
083 }
084
085 /**
086 * Create relative address from <code>origin</code> to <code>next</code>.
087 * The resulting file path has "/" as directory name separator.
088 * If the resulting file path is the same as origin specifies, we return "".
089 * Otherwise the result will always have an "/" as last character.
090 *
091 * @param origin This is the original location. Must be a directory.
092 * @param next This should be the next location. Must also be a directory.
093 * @return Relative (or if necessary absolute) file path.
094 */
095 public static final String createRelativePath(final File origin, final File next) {
096 if (origin.equals(next)) {
097 return "";
098 }
099 final Path org = new Path(origin.getPath().replace(File.separatorChar, '/'), "");
100 final Path ne = new Path(next.getPath().replace(File.separatorChar, '/'), "");
101 return org.createRelative(ne.toString()).toString();
102 }
103
104 /**
105 * Simplify file URL by returning a file path.
106 *
107 * @param url URL to simplify.
108 * @return File path (if protocol is "file"). Otherwise just return <code>url</code>.
109 */
110 public static String easyUrl(final String url) {
111 String result = url;
112 try {
113 final URL u = new URL(url);
114 // is this a file URL?
115 if (u.getProtocol().equalsIgnoreCase("file")) {
116 return transformURLPathToFilePath(u).getCanonicalPath();
117 }
118 } catch (RuntimeException e) {
119 // ignore
120 } catch (IOException e) {
121 // ignore
122 }
123 return result;
124 }
125
126 /**
127 * Make local copy of an URL.
128 *
129 * @param url Save this URL.
130 * @param f Save into this file. An existing file is overwritten.
131 * @param proxyHost Use this proxy host.
132 * @param proxyPort Use this port at proxy host.
133 * @param nonProxyHosts This are hosts not to be proxied.
134 * @param connectTimeout Connection timeout.
135 * @param readTimeout Read timeout.
136 * @param listener Here completion events are fired.
137 * @throws IOException Saving failed.
138 */
139 public static void saveUrlToFile(final String url, final File f, final String proxyHost,
140 final String proxyPort, final String nonProxyHosts, final int connectTimeout,
141 final int readTimeout, final LoadingListener listener)
142 throws IOException {
143 final String method = "saveUrlToFile()";
144 Trace.begin(CLASS, method);
145
146 // if we are not web started and running under Java 1.4 we use apache commons
147 // httpclient library (so we can set timeouts)
148 if (!isSetConnectionTimeOutSupported()
149 && !IoUtility.isWebStarted()) {
150 saveQedeqFromWebToBufferApache(url, f, proxyHost, proxyPort, nonProxyHosts, connectTimeout,
151 readTimeout, listener);
152 Trace.end(CLASS, method);
153 return;
154 }
155
156 // set proxy properties according to kernel configuration (if not webstarted)
157 if (!IoUtility.isWebStarted()) {
158 if (proxyHost != null) {
159 System.setProperty("http.proxyHost", proxyHost);
160 }
161 if (proxyPort != null) {
162 System.setProperty("http.proxyPort", proxyPort);
163 }
164 if (nonProxyHosts != null) {
165 System.setProperty("http.nonProxyHosts", nonProxyHosts);
166 }
167 }
168
169 FileOutputStream out = null;
170 InputStream in = null;
171 try {
172 final URLConnection connection = new URL(url).openConnection();
173
174 if (connection instanceof HttpURLConnection) {
175 final HttpURLConnection httpConnection = (HttpURLConnection) connection;
176 // if we are running at least under Java 1.5 the following code should be executed
177 if (isSetConnectionTimeOutSupported()) {
178 try {
179 YodaUtility.executeMethod(httpConnection, "setConnectTimeout",
180 new Class[] {Integer.TYPE}, new Object[] {new Integer(
181 connectTimeout)});
182 } catch (NoSuchMethodException e) {
183 Trace.fatal(CLASS, method,
184 "URLConnection.setConnectTimeout was previously found", e);
185 } catch (InvocationTargetException e) {
186 Trace.fatal(CLASS, method,
187 "URLConnection.setConnectTimeout throwed an error", e);
188 }
189 }
190 // if we are running at least under Java 1.5 the following code should be executed
191 if (isSetReadTimeoutSupported()) {
192 try {
193 YodaUtility.executeMethod(httpConnection, "setReadTimeout",
194 new Class[] {Integer.TYPE}, new Object[] {new Integer(
195 readTimeout)});
196 } catch (NoSuchMethodException e) {
197 Trace.fatal(CLASS, method,
198 "URLConnection.setReadTimeout was previously found", e);
199 } catch (InvocationTargetException e) {
200 Trace.fatal(CLASS, method,
201 "URLConnection.setReadTimeout throwed an error", e);
202 }
203 }
204 int responseCode = httpConnection.getResponseCode();
205 if (responseCode == 200) {
206 in = httpConnection.getInputStream();
207 } else {
208 in = httpConnection.getErrorStream();
209 final String errorText = IoUtility.loadStreamWithoutException(in, 1000);
210 throw new IOException("Response code from HTTP server was " + responseCode
211 + (errorText.length() > 0 ? "\nResponse text from HTTP server was:\n"
212 + errorText : ""));
213 }
214 } else {
215 Trace.paramInfo(CLASS, method, "connection.getClass", connection.getClass()
216 .toString());
217 in = connection.getInputStream();
218 }
219
220 if (!url.equals(connection.getURL().toString())) {
221 throw new FileNotFoundException("\"" + url + "\" was substituted by "
222 + "\"" + connection.getURL() + "\" from server");
223 }
224 final double maximum = connection.getContentLength();
225 IoUtility.createNecessaryDirectories(f);
226 out = new FileOutputStream(f);
227 final byte[] buffer = new byte[4096];
228 int bytesRead; // bytes read during one buffer read
229 int position = 0; // current reading position within the whole document
230 // continue writing
231 while ((bytesRead = in.read(buffer)) != -1) {
232 position += bytesRead;
233 out.write(buffer, 0, bytesRead);
234 if (maximum > 0) {
235 double completeness = position / maximum;
236 if (completeness < 0) {
237 completeness = 0;
238 }
239 if (completeness > 100) {
240 completeness = 1;
241 }
242 listener.loadingCompletenessChanged(completeness);
243 }
244 }
245 listener.loadingCompletenessChanged(1);
246 } finally {
247 IoUtility.close(out);
248 out = null;
249 IoUtility.close(in);
250 in = null;
251 Trace.end(CLASS, method);
252 }
253 }
254
255 /**
256 * Make local copy of a http accessable URL. This method uses apaches HttpClient,
257 * but it dosn't work under webstart with proxy configuration. If we don't use this
258 * method, the apache commons-httpclient library can be removed
259 *
260 * @param url Save this URL.
261 * @param f Save into this file. An existing file is overwritten.
262 * @param proxyHost Use this proxy host.
263 * @param proxyPort Use this port at proxy host.
264 * @param nonProxyHosts This are hosts not to be proxied.
265 * @param connectTimeout Connection timeout.
266 * @param readTimeout Read timeout.
267 * @param listener Here completion events are fired.
268 * @throws IOException Saving failed.
269 */
270 private static void saveQedeqFromWebToBufferApache(final String url, final File f,
271 final String proxyHost, final String proxyPort,
272 final String nonProxyHosts, final int connectTimeout, final int readTimeout,
273 final LoadingListener listener)
274 throws IOException {
275 final String method = "saveQedeqFromWebToBufferApache()";
276 Trace.begin(CLASS, method);
277
278 // Create an instance of HttpClient.
279 HttpClient client = new HttpClient();
280
281 // set proxy properties according to kernel configuration (if not webstarted)
282 if (!IoUtility.isWebStarted() && proxyHost != null && proxyHost.length() > 0) {
283 final String pHost = proxyHost;
284 int pPort = 80;
285 if (proxyPort != null) {
286 try {
287 pPort = Integer.parseInt(proxyPort);
288 } catch (RuntimeException e) {
289 Trace.fatal(CLASS, method, "proxy port not numeric: "
290 + proxyPort, e);
291 }
292 }
293 if (pHost.length() > 0) {
294 client.getHostConfiguration().setProxy(pHost, pPort);
295 }
296 }
297
298 // Create a method instance.
299 GetMethod httpMethod = new GetMethod(url);
300
301 try {
302 // Provide custom retry handler is necessary
303 httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
304 new DefaultHttpMethodRetryHandler(3, false));
305
306 httpMethod.getParams().setSoTimeout(connectTimeout);
307 // Throws IOException on TimeOut.
308
309 int statusCode = client.executeMethod(httpMethod);
310
311 if (statusCode != HttpStatus.SC_OK) {
312 throw new FileNotFoundException("Problems loading: " + url + "\n"
313 + httpMethod.getStatusLine());
314 }
315
316 // Read the response body.
317 byte[] responseBody = httpMethod.getResponseBody();
318 IoUtility.createNecessaryDirectories(f);
319 IoUtility.saveFileBinary(f, responseBody);
320 listener.loadingCompletenessChanged(1);
321 } finally {
322 // Release the connection.
323 httpMethod.releaseConnection();
324 Trace.end(CLASS, method);
325 }
326 }
327
328
329 /**
330 * This class ist just for solving the lazy loading problem thread save.
331 * see <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom">
332 * Initialization_on_demand_holder_idiom</a>.
333 */
334 private static final class LazyHolderTimeoutMethods {
335
336 /** Lazy initialized constant that knows about the existence of the method
337 * <code>URLConnection.setConnectTimeout</code>. This depends on the currently running
338 * JVM. */
339 private static final boolean IS_SET_CONNECTION_TIMEOUT_SUPPORTED = YodaUtility.existsMethod(
340 URLConnection.class, "setConnectTimeout",
341 new Class[] {Integer.TYPE});
342
343 /** Lazy initialized constant that knows about the existence of the method
344 * <code>URLConnection.setReadTimeout</code>. This depends on the currently running
345 * JVM. */
346 private static final boolean IS_SET_READ_TIMEOUT_SUSPPORTED = YodaUtility.existsMethod(
347 URLConnection.class, "setReadTimeout",
348 new Class[] {Integer.TYPE});
349
350 /**
351 * Hidden constructor.
352 */
353 private LazyHolderTimeoutMethods() {
354 // nothing to do
355 }
356
357 }
358
359 /**
360 * Is setting of connection timeout supported in current environment?
361 *
362 * @return Setting connection timeout supported?
363 */
364 public static boolean isSetConnectionTimeOutSupported() {
365 return LazyHolderTimeoutMethods.IS_SET_CONNECTION_TIMEOUT_SUPPORTED;
366 }
367
368 /**
369 * Is setting of read timeout supported in current environment?
370 *
371 * @return Setting read timeout supported?
372 */
373 public static boolean isSetReadTimeoutSupported() {
374 return LazyHolderTimeoutMethods.IS_SET_READ_TIMEOUT_SUSPPORTED;
375 }
376
377 }
|