blob: a72ab5bfdaac2ab1a9cb1a6227804703cb5da590 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.uima.tools.pear.merger;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.jar.JarFile;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.apache.uima.analysis_engine.AnalysisEngineDescription;
import org.apache.uima.pear.tools.InstallationDescriptor;
import org.apache.uima.pear.tools.PackageBrowser;
import org.apache.uima.pear.util.FileUtil;
/**
* The <code>PMController</code> class allows to merge several input PEAR files in one PEAR file
* and generate an aggregate analysis engine from the components encapsulated in the input PEARs.
*
* @see org.apache.uima.tools.pear.merger.PMControllerHelper
* @see org.apache.uima.tools.pear.merger.PMUimaAgent
*/
public class PMController {
// utility name
static final String PEAR_MERGER = "PEAR Merger";
// log file name
static final String LOG_FILE = "pm.log";
// command line argument names
static final String AGGREGATE_NAME_ARG = "-n";
static final String AGGREGATE_PEAR_FILE_ARG = "-f";
// command line parameters
private static File[] __pearFiles = null;
private static String __aggregateName = null;
private static File __aggregatePearFile = null;
/**
* The <code>PMLogFormatter</code> class formats messages for the log file.
*/
static class PMLogFormatter extends SimpleFormatter {
private boolean _firstTime = true;
public String format(LogRecord record) {
if (_firstTime) {
// 1st time - print with header
_firstTime = false;
record.setSourceMethodName("");
return super.format(record);
} else {
// print level: message
StringBuffer buffer = new StringBuffer();
buffer.append(record.getLevel());
String message = record.getMessage();
// for multi-line msg - start msg in new line
if (message.indexOf('\n') >= 0)
buffer.append(": \n");
else
// for single line msg - start msg in the same line
buffer.append(": ");
buffer.append(record.getMessage());
buffer.append('\n');
return buffer.toString();
}
}
}
// static attributes
private static Logger __logger = null;
private static boolean __logFileEnabled = false;
// static Logger initialization
static {
// create default logger
__logger = Logger.getLogger(PMController.class.getName());
__logger.setUseParentHandlers(false);
}
// internal attributes
private File[] _inpPearFiles = null;
private String _outAggCompName = null;
private File _outAggPearFile = null;
private File _tempWorkDir = null;
private File _outAggRootDir = null;
private File[] _outDlgRootDirs = null;
private InstallationDescriptor[] _dlgInstDescs = null;
private InstallationDescriptor _outAggInstDesc = null;
/**
* The command line application entry point. This method expects the following command line
* arguments:
* <ul>
* <li>pear_file_1 ... pear_file_n - input PEAR files (required)</li>
* <li>-n agg_name - a name of the output aggregate analysis engine (required) </li>
* <li>-f agg_pear_file - output aggregate PEAR file (optional). <br />
* If the output PEAR file is not specified, the default output PEAR file is created in the
* current working directory.</li>
* </ul>
*
* @param args
* pear_file_1 ... pear_file_n -n agg_name [-f agg_pear_file]
*/
public static void main(String[] args) {
// enable log file
setLogFileEnabled(true);
// parse and validate command line
if (!parseCommandLine(args)) {
logErrorMessage(PEAR_MERGER + " terminated: command line error");
return;
}
PMController controller = null;
try {
// create controller object
controller = new PMController(__pearFiles, __aggregateName, __aggregatePearFile);
// merge input PEARs and create merged aggregate PEAR file
if (controller.mergePears()) {
logInfoMessage("[" + PEAR_MERGER + "]: " + "operation completed successfully");
} else
logInfoMessage("[" + PEAR_MERGER + "]: " + "operation failed");
} catch (Throwable err) {
logErrorMessage("Error in " + PEAR_MERGER + ": " + err.toString());
} finally {
if (controller != null) {
try {
controller.cleanUp();
} catch (Exception e) {
}
}
}
}
/**
* Returns the instance of the class-specific <code>Logger</code> object.
*
* @return The instance of the class-specific <code>Logger</code> object.
*/
public static Logger getLogger() {
return __logger;
}
/**
* Logs a given error message to the log file and prints it to the standard error console stream.
*
* @param message
* The given error message.
*/
public static void logErrorMessage(String message) {
if (__logFileEnabled)
getLogger().severe(message);
System.err.println(message);
}
/**
* Logs a given info message to the log file and prints it to the standard output console stream.
*
* @param message
* The given info message.
*/
public static void logInfoMessage(String message) {
if (__logFileEnabled)
getLogger().info(message);
System.out.println(message);
}
/**
* Logs a given warning message to the log file and prints it to the standard error console
* stream.
*
* @param message
* The given warning message.
*/
public static void logWarningMessage(String message) {
if (__logFileEnabled)
getLogger().warning(message);
System.err.println(message);
}
/**
* Parses and validates the command line and initializes static attributes.
*
* @param args
* The given command line arguments.
* @return <code>true</code>, if the command line arguments are valid, <code>false</code>
* otherwise.
*/
private static boolean parseCommandLine(String[] args) {
// verify command line args (min 4 args: p_1 p_2 -n name)
if (args.length < 4) {
logErrorMessage(PEAR_MERGER + " args: " + "pear_file_1 ... pear_file_n -n agg_name "
+ "[-f agg_pear_file]");
return false;
}
ArrayList listOfPears = new ArrayList();
// parse command line
for (int i = 0; i < args.length; i++) {
if (args[i].equals(AGGREGATE_NAME_ARG)) {
// set aggregate name
if (++i < args.length)
__aggregateName = args[i];
} else if (args[i].equals(AGGREGATE_PEAR_FILE_ARG)) {
// set aggregate PEAR file
if (++i < args.length)
__aggregatePearFile = new File(args[i]);
} else if (args[i].startsWith( "-" )) {
logErrorMessage(PEAR_MERGER + " error: " + "unknown flag '" + args[i] + "'");
return false;
} else {
// add next input PEAR file
File inPear = new File(args[i]);
if (inPear.isFile()) {
// make sure this is not duplicated input file
for (int n = 0; n < listOfPears.size(); n++) {
try {
String fPath = ((File) listOfPears.get(n)).getCanonicalPath();
String inPath = inPear.getCanonicalPath();
if (fPath.equals(inPath)) {
logErrorMessage(PEAR_MERGER + " error: " + "duplicated input file " + args[i]);
return false;
}
} catch (IOException e) {
logErrorMessage(PEAR_MERGER + " error: " + e.toString());
return false;
}
}
listOfPears.add(inPear);
} else {
logErrorMessage(PEAR_MERGER + " error: " + "cannot find input file " + args[i]);
return false;
}
}
}
// validate command line parameters and set input files
if (listOfPears.size() < 2) {
logErrorMessage(PEAR_MERGER + " error: " + "input PEAR files not specified");
return false;
} else {
__pearFiles = new File[listOfPears.size()];
listOfPears.toArray(__pearFiles);
}
if (__aggregateName == null) {
logErrorMessage(PEAR_MERGER + " error: " + "output aggregate name not specified");
return false;
}
return true;
}
/**
* Enables/disables PM log file. By default, the log file is disabled.
*
* @param enable
* if <code>true</code>, the log file is enabled, otherwise it is disabled.
*/
public static void setLogFileEnabled(boolean enable) {
if (enable) {
Handler[] handlers = getLogger().getHandlers();
if (handlers == null || handlers.length == 0) {
// add default file handler
try {
FileHandler fileHandler = new FileHandler(LOG_FILE, false);
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new PMLogFormatter());
getLogger().addHandler(fileHandler);
} catch (Throwable err) {
System.err.println("Error initializing log file " + PMController.class.getName() + ": "
+ err.toString());
}
}
}
__logFileEnabled = enable;
}
/**
* Constructor that takes a given array of input PEAR files, a given output component name (ID)
* and a given output PEAR file.
*
* @param inpPearFiles
* The given array of input PEAR files.
* @param outCompName
* The given output component name (ID).
* @param outPearFile
* The given output PEAR file.
* @exception IOException
* If any I/O exception occurred during initialization.
*/
public PMController(File[] inpPearFiles, String outCompName, File outPearFile) throws IOException {
// initialize task attributes
_inpPearFiles = inpPearFiles;
_outAggCompName = outCompName;
_outAggPearFile = outPearFile;
initializeTaskAttributes();
// log PEAR Merger task parameters
logInfoMessage("[" + PEAR_MERGER + "]: task parameters =>");
logInfoMessage("> Input PEARs =>");
for (int i = 0; i < _inpPearFiles.length; i++)
logInfoMessage(">> " + _inpPearFiles[i].getAbsolutePath());
logInfoMessage("> Output PEAR =>");
logInfoMessage(">> Name = " + _outAggCompName);
logInfoMessage(">> File = " + _outAggPearFile.getAbsolutePath());
logInfoMessage("> Output root dir: " + _outAggRootDir.getAbsolutePath());
}
/**
* Deletes all temporary directories and files after the merging operation is completed.
*
* @throws IOException
* If an I/O exception occurred.
*/
public void cleanUp() throws IOException {
logInfoMessage("[" + PEAR_MERGER + "]: " + "deleting temporary files");
FileUtil.deleteDirectory(_outAggRootDir);
}
/**
* Extracts all specified input PEARs to their corresponding folders inside the output root
* folder. Returns the total size (bytes) of extracted files.
*
* @return The total size of extracted files.
* @throws IOException
* If any I/O exception occurred during extraction.
*/
private long extractInputPears() throws IOException {
long totalSize = 0;
for (int i = 0; i < _inpPearFiles.length; i++) {
JarFile pearFile = new JarFile(_inpPearFiles[i]);
totalSize += FileUtil.extractFilesFromJar(pearFile, _outDlgRootDirs[i]);
}
return totalSize;
}
/**
* Intializes the 'PEAR Merger' task attributes, based on the specified input.
*
* @exception IOException
* If any I/O exception occurred during initialization.
*/
private void initializeTaskAttributes() throws IOException {
if (_outAggPearFile == null) // set default output file
_outAggPearFile = new File(_outAggCompName + ".pear");
// set temporary working directory
String userHomePath = System.getProperty("user.home");
_tempWorkDir = new File(userHomePath);
if (!_tempWorkDir.isDirectory())
throw new IOException(userHomePath + " directory not found");
// set output aggregate root directory
_outAggRootDir = new File(_tempWorkDir, _outAggCompName);
// if output aggregate root directory exists, remove it
if (_outAggRootDir.isDirectory()) {
if (!FileUtil.deleteDirectory(_outAggRootDir))
throw new IOException("cannot delete existing folder " + _outAggRootDir.getAbsolutePath());
} else if (_outAggRootDir.isFile()) {
if (!_outAggRootDir.delete())
throw new IOException("cannot delete existing file " + _outAggRootDir.getAbsolutePath());
}
// set output delegate root directories
_outDlgRootDirs = new File[_inpPearFiles.length];
for (int i = 0; i < _outDlgRootDirs.length; i++) {
String fileName = _inpPearFiles[i].getName();
String sdirName = fileName.substring(0, fileName.lastIndexOf('.'));
_outDlgRootDirs[i] = new File(_outAggRootDir, sdirName);
}
// prepare array for delegate installation descriptors
_dlgInstDescs = new InstallationDescriptor[_inpPearFiles.length];
}
/**
* Merges specified input PEARs into one PEAR, which encapsulates aggregate AE that refers to
* input components, as delegates. Returns <code>true</code>, if the merging operation
* completed successfully, <code>false</code> otherwise.
*
* @return <code>true</code>, if the merge operation completed successfully, <code>false</code>
* otherwise.
* @throws IOException
* If an I/O exception occurred during the merging operation.
*/
public boolean mergePears() throws IOException {
boolean done = false;
// 1st step: extract delegate PEARs to their target dirs
logInfoMessage("[" + PEAR_MERGER + "]: " + "extracting delegate PEARs ...");
long totalSize = extractInputPears();
logInfoMessage("[" + PEAR_MERGER + "]: " + totalSize + " bytes extracted successfully");
// 2nd step: process files in delegate packages and create InsD objs
for (int i = 0; i < _outDlgRootDirs.length; i++) {
_dlgInstDescs[i] = PMControllerHelper.processDescriptors(_outDlgRootDirs[i]);
if (_dlgInstDescs[i] == null) {
logErrorMessage("[" + PEAR_MERGER + "]: " + "failed to process input package in "
+ _outDlgRootDirs[i] + "directory");
break;
}
}
// 3rd step: generate merged package directory structure
File pkgDescDir = new File(_outAggRootDir, PackageBrowser.DESCRIPTORS_DIR);
File pkgMetadataDir = new File(_outAggRootDir, PackageBrowser.METADATA_DIR);
if (pkgDescDir.mkdirs() && pkgMetadataDir.mkdirs())
logInfoMessage("[" + PEAR_MERGER + "]: " + "created merged package directory structure");
else
throw new IOException("cannot create merged package folders");
// 4th step: generate aggregate component descriptor
File aggDescFile = new File(pkgDescDir, _outAggCompName + ".xml");
AnalysisEngineDescription aggDescription = PMUimaAgent.createAggregateDescription(
_outAggCompName, _outAggRootDir, _dlgInstDescs);
if (aggDescription != null) {
PMUimaAgent.saveAggregateDescription(aggDescription, aggDescFile);
logInfoMessage("[" + PEAR_MERGER + "]: " + "generated aggregate component descriptor");
if (System.getProperty("DEBUG") != null)
logInfoMessage(PMUimaAgent.toXmlString(aggDescription));
} else
throw new IOException("cannot generate aggregate component descriptor");
// 5th step: generate merged installation descriptor
_outAggInstDesc = PMControllerHelper.generateMergedInstallationDescriptor(_outAggRootDir,
_outAggCompName, aggDescFile, _dlgInstDescs, _outDlgRootDirs);
if (_outAggInstDesc != null) {
logInfoMessage("[" + PEAR_MERGER + "]: "
+ "generated aggregate package installation descriptor");
if (System.getProperty("DEBUG") != null)
logInfoMessage(_outAggInstDesc.toString());
// 6th step: create merged PEAR file
File outPearFile = FileUtil.zipDirectory(_outAggRootDir, _outAggPearFile);
logInfoMessage("[" + PEAR_MERGER + "]: " + "created output aggregate PEAR file - "
+ outPearFile.getAbsolutePath());
done = true;
}
return done;
}
}