org.fcrepo.client.FedoraClient Maven / Gradle / Ivy
/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TimeZone;
import javax.xml.rpc.ServiceException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.fcrepo.common.Constants;
import org.fcrepo.server.access.FedoraAPIA;
import org.fcrepo.server.access.FedoraAPIAMTOM;
import org.fcrepo.server.management.FedoraAPIM;
import org.fcrepo.server.management.FedoraAPIMMTOM;
import org.fcrepo.utilities.DateUtility;
import org.jrdf.graph.Literal;
import org.jrdf.graph.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trippi.RDFFormat;
import org.trippi.TrippiException;
import org.trippi.TupleIterator;
/**
* General-purpose utility class for Fedora clients. Provides methods to get
* SOAP stubs for Fedora APIs. Also serves as one-stop-shopping for issuing HTTP
* requests using Apache's HttpClient. Provides option for client to handle HTTP
* redirects (notably 302 status that occurs with SSL auto-redirects at server.)
*
* @author Chris Wilper
* @author Sandy Payette
*/
public class FedoraClient
implements Constants {
public static final String FEDORA_URI_PREFIX = "info:fedora/";
/** Seconds to wait before a connection is established. */
public int TIMEOUT_SECONDS = 20;
/** Seconds to wait while waiting for data over the socket (SO_TIMEOUT). */
public int SOCKET_TIMEOUT_SECONDS = 1800; // 30 minutes
/** Maxiumum http connections per host (for REST calls only). */
public int MAX_CONNECTIONS_PER_HOST = 15;
/** Maxiumum total http connections (for REST calls only). */
public int MAX_TOTAL_CONNECTIONS = 30;
/** Whether to automatically follow HTTP redirects. */
public boolean FOLLOW_REDIRECTS = true;
private static final Logger logger = LoggerFactory
.getLogger(FedoraClient.class);
private final SOAPEndpoint m_accessMTOMEndpoint =
new SOAPEndpoint("accessMTOM", false);
private final SOAPEndpoint m_managementMTOMEndpoint =
new SOAPEndpoint("managementMTOM", true);
private final SOAPEndpoint m_accessEndpoint =
new SOAPEndpoint("access", false);
private final SOAPEndpoint m_managementEndpoint =
new SOAPEndpoint("management", true);
private String m_baseURL;
private final String m_user;
private final String m_pass;
private final AuthScope m_authScope;
private final UsernamePasswordCredentials m_creds;
private final MultiThreadedHttpConnectionManager m_cManager;
private String m_serverVersion;
/**
* Location of Fedora's upload interface, set on first call to
* getUploadURL().
*/
private String m_uploadURL;
public FedoraClient(String baseURL, String user, String pass)
throws MalformedURLException {
m_baseURL = baseURL;
m_user = user;
m_pass = pass;
if (!baseURL.endsWith("/")) {
m_baseURL += "/";
}
URL url = new URL(m_baseURL);
m_authScope =
new AuthScope(url.getHost(),
AuthScope.ANY_PORT,
AuthScope.ANY_REALM);
m_creds = new UsernamePasswordCredentials(user, pass);
m_cManager = new MultiThreadedHttpConnectionManager();
}
public HttpClient getHttpClient() {
m_cManager.getParams()
.setDefaultMaxConnectionsPerHost(MAX_CONNECTIONS_PER_HOST);
m_cManager.getParams().setMaxTotalConnections(MAX_TOTAL_CONNECTIONS);
m_cManager.getParams().setConnectionTimeout(TIMEOUT_SECONDS * 1000);
m_cManager.getParams().setSoTimeout(SOCKET_TIMEOUT_SECONDS * 1000);
HttpClient client = new HttpClient(m_cManager);
client.getState().setCredentials(m_authScope, m_creds);
client.getParams().setAuthenticationPreemptive(true);
return client;
}
/**
* Upload the given file to Fedora's upload interface via HTTP POST.
*
* @return the temporary id which can then be passed to API-M requests as a
* URL. It will look like uploaded://123
*/
public String uploadFile(File file) throws IOException {
PostMethod post = null;
try {
// prepare the post method
post = new PostMethod(getUploadURL());
post.setDoAuthentication(true);
post.getParams().setParameter("Connection", "Keep-Alive");
// chunked encoding is not required by the Fedora server,
// but makes uploading very large files possible
post.setContentChunked(true);
// add the file part
Part[] parts = {new FilePart("file", file)};
post.setRequestEntity(new MultipartRequestEntity(parts, post
.getParams()));
// execute and get the response
int responseCode = getHttpClient().executeMethod(post);
String body = null;
try {
body = post.getResponseBodyAsString();
} catch (Exception e) {
logger.warn("Error reading response body", e);
}
if (body == null) {
body = "[empty response body]";
}
body = body.trim();
if (responseCode != HttpStatus.SC_CREATED) {
throw new IOException("Upload failed: "
+ HttpStatus.getStatusText(responseCode) + ": "
+ replaceNewlines(body, " "));
} else {
return replaceNewlines(body, "");
}
} finally {
if (post != null) {
post.releaseConnection();
}
}
}
/**
* Replace newlines with the given string.
*/
private static String replaceNewlines(String in, String replaceWith) {
return in.replaceAll("\r", replaceWith).replaceAll("\n", replaceWith);
}
/**
* Get the URL to which API-M upload requests will be sent.
*/
public synchronized String getUploadURL() throws IOException {
if (m_uploadURL != null) {
return m_uploadURL;
} else {
m_uploadURL = m_baseURL + "management/upload";
if (m_uploadURL.startsWith("http:")) {
URL redirectURL = getRedirectURL(m_uploadURL);
if (redirectURL != null) {
m_uploadURL = redirectURL.toString();
}
}
return m_uploadURL;
}
}
/**
* Get an HTTP resource with the response as an InputStream, given a
* resource locator that either begins with 'info:fedora/' , 'http://', or
* '/'. This method will follow redirects if FOLLOW_REDIRECTS is true. Note
* that if the HTTP response has no body, the InputStream will be empty. The
* success of a request can be checked with getResponseCode(). Usually
* you'll want to see a 200. See
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for other codes.
*
* @param locator
* A URL, relative Fedora URL, or Fedora URI that we want to do an
* HTTP GET upon
* @param failIfNotOK
* boolean value indicating if an exception should be thrown if we do
* NOT receive an HTTP 200 response (OK)
* @return HttpInputStream the HTTP response
* @throws IOException
*/
public HttpInputStream get(String locator, boolean failIfNotOK)
throws IOException {
return get(locator, failIfNotOK, FOLLOW_REDIRECTS);
}
/**
* Get an HTTP resource with the response as an InputStream, given a URL.
* This method will follow redirects if FOLLOW_REDIRECTS is true. Note that
* if the HTTP response has no body, the InputStream will be empty. The
* success of a request can be checked with getResponseCode(). Usually
* you'll want to see a 200. See
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for other codes.
*
* @param url
* A URL that we want to do an HTTP GET upon
* @param failIfNotOK
* boolean value indicating if an exception should be thrown if we do
* NOT receive an HTTP 200 response (OK)
* @return HttpInputStream the HTTP response
* @throws IOException
*/
public HttpInputStream get(URL url, boolean failIfNotOK) throws IOException {
return get(url, failIfNotOK, FOLLOW_REDIRECTS);
}
/**
* Get an HTTP resource with the response as an InputStream, given a
* resource locator that either begins with 'info:fedora/' , 'http://', or
* '/'. Note that if the HTTP response has no body, the InputStream will be
* empty. The success of a request can be checked with getResponseCode().
* Usually you'll want to see a 200. See
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for other codes.
*
* @param locator
* A URL, relative Fedora URL, or Fedora URI that we want to do an
* HTTP GET upon
* @param failIfNotOK
* boolean value indicating if an exception should be thrown if we do
* NOT receive an HTTP 200 response (OK)
* @param followRedirects
* boolean value indicating whether HTTP redirects should be handled
* in this method, or be passed along so that they can be handled
* later.
* @return HttpInputStream the HTTP response
* @throws IOException
*/
public HttpInputStream get(String locator,
boolean failIfNotOK,
boolean followRedirects) throws IOException {
// Convert the locator to a proper Fedora URL and the do a get.
String url = getLocatorAsURL(locator);
return get(new URL(url), failIfNotOK, followRedirects);
}
/**
* Get an HTTP resource with the response as an InputStream, given a URL.
* Note that if the HTTP response has no body, the InputStream will be
* empty. The success of a request can be checked with getResponseCode().
* Usually you'll want to see a 200. See
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for other codes.
*
* @param url
* A URL that we want to do an HTTP GET upon
* @param failIfNotOK
* boolean value indicating if an exception should be thrown if we do
* NOT receive an HTTP 200 response (OK)
* @param followRedirects
* boolean value indicating whether HTTP redirects should be handled
* in this method, or be passed along so that they can be handled
* later.
* @return HttpInputStream the HTTP response
* @throws IOException
*/
public HttpInputStream get(URL url,
boolean failIfNotOK,
boolean followRedirects) throws IOException {
String urlString = url.toString();
logger.debug("FedoraClient is getting " + urlString);
HttpClient client = getHttpClient();
GetMethod getMethod = new GetMethod(urlString);
getMethod.setDoAuthentication(true);
getMethod.setFollowRedirects(followRedirects);
HttpInputStream in = new HttpInputStream(client, getMethod, urlString);
int status = in.getStatusCode();
if (failIfNotOK) {
if (status != 200) {
if (followRedirects && 300 <= status && status <= 399) {
// Handle the redirect here !
logger.debug("FedoraClient is handling redirect for HTTP STATUS="
+ status);
Header hLoc = in.getResponseHeader("location");
if (hLoc != null) {
logger.debug("FedoraClient is trying redirect location: "
+ hLoc.getValue());
// Try the redirect location, but don't try to handle another level of redirection.
return get(hLoc.getValue(), true, false);
} else {
try {
throw new IOException("Request failed [" + status
+ " " + in.getStatusText() + "]");
} finally {
try {
in.close();
} catch (Exception e) {
logger.error("Can't close InputStream: "
+ e.getMessage());
}
}
}
} else {
try {
throw new IOException("Request failed ["
+ in.getStatusCode() + " " + in.getStatusText()
+ "] : " + urlString);
} finally {
try {
in.close();
} catch (Exception e) {
logger.error("Can't close InputStream: "
+ e.getMessage());
}
}
}
}
}
return in;
}
/**
* Get an HTTP resource with the response as a String instead of an
* InputStream, given a resource locator that either begins with
* 'info:fedora/' , 'http://', or '/'.
*
* @param locator
* A URL, relative Fedora URL, or Fedora URI that we want to do an
* HTTP GET upon
* @param failIfNotOK
* boolean value indicating if an exception should be thrown if we do
* NOT receive an HTTP 200 response (OK)
* @param followRedirects
* boolean value indicating whether HTTP redirects should be handled
* in this method, or be passed along so that they can be handled
* later.
* @return String the HTTP response as a string
* @throws IOException
*/
public String getResponseAsString(String locator,
boolean failIfNotOK,
boolean followRedirects)
throws IOException {
InputStream in = get(locator, failIfNotOK, followRedirects);
// Convert the response into a String.
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(in));
StringBuffer buffer = new StringBuffer();
String line = reader.readLine();
while (line != null) {
buffer.append(line + "\n");
line = reader.readLine();
}
return buffer.toString();
} finally {
try {
in.close();
} catch (Exception e) {
logger.error("Can't close InputStream: " + e.getMessage());
}
}
}
private String getLocatorAsURL(String locator) throws IOException {
String url;
if (locator.startsWith(FEDORA_URI_PREFIX)) {
url =
m_baseURL + "get/"
+ locator.substring(FEDORA_URI_PREFIX.length());
} else if (locator.startsWith("http://")
|| locator.startsWith("https://")) {
url = locator;
} else if (locator.startsWith("/")) {
// assume it's for something within this Fedora server
while (locator.startsWith("/")) {
locator = locator.substring(1);
}
url = m_baseURL + locator;
} else {
throw new IOException("Bad locator (must start with '"
+ FEDORA_URI_PREFIX + "', 'http[s]://', or '/'");
}
return url;
}
/**
* Get a new SOAP stub for API-A. If the baseURL for this
* FedoraClient
specifies "http", regular HTTP communication
* will be attempted first. If the server redirects this client to use HTTPS
* instead, the redirect will be followed and SSL will be used
* automatically.
*/
public FedoraAPIA getAPIA() throws ServiceException, IOException {
return getSOAPStub(FedoraAPIA.class);
}
public URL getAPIAEndpointURL() throws IOException {
return m_accessEndpoint.getURL();
}
public FedoraAPIAMTOM getAPIAMTOM() throws ServiceException, IOException {
return getSOAPStub(FedoraAPIAMTOM.class);
}
public URL getAPIAMTOMEndpointURL() throws IOException {
return m_accessMTOMEndpoint.getURL();
}
/**
* Get a new SOAP stub for API-M. If the baseURL for this
* FedoraClient
specifies "http", regular HTTP communication
* will be attempted first. If the server redirects this client to use HTTPS
* instead, the redirect will be followed and SSL will be used
* automatically.
*/
public FedoraAPIM getAPIM() throws ServiceException, IOException {
return getSOAPStub(FedoraAPIM.class);
}
public URL getAPIMEndpointURL() throws IOException {
return m_managementEndpoint.getURL();
}
public FedoraAPIMMTOM getAPIMMTOM() throws ServiceException, IOException {
return getSOAPStub(FedoraAPIMMTOM.class);
}
public URL getAPIMMTOMEndpointURL() throws IOException {
return m_managementMTOMEndpoint.getURL();
}
/**
* Get the appropriate API-A/M stub, given a SOAPEndpoint.
* @param
*/
private T getSOAPStub(Class type) throws ServiceException,
IOException {
if (type == org.fcrepo.server.access.FedoraAPIAMTOM.class) {
org.fcrepo.client.mtom.APIAStubFactory.SOCKET_TIMEOUT_SECONDS = SOCKET_TIMEOUT_SECONDS;
URL url = m_accessMTOMEndpoint.getURL();
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
if (port == -1) {
port = url.getDefaultPort();
}
return (T) org.fcrepo.client.mtom.APIAStubFactory
.getStub(protocol, host, port, m_user, m_pass);
} else if (type == FedoraAPIMMTOM.class) {
org.fcrepo.client.mtom.APIMStubFactory.SOCKET_TIMEOUT_SECONDS = SOCKET_TIMEOUT_SECONDS;
URL url = m_managementMTOMEndpoint.getURL();
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
if (port == -1) {
port = url.getDefaultPort();
}
return (T) org.fcrepo.client.mtom.APIMStubFactory
.getStub(protocol, host, port, m_user, m_pass);
} else if (type == FedoraAPIM.class) {
org.fcrepo.client.APIMStubFactory.SOCKET_TIMEOUT_SECONDS = SOCKET_TIMEOUT_SECONDS;
URL url = m_managementEndpoint.getURL();
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
if (port == -1) {
port = url.getDefaultPort();
}
return (T) org.fcrepo.client.APIMStubFactory
.getStub(protocol, host, port, m_user, m_pass);
} else if (type == FedoraAPIA.class) {
org.fcrepo.client.APIAStubFactory.SOCKET_TIMEOUT_SECONDS = SOCKET_TIMEOUT_SECONDS;
URL url = m_accessEndpoint.getURL();
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
if (port == -1) {
port = url.getDefaultPort();
}
return (T) org.fcrepo.client.APIAStubFactory
.getStub(protocol, host, port, m_user, m_pass);
} else {
throw new IllegalArgumentException("Unrecognized api class: "
+ type.getName());
}
}
public static String getVersion() {
ResourceBundle bundle =
ResourceBundle.getBundle("org.fcrepo.client.resources.Client");
return bundle.getString("version");
}
public static List getCompatibleServerVersions() {
ResourceBundle bundle =
ResourceBundle.getBundle("org.fcrepo.client.resources.Client");
List list = new ArrayList();
String versions = bundle.getString("compatibleServerVersions");
if (versions != null && versions.trim().length() > 0) {
String[] va = versions.trim().split(" ");
for (String element : va) {
list.add(element);
}
}
String clientVersion = getVersion();
if (!list.contains(clientVersion)) {
list.add(getVersion());
}
return list;
}
/**
* Get the version reported by the remote Fedora server.
*/
public String getServerVersion() throws IOException {
// only do this once -- future invocations will use the known value
if (m_serverVersion == null) {
// Make the APIA call for describe repository
// and make sure that HTTP 302 status is handled.
String desc = getResponseAsString("/describe?xml=true", true, true);
logger.debug("describeRepository response:\n" + desc);
String[] parts = desc.split("");
if (parts.length < 2) {
throw new IOException("Could not find repositoryVersion element in content of /describe?xml=true");
}
int i = parts[1].indexOf("<");
if (i == -1) {
throw new IOException("Could not find end of repositoryVersion element in content of /describe?xml=true");
}
m_serverVersion = parts[1].substring(0, i).trim();
logger.debug("Server version is " + m_serverVersion);
}
return m_serverVersion;
}
/**
* Return the current date as reported by the Fedora server.
*
* @throws IOException
* if the HTTP Date header is not provided by the server for any
* reason, or it is in the wrong format.
*/
public Date getServerDate() throws IOException {
HttpInputStream in = get("/describe", false, false);
String dateString = null;
try {
Header header = in.getResponseHeader("Date");
if (header == null) {
throw new IOException("Date was not supplied in HTTP response "
+ "header for " + m_baseURL + "describe");
}
dateString = header.getValue();
// This is the date format recommended by RFC2616
SimpleDateFormat format =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",
Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
return format.parse(dateString);
} catch (ParseException e) {
throw new IOException("Unparsable date (" + dateString
+ ") in HTTP response header for " + m_baseURL + "describe");
} finally {
in.close();
}
}
public Date getLastModifiedDate(String locator) throws IOException {
if (locator.startsWith(FEDORA_URI_PREFIX)) {
String query =
"select $date " + "from <#ri> " + "where <" + locator
+ "> <" + VIEW.LAST_MODIFIED_DATE.uri + "> $date";
Map map = new HashMap();
map.put("lang", "itql");
map.put("query", query);
TupleIterator tuples = getTuples(map);
try {
if (tuples.hasNext()) {
Map row = tuples.next();
Literal dateLiteral = (Literal) row.get("date");
if (dateLiteral == null) {
throw new IOException("A row was returned, but it did not contain a 'date' binding");
}
return DateUtility.parseDateLoose(dateLiteral
.getLexicalForm());
} else {
throw new IOException("No rows were returned");
}
} catch (TrippiException e) {
throw new IOException(e.getMessage());
} finally {
try {
tuples.close();
} catch (Exception e) {
}
}
} else {
HttpClient client = getHttpClient();
HeadMethod head = new HeadMethod(locator);
head.setDoAuthentication(true);
head.setFollowRedirects(FOLLOW_REDIRECTS);
try {
int statusCode = client.executeMethod(head);
if (statusCode != HttpStatus.SC_OK) {
throw new IOException("Method failed: "
+ head.getStatusLine());
}
//Header[] headers = head.getResponseHeaders();
// Retrieve just the last modified header value.
Header header = head.getResponseHeader("last-modified");
if (header != null) {
String lastModified = header.getValue();
return DateUtility.parseDateLoose(lastModified);
} else {
// return current date time
return new Date();
}
} finally {
head.releaseConnection();
}
}
}
public void reloadPolicies() throws IOException {
InputStream in = null;
try {
in = get("/management/control?action=reloadPolicies", true, true);
} finally {
try {
in.close();
} catch (Exception e) {
logger.error("Can't close InputStream: " + e.getMessage());
}
}
}
/**
* Get tuples from the remote resource index. The map contains
* String values for parameters that should be passed to the
* service. Two parameters are required: 1) lang 2) query Two parameters to
* the risearch service are implied: 1) type = tuples 2) format = sparql See
* http
* ://www.fedora.info/download/2.0/userdocs/server/webservices/risearch/#
* app.tuples
*/
public TupleIterator getTuples(Map params)
throws IOException {
params.put("type", "tuples");
params.put("format", RDFFormat.SPARQL.getName());
try {
String url = getRIQueryURL(params);
return TupleIterator.fromStream(get(url, true, true),
RDFFormat.SPARQL);
} catch (TrippiException e) {
throw new IOException("Error getting tuple iterator: "
+ e.getMessage());
}
}
private String getRIQueryURL(Map params) throws IOException {
if (params.get("type") == null) {
throw new IOException("'type' parameter is required");
}
if (params.get("lang") == null) {
throw new IOException("'lang' parameter is required");
}
if (params.get("query") == null) {
throw new IOException("'query' parameter is required");
}
if (params.get("format") == null) {
throw new IOException("'format' parameter is required");
}
return m_baseURL + "risearch?" + encodeParameters(params);
}
private String encodeParameters(Map params) {
StringBuffer encoded = new StringBuffer();
Iterator iter = params.keySet().iterator();
int n = 0;
while (iter.hasNext()) {
String name = iter.next();
if (n > 0) {
encoded.append("&");
}
n++;
encoded.append(name);
encoded.append('=');
try {
encoded.append(URLEncoder.encode(params.get(name), "UTF-8"));
} catch (UnsupportedEncodingException e) { // UTF-8 won't fail
}
}
return encoded.toString();
}
/**
* Ping the given endpoint to see if an HTTP 302 status code is returned. If
* so, return the location given in the HTTP response header. If not, return
* null.
*/
private URL getRedirectURL(String location) throws IOException {
HttpInputStream in = get(location, false, false);
try {
if (in.getStatusCode() == 302) {
Header h = in.getResponseHeader("location");
if (h != null) {
return new URL(h.getValue());
}
}
return null;
} finally {
try {
in.close();
} catch (Exception e) {
}
}
}
/**
* Class for storing a Fedora SOAP endpoint, which consists of an endpoint
* name and a URL. The endpoint name is provided to the constructor. The URL
* is determined automatically, once, based on:
*
* - The baseURL provided to the FedoraClient instance.
* - The server version.
* - Whether the server automatically redirects non-SSL SOAP requests to
* an SSL endpoint.
*
*/
public class SOAPEndpoint {
String m_name;
URL m_url;
boolean m_apim;
public SOAPEndpoint(String name, boolean apim) {
m_name = name;
m_apim = apim;
}
public String getName() {
return m_name;
}
public URL getURL() throws IOException {
if (m_url == null) {
String url = getChannelBaseUrl() + "services/" + m_name;
m_url = new URL(url);
}
return m_url;
}
public String getChannelBaseUrl() throws IOException {
if (m_apim) {
URL redirect = getRedirectURL(m_baseURL + "management/upload");
if (redirect == null) return m_baseURL;
else return redirect.toString().replace("management/upload", "");
} else {
URL redirect = getRedirectURL(m_baseURL + "wsdl");
if (redirect == null) return m_baseURL;
else return redirect.toString().replace("wsdl", "");
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy