org.efaps.update.version.Application Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of efaps-kernel Show documentation
Show all versions of efaps-kernel Show documentation
eFaps is a framework used to map objects with or without attached files to
a relational database and optional file systems (only for attaches files). Configurable access control can be provided down to object and attribute level depending on implementation and use case. Depending on requirements, events (like triggers) allow to implement business logic and to separate business logic from user interface.
The framework includes integrations (e.g. webdav, full text search) and a web application as 'simple' configurable user interface. Some best practises, example web application modules (e.g. team work module) support administrators and implementers using this framework.
/*
* Copyright 2003 - 2012 The eFaps Team
*
* Licensed 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.
*
* Revision: $Rev: 7908 $
* Last Changed: $Date: 2012-08-13 16:39:28 -0500 (Mon, 13 Aug 2012) $
* Last Changed By: $Author: [email protected] $
*/
package org.efaps.update.version;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.annotations.FromAnnotationsRuleModule;
import org.apache.commons.digester3.annotations.rules.BeanPropertySetter;
import org.apache.commons.digester3.annotations.rules.CallMethod;
import org.apache.commons.digester3.annotations.rules.CallParam;
import org.apache.commons.digester3.annotations.rules.ObjectCreate;
import org.apache.commons.digester3.annotations.rules.SetNext;
import org.apache.commons.digester3.annotations.rules.SetProperty;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.tools.ant.DirectoryScanner;
import org.efaps.admin.datamodel.Type;
import org.efaps.admin.runlevel.RunLevel;
import org.efaps.ci.CIAdminCommon;
import org.efaps.db.Context;
import org.efaps.db.Insert;
import org.efaps.db.InstanceQuery;
import org.efaps.db.QueryBuilder;
import org.efaps.update.FileType;
import org.efaps.update.Install;
import org.efaps.update.Profile;
import org.efaps.update.schema.program.esjp.ESJPCompiler;
import org.efaps.update.schema.program.staticsource.AbstractStaticSourceCompiler;
import org.efaps.update.util.InstallationException;
import org.efaps.util.EFapsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author The eFaps Team
* @version $Id: Application.java 7908 2012-08-13 21:39:28Z [email protected] $
*/
@ObjectCreate(pattern = "install")
public final class Application
{
/**
* Logging instance used to give logging information of this class.
*/
private static final Logger LOG = LoggerFactory.getLogger(Application.class);
/**
* Default Mapping of a a file extension to a Type for import and update.
*/
private static final Map DEFAULT_TYPE_MAPPING = new HashMap();
static {
Application.DEFAULT_TYPE_MAPPING.put("css", FileType.CSS.getType());
Application.DEFAULT_TYPE_MAPPING.put("java", FileType.JAVA.getType());
Application.DEFAULT_TYPE_MAPPING.put("js", FileType.JS.getType());
Application.DEFAULT_TYPE_MAPPING.put("jrxml", FileType.JRXML.getType());
Application.DEFAULT_TYPE_MAPPING.put("wiki", FileType.WIKI.getType());
Application.DEFAULT_TYPE_MAPPING.put("xml", FileType.XML.getType());
Application.DEFAULT_TYPE_MAPPING.put("xsl", FileType.XSL.getType());
}
/**
* Default list of includes used to evaluate the files to copy.
*
* @see #getFiles
*/
private static final Set DEFAULT_INCLUDES = new HashSet();
static {
Application.DEFAULT_INCLUDES.add("**/*.css");
Application.DEFAULT_INCLUDES.add("**/*.java");
Application.DEFAULT_INCLUDES.add("**/*.js");
Application.DEFAULT_INCLUDES.add("**/*.jrxml");
Application.DEFAULT_INCLUDES.add("**/*.wiki");
Application.DEFAULT_INCLUDES.add("**/*.xml");
Application.DEFAULT_INCLUDES.add("**/*.xsl");
}
/**
* Default list of excludes used to evaluate the files to copy.
*
* @see #getFiles
*/
private static final Set DEFAULT_EXCLUDES = new HashSet();
static {
Application.DEFAULT_EXCLUDES.add("**/versions.xml");
Application.DEFAULT_EXCLUDES.add("**/package-info.java");
}
/**
* Stores the name of the application.
*
* @see #setApplication
*/
@BeanPropertySetter(pattern = "install/application")
private String application = null;
/**
* Stores all versions of this application which must be installed.
*
* @see #getVersions()
*/
private final Set versions = new TreeSet();
/**
* Install instance holding all XML definition / update files.
*
* @see #addURL(URL, String)
*/
private final Install install = new Install();
/**
* Caches not stores versions (because if the kernel install is made, the
* version could not be updated till the SQL tables and the data model is
* already installed and the cache is reloaded).
*/
private final List notStoredVersions = new ArrayList();
/**
* Stores the highest or maximum number of the versions to be installed.
*/
private Long maxVersion;
/**
* Project class path.
*
* @see #Application(URL, List)
* @see #getClassPathElements()
*/
private List classpathElements;
/**
* Dependencies to other applications for this application ordered.
*/
private final List dependencies = new ArrayList();
/**
* Root URL where the source files are located. Could be a file directory (
* for local installation) or a jar file.
*
* @see #Application(URL, List)
* @see #getRootUrl()
*/
private URL rootUrl;
/**
* Stores the name of the rootPackage.
*/
@SetProperty(pattern = "install/rootPackage", attributeName = "name")
private String rootPackageName;
/**
* USed in combination with the digester.
*/
private Map tmpElements = new HashMap();
/**
* Initializes the {@link #rootUrl root URL} of this application.
*
* @param _rootUrl root URL of the source
* @param _classpathElements elements of the class path
* @see #rootUrl
*/
private Application(final URL _rootUrl,
final List _classpathElements)
{
this.rootUrl = _rootUrl;
this.classpathElements = _classpathElements;
}
/**
* Constructor used by Digester.
*/
public Application()
{
}
/**
* null
is returned, of the version file could not be opened
* and read.
*
* @param _versionUrl URL of the version file which defines the application
* @param _rootUrl root URL where the source files are located (for local
* files); URL of the class file (if source is in a Jar file)
* @param _classpathElements elements of the class path
* @return application instance with all version information
* @throws InstallationException if version XML file could not be parsed
* TODO: description TODO: better definition of include dir /
* file
*/
public static Application getApplication(final URL _versionUrl,
final URL _rootUrl,
final List _classpathElements)
throws InstallationException
{
Application appl = null;
try {
final DigesterLoader loader = DigesterLoader.newLoader(new FromAnnotationsRuleModule()
{
@Override
protected void configureRules()
{
bindRulesFrom(Application.class);
}
});
final Digester digester = loader.newDigester();
appl = (Application) digester.parse(_versionUrl);
appl.rootUrl = _rootUrl;
appl.classpathElements = _classpathElements;
for (final Entry entry : appl.tmpElements.entrySet()) {
appl.addURL(new URL(_rootUrl, entry.getKey()), entry.getValue());
}
appl.tmpElements = null;
Collections.sort(appl.dependencies, new Comparator()
{
@Override
public int compare(final Dependency _dependency0,
final Dependency _dependency1)
{
return _dependency0.getOrder().compareTo(_dependency1.getOrder());
}
});
for (final ApplicationVersion applVers : appl.getVersions()) {
applVers.setApplication(appl);
appl.setMaxVersion(applVers.getNumber());
}
} catch (final IOException e) {
if (e.getCause() instanceof InstallationException) {
throw (InstallationException) e.getCause();
} else {
throw new InstallationException("Could not open / read version file '" + _versionUrl + "'");
}
//CHECKSTYLE:OFF
} catch (final Exception e) {
//CHECKSTYLE:ON
throw new InstallationException("Error while parsing file '" + _versionUrl + "'", e);
}
return appl;
}
/**
* Returns the application definition read from a source directory.
*
* @param _versionFile version file which defines the application
* @param _classpathElements class path elements (required to compile)
* @param _eFapsDir root directory with the XML installation
* files
* @param _outputDir directory used as target for generated code
* @param _includes list of includes; if null
* {@link #DEFAULT_INCLUDES} are used
* @param _excludes list of excludes; if null
* {@link #DEFAULT_EXCLUDES} are used
* @param _file2typeMapping mapping of file extension to type; if
* null
{@link #DEFAULT_TYPE_MAPPING} is used
* @return application instance with all version information
* @throws InstallationException if version file could not be read or opened
*/
public static Application getApplicationFromSource(final File _versionFile,
final List _classpathElements,
final File _eFapsDir,
final File _outputDir,
final List _includes,
final List _excludes,
final Map _file2typeMapping)
throws InstallationException
{
final Map file2typeMapping = (_file2typeMapping == null)
? Application.DEFAULT_TYPE_MAPPING
: _file2typeMapping;
final Application appl;
try {
appl = Application.getApplication(_versionFile.toURI().toURL(),
_eFapsDir.toURI().toURL(),
_classpathElements);
for (final String fileName : Application.getFiles(_eFapsDir, _includes, _excludes)) {
final String type = file2typeMapping.get(fileName.substring(fileName.lastIndexOf(".") + 1));
appl.addURL(new File(_eFapsDir, fileName).toURI().toURL(), type);
}
if (_outputDir.exists()) {
for (final String fileName : Application.getFiles(_outputDir, _includes, _excludes)) {
final String type = file2typeMapping.get(fileName.substring(fileName.lastIndexOf(".") + 1));
appl.addURL(new File(_outputDir, fileName).toURI().toURL(), type);
}
}
} catch (final IOException e) {
throw new InstallationException("Could not open / read version file " + "'" + _versionFile + "'", e);
//CHECKSTYLE:OFF
} catch (final Exception e) {
//CHECKSTYLE:ON
throw new InstallationException("Read version file '" + _versionFile + "' failed", e);
}
return appl;
}
/**
* Uses the _includes
and _excludes
together with
* the root directory _eFapsDir
to get all related and matched
* files.
*
* @param _eFapsDir root directory where the file are located
* @param _includes defines includes; if not specified the default value is
* **/*.xml
* @param _excludes defines excludes; if not specified the default value is
* **/version.xml
* @return array of file names
* @see #DEFAULT_INCLUDES
* @see #DEFAULT_EXCLUDES
*/
protected static String[] getFiles(final File _eFapsDir,
final List _includes,
final List _excludes)
{
final DirectoryScanner ds = new DirectoryScanner();
final String[] included = (_includes == null)
? Application.DEFAULT_INCLUDES.toArray(new String[Application.DEFAULT_INCLUDES.size()])
: _includes.toArray(new String[_includes.size()]);
final String[] excluded = (_excludes == null)
? Application.DEFAULT_EXCLUDES.toArray(new String[Application.DEFAULT_EXCLUDES.size()])
: _excludes.toArray(new String[_excludes.size()]);
ds.setIncludes(included);
ds.setExcludes(excluded);
ds.setBasedir(_eFapsDir.toString());
ds.setCaseSensitive(true);
ds.scan();
return ds.getIncludedFiles();
}
/**
* Method to get the applications from the class path.
*
* @param _application searched application in the class path
* @param _classpath class path (list of the complete class path)
* @return List of applications
* @throws InstallationException if the install.xml file in the class path
* could not be accessed
*/
public static Application getApplicationFromClassPath(final String _application,
final List _classpath)
throws InstallationException
{
final ClassLoader cl = Application.class.getClassLoader();
// get install application (read from all install xml files)
final Map appls = new HashMap();
try {
final Enumeration urlEnum = cl.getResources("META-INF/efaps/install.xml");
while (urlEnum.hasMoreElements()) {
// TODO: why class path?
final URL url = urlEnum.nextElement();
final Application appl = Application.getApplication(url, new URL(url, "../../../"), _classpath);
appls.put(appl.getApplication(), appl);
}
} catch (final IOException e) {
throw new InstallationException("Could not access the install.xml file "
+ "(in path META-INF/efaps/ path of each eFaps install jar).", e);
}
return appls.get(_application);
}
/**
* Returns the application read from given JAR file _jarFile
.
*
* @param _jarFile JAR file with the application to install
* @param _classpath class path (required to compile)
* @return application instance
* @throws InstallationException if application could not be fetched from
* the JAR file or the version XML file could not be parsed
*/
public static Application getApplicationFromJarFile(final File _jarFile,
final List _classpath)
throws InstallationException
{
try {
final URL url = new URL("jar", null, 0, _jarFile.toURI().toURL().toString() + "!/");
final URL url2 = new URL(url, "/META-INF/efaps/install.xml");
return Application.getApplication(url2, new URL(url2, "../../../"), _classpath);
} catch (final IOException e) {
throw new InstallationException("URL could not be parsed", e);
}
}
/**
* Compiles the ESJP's and all Cascade Styles Sheets within eFaps.
*
* @param _userName name of logged in user for which the compile is done
* (could be also null
)
* @param _classpath class path elements
* @param _addRuntimeClassPath must the classpath from the runtime be added
* to the classpath also
* @throws InstallationException if reload cache of compile failed
* @see #compileAll(String)
*/
public static void compileAll(final String _userName,
final List _classpath,
final boolean _addRuntimeClassPath)
throws InstallationException
{
(new Application((URL) null, _classpath)).compileAll(_userName, _addRuntimeClassPath);
}
/**
* Compiles the ESJP's and all Cascade Styles Sheets within eFaps.
*
* @param _userName name of logged in user for which the compile is done
* (could be also null
)
* @param _addRuntimeClassPath must the classpath from the runtime be added
* to the classpath also
* @throws InstallationException if reload cache of compile failed
*/
public void compileAll(final String _userName,
final boolean _addRuntimeClassPath)
throws InstallationException
{
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("..Compiling");
}
reloadCache();
try {
Context.begin(_userName);
try {
new ESJPCompiler(this.classpathElements).compile(null, _addRuntimeClassPath);
} catch (final InstallationException e) {
Application.LOG.error(" error during compilation of ESJP.");
}
AbstractStaticSourceCompiler.compileAll(this.classpathElements);
Context.commit();
} catch (final EFapsException e) {
throw new InstallationException("Compile failed", e);
}
}
/**
* Installs current application including existing {@link #dependencies}.
*
* @param _userName name of logged in user
* @param _password password of logged in user
* @throws InstallationException for all cases the installation failed
* @see #install(String, String, boolean)
*/
public void install(final String _userName,
final String _password,
final Set _profiles)
throws InstallationException
{
this.install(_userName, _password, _profiles, true);
}
/**
* For each version in {@link #versions} is tested, if it is already
* installed. If not already installed, the version is installed. Only if
* _withDependency
is defined, also the {@link #dependencies}
* are installed.
*
* @param _userName name of the installation user
* @param _password password of the installation user
* @param _withDependency must the dependency also installed?
* @throws InstallationException if installation failed
*/
protected void install(final String _userName,
final String _password,
final Set _profiles,
final boolean _withDependency
)
throws InstallationException
{
// install dependency if required
if (_withDependency) {
for (final Dependency dependency : this.dependencies) {
dependency.resolve();
final Application appl = Application.getApplicationFromJarFile(
dependency.getJarFile(), this.classpathElements);
appl.install(_userName, _password, dependency.getProfiles(), false);
}
}
// reload cache (if possible)
reloadCache();
// load latest installed versions
final Map latestVersions;
try {
Context.begin();
latestVersions = this.install.getLatestVersions();
Context.rollback();
} catch (final EFapsException e) {
throw new InstallationException("Could not get information about installed versions", e);
}
final Integer latestVersion = latestVersions.get(this.application);
Application.LOG.info("Install application '" + this.application + "'");
for (final ApplicationVersion version : this.versions) {
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Check version " + version.getNumber());
}
if ((latestVersion != null) && (version.getNumber() < latestVersion)) {
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Version " + version.getNumber() + " already installed");
}
} else {
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Starting installation of version " + version.getNumber());
final String desc = version.getDescription();
if (!"".equals(desc)) {
Application.LOG.info(desc);
}
}
try {
// TODO: correct exception handling in the installation
version.install(this.install, getLastVersion().getNumber(), _profiles, _userName, _password);
//CHECKSTYLE:OFF
} catch (final Exception e) {
//CHECKSTYLE:ON
throw new InstallationException("Installation failed", e);
}
storeVersion(_userName, version.getNumber());
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Finished installation of version " + version.getNumber());
}
}
}
// reload cache (if possible)
reloadCache();
}
/**
* Updates the last installed version.
*
* @param _userName name of logged in user
* @param _password password of logged in user TODO: throw Exceptions
* instead of logging errors
* @param _profiles Profiles to be applied
* @throws Exception on error
*/
public void updateLastVersion(final String _userName,
final String _password,
final Set _profiles)
throws Exception
{
// reload cache (if possible)
reloadCache();
// load installed versions
Context.begin();
final Map latestVersions = this.install.getLatestVersions();
Context.rollback();
final long latestVersion = latestVersions.get(this.application);
final ApplicationVersion version = getLastVersion();
if (version.getNumber() == latestVersion) {
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Update version "
+ version.getNumber()
+ " of application '"
+ this.application
+ "'");
}
version.install(this.install, version.getNumber(), _profiles, _userName, _password);
if (Application.LOG.isInfoEnabled()) {
Application.LOG.info("Finished update of version " + version.getNumber());
}
} else {
Application.LOG.error("Version "
+ version.getNumber()
+ " of application '"
+ this.application
+ "' not installed and could not updated!");
}
}
/**
* Store for this application that the version is already installed. If data
* model in the local type cache is not loaded (because, e.g., it is a new
* kernel install), the version numbers are cached.
* The first time, the version type could be get from the type cache, all
* cached versions are stored in eFaps.
*
* @param _userName logged in user name
* @param _version version id to store
* @throws InstallationException if version could not be stored
*/
protected void storeVersion(final String _userName,
final Long _version)
throws InstallationException
{
final Type versionType = CIAdminCommon.Version.getType();
if (versionType != null) {
try {
Context.begin(_userName);
// store cached versions
for (final Long version : this.notStoredVersions) {
final Insert insert = new Insert(versionType);
insert.add(CIAdminCommon.Version.Name, this.application);
insert.add(CIAdminCommon.Version.Revision, version);
insert.execute();
}
this.notStoredVersions.clear();
final QueryBuilder queryBldr = new QueryBuilder(CIAdminCommon.Version);
queryBldr.addWhereAttrEqValue(CIAdminCommon.Version.Name, this.application);
queryBldr.addWhereAttrEqValue(CIAdminCommon.Version.Revision, _version);
final InstanceQuery query = queryBldr.getQuery();
query.execute();
if (!query.next()) {
// store current version
final Insert insert = new Insert(CIAdminCommon.Version);
insert.add(CIAdminCommon.Version.Name, this.application);
insert.add(CIAdminCommon.Version.Revision, _version);
insert.execute();
}
Context.commit();
} catch (final EFapsException e) {
throw new InstallationException("Update of the version information failed", e);
}
} else {
// if version could not be stored, cache the version information
this.notStoredVersions.add(_version);
}
}
/**
*
* @param _dependency dependency
*/
@SetNext
public void addDependency(final Dependency _dependency)
{
this.dependencies.add(_dependency);
}
/**
* Reloads the eFaps cache.
*
* @throws InstallationException if reload of the cache failed
*/
protected void reloadCache()
throws InstallationException
{
try {
Context.begin();
if (RunLevel.isInitialisable()) {
RunLevel.init("shell");
RunLevel.execute();
}
Context.rollback();
} catch (final EFapsException e) {
throw new InstallationException("Reload cache failed", e);
}
}
/**
* Adds a n ew application version to this application which should be
* installed.
*
* @param _version new application version to add
*/
@SetNext
public void addVersion(final ApplicationVersion _version)
{
this.versions.add(_version);
}
/**
* Returns the last application version which must be installed.
*
* @return last application version to install
*/
public ApplicationVersion getLastVersion()
{
return (ApplicationVersion) this.versions.toArray()[this.versions.size() - 1];
}
/**
* Setter method for instance variable {@link #application}.
*
* @param _application value for instance variable {@link #application}
*/
public void setApplication(final String _application)
{
this.application = _application;
}
/**
* Adds a new URL with the XML definition file.
*
* @param _url url of XML definition files used to install
* @param _type type of the URL
* @see #install(String, String)
*/
public void addURL(final URL _url,
final String _type)
{
this.install.addFile(_url, _type);
}
/**
* Searches for the given file name (parameter _classPathFile) in the class
* path and adds them as URL to the list of XML installation / update /
* definition files ({@link #install}).
*
* @param _classPathFile file name from the class path to add
* @param _type type of the file to be added
* @throws MalformedURLException on error with the URL
* @see #addURL(URL, String)
*/
@CallMethod(pattern = "install/files/file")
public void addClassPathFile(
@CallParam(pattern = "install/files/file", attributeName = "name") final String _classPathFile,
@CallParam(pattern = "install/files/file", attributeName = "type") final String _type)
throws MalformedURLException
{
this.tmpElements.put(_classPathFile, _type);
}
/**
* Getter method for the instance variable {@link #install}.
*
* @return value of instance variable {@link #install}
*/
public Install getInstall()
{
return this.install;
}
/**
* Getter method for the instance variable {@link #dependencies}.
*
* @return value of instance variable {@link #dependencies}
*/
public List getDependencies()
{
return this.dependencies;
}
/**
* This is the getter method for instance variable {@link #application}.
*
* @return value of instance variable {@link #application}
* @see #application
*/
public String getApplication()
{
return this.application;
}
/**
* Returns all class path elements required to compile.
*
* @return class path elements
* @see #classpathElements
*/
public List getClassPathElements()
{
return this.classpathElements;
}
/**
* Returns the root URL where the source is located. For local sources it is
* an URL to a directory, for a Jar file it is the URL to the Jar file.
*
* @return value of instance variable {@link #rootUrl}
*/
public URL getRootUrl()
{
return this.rootUrl;
}
/**
* This is the getter method for instance variable {@link #versions}.
*
* @return value of instance variable {@link #versions}
* @see #versions
*/
public Set getVersions()
{
return this.versions;
}
/**
* This is the setter method for the instance variable {@link #maxVersion}.
*
* @param _maxVersion the maxVersion to set
*/
public void setMaxVersion(final Long _maxVersion)
{
this.maxVersion = _maxVersion;
}
/**
* This is the getter method for the instance variable {@link #maxVersion}.
*
* @return value of instance variable {@link #maxVersion}
*/
public Long getMaxVersion()
{
return this.maxVersion;
}
/**
* Setter method for instance variable {@link #rootPackageName}.
*
* @param _rootPackageName value for instance variable
* {@link #rootPackageName}
*/
public void setRootPackageName(final String _rootPackageName)
{
this.rootPackageName = _rootPackageName;
}
/**
* Getter method for instance variable {@link #rootPackageName}.
*
* @return value of instance variable {@link #rootPackageName}
*/
public String getRootPackageName()
{
return this.rootPackageName;
}
/**
* Returns a string representation with values of all instance variables.
*
* @return string representation of this Application
*/
@Override
public String toString()
{
return new ToStringBuilder(this)
.append("application", this.application)
.append("dependencies", this.dependencies)
.append("versions", this.versions)
.append("install", this.install)
.toString();
}
}