/* ===============================================================================
*
* Part of the InfoGlue Content Management Platform (www.infoglue.org)
*
* ===============================================================================
*
* Copyright (C)
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2, as published by the
* Free Software Foundation. See the file LICENSE.html for more information.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
* Place, Suite 330 / Boston, MA 02111-1307 / USA.
*
* ===============================================================================
*/
/*
* Created on 2003-apr-04
*
* ERROR IN REMOVEPACKAGE, FIX IT!
*
* */
package org.infoglue.cms.controllers.kernel.impl.simple;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Ant;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.infoglue.cms.entities.kernel.BaseEntityVO;
import org.infoglue.cms.entities.up2date.UpdateCollection;
import org.infoglue.cms.entities.up2date.UpdatePackage;
import org.xml.sax.InputSource;
/**
*
* Core infoglue up2date functionallity is provided through
* this controller.
*
* The constructor takes two arguments
* 1: url to the updateservers xml-file of available packages
* This delegates the problem of mirror handling and finding
* a working updateserver to the calling class.
*
* 2: path to up2date directory residing directly below the toplevel
* codebase directory. Ant operates with this directory as the basedirectory
* so the ant-files delivered by the updateserver should operate on ".." to
* get access to the base directory.
*
* The update process briefly:
*
* Overview:
* Available updates are served by a InfoGlue CMS repository
* where developers easily can publish update packages and
* deployment logic for installing those packages.
*
* The list of available updates, are served in xml-format from
* the InfoGlue Update Server. When requesting this document, a list
* of allready installed packages is passed to the server along with
* the query.
*
* The returned document looks something like this:
*
* <update-collection>
* <update-package>
* <package-id>pid</package-id>
* <title>Title</title>
* <description>breif description</description>
* <url>http://url_to_ant_install_script</url>
* <details-url>http://url_to_html-info_page_describing_the_package</details-url>
* </update-package>
* <update-package>
* ...
* <update-package>
* </update-collection>
*
* This document is retrieved by this controller on the client system
* and is unmarshaled to objects and placed in a list of available packages.
*
* The list is presented to the user (administrator of the client system)
* in the management tool. Where he may chose to access the detail page on
* the server, or install the package.
*
* Package Installation:
* When the user has decided that the has come to install an update. This class
* retrieves the ant-script (actually using ants get task) that shall be used
* for the installation. The url to the script is found in packagedefinition.
*
* The file is stored locally on the client system in CMS_BASEDIR/up2date/PID.xml
* Another ant session is started, using this file, and running target "runUpdate"
* Typically this target gets a zip-file from the server and unpacks it over the
* current codebase. But first a backup is made. But all this is up to
* the Update Server. A target "unInstall" should also exist in this file.
* Current setup at www.infoglue.org updateserver also executes two additional targets
* beforeUpdate and afterUpdate which can be edited by the packageauthor for each package
*
* After the update is made, the package object are added to the list of installed
* packages, and later marshalled to CMS_BASEDIR/up2date/installed.xml
*
*
*
* @author Stefan Sik
*
*
*
*/
public class UpdateController extends BaseController
{
private final static Logger logger = Logger.getLogger(UpdateController.class.getName());
/**
*
*/
// Initial handling of compatibility in the server response.
// Make this better later
private String protocolVersion = "20";
// Protocol version 12 adds title to the package description
private String path = "";
private String url = "";
Mapping mapping;
public UpdateController(String url, String path) throws FileNotFoundException, IOException, MappingException
{
// set variables
// Create and load the castor xml mapping
this.path = path;
this.url = url;
mapping = new Mapping();
mapping.loadMapping(new InputSource(new FileReader(path + "mapping.xml")));
}
public void refreshAvailableUpdates()
{
// Expire the cached availableUpdates list
UpdateListHandler.setAvailableUpdates(null);
}
public Date getLatestRefresh()
{
return UpdateListHandler.getLatestRefresh();
}
public Vector getAvailableUpdates()
{
logger.info("Get Available updates");
if (UpdateListHandler.getAvailableUpdates() == null)
{
logger.info("Getting from updateserver");
Vector ret = new Vector();
try
{
Unmarshaller unmar = new Unmarshaller(mapping);
unmar.setWhitespacePreserve(true);
unmar.setValidation(false);
// Fix url to updateserver, so that it filters
// packages allready installed locally
// also submit updatesystemversion
if(url.indexOf("?") == -1)
url += "?v=" + protocolVersion;
else
url += "&v=" + protocolVersion;
Vector installed = getInstalledUpdates();
Iterator iterator = installed.iterator();
while (iterator.hasNext()) {
UpdatePackage u = (UpdatePackage) iterator.next();
url += "&p=" + u.getPackageId();
}
URL u = new URL(url);
URLConnection urlConn = u.openConnection();
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches (false);
UpdateCollection coll = (UpdateCollection) unmar.unmarshal( new InputStreamReader(urlConn.getInputStream()));
ret = coll.getUpdatePackageList();
}
catch (ValidationException e2)
{
e2.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (MappingException e) {
e.printStackTrace();
} catch (MarshalException e) {
e.printStackTrace();
}
UpdateListHandler.setAvailableUpdates(ret);
}
return UpdateListHandler.getAvailableUpdates();
}
public Vector getInstalledUpdates()
{
logger.info("GetInstalled Updates");
Unmarshaller unmar = null;
UpdateCollection coll = new UpdateCollection();
try
{
unmar = new Unmarshaller(mapping);
unmar.setWhitespacePreserve(true);
unmar.setValidation(false);
coll = (UpdateCollection) unmar.unmarshal(new InputSource(new FileReader(path + "installed.xml")));
}
catch (MarshalException e2)
{
logger.info("Marshal exception");
}
catch (ValidationException e2)
{
e2.printStackTrace();
}
catch (FileNotFoundException e2)
{
logger.info("No packages found");
}
catch (MappingException e1)
{
e1.printStackTrace();
}
logger.info("Leaving GetInstalled Updates");
return coll.getUpdatePackageList();
}
private void addInstalledUpdate(UpdatePackage upd)
{
Unmarshaller unmar = null;
try {
unmar = new Unmarshaller(mapping);
unmar.setWhitespacePreserve(true);
unmar.setValidation(false);
} catch (MappingException e1) {
e1.printStackTrace();
}
UpdateCollection coll = new UpdateCollection();
try {
coll =
(UpdateCollection) unmar.unmarshal(
new InputSource(
new FileReader(path + "installed.xml")));
} catch (MarshalException e2) {
e2.printStackTrace();
} catch (ValidationException e2) {
e2.printStackTrace();
} catch (FileNotFoundException e2) {
logger.info("No previous installations");
}
coll.getUpdatePackageList().add(upd);
Marshaller marshaller = null;
try {
marshaller =
new Marshaller(
new FileWriter(new File(path + "installed.xml")));
} catch (IOException e3) {
e3.printStackTrace();
}
try {
marshaller.setMapping(mapping);
} catch (MappingException e4) {
e4.printStackTrace();
}
try {
marshaller.marshal(coll);
} catch (MarshalException e5) {
e5.printStackTrace();
} catch (ValidationException e5) {
e5.printStackTrace();
}
}
private void removeInstalledUpdate(UpdatePackage upd)
{
Unmarshaller unmar = null;
try {
unmar = new Unmarshaller(mapping);
unmar.setWhitespacePreserve(true);
unmar.setValidation(false);
} catch (MappingException e1) {
e1.printStackTrace();
}
UpdateCollection coll = new UpdateCollection();
try {
coll =
(UpdateCollection) unmar.unmarshal(
new InputSource(
new FileReader(path + "installed.xml")));
} catch (MarshalException e2) {
e2.printStackTrace();
} catch (ValidationException e2) {
e2.printStackTrace();
} catch (FileNotFoundException e2) {
logger.info("No previous installations");
}
// Find the update with id updatePackageId
Iterator iterator = coll.getUpdatePackageList().iterator();
while (iterator.hasNext()) {
UpdatePackage u = (UpdatePackage) iterator.next();
if(upd.getPackageId().compareTo(u.getPackageId())==0)
{
coll.getUpdatePackageList().remove(u);
iterator = coll.getUpdatePackageList().iterator();
}
}
Marshaller marshaller = null;
try {
marshaller =
new Marshaller(
new FileWriter(new File(path + "installed.xml")));
} catch (IOException e3) {
e3.printStackTrace();
}
try {
marshaller.setMapping(mapping);
} catch (MappingException e4) {
e4.printStackTrace();
}
try {
marshaller.marshal(coll);
} catch (MarshalException e5) {
e5.printStackTrace();
} catch (ValidationException e5) {
e5.printStackTrace();
}
}
public void runUpdatePackage(String updatePackageId, OutputStreamWriter writer) throws IOException
{
// Find the update with id updatePackageId
Vector updates = getAvailableUpdates();
Iterator iterator = updates.iterator();
while (iterator.hasNext()) {
UpdatePackage u = (UpdatePackage) iterator.next();
if(updatePackageId.compareTo(u.getPackageId())==0)
{
logger.info("Found package to install: " + u.getPackageId());
runUpdatePackage(u, writer);
break;
}
}
}
public void unInstallPackage(String updatePackageId, PrintWriter out) throws MalformedURLException
{
// Find the update with id updatePackageId
Vector updates = getInstalledUpdates();
Iterator iterator = updates.iterator();
while (iterator.hasNext()) {
UpdatePackage u = (UpdatePackage) iterator.next();
if(updatePackageId.compareTo(u.getPackageId())==0)
{
logger.info("Found package to uninstall: " + u.getPackageId());
unInstallPackage(u, out);
break;
}
}
}
public void runUpdatePackage(UpdatePackage upd, final OutputStreamWriter out) throws IOException
{
/* TODO:
* Capture output from ant session and return it, s� that we can produce
* a summary of the installation to the user.
*/
try {
// Get the zip-file!
String url = upd.getDecodedBinaryUrl();
out.write("Downloading the package");
out.flush();
String zipFile = path + "autoUpdate/" + upd.getPackageId() + ".zip";
getFile(url, zipFile, upd.getBinarySize().intValue(), out);
// Get the ant-file and run it!
String antFile = path + upd.getPackageId() + ".xml";
getFile(upd.getDecodedUrl(), antFile, -1, out);
out.flush();
// Run the ant file!
final class AntRun extends Ant {
public AntRun() {
project = new Project();
project.init();
}
}
AntRun a = new AntRun();
a.setAntfile(antFile);
a.setDir(new File(path));
a.setTarget("runUpdate");
out.write("Executing ant update script...<br>");
out.flush();
a.execute();
// Add this update!
out.write("Finalizing...<br>");
out.flush();
refreshAvailableUpdates();
addInstalledUpdate(upd);
// We are done
out.write("DONE!<br>");
out.flush();
}
catch(BuildException e)
{
out.write(e.getMessage());
out.write("<br>");
} catch (MalformedURLException e)
{
out.write(e.getMessage());
out.write("<br>");
}
}
public void unInstallPackage(UpdatePackage upd, final PrintWriter out) throws MalformedURLException
{
/* TODO:
* Capture output from ant session and return it, s� that we can produce
* a summary of the installation to the user.
*
* Should maybe use outputstream instead of printwriter
*/
out.println("About to run uninstallation...<br>");
final String antfile = upd.getPackageId();
String destFile = path + antfile + ".xml";
// Run the ant file!
final class AntRun extends Ant {
public AntRun() {
project = new Project();
project.init();
}
}
AntRun a = new AntRun();
a.setAntfile(destFile);
a.setDir(new File(path));
a.setTarget("unInstall");
out.println("Running Ant...<br>");
a.execute();
// Add this update!
out.println("Finalizing...<br>");
refreshAvailableUpdates();
removeInstalledUpdate(upd);
// We are done
out.println("DONE!<br>");
}
private void getFile(String src, String dest, int size, OutputStreamWriter out) throws IOException
{
File destFile = new File(dest);
destFile.getParentFile().mkdirs();
URL u = new java.net.URL(src);
URLConnection urlConn = u.openConnection();
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches (false);
InputStream is = urlConn.getInputStream();
String fileName = dest.substring(dest.lastIndexOf("/"));
FileOutputStream fo = new FileOutputStream(new File(dest));
int cnt = 0;
int cntcheck = 0;
int steps = 50;
int interval = size / steps;
out.write("<font color='blue'><pre>");
for(int x = 0; x< steps;x++)
out.write("_");
out.write("<br>");
out.flush();
byte[] buffer = new byte[64];// Or any length you want
BufferedInputStream br = new BufferedInputStream(urlConn.getInputStream());
for (int lengthRead = 0; (lengthRead = br.read(buffer)) >= 0;)
{
cnt+=lengthRead;
fo.write(buffer, 0, lengthRead);
if(size > 0)
{
if(cnt % (size / steps) < cntcheck)
{
out.write("*");
out.flush();
}
cntcheck = cnt % (size / steps);
}
}
if(size <= 0)
{
for(int x = 0; x< steps;x++)
out.write("*");
}
out.flush();
out.write("</font><font color='green'> [OK] - " + fileName + "</font>");
out.write("<br>");
out.write("</pre>");
out.flush();
is.close();
fo.close();
out.flush();
}
public BaseEntityVO getNewVO()
{
// TODO Auto-generated method stub
return null;
}
}