/**************************************************************************** * This software is part of the gCube Project. * Site: http://www.gcube-system.org/ **************************************************************************** * The gCube/gCore software is licensed as Free Open Source software * conveying to the EUPL (http://ec.europa.eu/idabc/eupl). * The software and documentation is provided by its authors/distributors * "as is" and no expressed or * implied warranty is given for its use, quality or fitness for a * particular case. **************************************************************************** * Filename: SWRepositoryUpgrader.java **************************************************************************** * @author Daniele Strollo ***************************************************************************/ package org.gcube.resourcemanagement.support.server.managers.services; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.HttpURLConnection; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.gcube.common.core.types.StringArray; import org.gcube.resourcemanagement.support.server.exceptions.ResourceAccessException; import org.gcube.resourcemanagement.support.server.exceptions.ResourceParameterException; import org.gcube.resourcemanagement.support.server.managers.scope.ScopeManager; import org.gcube.resourcemanagement.support.server.utils.Assertion; import org.gcube.resourcemanagement.support.server.utils.ServerConsole; import org.gcube.vremanagement.softwarerepository.stubs.ListServicePackagesMessage; import org.gcube.vremanagement.softwarerepository.stubs.SoftwareRepositoryPortType; import org.gcube.vremanagement.softwarerepository.stubs.Store; import org.gcube.vremanagement.softwarerepository.stubs.StoreItem; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; class UpgradeStatus implements Serializable { private static final long serialVersionUID = 2884364307629521252L; private double currentPackage = 0; private double totalPackages = 0; /** * @deprecated for serialization only */ public UpgradeStatus() { super(); } public UpgradeStatus(final double currentPackage, final double totalPackages) { this.currentPackage = currentPackage; this.totalPackages = totalPackages; } public double getCurrentPackage() { return this.currentPackage; } public double getTotalPackages() { return this.totalPackages; } } /** *

* Responsible to interact with the repository manager to require * an update of the infrastructure. *

*

To facilitate the monitoring of main steps involved the * {@link UpgradeListener} class has been introduced. *

*

* Notice that one single update per time can be required. * If a pending update is running the next request will be blocked * and an exception will be raised. *

*

* Three files will be created at update request (the folder in which they * will be created depends on the parameter reportsDir). *

*

* Usage: *
 * // [optional]
 * // to ensure at least a repository manager is registered in the scope
 * new SWRepositoryManager().getSoftwareRepository(scope);
 * ServerConsole.info(LOG_PREFIX, "Software repository [FOUND]");
 *
 * new SWRepositoryUpgrader(
 *  // The URL containing the list of packages to update
 *  "http://acme.org/builds/build.txt",
 *  // The directory to store report files
 *  "temp",
 *  // The scope
 *  "/gcube/devsec")
 *  // requires the upgrade
 *  .doUpgrade();
 * 
* @author Daniele Strollo (ISTI-CNR) * */ public class SWRepositoryUpgrader implements Serializable { private static final long serialVersionUID = 733460314188332825L; private static final String LOG_PREFIX = "[SW-UPGR]"; private String todeployTxtURL = null; private String reportFileName = null; private String distributionFileName = null; private String scope = null; private String reportsDir = "temp"; private String lockFile = null; private List listeners = new Vector(); /** * @deprecated for internal use only. Required for serialization. */ private SWRepositoryUpgrader() { super(); } /** * Builds a new instance of repository update manager. * * @param todeployTxtURL the URL to use to retrieve the list of software to update * @param reportsDir where the reports must be stored * @throws Exception */ public SWRepositoryUpgrader( final String todeployTxtURL, final String reportsDir, final String scope) throws Exception { this(); // Checks parameters Assertion checker = new Assertion(); checker.validate(todeployTxtURL != null && todeployTxtURL.trim().length() > 0, new ResourceParameterException("Invalid parameter todeployTxtURL")); checker.validate(reportsDir != null && reportsDir.trim().length() > 0, new ResourceParameterException("Invalid parameter reportsDir")); checker.validate(scope != null && scope.trim().length() > 0 && ScopeManager.getScope(scope) != null, new ResourceParameterException("Invalid parameter scope")); this.reportsDir = reportsDir; // Create the folder if it does not exist boolean success = (new File(this.reportsDir)).mkdirs(); if (success) { ServerConsole.trace(LOG_PREFIX, "Directory " + this.reportsDir + " [CREATED]"); } this.todeployTxtURL = todeployTxtURL; SimpleDateFormat dateFormatter = new SimpleDateFormat("yyMMdd-hhmm"); Date date = new Date(); String curDate = dateFormatter.format(date); this.reportFileName = this.reportsDir + File.separator + "swupdt-rept-" + curDate + ".txt"; this.distributionFileName = this.reportsDir + File.separator + "swupdt-dist-" + curDate + ".txt"; this.lockFile = this.reportsDir + File.separator + "swreport.lock"; ServerConsole.trace(LOG_PREFIX, "Setting report file: " + this.reportFileName); ServerConsole.trace(LOG_PREFIX, "Setting distribution file: " + this.distributionFileName); this.scope = scope; } public final String getScope() { return this.scope; } /** * Upgrades a software repository. * @param softwareRepositoryURL the url to contact the service repository * @param todeployTxtURL the txt file containing software descriptions * @param reportFileName the filename where report must be stored * @param distributionFileName the filename where distribution must be stored * @param scope */ public final void doUpgrade() throws Exception { try { this.reserve(); } catch (Exception e) { this.release(); throw new Exception("The required report file already exists. It cannot be replaced."); } File reportFile = new File(reportFileName); if (reportFile.exists()) { this.release(); throw new Exception("The required report file already exists. It cannot be replaced."); } File distributionFile = new File(distributionFileName); if (distributionFile.exists()) { this.release(); throw new Exception("The required distribution file already exists. It cannot be replaced."); } SoftwareRepositoryPortType swManager = null; try { swManager = new SWRepositoryManager().getSoftwareRepository(ScopeManager.getScope(this.scope)); } catch (Exception e) { this.release(); throw new Exception("The swManager cannot be instantiated. " + e.getMessage()); } ArrayList listIds = new ArrayList(); List listServicePackagesMessages = new ArrayList(); List storeItemsList = null; try { storeItemsList = parseTxtFile(this.todeployTxtURL); } catch (Exception e) { this.release(); throw new Exception("The passed update URL is not valid. " + e.getMessage()); } try { stringToFile("\n", reportFile); } catch (IOException e) { ServerConsole.error(LOG_PREFIX, "doUpgrade", e); } for (int i = 0; i < storeItemsList.size(); i++) { StoreItem[] storeItems = new StoreItem[1]; storeItems[0] = storeItemsList.get(i); Store store = new Store(); store.setStoreMessage(storeItems); try { String ret = swManager.store(store); ret = ret.replace("\n", ""); ret = ret.replace("\n", ""); listIds.add(parseXMLStoreSoftwareArchive(ret, listServicePackagesMessages)); stringToFile(ret, reportFile); // -- PROGRESS UPDATE PROCEDURE // updates the status so that the client can retrieve // the current update progress (0..1). UpgradeStatus status = new UpgradeStatus(i + 1, storeItemsList.size()); this.storeStatus(status); double progress = 1.0d * (status.getCurrentPackage() / status.getTotalPackages()); // Informs the listeners of update progress for (UpgradeListener listener : this.listeners) { listener.onProgress(progress); } ServerConsole.trace(LOG_PREFIX, "*** UPDATED [" + (i + 1) + "/" + storeItemsList.size() + "]"); // -- ENDOF PROGRESS UPDATE PROCEDURE } catch (Exception e) { ServerConsole.error(LOG_PREFIX, e); } } try { stringToFile("\n", reportFile); } catch (IOException e) { e.printStackTrace(); } StringBuilder sb = new StringBuilder(); sb.append("\n"); for (int k = 0; k < listIds.size(); k++) { try { StringArray stringArray = swManager.listUniquesServicePackages(listServicePackagesMessages.get(k)); if (stringArray != null) { String[] aux = stringArray.getItems(); for (int i = 0; i < aux.length; i++) { sb.append(aux[i]); } } else { ServerConsole.info(LOG_PREFIX, "listServicePackages return null"); continue; } } catch (Exception e) { ServerConsole.error(LOG_PREFIX, "Error getting package list Service with Class=" + listServicePackagesMessages.get(k).getServiceClass() + " Name=" + listServicePackagesMessages.get(k).getServiceName() + " Version=" + listServicePackagesMessages.get(k).getServiceVersion() + "\n\n"); // Informs the listeners of update exception for (UpgradeListener listener : this.listeners) { listener.onError( // provides the package raising update failure "Error getting package list Service with Class=" + listServicePackagesMessages.get(k).getServiceClass() + " Name=" + listServicePackagesMessages.get(k).getServiceName() + " Version=" + listServicePackagesMessages.get(k).getServiceVersion(), e); } // Continues to next package continue; } } sb.append(""); try { stringToFile(sb.toString(), distributionFile); } catch (IOException e) { ServerConsole.info(LOG_PREFIX, "Unable to write on output file"); ServerConsole.error(LOG_PREFIX, e); } this.release(); ServerConsole.debug(LOG_PREFIX, "Infrastructure Update [DONE]"); // Informs the release has been done for (UpgradeListener listener : this.listeners) { listener.onFinish(this.todeployTxtURL, this.reportFileName, this.distributionFileName); } } /** * Each time a single update can be required. * For a such reason the repository upgrader must be reserved and * released at the end of the process. */ private void reserve() throws Exception { if (isReserved()) { throw new ResourceAccessException("The Infrastracture update cannot be executed. It is already locked by another process. Try later."); } // Informs the release has been done for (UpgradeListener listener : this.listeners) { listener.onReserve(); } stringToFile("Reserved by: " + this.reportFileName, new File(this.lockFile)); this.storeStatus(new UpgradeStatus(0, 1)); } private void storeStatus(final UpgradeStatus status) { ServerConsole.info(LOG_PREFIX, "Storing status"); try { File file = new File(this.lockFile); ObjectOutputStream mine = new ObjectOutputStream(new FileOutputStream(file)); mine.reset(); mine.writeObject(status); mine.flush(); mine.close(); } catch (Exception e) { ServerConsole.error(LOG_PREFIX, e); } } /** * Gives the completed upgrade percentage (0...1). * @return */ public final double getProgressStatus() { ServerConsole.info(LOG_PREFIX, "Getting status"); if (!this.isReserved()) { return 1.0; } try { File file = new File(this.lockFile); ObjectInputStream mine = new ObjectInputStream(new FileInputStream(file)); UpgradeStatus status = (UpgradeStatus) mine.readObject(); mine.close(); double retval = 1.0d * (status.getCurrentPackage() / status.getTotalPackages()); ServerConsole.info(LOG_PREFIX, "Current Status: " + retval); return retval; } catch (Exception e) { return 1; } } /** * Checks if the software update is locked by another process. * @return */ private boolean isReserved() { return new File(this.lockFile).exists(); } /** * Releases the reservation (removes the lock file). */ private void release() { // Informs the release has been done for (UpgradeListener listener : this.listeners) { listener.onRelease(); } ServerConsole.debug(LOG_PREFIX, "Releasing lock for " + this.reportFileName); new File(this.lockFile).delete(); } private List parseTxtFile(final String txtURL) throws Exception { List ret = new ArrayList(); ServerConsole.info(LOG_PREFIX, "Opening URL connection to " + txtURL); final URL url = new URL(txtURL); final HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); httpURLConnection.setUseCaches(false); ServerConsole.info(LOG_PREFIX, "Getting data from given URL " + txtURL); InputStream is = httpURLConnection.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); ServerConsole.info(LOG_PREFIX, "Connection return code: " + httpURLConnection.getResponseCode()); ServerConsole.info(LOG_PREFIX, "Bytes available: " + is.available() + "\n\n\n"); String nextStr = bufferedReader.readLine(); // read a row while (nextStr != null) { String row = nextStr.toLowerCase(); if (!row.contains("-servicearchive-")) { nextStr = bufferedReader.readLine(); // read the next row before continuing continue; } String serviceClass = "Class"; ServerConsole.info(LOG_PREFIX, "Service Class = " + serviceClass); String serviceName = "Name"; ServerConsole.info(LOG_PREFIX, "Service Name = " + serviceName); //String version = nextStr.split("-servicearchive-")[1]; String version = "1.0.0"; // version = version.split("-")[0]; ServerConsole.info(LOG_PREFIX, "Version = " + version); String softwareArchiveURL = nextStr; ServerConsole.info(LOG_PREFIX, "URL = " + nextStr); String description = "ETICS Client programmed Description"; ServerConsole.info(LOG_PREFIX, "Description = " + description + "\n\n\n"); String[] scopes = new String[] {scope}; StoreItem storeItem = new StoreItem(); storeItem.setServiceClass(serviceClass); storeItem.setServiceName(serviceName); storeItem.setServiceVersion(version); storeItem.setURL(softwareArchiveURL); storeItem.setDescription(description); storeItem.setScopes(scopes); ret.add(storeItem); nextStr = bufferedReader.readLine(); // read the next row } bufferedReader.close(); httpURLConnection.disconnect(); return ret; } /** * * @param xml * @return */ private String parseXMLStoreSoftwareArchive( final String xml, final List listServicePackagesMessages) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = null; try { db = dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { e.printStackTrace(); } Document document = null; try { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xml.getBytes()); document = db.parse(byteArrayInputStream); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } Element root = document.getDocumentElement(); NodeList nl = root.getChildNodes(); String id = ""; ListServicePackagesMessage listServicePackagesMessage = new ListServicePackagesMessage(); for (int i = 0; i < nl.getLength(); i++) { if (!(nl.item(i) instanceof Element)) { continue; } Element el = (Element) nl.item(i); if (el == null) { continue; } else if (el.getTagName().compareTo("ServiceClass") == 0) { listServicePackagesMessage.setServiceClass(el.getTextContent()); } else if (el.getTagName().compareTo("ID") == 0) { id = el.getTextContent(); } else if (el.getTagName().compareTo("ServiceName") == 0) { listServicePackagesMessage.setServiceName(el.getTextContent()); } else if (el.getTagName().compareTo("ServiceVersion") == 0) { listServicePackagesMessage.setServiceVersion(el.getTextContent()); } } listServicePackagesMessages.add(listServicePackagesMessage); ServerConsole.info(LOG_PREFIX, "required store for SA ID: " + id); return id; } /** * @param str string * @param targetFile Target File * @throws IOException if fails */ private void stringToFile( final String str, final File targetFile) throws IOException { try { FileWriter fw = new FileWriter(targetFile, true); fw.append(str); fw.flush(); fw.close(); } catch (IOException e) { throw e; } } public final void addListener(final UpgradeListener listener) { if (listener == null) { return; } listener.setUpgrader(this); this.listeners.add(listener); } }