
org.nuiton.util.ApplicationUpdater Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nuiton-utils Show documentation
Show all versions of nuiton-utils Show documentation
Library of usefull class to be used in any project.
package org.nuiton.util;
/*
* #%L
* Nuiton Utils :: Nuiton Utils
* $Id: ApplicationUpdater.java 2513 2013-02-26 07:22:43Z tchemit $
* $HeadURL: http://svn.nuiton.org/svn/nuiton-utils/tags/nuiton-utils-2.6.10/nuiton-utils/src/main/java/org/nuiton/util/ApplicationUpdater.java $
* %%
* Copyright (C) 2004 - 2013 CodeLutin
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs2.AllFileSelector;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.provider.http.HttpFileSystemConfigBuilder;
/**
* Permet de telecharger des mises a jour d'application.
*
* Le principe est qu'un fichier properties pointe par une URL indique les
* information necessaire pour la recuperation de l'application.
*
* Si une nouvelle version de l'application existe, elle est alors telechargee
* et decompressee dans un repertoire specifique (elle ne remplace pas l'application
* courante).
*
* Il est alors a la charge d'un script de mettre en place cette nouvelle application
* a la place de l'ancienne.
*
* Il est possible d'interagir avec ApplicationUpdater via l'implantation d'un
* {@link ApplicationUpdaterCallback} passer en parametre de la methode {@link #update}
*
* Configuration possible
* Vous pouvez passer un ApplicationConfig dans le constructeur ou utiliser
* la recherche du fichier de configuration par defaut (ApplicationUpdater.properties)
*
* Cette configuration permet de récupérer les informations suivantes:
* http_proxy: le proxy a utiliser pour l'acces au reseau (ex: squid.chezmoi.fr:8080)
* os.name: le nom du systeme d'exploitation sur lequel l'application fonctionne (ex: Linux)
* os.arch: l'architecture du systeme d'exploitation sur lequel l'application fonctionne (ex: amd64)
*
* format du fichier de properties
*
* [osName.][osArch.]appName.version=version de l'application
* [osName.][osArch.]appName.auth=true ou false selon que l'acces a l'url
* demande une authentification a fournir par le callback
* (voir {@link ApplicationUpdaterCallback#updateToDo})
* [osName.][osArch.]appName.url=url du fichier compresse de la nouvelle version
* (format commons-vfs2)
*
* appName est a remplacer par le nom de l'application. Il est possible
* d'avoir plusieurs application dans le meme fichier ou plusieurs version
* en fonction de l'os et de l'architecture.
*
* osName et osArch sont toujours en minuscule
*
* format des fichiers compresses
*
* Le fichier compresse doit avoir un repertoire racine qui contient l'ensemble de l'application
* c-a-d que les fichiers ne doivent pas etre directement a la racine lorsqu'on
* decompresse le fichier.
*
* exemple de contenu de fichier compresse convenable
*
* MonApp-0.3/Readme.txt
* MonApp-0.3/License.txt
*
*
* Ceci est du au fait qu'on renomme le repertoire racine avec le nom de l'application,
* donc si le repertoire racine n'existe pas ou qu'il y a plusieurs repertoires
* a la racine le resultat de l'operation n'est pas celui souhaite
*
* os.name and os.arch
*
* os.name os.arch
* linux amd64
* linux i386
* mac ppc
* windows x86
* solaris sparc
*
*
* os.name est tronque apres le 1er mot donc "windows 2000" et "windows 2003"
* deviennet tous les deux "windows". Si vous souhaitez gérer plus finement vos
* url de telechargement vous pouvez modifier les donnees via
* {@link ApplicationUpdaterCallback#updateToDo(java.util.Map) } en modifiant
* l'url avant de retourner la map
*
* @author poussin
* @version $Revision: 2513 $
*
* Last update: $Date: 2013-02-26 08:22:43 +0100 (Tue, 26 Feb 2013) $
* by : $Author: tchemit $
*
* @since 2.6.6
* @deprecated since 2.6.10 (replaced by org.nuiton.util.updater.ApplicationUpdater
* in nuiton-updater module), will be removed in version 2.7.1.
*/
@Deprecated
public class ApplicationUpdater {
/** to use log facility, just put in your code: log.info(\"...\"); */
static private Log log = LogFactory.getLog(ApplicationUpdater.class);
final static private String SEPARATOR_KEY = ".";
final static public String HTTP_PROXY = "http_proxy";
final static public String URL_KEY = "url";
final static public String AUTHENTICATION_KEY = "auth";
final static public String VERSION_KEY = "version";
final static public String VERSION_FILE = "version.appup";
protected ApplicationConfig config;
/**
* Utilise le fichier de configuration par defaut: ApplicationUpdater.properties
*/
public ApplicationUpdater() {
this(null);
}
/**
*
* @param config La configuration a utiliser pour rechercher le proxy (http_proxy)
* et os.name, os.arch
*/
public ApplicationUpdater(ApplicationConfig config) {
if (config == null) {
try {
config = new ApplicationConfig(
ApplicationUpdater.class.getSimpleName() + ".properties");
config.parse();
config = config.getSubConfig(
ApplicationUpdater.class.getSimpleName() + SEPARATOR_KEY);
} catch (ArgumentsParserException eee) {
throw new RuntimeException(eee);
}
}
this.config = config;
}
/**
*
* @param url url where properties file is downloadable. This properties
* must contains information on application release
* @param currentDir directory where application is currently
* @param destDir default directory to put new application version, can be null if you used callback
* @param async if true, check is done in background mode
* @param callback callback used to interact with updater, can be null
*/
public void update(String vfsPropertiesURL, File currentDir, File destDir, boolean async, ApplicationUpdaterCallback callback) {
Updater up = new Updater(config, vfsPropertiesURL, currentDir, destDir, callback);
if (async) {
Thread thread = new Thread(up, ApplicationUpdater.class.getSimpleName());
thread.start();
} else {
up.run();
}
}
/**
* Permet d'interagir avec ApplicationUpdater
*/
static public interface ApplicationUpdaterCallback {
/**
* Appeler avant la recuperation des nouvelles versions
*
* Permet de modifier le repertoire destination ou l'url du zip de
* l'application pour une application/version
* particuliere ou d'annuler la mise a jour en le supprimant de la map
* qui sera retourne.
*
* Si {@link ApplicationInfo#needAuthentication} est vrai, il faut que
* les valeurs {@link ApplicationInfo#login} et {@link ApplicationInfo#password}
* soient renseignees. Si elle ne le sont pas la recuperation de la
* ressource echouera. Pour des raisons de securite vous pouvez souhaiter
* mettre le mot de passe sous une forme encrypte. Dans ce cas il doit
* etre encadrer par '{' et '}'. Pour encrypter le mot de passe
* vous devez utiliser:
*
* java -cp commons-vfs-2.0.jar org.apache.commons.vfs2.util.EncryptUtil encrypt mypassword
*
* @param appToUpdate liste des applications a mettre a jour
* @return null or empty map if we don't want update, otherwize list of
* app to update
*
*/
Map updateToDo(Map appToUpdate);
/**
* Appeler au démarrage d'une mise à jour.
*
* @param info application à mettre à jour
* @since 2.7
*/
void startUpdate(ApplicationInfo info);
/**
* Appeler une fois qu'une mise a jour a parfaitement fonctionne
*
* @param name le nom de l'application
* @param oldVersion l'ancienne version
* @param newVersion la nouvelle version
* @param applicationURL l'url d'ou provient le zip de l'application
* @param dest le repertoire ou se trouve la nouvelle version
*/
void updateDone(
Map appToUpdate,
Map appUpdateError);
/**
* Called when exception occur during process initialization
* @param propertiesURL url use to download properties release information
* @param eee exception throw during process
*/
void aborted(String propertiesURL, Exception eee);
}
static public class ApplicationInfo {
public String name;
public String oldVersion;
public String newVersion;
public String url;
public boolean needAuthentication;
public String login;
public char[] password;
public File destDir;
public ApplicationInfo(String name, String oldVersion, String newVersion, String url, File destDir, boolean needAuthentication) {
this.name = name;
this.oldVersion = oldVersion;
this.newVersion = newVersion;
this.url = url;
this.needAuthentication = needAuthentication;
this.destDir = destDir;
}
public void setAuthentication(String login, char[] password) {
this.login = login;
this.password = password;
}
@Override
public String toString() {
String result = String.format(
"App: %s, oldVersion: %s, newVersion: %s, url: %s, destDir:%s",
name, oldVersion, newVersion, url, destDir);
return result;
}
}
/**
* La classe ou le travail est reellement fait, peut-etre appeler dans
* un thread si necessaire
*/
static public class Updater implements Runnable {
protected ApplicationConfig config;
protected String vfsPropertiesUrl;
protected File currentDir;
protected File destDir;
protected ApplicationUpdaterCallback callback;
public Updater(ApplicationConfig config, String vfsPropertiesUrl,
File currentDir, File destDir, ApplicationUpdaterCallback callback) {
this.config = config;
this.vfsPropertiesUrl = vfsPropertiesUrl;
this.currentDir = currentDir;
this.destDir = destDir;
this.callback = callback;
}
/**
* Recupere le fichier properties contenant les informations de mise a jour
* liste les applications et leur version actuelle
* pour chaque application a mettre a jour recupere le zip et le decompresse
*
* Si callback existe envoi les messages necessaire
*/
public void run() {
try {
FileSystemOptions vfsConfig = getVFSConfig(config);
ApplicationConfig releaseConfig = getUpdaterConfig(vfsConfig, vfsPropertiesUrl);
List appNames = getApplicationName(releaseConfig);
Map appVersions = getCurrentVersion(appNames, currentDir);
log.debug("application current version: " + appVersions);
// recherche des applications a mettre a jour
Map appToUpdate = new HashMap();
for (String app : appNames) {
String currentVersion = appVersions.get(app);
String newVersion = releaseConfig.getOption(app + SEPARATOR_KEY + VERSION_KEY);
boolean greater = VersionUtil.greaterThan(newVersion, currentVersion);
log.debug(String.format("for %s Current(%s) < newVersion(%s) ? %s",
app, currentVersion, newVersion, greater));
if (greater) {
String urlString = releaseConfig.getOption(
app + SEPARATOR_KEY + URL_KEY);
boolean needAuthentication = releaseConfig.getOptionAsBoolean(
app + SEPARATOR_KEY + AUTHENTICATION_KEY);
appToUpdate.put(app, new ApplicationInfo(
app, currentVersion, newVersion, urlString, destDir, needAuthentication));
}
}
// offre la possibilite a l'appelant de modifier les valeurs par defaut
if (callback != null) {
appToUpdate = callback.updateToDo(appToUpdate);
}
// mise a jour
Map appUpdateError = new HashMap();
for (Map.Entry appInfo : appToUpdate.entrySet()) {
String app = appInfo.getKey();
ApplicationInfo info = appInfo.getValue();
try {
doUpdate(vfsConfig, appInfo.getValue());
} catch (Exception eee) {
appUpdateError.put(app, eee);
try {
// clear data if error occur during uncompress operation
File dest = new File(info.destDir, info.name);
if (dest.exists()) {
log.debug(String.format("Cleaning destination directory due to error '%s'", dest));
FileUtils.deleteDirectory(dest);
}
} catch(Exception doNothing) {
log.debug("Can't clean directory", doNothing);
}
log.warn(String.format(
"Can't update application '%s' with url '%s'",
app, info.url));
log.debug("Application update aborted because: ", eee);
}
}
// envoi le resultat a l'appelant s'il le souhaite
if (callback != null) {
callback.updateDone(appToUpdate, appUpdateError);
}
} catch(Exception eee) {
log.warn("Can't update");
log.info("Application update aborted because: ", eee);
if (callback != null) {
callback.aborted(vfsPropertiesUrl, eee);
}
}
}
/**
* Decompresse le zip qui est pointer par l'url dans le repertoire
* specifie, et ajoute le fichier contenant la version de l'application.
* Le repertoire root du zip est renomme par le nom de l'application.
* Par exemple si un fichier se nomme "monApp-1.2/Readme.txt" il se
* nommera au final "monApp/Readme.txt"
*
* @param proxy le proxy a utiliser pour la connexion a l'url
* @param info information sur l'application a mettre a jour
* @throws Exception
*/
protected void doUpdate(FileSystemOptions vfsConfig, ApplicationInfo info) throws Exception {
if (info.destDir != null) {
File dest = new File(info.destDir, info.name);
String url = toVfsURL(info.url);
if (info.needAuthentication) {
url = StringUtils.replaceOnce(url, "://",
String.format("://%s:%s@", info.login, new String(info.password)));
}
if (callback!=null) {
callback.startUpdate(info);
}
deepCopy(vfsConfig, url, dest.getAbsolutePath());
// ajout du fichier de version
File versionFile = new File(dest, VERSION_FILE);
FileUtils.writeStringToFile(versionFile, info.newVersion);
log.info(String.format(
"Application '%s' is uptodate with version '%s' in '%s'",
info.name, info.newVersion, info.destDir));
} else {
log.info(String.format("Update for '%s' aborted because destination dir is set to null", info.name));
}
}
/**
* Recupere le contenu du repertoire de l'archive pour le mettre dans targetPath
* si targetPath existait deja, il est supprime au prealable.
*
* Si l'archive a plus d'un repertoire root, une exception est levee
*
* @param srcPath source path de la forme vfs2 ex:"zip:http://www.nuiton.org/attachments/download/830/nuiton-utils-2.6.5-deps.zip"
* @param targetPath le path destination
* @throws FileSystemException
*/
protected void deepCopy(FileSystemOptions vfsConfig,
String srcPath, String targetPath) throws FileSystemException {
FileSystemManager fsManager = VFS.getManager();
FileObject archive = fsManager.resolveFile(srcPath, vfsConfig);
FileObject[] children = archive.getChildren();
if (children.length == 1) {
FileObject child = children[0];
FileObject target = fsManager.resolveFile(toVfsURL(targetPath), vfsConfig);
target.delete(new AllFileSelector());
target.copyFrom(child, new AllFileSelector());
} else {
throw new RuntimeException("must have only one root directory");
}
}
/**
* Converti le path en URL vfs2. Path doit etre une URL, mais pour les fichiers
* au lieu d'etre absolue ils peuvent etre relatif, un traitement special
* est donc fait pour ce cas. Cela est necessaire pour facilement faire
* des tests unitaires independant de la machine ou il sont fait
*
* @param path
* @return
*/
protected String toVfsURL(String path) {
String result = path;
Pattern p = Pattern.compile("(.*?file:)([^/][^!]*)(.*)");
Matcher m = p.matcher(path);
if (m.matches()) {
String filepath = m.group(2);
File f = new File(filepath);
result = path.replaceAll(
"(.*?file:)([^/][^!]*)(.*)",
"$1"+f.getAbsolutePath()+"$3");
}
return result;
}
/**
* Return config prepared for os and arch
*
* @return
* @throws Exception
*/
protected ApplicationConfig getUpdaterConfig(FileSystemOptions vfsConfig, String vfsPropertiesUrl) throws Exception {
String osName = StringUtils.lowerCase(config.getOsName());
String osArch = StringUtils.lowerCase(config.getOsArch());
// take only first part for osName (windows 2000 or windows 2003 -> windows)
osName = StringUtils.substringBefore(osName, " ");
if (log.isDebugEnabled()) {
log.debug(String.format("Try to load properties from '%s'", vfsPropertiesUrl));
}
Properties prop = new Properties();
FileSystemManager fsManager = VFS.getManager();
FileObject properties = fsManager.resolveFile(toVfsURL(vfsPropertiesUrl), vfsConfig);
try {
InputStream in = new BufferedInputStream(properties.getContent().getInputStream());
prop.load(in);
} finally {
try {
properties.close();
} catch (Exception doNothing) {
log.debug("Can't close vfs file", doNothing);
}
}
if (log.isDebugEnabled()) {
log.debug(String.format(
"Properties loaded from '%s'\n%s",
vfsPropertiesUrl, prop));
}
// load config with new properties as default
ApplicationConfig result = new ApplicationConfig(prop);
// don't parse. We want only prop in applicationConfig
result = result.getSubConfig(
ApplicationUpdater.class.getSimpleName() + SEPARATOR_KEY);
result = result.getSubConfig(osName + SEPARATOR_KEY);
result = result.getSubConfig(osArch + SEPARATOR_KEY);
return result;
}
/**
* Recupere le proxy http a utiliser pour les connexions reseaux
*
* @param config
* @return
*/
protected FileSystemOptions getVFSConfig(ApplicationConfig config) {
FileSystemOptions result = new FileSystemOptions();
String proxyHost = config.getOption(HTTP_PROXY);
try {
proxyHost = StringUtils.substringAfter(proxyHost, "://");
if (StringUtils.isNotBlank(proxyHost)) {
String hostname = StringUtils.substringBefore(proxyHost, ":");
String port = StringUtils.substringAfter(proxyHost, ":");
if (StringUtils.isNumeric(port)) {
int portNumber = Integer.parseInt(port);
HttpFileSystemConfigBuilder.getInstance().setProxyHost(result, hostname);
HttpFileSystemConfigBuilder.getInstance().setProxyPort(result, portNumber);
} else {
log.warn(String.format("Invalide proxy port number '%s', not used proxy", port));
}
}
} catch (Exception eee) {
log.warn(String.format("Can't use proxy '%s'", proxyHost), eee);
}
return result;
}
/**
* Recherche pour chaque application la version courante
* @param apps la liste des applications a rechercher
* @return
*/
protected Map getCurrentVersion(List apps, File dir) {
Map result = new HashMap();
for (String app : apps) {
File f = new File(dir, app + File.separator + VERSION_FILE);
String version = "0";
try {
version = FileUtils.readFileToString(f);
} catch (IOException ex) {
log.warn(String.format(
"Can't find file version '%s' for application '%s', this file should be '%s'",
VERSION_FILE, app, f));
}
version = StringUtils.trim(version);
result.put(app, version);
}
return result;
}
/**
* Retourne la liste des noms d'application se trouvant dans la
* configuration
*
* @param config
* @return
*/
protected List getApplicationName(ApplicationConfig config) {
Pattern p = Pattern.compile("([^.]+)\\.version");
List result = new LinkedList();
for (String v : config.getFlatOptions().stringPropertyNames()) {
Matcher match = p.matcher(v);
if (match.matches()) {
result.add(match.group(1));
} else if (StringUtils.endsWith(v, ".version")) {
log.debug(String.format("value is not valid application version '%s'",v));
}
}
return result;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy