/*
Copyright 1996-2008 Ariba, Inc.
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.
$Id: //ariba/platform/util/core/ariba/util/core/URLUtil.java#13 $
*/
package ariba.util.core;
import ariba.util.log.Log;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLStreamHandler;
/**
URL Utilities. These are helper functions for dealing with
urls.
@aribaapi documented
*/
public final class URLUtil
{
/* keep people from creating this class */
private URLUtil ()
{
}
/**
specifies if there is a particular handler we want to use. Normally, when
we instantiate java.net.URL objects, we let java pick the URLStreamHandler.
@see java.net.URL
We need to provide control to the https URLStreamHandler when run under
Websphere. We should revisit this in future.
The value of this variable is set by the object instance specified by the
property HttpsUrlStreamHandlerProperty, and should represent a class which
is a subclass of java.util.URLStreamHandler. Currently this property is
defined when we run under websphere with value set to ariba.util.net.https.Handler.
@see HttpsUrlStreamHandlerProperty
*/
private static URLStreamHandler HTTPS_URL_STREAMHANDLER = null;
/**
Ariba specific system property that defines a specific urlStreamHandler to use.
ToDo: This is a temporary solution to websphere issues.
DO NOT use this property without talking to Server team first.
*/
private static final String HttpsUrlStreamHandlerProperty =
"ariba.httpsUrlStreamHandler";
/**
Return the secure URL stream handler.
This provides lazy initialization since we want to
avoid calls at static initialization that could result
in logging. There have been bugs due to logging static
initialization recursion.
@aribaapi private
*/
private static final URLStreamHandler getHttpsURLStreamHandler ()
{
if (HTTPS_URL_STREAMHANDLER == null) {
// no need to synchronize here--the last one wins
HTTPS_URL_STREAMHANDLER = setupHttpsURLStreamHandler();
}
return HTTPS_URL_STREAMHANDLER;
}
/**
set up the URLStreamHandler for https protocol, as specified by
the property "ariba.httpsUrlStreamHandler".
@return the URLStreamHandler object, <b>null</b> if:
(1) the property "ariba.httpsUrlStreamHandler" is not specified, OR
(2) the value specified by the above property is not a subclass of
java.util.URLStreamHandler, OR
(3) security exception is raised when the system property is accessed.
*/
private static URLStreamHandler setupHttpsURLStreamHandler ()
{
String handlerClassString = null;
try {
handlerClassString = System.getProperty(HttpsUrlStreamHandlerProperty);
}
catch (SecurityException e) {
Log.util.warning(6725, HttpsUrlStreamHandlerProperty, e);
return null;
}
if (StringUtil.nullOrEmptyOrBlankString(handlerClassString)) {
Log.util.debug("value of %s is either null or blank or empty. " +
"No https URLStreamHandler set.",
HttpsUrlStreamHandlerProperty);
return null;
}
Object object = ClassUtil.newInstance(handlerClassString,
"java.net.URLStreamHandler",
true);
Log.util.debug("set up %s to handle https URL streams",
handlerClassString);
return (URLStreamHandler)object;
}
/**
Generate a File for a URL.
@param urlContext a URL to open in a file. Assumes URL is of
type file protocol (e.g. file://) and is not null.
@param warning if <b>true</b>, on error, a warning will be printed
@return a File representing the URL, or <b>null</b> if the file
could not be opened
@aribaapi documented
*/
public static File file (URL urlContext, boolean warning)
{
String protocol = urlContext.getProtocol();
if (!protocol.equals("file")) {
if (warning) {
Log.util.error(2772, urlContext);
}
return null;
}
String filePath = urlContext.getFile();
// Add UNC support
String host = urlContext.getHost();
if (!StringUtil.nullOrEmptyOrBlankString(host) &&
SystemUtil.isWin32()) {
filePath = Fmt.S("\\\\%s\\%s", StringUtil.removeTrailingSlashIfAny(host),
StringUtil.removeLeadingSlashIfAny(filePath));
}
if (filePath.endsWith("/.")) {
filePath = filePath.substring(0, filePath.length() - 1);
}
// interestingly if one constructs a file with invalid
// separatorChar (e.g. '/' on windows) most File members
// work (e.g. exists()) but getParent() does not. Thus we
// have to flip the separator on Windows.
return new File(filePath.replace('/', File.separatorChar));
}
/**
Returns true if the URL may exist.
If the URL is a file: url, we convert it to a File and call
exists(), which is faster than dealing with a
FileNotFoundException.
This is primarily when the file is likely not to exist, such
as in the ResourceService which probes many possible filenames.
@param url the input URL
@return boolean indicating if URL may exist
@aribaapi documented
*/
public static boolean maybeURLExists (URL url)
{
File file = URLUtil.file(url, false);
if (file == null) {
// it may exist, we aren't sure.
return true;
}
return file.exists();
}
/**
Evaluates a string of a possibly relative url in the context
of a different url.
@param context the context in which to parse the
specification.
@param spec a string representation of a URL.
@return a new URL, or <b>null</b> if there was an exception in
the creation
@see java.net.URL#URL(URL, String)
@aribaapi documented
*/
public static URL asURL (URL context, String spec)
{
// Needed for java 1.2 bug - PR 12590
if ("".equals(spec) &&
context != null &&
"file".equals(context.getProtocol()))
{
spec = "./.";
}
try {
return makeURL(context, spec);
}
catch (MalformedURLException e) {
Log.util.error(2773, spec, e);
return null;
}
}
/**
Creates a URL relative to the application working directory. This
method should only be used within server code or by command line (i.e.
non-GUI) clients.
If you are in GUI code, use Widgets.url().
If you are in shared code, use Base.service().url().
@param file the file object used to create the url, cannot be null.
@return the URL created, or <b>null</b> if it cannot be created.
@aribaapi private
*/
public static URL url (File file)
{
try {
return file.toURL();
}
catch (MalformedURLException e) {
Log.util.error(2773, file.getPath(), e);
return null;
}
}
/**
Creates a URL relative to the application working directory. This
method should only be used within server code or by command line (i.e.
non-GUI) clients.
If you are in GUI code, use Widgets.url().
If you are in shared code, use Base.service().url().
@param spec the String that specifies the resource (typically a file). This String
must be a relative path (relative to the application working directory).
@return the URL representing the given spec.
@aribaapi private
*/
public static URL url (String spec)
{
return asURL(url(), spec);
}
/**
Creates a URL for the application working directory. This method
should only be used within server code or by command line clients.
If you are in GUI code, use Application.codeBase().
@aribaapi private
*/
public static URL url ()
{
return urlAbsolute(SystemUtil.getCwdFile());
}
/**
Creates a URL that references the given file. This method
should only be used within server code or by command line clients.
@aribaapi private
*/
public static URL urlAbsolute (File file)
{
String filePath = file.getAbsolutePath();
filePath = filePath.replace(File.separatorChar, '/');
if (file.isDirectory()) {
if (filePath.endsWith("/.")) {
filePath = filePath.substring(0, filePath.length() - 1);
}
if (!filePath.endsWith("/")) {
filePath = Fmt.S("%s/", filePath);
}
}
try {
/*
If the file path starts with "/" then don't prepend
one. Adding the extra slash will yield 'file://..."
which causes the first node of the path to be
considered a hostname not part of the path.
*/
if (filePath.startsWith("/")) {
return new URL("file", null, Fmt.S("%s", filePath)); // OK
}
else {
return new URL("file", null, Fmt.S("/%s", filePath)); // OK
}
}
catch (MalformedURLException e) {
Log.util.error(2774, filePath, e);
return null;
}
}
/**
Returns whether the specified specification is a
fully-qualified URL. The heuristic that we use is to check
whether it contains the string ":/" before any other
slashes. Note: Applet file codebases a prefixed by "file:/".
This is why we check for only one slash.
@aribaapi private
*/
public static boolean fullyQualifiedURLSpec (String spec)
{
int slash = spec.indexOf("/");
int colon = spec.indexOf(":");
// We have a complete URL if we have a protocol (the first slash
// is preceded by a colon)
return (slash > 0 && colon == slash - 1);
}
/**
Create a URL to a web server. If the url is absolute (includes
http) use it as is.
Otherwise, assume the URL is relative to the <code>context</code>.
@param url the URL string we are making a URL object for
@param context the root URL
@return a new URL as requested
@exception MalformedURLException
@aribaapi documented
*/
public static URL formURL (String url, String context)
throws MalformedURLException
{
URL completeURL = null;
if (url.startsWith(HTTP.Protocol)) {
completeURL = makeURL(null, url);
}
else {
URL contextURL = null;
if (context.endsWith("/")) {
contextURL = makeURL(null, context);
}
else {
contextURL = makeURL(null, Fmt.S("%s/", context));
}
completeURL = makeURL(contextURL, url);
}
return completeURL;
}
/**
Create a URL from a path and file name. The resulting URL will
be constructed independently of what VM (1.1 or 1.2) is being
used.
@param path the path (either absolute or relative)
@param file the file name.
@return a new URL as requested
@aribaapi documented
*/
public static URL concatURL (String path, String file)
{
String string = Fmt.S("%s/%s", path, file);
try {
return makeURL(null, string);
}
catch (MalformedURLException e) {
Log.util.error(2773, string, e);
return null;
}
}
/**
Wrapper class to create a new URL object. Did this to
centralize the logic to pass in the https URLStreamHandler.
new java.net.URL (String) should not be called directly. This
method should be called instead.
@param spec the URL spec, cannot be null.
@exception MalformedURLException if the spec is illegal.
@return the URL instance given the spec.
@aribaapi documented
*/
public static URL makeURL (String spec)
throws MalformedURLException
{
return makeURL(null, spec);
}
/**
Wrapper class to create a new URL object. Did this to
centralize the logic to pass in the https URLStreamHandler.
new java.net.URL (...) should not be called directly. This
method should be called instead.
@param protocol the name of the protocol to use.
@param host the name of the host.
@param port the port number on the host.
@param file the file on the host
@exception MalformedURLException if the spec is illegal.
@return the URL instance given the spec.
@aribaapi documented
*/
public static URL makeURL (String protocol, String host, int port, String file)
throws MalformedURLException
{
URLStreamHandler handler = null;
if (HTTP.SecureProtocol.equalsIgnoreCase(protocol)) {
handler = getHttpsURLStreamHandler();
}
return new URL(protocol, host, port, file, handler); // OK
}
/**
Wrapper class to create a new URL object. Did this to
centralize the logic to pass in the https URLStreamHandler.
@param context the context URL, can be null.
@param spec the URL spec, can be null. But if both spec and
context are null, java.net.URL will catch it.
@return the URL instance given the context and spec.
*/
private static URL makeURL (URL context, String spec)
throws MalformedURLException
{
// The ultimate goal is to detemine what handler to pass to
// instantiate a URL object. Javadoc from java.net.URL says
// the protocol from spec will override the one in context.
// So we find out the protocol used by spec.
// If it is https, we just passed in the static URLStreamHandler
// specified by HttpsUrlStreamHandlerProperty (can be null).
// Otherwise, we will let java take care of the handler unless
// the spec protocol is not specified (i.e. null).
// Then we determine the protocol from the context, and pass in
// our handler if the context protocol is https.
URLStreamHandler handler = null;
if (spec == null) {
if (context != null) {
if (HTTP.SecureProtocol.equalsIgnoreCase(context.getProtocol())) {
handler = getHttpsURLStreamHandler();
}
}
}
else if (isHttps(spec)) {
handler = getHttpsURLStreamHandler();
}
Log.util.debug("making URL: context = %s, spec = %s, handler = %s",
context, spec, handler);
return new URL(context, spec, handler); // OK
}
/**
Determine whether the given spec indicates the protocol to be https.
@param spec that specifies the URL, must be non-null
@return whether the spec indicates the protocol to be https
*/
private static boolean isHttps (String spec)
{
return spec.regionMatches(true, 0, HTTP.SecureProtocol,
0, HTTP.SecureProtocol.length());
}
/**
Create a URL string to a web server. If the url is absolute
(includes http) use it as is.
Otherwise, assume the URL is relative to the <code>context</code>, e.g.
"http://hostname/AribaORMS".
@param url url string we are making a URL object for
@param context the root url
@return a String representing the URL
@aribaapi documented
*/
public static String formURLString (String url, String context)
{
if (fullyQualifiedURLSpec(url)) {
return url;
}
// Concantenate context + url, making sure we don't have double
// slashes
FastStringBuffer buf = new FastStringBuffer(context);
boolean contextSlash = context.endsWith("/");
boolean urlSlash = url.startsWith("/");
if (contextSlash && urlSlash) {
buf.truncateToLength(buf.length()-1);
}
else if (!contextSlash && !urlSlash) {
buf.append("/");
}
buf.append(url);
return buf.toString();
}
}