/* 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.storage;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.fcrepo.common.http.HttpInputStream;
import org.fcrepo.common.http.WebClient;
import org.fcrepo.common.http.WebClientConfiguration;
import org.fcrepo.server.Context;
import org.fcrepo.server.Server;
import org.fcrepo.server.errors.ModuleInitializationException;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.storage.types.DigitalObject;
/**
* DefaultDOManager extension that updates a GSearch (Fedora Generic Search)
* service as object changes are committed.
* <p>
* To use, simply change fedora.fcfg, replacing "DefaultDOManager" with
* "GSearchDOManager", and add the following xml param elements:
* </p>
* <p>
* Required:
* </p>
* <ul>
* <li> <param name="gSearchRESTURL"
* value="http://localhost:8080/fedoragsearch/rest"/></li>
* </ul>
* <p>
* Optional (only needed if basic auth is required for GSearch REST access):
* </p>
* <ul>
* <li> <param name="gSearchUsername" value="exampleUsername"/></li>
* <li> <param name="gSearchPassword" value="examplePassword"/></li>
* </ul>
*
* @author Chris Wilper
*/
public class GSearchDOManager
extends DefaultDOManager {
private static final Logger logger =
LoggerFactory.getLogger(GSearchDOManager.class);
/** Required param: URL of GSearch REST interface. */
public static final String GSEARCH_REST_URL = "gSearchRESTURL";
/** Optional param: User to authenticate to GSearch as. */
public static final String GSEARCH_USERNAME = "gSearchUsername";
/** Optional param: Password to use for GSearch authentication. */
public static final String GSEARCH_PASSWORD = "gSearchPassword";
/** Configured value for GSEARCH_REST_URL parameter. */
private String _gSearchRESTURL;
/** Credentials we'll use for GSearch authentication, if enabled. */
private UsernamePasswordCredentials _gSearchCredentials;
/** HTPP client configuration */
private WebClientConfiguration _webClientConfig;
/** HTTP client we'll use for sending GSearch update signals. */
private WebClient _webClient;
/**
* Delegates construction to the superclass.
*/
public GSearchDOManager(Map<String, String> moduleParameters, Server server, String role)
throws ModuleInitializationException {
super(moduleParameters, server, role);
}
//
// Overrides of DefaultDOManager methods
//
/**
* Performs superclass post-initialization, then completes initialization
* using GSearch-specific parameters.
*/
@Override
public void postInitModule() throws ModuleInitializationException {
super.postInitModule();
// validate required param: GSEARCH_REST_URL
_gSearchRESTURL = getParameter(GSEARCH_REST_URL);
if (_gSearchRESTURL == null) {
throw new ModuleInitializationException("Required parameter, "
+ GSEARCH_REST_URL + " was not specified", getRole());
} else {
try {
new URL(_gSearchRESTURL);
logger.debug("Configured GSearch REST URL: " + _gSearchRESTURL);
} catch (MalformedURLException e) {
throw new ModuleInitializationException("Malformed URL given "
+ "for " + GSEARCH_REST_URL + " parameter: "
+ _gSearchRESTURL, getRole());
}
}
// validate credentials: if GSEARCH_USERNAME is given, GSEARCH_PASSWORD
// should also be.
String user = getParameter(GSEARCH_USERNAME);
if (user != null) {
logger.debug("Will authenticate to GSearch service as user: " + user);
String pass = getParameter(GSEARCH_PASSWORD);
if (pass != null) {
_gSearchCredentials =
new UsernamePasswordCredentials(user, pass);
} else {
throw new ModuleInitializationException(GSEARCH_PASSWORD
+ " must be specified because " + GSEARCH_USERNAME
+ " was specified", getRole());
}
} else {
logger.debug(GSEARCH_USERNAME + " unspecified; will not attempt "
+ "to authenticate to GSearch service");
}
// finally, init the http client we'll use
_webClientConfig = getServer().getWebClientConfig();
_webClient = new WebClient(_webClientConfig);
}
/**
* Commits the changes to the given object as usual, then attempts to
* propagate the change to the GSearch service.
*/
@Override
public void doCommit(boolean cachedObjectRequired,
Context context,
DigitalObject obj,
String logMessage,
boolean remove) throws ServerException {
super.doCommit(cachedObjectRequired, context, obj, logMessage, remove);
// determine the url we need to invoke
StringBuffer url = new StringBuffer();
url.append(_gSearchRESTURL + "?operation=updateIndex");
String pid = obj.getPid();
url.append("&value=" + urlEncode(pid));
if (remove) {
logger.info("Signaling removal of {} to GSearch", pid);
url.append("&action=deletePid");
} else {
if (logger.isInfoEnabled()) {
if (obj.isNew()) {
logger.info("Signaling add of {} to GSearch", pid);
} else {
logger.info("Signaling mod of {} to GSearch", pid);
}
}
url.append("&action=fromPid");
}
// send the signal
sendRESTMessage(url.toString());
}
//
// Private utility methods
//
/**
* Performs the given HTTP request, logging a warning if we don't get a 200
* OK response.
*/
private void sendRESTMessage(String url) {
HttpInputStream response = null;
try {
logger.debug("Getting " + url);
response = _webClient.get(url, false, _gSearchCredentials);
int code = response.getStatusCode();
if (code != 200) {
logger.warn("Error sending update to GSearch service (url=" + url
+ "). HTTP response code was " + code + ". "
+ "Body of response from GSearch follows:\n"
+ getString(response));
}
} catch (Exception e) {
logger.warn("Error sending update to GSearch service via URL: " + url,
e);
} finally {
if (response != null) {
try {
response.close();
} catch (Exception e) {
logger.warn("Error closing GSearch response", e);
}
}
}
}
/**
* Read the remainder of the given stream as a String and return it, or an
* error message if we encounter an error.
*/
private static String getString(InputStream in) {
try {
StringBuffer out = new StringBuffer();
BufferedReader reader =
new BufferedReader(new InputStreamReader(in));
String line = reader.readLine();
while (line != null) {
out.append(line + "\n");
line = reader.readLine();
}
return out.toString();
} catch (Exception e) {
return "[Error reading response body: " + e.getClass().getName()
+ ": " + e.getMessage() + "]";
}
}
/**
* URL-encode the given string using UTF-8 encoding.
*/
private static final String urlEncode(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (Exception e) {
logger.warn("Failed to encode '" + s + "'", e);
return s;
}
}
}