com.helger.commons.url.URLHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-commons Show documentation
Show all versions of ph-commons Show documentation
Java 1.6+ Library with tons of utility classes required in all projects
/**
* Copyright (C) 2014-2016 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.helger.commons.url;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import javax.annotation.CheckForSigned;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.charset.CCharset;
import com.helger.commons.codec.IDecoder;
import com.helger.commons.codec.IEncoder;
import com.helger.commons.collection.CollectionHelper;
import com.helger.commons.collection.ext.CommonsHashMap;
import com.helger.commons.collection.ext.ICommonsList;
import com.helger.commons.collection.ext.ICommonsMap;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.io.file.FilenameHelper;
import com.helger.commons.io.resource.ClassPathResource;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.lang.ClassHelper;
import com.helger.commons.lang.ClassLoaderHelper;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.StringParser;
import com.helger.commons.wrapper.IMutableWrapper;
/**
* URL utilities.
*
* @author Philip Helger
*/
@ThreadSafe
public final class URLHelper
{
/** Default URL charset is UTF-8 */
public static final Charset CHARSET_URL_OBJ = CCharset.CHARSET_UTF_8_OBJ;
/** Separator before first param: ? */
public static final char QUESTIONMARK = '?';
public static final String QUESTIONMARK_STR = Character.toString (QUESTIONMARK);
/** Separator between params: & */
public static final char AMPERSAND = '&';
public static final String AMPERSAND_STR = Character.toString (AMPERSAND);
/** Separator between param name and param value: = */
public static final char EQUALS = '=';
public static final String EQUALS_STR = Character.toString (EQUALS);
/** Separator between URL path and anchor name: # */
public static final char HASH = '#';
public static final String HASH_STR = Character.toString (HASH);
/** The protocol for file resources */
public static final String PROTOCOL_FILE = "file";
private static final Logger s_aLogger = LoggerFactory.getLogger (URLHelper.class);
private static char [] s_aCleanURLOld;
private static char [] [] s_aCleanURLNew;
/** Internal debug logging flag */
private static final boolean DEBUG_GET_IS = false;
private static final BitSet NO_URL_ENCODE = new BitSet (256);
private static final int CASE_DIFF = ('a' - 'A');
static
{
/**
*
* The list of characters that are not encoded has been
* determined as follows:
*
* RFC 2396 states:
* -----
* Data characters that are allowed in a URI but do not have a
* reserved purpose are called unreserved. These include upper
* and lower case letters, decimal digits, and a limited set of
* punctuation marks and symbols.
*
* unreserved = alphanum | mark
*
* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
*
* Unreserved characters can be escaped without changing the
* semantics of the URI, but this should not be done unless the
* URI is being used in a context that does not allow the
* unescaped character to appear.
* -----
*
* It appears that both Netscape and Internet Explorer escape
* all special characters from this list with the exception
* of "-", "_", ".", "*". While it is not clear why they are
* escaping the other characters, perhaps it is safest to
* assume that there might be contexts in which the others
* are unsafe if not escaped. Therefore, we will use the same
* list. It is also noteworthy that this is consistent with
* O'Reilly's "HTML: The Definitive Guide" (page 164).
*
* As a last note, Internet Explorer does not encode the "@"
* character which is clearly not unreserved according to the
* RFC. We are being consistent with the RFC in this matter,
* as is Netscape.
*
*/
for (int i = 'a'; i <= 'z'; i++)
NO_URL_ENCODE.set (i);
for (int i = 'A'; i <= 'Z'; i++)
NO_URL_ENCODE.set (i);
for (int i = '0'; i <= '9'; i++)
NO_URL_ENCODE.set (i);
/*
* encoding a space to a + is done in the encode() method
*/
NO_URL_ENCODE.set (' ');
NO_URL_ENCODE.set ('-');
NO_URL_ENCODE.set ('_');
NO_URL_ENCODE.set ('.');
NO_URL_ENCODE.set ('*');
}
@PresentForCodeCoverage
private static final URLHelper s_aInstance = new URLHelper ();
private URLHelper ()
{}
/**
* URL-decode the passed value automatically handling charset issues. The used
* char set is determined by {@link #CHARSET_URL_OBJ}.
*
* @param sValue
* The value to be decoded. May not be null
.
* @return The decoded value.
*/
@Nonnull
public static String urlDecode (@Nonnull final String sValue)
{
return urlDecode (sValue, CHARSET_URL_OBJ);
}
/**
* URL-decode the passed value automatically handling charset issues. The
* implementation is ripped from URLDecoder but does not throw an
* UnsupportedEncodingException for nothing.
*
* @param sValue
* The value to be decoded. May not be null
.
* @param aCharset
* The charset to use. May not be null
.
* @return The decoded value.
* @see URLDecoder#decode(String, String)
*/
@Nonnull
public static String urlDecode (@Nonnull final String sValue, @Nonnull final Charset aCharset)
{
ValueEnforcer.notNull (sValue, "Value");
ValueEnforcer.notNull (aCharset, "Charset");
boolean bNeedToChange = false;
final int nLen = sValue.length ();
final StringBuilder aSB = new StringBuilder (nLen);
int nIndex = 0;
byte [] aBytes = null;
while (nIndex < nLen)
{
char c = sValue.charAt (nIndex);
switch (c)
{
case '+':
aSB.append (' ');
nIndex++;
bNeedToChange = true;
break;
case '%':
/*
* Starting with this instance of %, process all consecutive
* substrings of the form %xy. Each substring %xy will yield a byte.
* Convert all consecutive bytes obtained this way to whatever
* character(s) they represent in the provided encoding.
*/
try
{
// (numChars-i)/3 is an upper bound for the number
// of remaining bytes
if (aBytes == null)
aBytes = new byte [(nLen - nIndex) / 3];
int nPos = 0;
while ((nIndex + 2) < nLen && c == '%')
{
final int nValue = Integer.parseInt (sValue.substring (nIndex + 1, nIndex + 3), 16);
if (nValue < 0)
throw new IllegalArgumentException ("URLDecoder: Illegal hex characters in escape (%) pattern - negative value");
aBytes[nPos++] = (byte) nValue;
nIndex += 3;
if (nIndex < nLen)
c = sValue.charAt (nIndex);
}
// A trailing, incomplete byte encoding such as
// "%x" will cause an exception to be thrown
if (nIndex < nLen && c == '%')
throw new IllegalArgumentException ("URLDecoder: Incomplete trailing escape (%) pattern");
aSB.append (new String (aBytes, 0, nPos, aCharset));
}
catch (final NumberFormatException e)
{
throw new IllegalArgumentException ("URLDecoder: Illegal hex characters in escape (%) pattern - " +
e.getMessage ());
}
bNeedToChange = true;
break;
default:
aSB.append (c);
nIndex++;
break;
}
}
return bNeedToChange ? aSB.toString () : sValue;
}
/**
* URL-encode the passed value automatically handling charset issues. The used
* char set is determined by {@link #CHARSET_URL_OBJ}.
*
* @param sValue
* The value to be encoded. May not be null
.
* @return The encoded value.
*/
@Nonnull
public static String urlEncode (@Nonnull final String sValue)
{
return urlEncode (sValue, CHARSET_URL_OBJ);
}
/**
* URL-encode the passed value automatically handling charset issues. This is
* a ripped, optimized version of URLEncoder.encode but without the
* UnsupportedEncodingException.
*
* @param sValue
* The value to be encoded. May not be null
.
* @param aCharset
* The charset to use. May not be null
.
* @return The encoded value.
*/
@Nonnull
public static String urlEncode (@Nonnull final String sValue, @Nonnull final Charset aCharset)
{
ValueEnforcer.notNull (sValue, "Value");
ValueEnforcer.notNull (aCharset, "Charset");
final int nLen = sValue.length ();
boolean bNeedToChange = false;
final StringBuilder out = new StringBuilder (nLen * 2);
final CharArrayWriter aCAW = new CharArrayWriter ();
for (int nIndex = 0; nIndex < nLen;)
{
char c = sValue.charAt (nIndex);
// System.out.println("Examining character: " + c);
if (NO_URL_ENCODE.get (c))
{
if (c == ' ')
{
c = '+';
bNeedToChange = true;
}
// System.out.println("Storing: " + c);
out.append (c);
nIndex++;
}
else
{
// convert to external encoding before hex conversion
do
{
aCAW.write (c);
/*
* If this character represents the start of a Unicode surrogate pair,
* then pass in two characters. It's not clear what should be done if
* a bytes reserved in the surrogate pairs range occurs outside of a
* legal surrogate pair. For now, just treat it as if it were any
* other character.
*/
if (Character.isHighSurrogate (c))
{
/*
* System.out.println(Integer.toHexString(c) + " is high surrogate"
* );
*/
if (nIndex + 1 < nLen)
{
final char d = sValue.charAt (nIndex + 1);
/*
* System.out.println("\tExamining " + Integer.toHexString(d));
*/
if (Character.isLowSurrogate (d))
{
/*
* System.out.println("\t" + Integer.toHexString(d) +
* " is low surrogate");
*/
aCAW.write (d);
nIndex++;
}
}
}
nIndex++;
} while (nIndex < nLen && !NO_URL_ENCODE.get ((c = sValue.charAt (nIndex))));
aCAW.flush ();
final String str = new String (aCAW.toCharArray ());
final byte [] ba = str.getBytes (aCharset);
for (final byte element : ba)
{
out.append ('%');
char ch = Character.forDigit ((element >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter (ch))
ch -= CASE_DIFF;
out.append (ch);
ch = Character.forDigit (element & 0xF, 16);
if (Character.isLetter (ch))
ch -= CASE_DIFF;
out.append (ch);
}
aCAW.reset ();
bNeedToChange = true;
}
}
return bNeedToChange ? out.toString () : sValue;
}
private static void _initCleanURL ()
{
// This one cannot be in the static initializer of the class, because
// ClassPathResource internally uses
// URLUtils.getInputStream and this static initialization code of this class
// can therefore not use ClasspathResource because it would create a
// recursive dependency!
// Ever trickier is the when running multiple threads for reading XML (e.g.
// in the unit test) this code would wait forever in the static initializer
// because XMLMapHandler internally also acquires an XML reader....
final ICommonsMap aCleanURLMap = new CommonsHashMap <> ();
StreamHelper.readStreamLines (ClassPathResource.getInputStream ("codelists/cleanurl-data.dat"),
CCharset.CHARSET_UTF_8_OBJ,
sLine -> {
if (sLine.length () > 0 && sLine.charAt (0) == '"')
{
final String [] aParts = StringHelper.getExplodedArray ('=', sLine, 2);
String sKey = StringHelper.trimStartAndEnd (aParts[0], '"');
if (sKey.startsWith (""))
{
// E.g. "〹"
sKey = StringHelper.trimStartAndEnd (sKey, "", ";");
sKey = Character.toString ((char) StringParser.parseInt (sKey, -1));
}
final String sValue = StringHelper.trimStartAndEnd (aParts[1], '"');
aCleanURLMap.put (sKey, sValue);
}
});
// if (XMLMapHandler.readMap (new ClassPathResource
// ("codelists/cleanurl-data.xml"), aCleanURLMap).isFailure ())
// throw new InitializationException ("Failed to init CleanURL data!");
s_aCleanURLOld = new char [aCleanURLMap.size ()];
s_aCleanURLNew = new char [aCleanURLMap.size ()] [];
// Convert to char array
int i = 0;
for (final Map.Entry aEntry : aCleanURLMap.entrySet ())
{
final String sKey = aEntry.getKey ();
if (sKey.length () != 1)
throw new IllegalStateException ("Clean URL source character has an invalid length: " + sKey.length ());
s_aCleanURLOld[i] = sKey.charAt (0);
s_aCleanURLNew[i] = aEntry.getValue ().toCharArray ();
++i;
}
}
/**
* Clean an URL part from nasty Umlauts. This mapping needs extension!
*
* @param sURLPart
* The original URL part. May be null
.
* @return The cleaned version or null
if the input was
* null
.
*/
@Nullable
public static String getCleanURLPartWithoutUmlauts (@Nullable final String sURLPart)
{
if (s_aCleanURLOld == null)
_initCleanURL ();
final char [] ret = StringHelper.replaceMultiple (sURLPart, s_aCleanURLOld, s_aCleanURLNew);
return new String (ret);
}
@Nonnull
public static IURLData getAsURLData (@Nonnull final String sHref)
{
return getAsURLData (sHref, null);
}
/**
* Parses the passed URL into a structured form
*
* @param sHref
* The URL to be parsed
* @param aParameterDecoder
* The parameter decoder to use. May be null
.
* @return the corresponding {@link IURLData} representation of the passed URL
*/
@Nonnull
public static IURLData getAsURLData (@Nonnull final String sHref,
@Nullable final IDecoder aParameterDecoder)
{
ValueEnforcer.notNull (sHref, "Href");
final String sRealHref = sHref.trim ();
// Is it a protocol that does not allow for query parameters?
final IURLProtocol eProtocol = URLProtocolRegistry.getInstance ().getProtocol (sRealHref);
if (eProtocol != null && !eProtocol.allowsForQueryParameters ())
return new URLData (sRealHref, null, null);
if (GlobalDebug.isDebugMode ())
if (eProtocol != null)
try
{
new URL (sRealHref);
}
catch (final MalformedURLException ex)
{
s_aLogger.warn ("java.net.URL claims URL '" + sRealHref + "' to be invalid: " + ex.getMessage ());
}
String sPath;
URLParameterList aParams = null;
String sAnchor;
// First get the anchor out
String sRemainingHref = sRealHref;
final int nIndexAnchor = sRemainingHref.indexOf (HASH);
if (nIndexAnchor >= 0)
{
// Extract anchor
sAnchor = sRemainingHref.substring (nIndexAnchor + 1).trim ();
sRemainingHref = sRemainingHref.substring (0, nIndexAnchor).trim ();
}
else
sAnchor = null;
// Find parameters
final int nQuestionIndex = sRemainingHref.indexOf (QUESTIONMARK);
if (nQuestionIndex >= 0)
{
// Use everything after the '?'
final String sQueryString = sRemainingHref.substring (nQuestionIndex + 1).trim ();
// Maybe empty, if the URL ends with a '?'
if (StringHelper.hasText (sQueryString))
aParams = getParsedQueryParameters (sQueryString, aParameterDecoder);
sPath = sRemainingHref.substring (0, nQuestionIndex).trim ();
}
else
sPath = sRemainingHref;
return new URLData (sPath, aParams, sAnchor);
}
@Nonnull
@ReturnsMutableCopy
public static URLParameterList getParsedQueryParameters (@Nullable final String sQueryString,
@Nullable final IDecoder aParameterDecoder)
{
final URLParameterList aMap = new URLParameterList ();
if (StringHelper.hasText (sQueryString))
{
for (final String sKeyValuePair : StringHelper.getExploded (AMPERSAND, sQueryString))
if (sKeyValuePair.length () > 0)
{
final ICommonsList aParts = StringHelper.getExploded (EQUALS, sKeyValuePair, 2);
final String sKey = aParts.get (0);
// Maybe empty when passing something like "url?=value"
if (StringHelper.hasText (sKey))
{
final String sValue = aParts.size () == 2 ? aParts.get (1) : "";
if (sValue == null)
throw new NullPointerException ("parameter value may not be null");
if (aParameterDecoder != null)
{
// Now decode the name and the value
aMap.add (aParameterDecoder.getDecoded (sKey), aParameterDecoder.getDecoded (sValue));
}
else
aMap.add (sKey, sValue);
}
}
}
return aMap;
}
@Nonnull
@ReturnsMutableCopy
public static URLParameterList getParsedQueryParameters (@Nullable final String sQueryString)
{
return getParsedQueryParameters (sQueryString, null);
}
/**
* Get the final representation of the URL using the specified elements.
*
* @param sPath
* The main path. May be null
.
* @param sQueryParams
* The set of all query parameters already concatenated with the
* correct characters (& and =). May be null
.
* @param sAnchor
* An optional anchor to be added. May be null
.
* @return May be null
if path, anchor and parameters are
* null
.
*/
@Nullable
public static String getURLString (@Nullable final String sPath,
@Nullable final String sQueryParams,
@Nullable final String sAnchor)
{
final boolean bHasPath = StringHelper.hasText (sPath);
final boolean bHasQueryParams = StringHelper.hasText (sQueryParams);
final boolean bHasAnchor = StringHelper.hasText (sAnchor);
// return URL as is?
if (!bHasQueryParams && !bHasAnchor)
{
// Return URL as is (may be null)
return sPath;
}
final StringBuilder aSB = new StringBuilder ();
if (bHasPath)
{
aSB.append (sPath);
if (sPath.contains (QUESTIONMARK_STR))
s_aLogger.warn ("Path contains the question mark ('?') character: '" + sPath + "'");
if (sPath.contains (AMPERSAND_STR))
s_aLogger.warn ("Path contains the ampersand ('&') character: '" + sPath + "'");
if (sPath.contains (HASH_STR))
s_aLogger.warn ("Path contains the hash ('#') character: '" + sPath + "'");
}
if (bHasQueryParams)
{
if (sQueryParams.contains (QUESTIONMARK_STR))
s_aLogger.warn ("Query parameters contain the question mark ('?') character: '" + sQueryParams + "'");
final boolean bHasQuestionMark = aSB.indexOf (QUESTIONMARK_STR) >= 0;
if (bHasQuestionMark)
{
// Only if the "?" is not the last char otherwise the base href already
// contains a parameter!
final char cLast = StringHelper.getLastChar (aSB);
if (cLast != QUESTIONMARK && cLast != AMPERSAND)
aSB.append (AMPERSAND);
}
else
{
// First parameter
aSB.append (QUESTIONMARK);
}
// add all parameters
aSB.append (sQueryParams);
}
// Append anchor
if (bHasAnchor)
{
if (sAnchor.contains (HASH_STR))
s_aLogger.warn ("Anchor contains the hash ('#') character: '" + sAnchor + "'");
if (StringHelper.getLastChar (aSB) != HASH)
aSB.append (HASH);
aSB.append (sAnchor);
}
// Avoid empty URLs
if (aSB.length () == 0)
return QUESTIONMARK_STR;
return aSB.toString ();
}
/**
* Create a parameter string. This is also suitable for POST body (e.g. for
* web form submission).
*
* @param aQueryParams
* Parameter map. May be null
or empty.
* @param aQueryParameterEncoder
* The encoder to be used to encode parameter names and parameter
* values. May be null
. This may be e.g. a
* {@link URLParameterEncoder}.
* @return null
if no parameter is present.
*/
@Nullable
public static String getQueryParametersAsString (@Nullable final List aQueryParams,
@Nullable final IEncoder aQueryParameterEncoder)
{
if (CollectionHelper.isEmpty (aQueryParams))
return null;
final StringBuilder aSB = new StringBuilder ();
// add all values
for (final URLParameter aParam : aQueryParams)
{
// Separator
if (aSB.length () > 0)
aSB.append (AMPERSAND);
aParam.appendTo (aSB, aQueryParameterEncoder);
}
return aSB.toString ();
}
/**
* Create a parameter string. This is also suitable for POST body (e.g. for
* web form submission).
*
* @param aQueryParams
* Parameter map. May be null
or empty.
* @param aQueryParameterEncoder
* The encoder to be used to encode parameter names and parameter
* values. May be null
. This may be e.g. a
* {@link URLParameterEncoder}.
* @return null
if no parameter is present.
*/
@Nullable
@Deprecated
public static String getQueryParametersAsString (@Nullable final Map aQueryParams,
@Nullable final IEncoder aQueryParameterEncoder)
{
if (CollectionHelper.isEmpty (aQueryParams))
return null;
final StringBuilder aSB = new StringBuilder ();
// add all values
for (final Map.Entry aEntry : aQueryParams.entrySet ())
{
// Separator
if (aSB.length () > 0)
aSB.append (AMPERSAND);
// Key
final String sKey = aEntry.getKey ();
if (aQueryParameterEncoder != null)
aSB.append (aQueryParameterEncoder.getEncoded (sKey));
else
aSB.append (sKey);
// Value
final String sValue = aEntry.getValue ();
if (StringHelper.hasText (sValue))
if (aQueryParameterEncoder != null)
aSB.append (EQUALS).append (aQueryParameterEncoder.getEncoded (sValue));
else
aSB.append (EQUALS).append (sValue);
}
return aSB.toString ();
}
@Nonnull
public static String getURLString (@Nonnull final IURLData aURL, @Nullable final Charset aParameterCharset)
{
return getURLString (aURL.getPath (), aURL.directGetAllParams (), aURL.getAnchor (), aParameterCharset);
}
/**
* Get the final representation of the URL using the specified elements.
*
* @param sPath
* The main path. May be null
.
* @param aQueryParams
* The list of query parameters to be appended. May be
* null
.
* @param sAnchor
* An optional anchor to be added. May be null
.
* @param aQueryParameterEncoder
* The parameters encoding to be used. May be null
.
* @return May be null
if path, anchor and parameters are
* null
.
*/
@Nullable
public static String getURLString (@Nullable final String sPath,
@Nullable final List aQueryParams,
@Nullable final String sAnchor,
@Nullable final IEncoder aQueryParameterEncoder)
{
return getURLString (sPath, getQueryParametersAsString (aQueryParams, aQueryParameterEncoder), sAnchor);
}
/**
* Get the final representation of the URL using the specified elements.
*
* @param sPath
* The main path. May be null
.
* @param aQueryParams
* The list of parameters to be appended. May be null
.
* @param sAnchor
* An optional anchor to be added. May be null
.
* @param aParameterCharset
* If not null
the parameters are encoded using this
* charset.
* @return May be null
if all parameters are null
.
*/
@Nullable
public static String getURLString (@Nullable final String sPath,
@Nullable final List aQueryParams,
@Nullable final String sAnchor,
@Nullable final Charset aParameterCharset)
{
final IEncoder aQueryParameterEncoder = aParameterCharset == null ? null
: new URLParameterEncoder (aParameterCharset);
return getURLString (sPath, getQueryParametersAsString (aQueryParams, aQueryParameterEncoder), sAnchor);
}
/**
* Get the passed String as an URL. If the string is empty or not an URL
* null
is returned.
*
* @param sURL
* Source URL. May be null
.
* @return null
if the passed URL is empty or invalid.
*/
@Nullable
public static URL getAsURL (@Nullable final String sURL)
{
if (StringHelper.hasText (sURL))
try
{
return new URL (sURL);
}
catch (final MalformedURLException ex)
{
// fall-through
}
return null;
}
/**
* Get the passed URI as an URL. If the URI is null or cannot be converted to
* an URL null
is returned.
*
* @param aURI
* Source URI. May be null
.
* @return null
if the passed URI is null or cannot be converted
* to an URL.
*/
@Nullable
public static URL getAsURL (@Nullable final URI aURI)
{
if (aURI != null)
try
{
return aURI.toURL ();
}
catch (final MalformedURLException ex)
{
// fall-through
}
return null;
}
/**
* Get the passed String as an URI. If the string is empty or not an URI
* null
is returned.
*
* @param sURI
* Source URI. May be null
.
* @return null
if the passed URI is empty or invalid.
*/
@Nullable
public static URI getAsURI (@Nullable final String sURI)
{
if (StringHelper.hasText (sURI))
try
{
return new URI (sURI);
}
catch (final URISyntaxException ex)
{
// fall-through
}
return null;
}
/**
* Get the passed URL as an URI. If the URL is null or not an URI
* null
is returned.
*
* @param aURL
* Source URL. May be null
.
* @return null
if the passed URL is empty or invalid.
*/
@Nullable
public static URI getAsURI (@Nullable final URL aURL)
{
if (aURL != null)
try
{
return aURL.toURI ();
}
catch (final URISyntaxException ex)
{
// fall-through
}
return null;
}
/**
* Get an input stream from the specified URL. By default caching is disabled.
* This method only handles GET requests - POST requests are not possible.
*
* @param aURL
* The URL to use. May not be null
.
* @param nConnectTimeoutMS
* Connect timeout milliseconds. 0 == infinite. < 0: ignored.
* @param nReadTimeoutMS
* Read timeout milliseconds. 0 == infinite. < 0: ignored.
* @param aConnectionModifier
* An optional callback object to modify the URLConnection before it is
* opened.
* @param aExceptionHolder
* An optional exception holder for further outside investigation.
* @return null
if the input stream could not be opened.
*/
@Nullable
public static InputStream getInputStream (@Nonnull final URL aURL,
@CheckForSigned final int nConnectTimeoutMS,
@CheckForSigned final int nReadTimeoutMS,
@Nullable final Consumer aConnectionModifier,
@Nullable final IMutableWrapper aExceptionHolder)
{
ValueEnforcer.notNull (aURL, "URL");
if (DEBUG_GET_IS)
s_aLogger.info ("getInputStream ('" +
aURL +
"', " +
nConnectTimeoutMS +
", " +
nReadTimeoutMS +
", " +
aConnectionModifier +
", " +
aExceptionHolder +
")",
new Exception ());
URLConnection aConnection;
HttpURLConnection aHTTPConnection = null;
try
{
aConnection = aURL.openConnection ();
if (nConnectTimeoutMS >= 0)
aConnection.setConnectTimeout (nConnectTimeoutMS);
if (nReadTimeoutMS >= 0)
aConnection.setReadTimeout (nReadTimeoutMS);
if (aConnection instanceof HttpURLConnection)
aHTTPConnection = (HttpURLConnection) aConnection;
// Disable caching
aConnection.setUseCaches (false);
// Apply optional callback
if (aConnectionModifier != null)
aConnectionModifier.accept (aConnection);
if (aConnection instanceof JarURLConnection)
{
final JarEntry aJarEntry = ((JarURLConnection) aConnection).getJarEntry ();
if (aJarEntry != null)
{
// Directories are identified by ending with a "/"
if (aJarEntry.isDirectory ())
{
// Cannot open an InputStream on a directory
return null;
}
// Heuristics for directories not ending with a "/"
if (aJarEntry.getSize () == 0 && aJarEntry.getCrc () == 0)
{
// Cannot open an InputStream on a directory
s_aLogger.warn ("Heuristically determined " + aURL + " to be a directory!");
return null;
}
}
}
// by default follow-redirects is true for HTTPUrlConnections
final InputStream ret = aConnection.getInputStream ();
if (DEBUG_GET_IS)
s_aLogger.info (" returning " + ret);
return ret;
}
catch (final IOException ex)
{
if (aExceptionHolder != null)
{
// Remember the exception
aExceptionHolder.set (ex);
}
else
{
if (ex instanceof SocketTimeoutException)
s_aLogger.warn ("Timeout to open input stream for '" +
aURL +
"': " +
ex.getClass ().getName () +
" - " +
ex.getMessage ());
else
s_aLogger.warn ("Failed to open input stream for '" +
aURL +
"': " +
ex.getClass ().getName () +
" - " +
ex.getMessage ());
}
if (aHTTPConnection != null)
{
// Read error completely for keep-alive (see
// http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html)
InputStream aErrorIS = null;
try
{
aErrorIS = aHTTPConnection.getErrorStream ();
if (aErrorIS != null)
{
final byte [] aBuf = new byte [1024];
// read the response body
while (aErrorIS.read (aBuf) > 0)
{
// Read next
}
}
}
catch (final IOException ex2)
{
// deal with the exception
s_aLogger.warn ("Failed to consume error stream for '" +
aURL +
"': " +
ex2.getClass ().getName () +
" - " +
ex2.getMessage ());
}
finally
{
StreamHelper.close (aErrorIS);
}
}
}
return null;
}
@Nonnull
public static File getAsFile (@Nonnull final URL aURL)
{
ValueEnforcer.notNull (aURL, "URL");
ValueEnforcer.isEqual (PROTOCOL_FILE, aURL.getProtocol (), () -> "Not a file URL: " + aURL.toExternalForm ());
String sPath;
File aFile;
try
{
sPath = aURL.toURI ().getSchemeSpecificPart ();
aFile = new File (sPath);
}
catch (final URISyntaxException ex)
{
// Fallback for URLs that are not valid URIs
sPath = aURL.getPath ();
aFile = new File (sPath);
}
// In case the URL starts with a slash, make it absolute
if (FilenameHelper.startsWithPathSeparatorChar (sPath))
aFile = aFile.getAbsoluteFile ();
// This file may be non-existing
return aFile;
}
@Nullable
public static File getAsFileOrNull (@Nonnull final URL aURL)
{
if (aURL != null)
try
{
return getAsFile (aURL);
}
catch (final IllegalArgumentException ex)
{
// Happens for non-file URLs
}
return null;
}
/**
* Get the URL for the specified path using automatic class loader handling.
* The class loaders are iterated in the following order:
*
* - Default class loader (usually the context class loader)
* - The class loader of this class
* - The system class loader
*
*
* @param sPath
* The path to be resolved. May neither be null
nor empty.
* @return null
if the path could not be resolved.
*/
@Nullable
public static URL getClassPathURL (@Nonnull @Nonempty final String sPath)
{
ValueEnforcer.notEmpty (sPath, "Path");
// Use the default class loader. Returns null if not found
URL ret = ClassLoaderHelper.getResource (ClassLoaderHelper.getDefaultClassLoader (), sPath);
if (ret == null)
{
// This is essential if we're running as a web application!!!
ret = ClassHelper.getResource (URLHelper.class, sPath);
if (ret == null)
{
// this is a fix for a user that needed to have the application
// loaded by the bootstrap class loader
ret = ClassLoaderHelper.getResource (ClassLoaderHelper.getSystemClassLoader (), sPath);
}
}
return ret;
}
/**
* Get the input stream of the passed resource using the specified class
* loader only.
*
* @param sPath
* The path to be resolved. May neither be null
nor empty.
* @param aClassLoader
* The class loader to be used. May not be null
.
* @return null
if the path could not be resolved using the
* specified class loader.
*/
@Nullable
@Deprecated
public static URL getClassPathURL (@Nonnull @Nonempty final String sPath, @Nonnull final ClassLoader aClassLoader)
{
return ClassLoaderHelper.getResource (aClassLoader, sPath);
}
public static boolean isClassPathURLExisting (@Nonnull @Nonempty final String sPath)
{
return getClassPathURL (sPath) != null;
}
public static boolean isClassPathURLExisting (@Nonnull @Nonempty final String sPath,
@Nonnull final ClassLoader aClassLoader)
{
return ClassLoaderHelper.getResource (aClassLoader, sPath) != null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy