/* Copyright (c) 2007, 2008 Bug Labs, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
package com.buglabs.bug.ws.program;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.knapsack.init.pub.KnapsackInitService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import com.buglabs.bug.ws.Activator;
import com.buglabs.util.osgi.BUGBundleConstants;
import com.buglabs.util.xml.XmlNode;
public class ProgramServlet extends HttpServlet {
private static final String HTTP_OK_RESPONSE = "OK";
private static final String MANIFEST_FILENAME = "MANIFEST.MF";
private static final String CVS_DIRECTORY_NAME = "CVS";
private static final String TEXT_XML_MIME_TYPE = "text/xml";
private static final String TEXT_PLAIN_MIME_TYPE = "text/html";
private static final String BUNDLE_TEMP_FILENAME = "bundle.jar";
private static final String APPLICATION_JAVA_ARCHIVE_MIME_TYPE = "application/java-archive";
private static final long serialVersionUID = -6977609397049223637L;
public static int BUFFER_SIZE = 10240;
private final BundleContext context;
private String incomingBundleDir;
private LogService log;
private final KnapsackInitService initService;
public ProgramServlet(BundleContext context, KnapsackInitService initService) {
this.context = context;
this.initService = initService;
log = Activator.getLog();
//Make sure we know where to install bundles to.
this.incomingBundleDir = context.getProperty(Activator.APP_BUNDLE_PATH);
if (incomingBundleDir == null) {
throw new RuntimeException("Bundle property " + Activator.APP_BUNDLE_PATH + " is not defined. " + this.getClass().getSimpleName() + " cannot start.");
}
//Make sure the configured directory is being scanned by knapsack
boolean found = false;
for (File f : initService.getBundleDirectories())
if (f.getAbsolutePath().equals(incomingBundleDir))
found = true;
if (!found)
throw new RuntimeException(Activator.APP_BUNDLE_PATH + " is set to a directory that knapsack is not configured to read from. Check knapsack property 'org.knapsack.bundleDirs'.");
log.log(LogService.LOG_DEBUG, this.getClass().getSimpleName() + " initialization complete. BUGapp storage location set to: " + incomingBundleDir);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getPathInfo();
if (path == null) {
log.log(LogService.LOG_WARNING, "Error: null program path.");
resp.sendError(665, "Error: invalid program path.");
return;
}
String[] toks = path.split("/");
String jarName = toks[toks.length - 1];
File bundleDir = new File(incomingBundleDir);
if (!bundleDir.exists()) {
log.log(LogService.LOG_ERROR, "Unable to save bundle; can't create file.");
resp.sendError(0, "Unable to save bundle; can't create file.");
}
Bundle existingBundle = findBundleByName(jarName);
if (existingBundle != null) {
try {
uninstallBundle(existingBundle);
} catch (BundleException e) {
log.log(LogService.LOG_ERROR, "Unable to uninstall existing bundle. Aborting install.", e);
}
}
File jarFile = new File(bundleDir.getAbsolutePath() + File.separator + jarName + ".jar");
FileOutputStream fos = new FileOutputStream(jarFile);
IOUtils.copy(req.getInputStream(), fos);
fos.flush();
fos.close();
//Tell knapsack to start the bundle.
jarFile.setExecutable(true);
initService.updateBundles();
resp.getWriter().println(HTTP_OK_RESPONSE);
req.getInputStream().close();
}
private void uninstallBundle(Bundle bundle) throws BundleException {
if (bundle != null && bundle.getState() != Bundle.UNINSTALLED) {
bundle.stop();
bundle.uninstall();
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getPathInfo();
String response = null;
if (path == null) {
// Get a list of description of all programs.
resp.setContentType(TEXT_XML_MIME_TYPE);
response = getAllBundles();
resp.getWriter().println(response);
} else {
String id = path.substring(1).trim();
// User should not be able to access System Bundle
if (Long.parseLong(id) == 0) {
resp.setContentType(TEXT_PLAIN_MIME_TYPE);
resp.sendError(503);
return;
}
if (!isNumber(id)) {
resp.setContentType(TEXT_PLAIN_MIME_TYPE);
resp.sendError(0, "Invalid program id: " + id);
return;
}
Bundle b = findBundleById(id);
if (b == null) {
resp.setContentType(TEXT_PLAIN_MIME_TYPE);
resp.sendError(0, "No program with id: " + id);
return;
}
File jarfile = null;
try {
URI uri = new URI(b.getLocation());
jarfile = new File(uri);
} catch (URISyntaxException e) {
log.log(LogService.LOG_ERROR, "Unable to get bundle location, invalid URL.", e);
}
if (jarfile != null && jarfile.exists() && jarfile.isFile()) {
resp.setContentType(APPLICATION_JAVA_ARCHIVE_MIME_TYPE);
FileInputStream fis = new FileInputStream(jarfile);
IOUtils.copy(fis, resp.getOutputStream());
} else if (jarfile != null && jarfile.exists() && jarfile.isDirectory()) {
resp.setContentType(APPLICATION_JAVA_ARCHIVE_MIME_TYPE);
File bundleFile = new File(incomingBundleDir + File.separator + BUNDLE_TEMP_FILENAME);
jarfile = createJar(jarfile, bundleFile);
FileInputStream fis = new FileInputStream(jarfile);
IOUtils.copy(fis, resp.getOutputStream());
} else {
resp.setContentType(TEXT_PLAIN_MIME_TYPE);
resp.sendError(0, "No program with id: " + id);
}
}
}
/**
* Implementation of doDelete that uninstalls supplied bundle
*/
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getPathInfo().replace('+', ' ');
if (path == null) {
resp.sendError(665, "Error: invalid program path.");
return;
}
String[] toks = path.split("/");
try {
Bundle b = findBundleByName(toks[toks.length - 1]);
//TODO: Implement telling knapsack that bundles have changed
log.log(LogService.LOG_ERROR, "IMPLEMENT ME.");
//if (b != null && b.getState() != Bundle.UNINSTALLED) {
if (b != null) {
uninstallBundle(b);
}
} catch (BundleException e) {
resp.sendError(0, "Error: Unable to remove bundle." + e.getMessage());
}
}
private boolean isNumber(String id) {
try {
Long.parseLong(id);
} catch (NumberFormatException e) {
return false;
}
return true;
}
/**
* Geenerate a Jar file from a directory.
*
* @param jarDir
* @param bundleFile
* @return
* @throws IOException
*/
private File createJar(File jarDir, File bundleFile) throws IOException {
List<File> files = new ArrayList<File>();
getAllChildFiles(jarDir, files);
createJarArchive(bundleFile, (File[]) files.toArray(new File[files.size()]), jarDir);
return bundleFile;
}
/**
* Recursively return set of all child files.
*
* @param root
* @param files
*/
private void getAllChildFiles(File root, List<File> files) {
File[] children = root.listFiles(new FilenameFilter() {
public boolean accept(File arg0, String arg1) {
if (arg1.startsWith(".")) {
return false;
}
if (arg1.equals(CVS_DIRECTORY_NAME)) {
return false;
}
if (arg1.equals(MANIFEST_FILENAME)) {
return false;
}
return true;
}
});
if (children != null) {
for (int i = 0; i < children.length; ++i) {
if (children[i].isFile()) {
files.add(children[i].getAbsoluteFile());
} else {
getAllChildFiles(children[i], files);
}
}
}
}
/**
* Code from forum board for creating a Jar.
*
* @param archiveFile
* @param tobeJared
* @param rootDir
* @throws IOException
*/
protected void createJarArchive(File archiveFile, File[] tobeJared, File rootDir) throws IOException {
byte buffer[] = new byte[BUFFER_SIZE];
// Open archive file
FileOutputStream stream = new FileOutputStream(archiveFile);
JarOutputStream out = new JarOutputStream(stream, new Manifest(new FileInputStream(rootDir.getAbsolutePath() + File.separator + "META-INF" + File.separator
+ MANIFEST_FILENAME)));
for (int i = 0; i < tobeJared.length; i++) {
if (tobeJared[i] == null || !tobeJared[i].exists() || tobeJared[i].isDirectory())
continue; // Just in case...
String relPath = getRelPath(rootDir.getAbsolutePath(), tobeJared[i].getAbsolutePath());
// Add archive entry
JarEntry jarAdd = new JarEntry(relPath);
jarAdd.setTime(tobeJared[i].lastModified());
out.putNextEntry(jarAdd);
// Write file to archive
FileInputStream in = new FileInputStream(tobeJared[i]);
while (true) {
int nRead = in.read(buffer, 0, buffer.length);
if (nRead <= 0)
break;
out.write(buffer, 0, nRead);
}
in.close();
}
out.close();
stream.close();
}
/**
* Return the difference of two paths.
*
* @param rootPath
* @param subPath
* @return
*/
private String getRelPath(String rootPath, String subPath) {
return subPath.substring(rootPath.length() + 1);
}
private XmlNode getBundleXmlNode(Bundle b) {
XmlNode pnode = new XmlNode(IProgramXml.NODE_PROGRAM);
int state = b.getState();
String strState = "false";
if (state == Bundle.ACTIVE) {
strState = "true";
}
pnode.addAttribute(IProgramXml.ATTRIB_ACTIVE, strState);
pnode.addAttribute(IProgramXml.ATTRIB_VERSION, (String) b.getHeaders().get("Bundle-Version"));
pnode.addAttribute(IProgramXml.ATTRIB_ID, "" + b.getBundleId());
pnode.addAttribute(IProgramXml.ATTRIB_TYPE, getBugBundleType(b));
new XmlNode(pnode, IProgramXml.NODE_TITLE, (String) b.getHeaders().get("Bundle-Name"));
new XmlNode(pnode, IProgramXml.NODE_AUTHOR, (String) b.getHeaders().get("Bundle-Vendor"));
// We don't have date created in the Bundle interface so for now use
// modified date. TODO Fix this.
new XmlNode(pnode, "date_created", "1-1-2007");
new XmlNode(pnode, IProgramXml.NODE_DATE_MODIFIED, "1-1-2007");
new XmlNode(pnode, IProgramXml.NODE_NOTES);
XmlNode sNode = new XmlNode(pnode, IProgramXml.NODE_SERVICES);
XmlNode servicePropsNode = new XmlNode(pnode, IProgramXml.NODE_SERVICES2);
ServiceReference[] sr = b.getRegisteredServices();
if (sr != null) {
for (int i = 0; i < sr.length; ++i) {
String[] s = (String[]) sr[i].getProperty("objectClass");
new XmlNode(sNode, IProgramXml.NODE_SERVICE, s[0]);
// the following mass of code adds a services2 node to the xml
// that includes the service properties
XmlNode propertiesNode = new XmlNode(servicePropsNode, IProgramXml.NODE_SERVICE);
propertiesNode.addAttribute(IProgramXml.ATTRIB_NAME, s[0]);
String[] properties = sr[i].getPropertyKeys();
for (int j = 0; j < properties.length; j++) {
if (properties[j] == "service.id" || properties[j] == "objectClass")
continue;
XmlNode propNode = new XmlNode(propertiesNode, IProgramXml.NODE_PROPERTY);
propNode.addAttribute(IProgramXml.ATTRIB_NAME, properties[j]);
propNode.addAttribute(IProgramXml.ATTRIB_VALUE, "" + sr[i].getProperty(properties[j]));
}
}
}
return pnode;
}
private String getBugBundleType(Bundle b) {
String type = "";
String temp = (String) b.getHeaders().get(BUGBundleConstants.BUG_BUNDLE_TYPE_HEADER);
if (temp != null) {
type = temp;
}
return type;
}
private Bundle findBundleById(String id) {
Bundle[] bundles = context.getBundles();
for (int i = 0; i < bundles.length; ++i) {
if (bundles[i].getBundleId() == Long.parseLong(id)) {
return bundles[i];
}
}
return null;
}
private Bundle findBundleByName(String name) {
Bundle[] bundles = context.getBundles();
for (int i = 0; i < bundles.length; ++i) {
Dictionary headers = bundles[i].getHeaders();
if (headers != null) {
String symbolicName = (String) headers.get("Bundle-Name");
if (symbolicName != null) {
if (symbolicName.equals(name)) {
return bundles[i];
}
}
}
}
return null;
}
private String getAllBundles() {
Bundle[] bundles = context.getBundles();
XmlNode root = new XmlNode(IProgramXml.NODE_PROGRAMS);
for (int i = 0; i < bundles.length; ++i) {
Bundle b = bundles[i];
root.addChild(getBundleXmlNode(b));
}
return root.toString();
}
}