/* 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.server.utilities;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Date;
import javax.ws.rs.core.HttpHeaders;
import org.apache.commons.io.IOUtils;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fcrepo.common.Constants;
import org.fcrepo.common.http.WebClient;
import org.fcrepo.common.http.WebClientConfiguration;
import org.fcrepo.server.Context;
import org.fcrepo.server.config.Parameter;
import org.fcrepo.server.config.ServerConfiguration;
import org.fcrepo.server.config.ServerConfigurationParser;
import org.fcrepo.server.storage.types.Property;
import org.fcrepo.utilities.DateUtility;
public class ServerUtility {
private static final Logger logger =
LoggerFactory.getLogger(ServerUtility.class);
private static final String IMS_KEY = HttpHeaders.IF_MODIFIED_SINCE.toLowerCase();
private static final String INM_KEY = HttpHeaders.IF_NONE_MATCH.toLowerCase();
public static final String HTTP = "http";
public static final String HTTPS = "https";
public static final String FEDORA_SERVER_HOST = "fedoraServerHost";
public static final String FEDORA_SERVER_PORT = "fedoraServerPort";
public static final String FEDORA_SERVER_CONTEXT = "fedoraAppServerContext";
public static final String FEDORA_REDIRECT_PORT = "fedoraRedirectPort";
private static ServerConfiguration CONFIG =
getServerConfig();
private static WebClient s_webClient =
getWebClient();
private static ServerConfiguration getServerConfig() {
String fedoraHome = Constants.FEDORA_HOME;
if (fedoraHome == null) {
logger.warn("FEDORA_HOME not set; unable to initialize");
} else {
File fcfgFile = new File(fedoraHome, "server/config/fedora.fcfg");
try {
return new ServerConfigurationParser(new FileInputStream(fcfgFile))
.parse();
} catch (IOException e) {
logger.warn("Unable to read server configuration from "
+ fcfgFile.getPath(), e);
}
}
return null;
}
private static WebClient getWebClient() {
WebClientConfiguration webconfig = new WebClientConfiguration();
initWebClientConfig(webconfig);
return new WebClient(webconfig);
}
/**
* Tell whether the server is running by pinging it as a client.
*/
public static boolean pingServer(String protocol, String user, String pass) {
try {
getServerResponse(protocol, user, pass, "/describe");
return true;
} catch (Exception e) {
logger.debug("Assuming the server isn't running because "
+ "describe request failed", e);
return false;
}
}
/**
* Get the baseURL of the Fedora server from the host and port configured.
* It will look like http://localhost:8080/fedora
*/
public static String getBaseURL(String protocol) {
String port;
if (protocol.equals("http")) {
port = CONFIG.getParameter(FEDORA_SERVER_PORT,Parameter.class).getValue();
} else if (protocol.equals("https")) {
port = CONFIG.getParameter(FEDORA_REDIRECT_PORT,Parameter.class).getValue();
} else {
throw new RuntimeException("Unrecogonized protocol: " + protocol);
}
return protocol + "://"
+ CONFIG.getParameter(FEDORA_SERVER_HOST,Parameter.class).getValue() + ":"
+ port + "/" + CONFIG.getParameter(FEDORA_SERVER_CONTEXT,Parameter.class).getValue();
}
/**
* Signals for the server to reload its policies.
*/
public static void reloadPolicies(String protocol, String user, String pass)
throws IOException {
getServerResponse(protocol,
user,
pass,
"/management/control?action=reloadPolicies");
}
/**
* Hits the given Fedora Server URL and returns the response as a String.
* Throws an IOException if the response code is not 200(OK).
*/
private static String getServerResponse(String protocol,
String user,
String pass,
String path) throws IOException {
String url = getBaseURL(protocol) + path;
logger.info("Getting URL: {}", url);
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials(user, pass);
return s_webClient.getResponseAsString(url, true, creds);
}
/**
* Hits the given Fedora Server URL and returns the response as a String.
* Throws an IOException if the response code is not 200(OK).
*/
private static InputStream getServerResponseAsStream(String protocol,
String user,
String pass,
String path) throws IOException {
String url = getBaseURL(protocol) + path;
logger.info("Getting URL: {}", url);
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials(user, pass);
return s_webClient.get(url, true, creds);
}
/**
* Tell whether the given URL appears to be referring to somewhere within
* the Fedora webapp.
*/
public static boolean isURLFedoraServer(String url) {
// scheme must be http or https
URI uri = URI.create(url);
String scheme = uri.getScheme();
if (!scheme.equals("http") && !scheme.equals("https")) {
return false;
}
// host must be configured hostname or localhost
String fHost = CONFIG.getParameter(FEDORA_SERVER_HOST,Parameter.class).getValue();
String host = uri.getHost();
if (!host.equals(fHost) && !host.equals("localhost")) {
return false;
}
// path must begin with configured webapp context
String path = uri.getPath();
String fedoraContext = CONFIG.getParameter(
FEDORA_SERVER_CONTEXT,Parameter.class).getValue();
if (!path.startsWith("/" + fedoraContext + "/")) {
return false;
}
// port specification must match http or https port as appropriate
String httpPort = CONFIG.getParameter(FEDORA_SERVER_PORT,Parameter.class).getValue();
String httpsPort = CONFIG.getParameter(FEDORA_REDIRECT_PORT,Parameter.class).getValue();
if (uri.getPort() == -1) {
// unspecified, so fedoraPort must be 80 (http), or 443 (https)
if (scheme.equals("http")) {
return httpPort.equals("80");
} else {
return httpsPort.equals("443");
}
} else {
// specified, so must match appropriate http or https port
String port = "" + uri.getPort();
if (scheme.equals("http")) {
return port.equals(httpPort);
} else {
return port.equals(httpsPort);
}
}
}
public static InputStream modifyDatastreamControlGroup(String protocol, String user, String pass, String pidspec, String datastreamspec, String controlGroup, boolean addXMLHeader, boolean reformat, boolean setMIMETypeCharset) throws IOException {
String path = "/management/control?action=modifyDatastreamControlGroup" +
"&pid=" + pidspec +
"&dsID=" + datastreamspec +
"&controlGroup=" + controlGroup +
"&addXMLHeader=" + addXMLHeader +
"&reformat=" + reformat +
"&setMIMETypeCharset=" + setMIMETypeCharset;
return getServerResponseAsStream(protocol,
user,
pass,
path);
}
/**
* Initializes the web client http connection settings.
*/
private static void initWebClientConfig(WebClientConfiguration wconf) {
if (CONFIG == null) {
logger.warn("Web client config was null; cannot configure web client for {}", ServerUtility.class);
logger.info("FEDORA_HOME is used to configure web client; was set to {}", Constants.FEDORA_HOME);
return;
}
if (CONFIG.getParameter("httpClientTimeoutSecs") != null)
wconf.setTimeoutSecs(Integer.parseInt(CONFIG.getParameter("httpClientTimeoutSecs")));
if (CONFIG.getParameter("httpClientSocketTimeoutSecs") != null)
wconf.setSockTimeoutSecs(Integer.parseInt(CONFIG.getParameter("httpClientSocketTimeoutSecs")));
if (CONFIG.getParameter("httpClientMaxConnectionsPerHost") != null)
wconf.setMaxConnPerHost(Integer.parseInt(CONFIG.getParameter("httpClientMaxConnectionsPerHost")));
if (CONFIG.getParameter("httpClientMaxTotalConnections") != null)
wconf.setMaxTotalConn(Integer.parseInt(CONFIG.getParameter("httpClientMaxTotalConnections")));
if (CONFIG.getParameter("httpClientFollowRedirects") != null)
wconf.setFollowRedirects(Boolean.parseBoolean(CONFIG.getParameter("httpClientFollowRedirects")));
if (CONFIG.getParameter("httpClientMaxFollowRedirects") != null)
wconf.setMaxRedirects(Integer.parseInt(CONFIG.getParameter("httpClientMaxFollowRedirects")));
if (CONFIG.getParameter("httpClientUserAgent") != null)
wconf.setUserAgent(CONFIG.getParameter("httpClientUserAgent"));
}
/**
* Command-line entry point to reload policies. Takes 3 args: protocol user
* pass
*/
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Parameters: method arg1 arg2 arg3 etc");
System.out.println("");
System.out.println("Methods:");
System.out.println(" reloadpolicies");
System.out.println(" migratedatastreamcontrolgroup");
System.exit(0);
}
String method = args[0].toLowerCase();
if (method.equals("reloadpolicies")) {
if (args.length == 4) {
try {
reloadPolicies(args[1], args[2], args[3]);
System.out.println("SUCCESS: Policies have been reloaded");
System.exit(0);
} catch (Throwable th) {
th.printStackTrace();
System.err
.println("ERROR: Reloading policies failed; see above");
System.exit(1);
}
} else {
System.err.println("ERROR: Three arguments required: "
+ "http|https username password");
System.exit(1);
}
} else if (method.equals("migratedatastreamcontrolgroup")) {
// too many args
if (args.length > 10) {
System.err.println("ERROR: too many arguments provided");
System.exit(1);
}
// too few
if (args.length < 7) {
System.err.println("ERROR: insufficient arguments provided. Arguments are: ");
System.err.println(" protocol [http|https]"); // 1; 0 is method
System.err.println(" user"); // 2
System.err.println(" password"); // 3
System.err.println(" pid - either"); // 4
System.err.println(" a single pid, eg demo:object");
System.err.println(" list of pids separated by commas, eg demo:object1,demo:object2");
System.err.println(" name of file containing pids, eg file:///path/to/file");
System.err.println(" dsid - either"); // 5
System.err.println(" a single datastream id, eg DC");
System.err.println(" list of ids separated by commas, eg DC,RELS-EXT");
System.err.println(" controlGroup - target control group (note only M is implemented)"); // 6
System.err.println(" addXMLHeader - add an XML header to the datastream [true|false, default false]"); // 7
System.err.println(" reformat - reformat the XML [true|false, default false]"); // 8
System.err.println(" setMIMETypeCharset - add charset=UTF-8 to the MIMEType [true|false, default false]"); // 9
System.exit(1);
}
try {
// optional args
boolean addXMLHeader = getArgBoolean(args, 7, false);
boolean reformat = getArgBoolean(args, 8, false);
boolean setMIMETypeCharset = getArgBoolean(args, 9, false);;
InputStream is = modifyDatastreamControlGroup(args[1], args[2], args[3], args[4], args[5], args[6], addXMLHeader, reformat, setMIMETypeCharset);
IOUtils.copy(is, System.out);
is.close();
System.out.println("SUCCESS: Datastreams modified");
System.exit(0);
} catch (Throwable th) {
th.printStackTrace();
System.err
.println("ERROR: migrating datastream control group failed; see above");
System.exit(1);
}
} else {
System.err.println("ERROR: unrecognised method " + method);
System.exit(1);
}
}
/**
* Get boolean argument from list of arguments at position; use defaultValue if not present
* @param args
* @param position
* @param defaultValue
* @return
*/
private static boolean getArgBoolean(String[] args, int position, boolean defaultValue) {
if (args.length > position) {
String lowerArg = args[position].toLowerCase();
if (lowerArg.equals("true") || lowerArg.equals("yes")) {
return true;
} else if (lowerArg.equals("false") || lowerArg.equals("no")) {
return false;
} else {
throw new IllegalArgumentException(args[position] + " not a valid value. Specify true or false");
}
} else {
return defaultValue;
}
}
public static boolean isStaleCache(Context context, Property[] headers) {
String ifNoneMatch = context.getHeaderValue(INM_KEY);
String ifModSince = context.getHeaderValue(IMS_KEY);
if (ifModSince == null && ifNoneMatch == null) return true;
// parse out the data for comparison
Date imsDate = (ifModSince == null) ? null : DateUtility.parseDateLoose(ifModSince);
long ims = (imsDate == null) ? Long.MIN_VALUE : imsDate.getTime();
// get the local comparison values
String etag = null;
long lastMod = Long.MAX_VALUE;
for (Property header: headers) {
if (HttpHeaders.ETAG.equalsIgnoreCase(header.name)) etag = header.value;
if (HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(header.name)) {
Date d = DateUtility.parseDateLoose(header.value);
if (d != null) lastMod = d.getTime();
}
}
if (ifNoneMatch != null && !ifNoneMatch.equals(etag)) return true;
return (ifModSince != null && ims < lastMod);
}
}