
jodd.joy.core.DefaultAppCore Maven / Gradle / Ivy
Show all versions of jodd-joy Show documentation
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.joy.core;
import jodd.db.DbManager;
import jodd.db.DbSessionProvider;
import jodd.db.connection.ConnectionProvider;
import jodd.db.oom.DbOomManager;
import jodd.db.oom.config.AutomagicDbOomConfigurator;
import jodd.joy.exception.AppException;
import jodd.joy.jtx.meta.ReadWriteTransaction;
import jodd.jtx.JtxTransactionManager;
import jodd.db.jtx.DbJtxSessionProvider;
import jodd.db.jtx.DbJtxTransactionManager;
import jodd.jtx.meta.Transaction;
import jodd.jtx.proxy.AnnotationTxAdvice;
import jodd.jtx.proxy.AnnotationTxAdviceManager;
import jodd.jtx.proxy.AnnotationTxAdviceSupport;
import jodd.petite.PetiteContainer;
import jodd.petite.config.AutomagicPetiteConfigurator;
import jodd.petite.proxetta.ProxettaAwarePetiteContainer;
import jodd.petite.scope.SessionScope;
import jodd.petite.scope.SingletonScope;
import jodd.props.Props;
import jodd.props.PropsUtil;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.impl.ProxyProxetta;
import jodd.proxetta.pointcuts.MethodAnnotationPointcut;
import jodd.util.ClassLoaderUtil;
import jodd.util.SystemUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
/**
* Default application core. Contains init points to
* all application frameworks and layers.
*/
public abstract class DefaultAppCore {
/**
* Application system property - application folder.
*/
public static final String APP_DIR = "app.dir";
/**
* Application system property - flag if web application is detected..
*/
public static final String APP_WEB = "app.web";
/**
* Petite bean name for AppCore (this instance).
*/
public static final String PETITE_CORE = "core";
/**
* Petite bean name for database pool.
*/
public static final String PETITE_DBPOOL = "dbpool";
/**
* Petite bean name for DbManager
instance.
*/
public static final String PETITE_DB = "db";
/**
* Petite bean name for DbOomManager
instance.
*/
public static final String PETITE_DBOOM = "dboom";
/**
* Petite bean name for {@link AppInit} bean.
*/
public static final String PETITE_INIT = "init";
/**
* Petite bean name for application props.
*/
public static final String PETITE_PROPS = "props";
/**
* Petite bean name for {@link AppScanner} bean.
*/
public static final String PETITE_SCAN = "scan";
/**
* Logger. Resolved during {@link #initLogger() initialization}.
*/
protected static Logger log;
/**
* App dir. Resolved during initialization.
*/
protected String appDir;
/**
* Is web application. Resolved during initialization.
*/
protected boolean isWebApplication;
/**
* Props profiles. If set, overrides any default profile set
* in the props files.
*/
protected String[] appPropsProfiles;
/**
* Default constructor.
*/
protected DefaultAppCore() {
}
// ---------------------------------------------------------------- init
/**
* Returns true
if application is started as a part of web application.
*/
public boolean isWebApplication() {
return isWebApplication;
}
/**
* Returns application directory.
*/
public String getAppDir() {
return appDir;
}
/**
* Initializes application core, invoked very first!
* Important: logging is not yet available in this method!
*/
@SuppressWarnings("unchecked")
public void initCore() {
if (appPropsName == null) {
appPropsName = "app.props";
}
if (appPropsNamePattern == null) {
int index = appPropsName.lastIndexOf('.');
appPropsNamePattern = '/' + appPropsName.substring(0, index) + "*.prop*";
}
if (jtxAnnotations == null) {
jtxAnnotations = new Class[] {Transaction.class, ReadWriteTransaction.class};
}
if (jtxScopePattern == null) {
jtxScopePattern = "$class";
}
if (appDir == null) {
resolveAppDir(appPropsName); // app directory is resolved from location of 'app.props'.
}
System.setProperty(APP_DIR, appDir);
System.setProperty(APP_WEB, Boolean.toString(isWebApplication));
}
/**
* Initializes the logger, after the log path is {@link #init() defined}.
*/
protected void initLogger() {
if (log != null) {
return;
}
log = LoggerFactory.getLogger(DefaultAppCore.class);
log.info("app dir: " + appDir);
}
/**
* Resolves application root folders.
*
* If application is started as web application, app folder is one below the WEB-INF folder.
* Otherwise, the root folder is equal to the working folder.
*/
protected void resolveAppDir(String classPathFileName) {
URL url = ClassLoaderUtil.getResourceUrl(classPathFileName);
if (url == null) {
throw new AppException("Failed to resolve app dir, missing: " + classPathFileName);
}
String protocol = url.getProtocol();
if (!protocol.equals("file")) {
try {
url = new URL(url.getFile());
} catch (MalformedURLException ignore) {
}
}
appDir = url.getFile();
int ndx = appDir.indexOf("WEB-INF");
isWebApplication = (ndx != -1);
appDir = isWebApplication ? appDir.substring(0, ndx) : SystemUtil.workingFolder();
}
// ---------------------------------------------------------------- ready
/**
* Called after the {@link #init() core initialization},
* during the {@link #start() application startup}.
*/
protected void ready() {
}
// ---------------------------------------------------------------- start
protected boolean initialized;
/**
* Initializes application.
* May be called several times, but the core
* will be initialized just once.
* Usually called manually when core needs to
* be created before server is started
* (e.g. in embedded environments)
*/
public void init() {
if (initialized) {
return;
}
initCore();
initLogger();
initProps();
initScanner();
initialized = true;
}
/**
* Starts the application and performs all initialization.
*/
public void start() {
init();
ready();
try {
startProxetta();
startPetite();
startDb();
startApp();
log.info("app started");
} catch (RuntimeException rex) {
if (log != null) {
log.error(rex.toString(), rex);
} else {
System.out.println(rex.toString());
rex.printStackTrace();
}
try {
stop();
} catch (Exception ignore) {
}
throw rex;
}
}
/**
* Stops the application.
*/
public void stop() {
if (log != null) {
log.info("shutting down...");
}
stopApp();
stopDb();
stopPetite();
if (log != null) {
log.info("app stopped");
}
}
// ---------------------------------------------------------------- props
/**
* Main application props file name, must exist in class path.
*/
protected String appPropsName;
/**
* Application props file name pattern.
*/
protected String appPropsNamePattern;
/**
* Application props.
*/
protected Props appProps;
/**
* Returns applications properties loaded from props files.
*/
public Props getAppProps() {
return appProps;
}
/**
* Creates and loads application props.
* It first loads system properties (registered as sys.*
)
* and then environment properties (registered as env.*
).
* Finally, props files are read from the classpath. All properties
* are loaded using
*
* If props have been already loaded, does nothing.
*/
protected void initProps() {
if (appProps != null) {
return;
}
appProps = createProps();
appProps.loadSystemProperties("sys");
appProps.loadEnvironment("env");
PropsUtil.loadFromClasspath(appProps, appPropsNamePattern);
if (appPropsProfiles != null) {
appProps.setActiveProfiles(appPropsProfiles);
}
}
/**
* Creates new Props. Empty props will be ignored,
* and missing macros will be resolved as empty string.
*/
protected Props createProps() {
Props props = new Props();
props.setSkipEmptyProps(true);
props.setIgnoreMissingMacros(true);
return props;
}
// ---------------------------------------------------------------- scanning
protected AppScanner appScanner;
/**
* Returns scanner.
*/
public AppScanner getAppScanner() {
return appScanner;
}
/**
* Initializes {@link AppScanner}.
*/
protected void initScanner() {
if (appScanner != null) {
return;
}
appScanner = new AppScanner(this);
}
// ---------------------------------------------------------------- proxetta
protected ProxyProxetta proxetta;
/**
* Returns proxetta.
*/
public ProxyProxetta getProxetta() {
return proxetta;
}
/**
* Creates Proxetta with all aspects. The following aspects are created:
*
* - Transaction proxy - applied on all classes that contains public top-level methods
* annotated with
@Transaction
annotation. This is just one way how proxies
* can be applied - since base configuration is in Java, everything is possible.
*
*/
protected void startProxetta() {
log.info("proxetta initialization");
proxetta = ProxyProxetta.withAspects(createAppAspects());
}
/**
* Creates all application aspects. By default it creates just
* {@link #createTxProxyAspects() transactional aspect}.
*/
protected ProxyAspect[] createAppAspects() {
return new ProxyAspect[] {createTxProxyAspects()};
}
/**
* Creates TX aspect that will be applied on all classes
* having at least one public top-level method annotated
* with {@link #jtxAnnotations registered JTX annotations}.
*/
protected ProxyAspect createTxProxyAspects() {
return new ProxyAspect(
AnnotationTxAdvice.class,
new MethodAnnotationPointcut(jtxAnnotations) {
@Override
public boolean apply(MethodInfo methodInfo) {
return
isPublic(methodInfo) &&
isTopLevelMethod(methodInfo) &&
super.apply(methodInfo);
}
});
}
// ---------------------------------------------------------------- petite
protected PetiteContainer petite;
/**
* Returns application container (Petite).
*/
public PetiteContainer getPetite() {
return petite;
}
/**
* Creates and initializes Petite container.
* It will be auto-magically configured by scanning the classpath.
* Also, all 'app*.prop*' will be loaded and values will
* be injected in the matched beans. At the end it registers
* this instance of core into the container.
*/
protected void startPetite() {
log.info("petite initialization");
petite = createPetiteContainer();
log.info("app in web: " + Boolean.valueOf(isWebApplication));
if (!isWebApplication) {
// make session scope to act as singleton scope
// if this is not a web application (and http session is not available).
petite.registerScope(SessionScope.class, new SingletonScope());
}
// load parameters from properties files
petite.defineParameters(appProps);
// adds a scanner bean, so it can be immediately configured from props
petite.addBean(PETITE_SCAN, appScanner);
// automagic configuration
registerPetiteContainerBeans(petite);
// add AppCore instance to Petite
petite.addBean(PETITE_CORE, this);
petite.addBean(PETITE_PROPS, appProps);
}
/**
* Configures Petite container. By default scans the class path
* for petite beans and registers them automagically.
*/
protected void registerPetiteContainerBeans(PetiteContainer petiteContainer) {
AutomagicPetiteConfigurator pcfg = new AutomagicPetiteConfigurator();
appScanner.configure(pcfg);
pcfg.configure(petiteContainer);
}
/**
* Creates Petite container. By default, it creates
* {@link jodd.petite.proxetta.ProxettaAwarePetiteContainer proxetta aware petite container}.
*/
protected PetiteContainer createPetiteContainer() {
return new ProxettaAwarePetiteContainer(proxetta);
}
/**
* Stops Petite container.
*/
protected void stopPetite() {
if (petite != null) {
petite.shutdown();
}
}
// ---------------------------------------------------------------- database
protected boolean useDatabase = true;
/**
* JTX manager.
*/
protected JtxTransactionManager jtxManager;
/**
* Returns JTX transaction manager.
*/
public JtxTransactionManager getJtxManager() {
return jtxManager;
}
/**
* Database connection provider.
*/
protected ConnectionProvider connectionProvider;
/**
* JTX annotations.
*/
protected Class extends Annotation>[] jtxAnnotations;
/**
* JTX scope pattern.
* @see AnnotationTxAdviceManager
*/
protected String jtxScopePattern;
/**
* Initializes database. First, creates connection pool.
* and transaction manager. Then, Jodds DbOomManager is
* configured. It is also configured automagically, by scanning
* the class path for entities.
*/
@SuppressWarnings("unchecked")
protected void startDb() {
if (!useDatabase) {
log.info("database is not used");
return;
}
log.info("database initialization");
// connection pool
connectionProvider = createConnectionProvider();
petite.addBean(PETITE_DBPOOL, connectionProvider);
connectionProvider.init();
checkConnectionProvider();
// transactions manager
jtxManager = createJtxTransactionManager(connectionProvider);
jtxManager.setValidateExistingTransaction(true);
AnnotationTxAdviceManager annTxAdviceManager = new AnnotationTxAdviceManager(jtxManager, jtxScopePattern);
annTxAdviceManager.registerAnnotations(jtxAnnotations);
AnnotationTxAdviceSupport.manager = annTxAdviceManager;
DbSessionProvider sessionProvider = new DbJtxSessionProvider(jtxManager);
// global settings
DbManager dbManager = DbManager.getInstance();
dbManager.setConnectionProvider(connectionProvider);
dbManager.setSessionProvider(sessionProvider);
petite.addBean(PETITE_DB, dbManager);
DbOomManager dbOomManager = DbOomManager.getInstance();
petite.addBean(PETITE_DBOOM, dbOomManager);
// automatic configuration
registerDbEntities(dbOomManager);
}
/**
* Registers DbOom entities. By default, scans the
* class path and register entities automagically.
*/
protected void registerDbEntities(DbOomManager dbOomManager) {
AutomagicDbOomConfigurator dbcfg = new AutomagicDbOomConfigurator();
appScanner.configure(dbcfg);
dbcfg.configure(dbOomManager);
}
/**
* Creates JTX transaction manager.
*/
protected JtxTransactionManager createJtxTransactionManager(ConnectionProvider connectionProvider) {
return new DbJtxTransactionManager(connectionProvider);
}
/**
* Returns ConnectionProvider
instance.
*/
protected ConnectionProvider createConnectionProvider() {
throw new UnsupportedOperationException("Please provide ConnectionProvider implementation.");
}
/**
* Checks if connection provider can return a connection.
*/
protected void checkConnectionProvider() {
Connection connection = connectionProvider.getConnection();
try {
DatabaseMetaData databaseMetaData = connection.getMetaData();
String name = databaseMetaData.getDatabaseProductName();
String version = databaseMetaData.getDatabaseProductVersion();
if (log.isInfoEnabled()) {
log.info("Connected to database: " + name + " v" + version);
}
} catch (SQLException sex) {
log.error("DB connection failed: ", sex);
} finally {
connectionProvider.closeConnection(connection);
}
}
/**
* Closes database resources at the end.
*/
protected void stopDb() {
if (!useDatabase) {
return;
}
if (log != null) {
log.info("database shutdown");
}
if (jtxManager != null) {
jtxManager.close();
}
if (connectionProvider != null) {
connectionProvider.close();
}
}
// ---------------------------------------------------------------- init
protected AppInit appInit;
/**
* Initializes business part of the application.
* Simply delegates to {@link AppInit#init()}.
*/
protected void startApp() {
appInit = petite.getBean(PETITE_INIT);
if (appInit != null) {
appInit.init();
}
}
/**
* Stops business part of the application.
* Simply delegates to {@link AppInit#stop()}.
*/
protected void stopApp() {
if (appInit != null) {
appInit.stop();
}
}
}