package ca.carleton.gcrc.couch.config;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONTokener;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import ca.carleton.gcrc.couch.app.JSONDirectoryBuilder;
import ca.carleton.gcrc.couch.client.CouchDb;
import ca.carleton.gcrc.couch.client.CouchDesignDocument;
import ca.carleton.gcrc.couch.client.CouchFactory;
import ca.carleton.gcrc.couch.client.CouchClient;
import ca.carleton.gcrc.couch.onUpload.UploadListener;
import ca.carleton.gcrc.couch.onUpload.UploadWorker;
import ca.carleton.gcrc.olkit.multimedia.utils.MultimediaConfiguration;
import ca.carleton.gcrc.upload.OnUploadedListenerSingleton;
import ca.carleton.gcrc.upload.UploadServlet;
import ca.carleton.gcrc.upload.UploadUtils;
@SuppressWarnings("serial")
public class ConfigServlet extends HttpServlet {
final static public String DESIGN_DOC_NAME = "ss_upload";
final static public String DESIGN_DOC_SIG_KEY = "signature";
final protected Logger logger = Logger.getLogger(this.getClass());
private CouchClient couchClient = null;
private CouchDb couchDb = null;
private CouchDesignDocument couchDd = null;
private UploadWorker uploadWorker = null;
private File mediaDir = null;
public ConfigServlet() {
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
// Instantiate CouchDb client
try {
initCouchDbClient(config);
} catch(ServletException e) {
logger.error("Error while initializing couch client",e);
throw e;
}
// Upload design documents
try {
initCouchDesignDocument(config);
} catch(ServletException e) {
logger.error("Error while initializing design document",e);
throw e;
}
// Configure multimedia
try {
initMultimedia(config);
} catch(ServletException e) {
logger.error("Error while initializing multimedia",e);
throw e;
}
mediaDir = UploadUtils.getMediaDir(config.getServletContext());
UploadListener uploadListener = new UploadListener();
uploadListener.setCouchDb(couchDb);
config.getServletContext().setAttribute(UploadServlet.OnUploadedListenerAttributeName, uploadListener);
OnUploadedListenerSingleton.configure(uploadListener);
try {
uploadWorker = new UploadWorker();
uploadWorker.setDesignDocument(couchDd);
uploadWorker.setMediaDir(mediaDir);
uploadWorker.start();
} catch (Exception e) {
logger.error("Error starting upload worker",e);
throw new ServletException("Error starting upload worker",e);
}
}
private void initCouchDbClient(ServletConfig config) throws ServletException {
File rootFile = null;
{
ServletContext servletContext = config.getServletContext();
if( null != servletContext ) {
String realRoot = servletContext.getRealPath(".");
if( null != realRoot ) {
rootFile = new File(realRoot);
}
}
}
// Load up configuration information
Properties props = new Properties();
{
File propFile = null;
if( null != rootFile ) {
propFile = new File(rootFile, "WEB-INF/couch.properties");
if( false == propFile.exists() || false == propFile.isFile() ) {
propFile = null;
}
}
if( null != rootFile && null == propFile ) {
propFile = new File(rootFile, "WEB-INF/couch.properties.default");
if( false == propFile.exists() || false == propFile.isFile() ) {
propFile = null;
}
}
if( null == propFile ) {
logger.info("Couch Client property file location can not be determined");
} else {
logger.info("Reading Couch Client properties from "+propFile.getAbsolutePath());
FileInputStream fis = null;
try {
fis = new FileInputStream(propFile);
props.load(fis);
} catch (Exception e) {
logger.error("Unable to read properties from "+propFile.getAbsolutePath(),e);
} finally {
if( null != fis ) {
try {
fis.close();
} catch (Exception e) {
// Ignore
}
}
}
}
}
// Create Couch Server from properties
CouchFactory factory = new CouchFactory();
try {
couchClient = factory.getClient(props);
} catch(Exception e) {
logger.error("Unable to get Couch Server",e);
throw new ServletException("Unable to get Couch Server",e);
}
// Create database
try {
if( props.containsKey("dbUrl") ) {
couchDb = factory.getDb(couchClient, props.getProperty("dbUrl"));
} else if( props.containsKey("dbName") ) {
couchDb = couchClient.getDatabase(props.getProperty("dbName"));
} else {
throw new Exception("dbUrl or dbName must be provided");
}
} catch(Exception e) {
logger.error("Unable to build Couch Database",e);
throw new ServletException("Unable to build Couch Database",e);
}
logger.info("CouchDb configured: "+couchDb.getUrl());
}
private void initCouchDesignDocument(ServletConfig config) throws ServletException {
// Find root directory for design document
File ddDir = null;
{
File rootFile = null;
{
ServletContext servletContext = config.getServletContext();
if( null != servletContext ) {
String realRoot = servletContext.getRealPath(".");
if( null != realRoot ) {
rootFile = new File(realRoot);
}
}
}
if( null != rootFile ) {
ddDir = new File(rootFile, "WEB-INF/uploadDesignDoc");
if( false == ddDir.exists() || false == ddDir.isDirectory() ) {
ddDir = null;
}
}
if( null == ddDir ) {
throw new ServletException("Unable to find design document source for upload");
}
}
// Load design document
StringWriter ddWriter = new StringWriter();
JSONObject designDoc = new JSONObject();
try {
JSONDirectoryBuilder builder = new JSONDirectoryBuilder(ddDir);
builder.write(ddWriter);
JSONTokener jsonTokener = new JSONTokener(ddWriter.toString());
Object obj = jsonTokener.nextValue();
if( obj instanceof JSONObject ) {
designDoc = (JSONObject)obj;
} else {
throw new Exception("Unexpected returned object type: "+obj.getClass().getSimpleName());
}
designDoc.put("_id", "_design/"+DESIGN_DOC_NAME);
} catch(Exception e) {
throw new ServletException("Unable to load design document", e);
}
// Compute signature
String signature = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(baos, "UTF-8");
osw.write(ddWriter.toString());
osw.flush();
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(baos.toByteArray());
byte[] sigBytes = md.digest();
// B64
byte[] encoded = Base64.encodeBase64(sigBytes);
ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
InputStreamReader isr = new InputStreamReader(bais, "UTF-8");
BufferedReader br = new BufferedReader(isr);
signature = br.readLine();
// Add signature to JSON doc
designDoc.put(DESIGN_DOC_SIG_KEY, signature);
logger.info("Design document signature: "+signature);
} catch(Exception e) {
throw new ServletException("Unable to compute signature on design document", e);
}
// Check if design doc exists
boolean creationRequired = false;
boolean updateNeeded = false;
try {
if( false == couchDb.documentExists("_design/"+DESIGN_DOC_NAME) ) {
creationRequired = true;
} else {
// Check if it is the right signature
JSONObject currentDesignDoc = couchDb.getDocument("_design/"+DESIGN_DOC_NAME);
if( false == currentDesignDoc.containsKey(DESIGN_DOC_SIG_KEY) ) {
updateNeeded = true;
} else {
String currentSignature = currentDesignDoc.getString(DESIGN_DOC_SIG_KEY);
if( false == signature.equals(currentSignature) ) {
updateNeeded = true;
}
}
// Update revision
designDoc.put("_rev", currentDesignDoc.get("_rev"));
}
} catch(Exception e) {
throw new ServletException("Unable to access current design document", e);
}
// Upload, if required
if( creationRequired ) {
logger.info("Creating design document");
try {
couchDb.createDocument(designDoc);
} catch (Exception e) {
throw new ServletException("Unable to create design document", e);
}
} else if( updateNeeded ) {
logger.info("Uploading design document");
try {
couchDb.updateDocument(designDoc);
} catch (Exception e) {
throw new ServletException("Unable to update design document", e);
}
}
try {
couchDd = couchDb.getDesignDocument(DESIGN_DOC_NAME);
} catch (Exception e) {
throw new ServletException("Unable to get design document", e);
}
}
private void initMultimedia(ServletConfig config) throws ServletException {
File rootFile = null;
{
ServletContext servletContext = config.getServletContext();
if( null != servletContext ) {
String realRoot = servletContext.getRealPath(".");
if( null != realRoot ) {
rootFile = new File(realRoot);
}
}
}
// Load up configuration information
Properties props = new Properties();
{
File propFile = null;
if( null != rootFile ) {
propFile = new File(rootFile, "WEB-INF/multimedia.properties");
if( false == propFile.exists() || false == propFile.isFile() ) {
propFile = null;
}
}
if( null != rootFile && null == propFile ) {
propFile = new File(rootFile, "WEB-INF/multimedia.properties.default");
if( false == propFile.exists() || false == propFile.isFile() ) {
propFile = null;
}
}
if( null == propFile ) {
logger.info("Multimedia property file location can not be determined");
} else {
logger.info("Reading multimedia properties from "+propFile.getAbsolutePath());
FileInputStream fis = null;
try {
fis = new FileInputStream(propFile);
props.load(fis);
} catch (Exception e) {
logger.error("Unable to read properties from "+propFile.getAbsolutePath(),e);
} finally {
if( null != fis ) {
try {
fis.close();
} catch (Exception e) {
// Ignore
}
}
}
}
}
MultimediaConfiguration.configureFromProperties(props);
}
public void destroy() {
try {
uploadWorker.stopTimeoutMillis(5*1000); // 5 seconds
} catch (Exception e) {
logger.error("Unable to shutdown upload worker", e);
}
}
}