/*******************************************************************************
* Copyright (c) 2015-2016 MEDEVIT <office@medevit.at>.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* MEDEVIT <office@medevit.at> - initial API and implementation
******************************************************************************/
package ch.elexis.core.data.activator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import org.eclipse.equinox.internal.app.CommandLineArgs;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.elexis.Desk;
import ch.elexis.admin.AbstractAccessControl;
import ch.elexis.admin.RoleBasedAccessControl;
import ch.elexis.core.constants.Preferences;
import ch.elexis.core.constants.StringConstants;
import ch.elexis.core.data.constants.ElexisSystemPropertyConstants;
import ch.elexis.core.data.events.ElexisEvent;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.data.events.Heartbeat;
import ch.elexis.core.data.events.Heartbeat.HeartListener;
import ch.elexis.core.data.events.PatientEventListener;
import ch.elexis.core.data.interfaces.ShutdownJob;
import ch.elexis.core.data.interfaces.events.MessageEvent;
import ch.elexis.core.data.interfaces.scripting.Interpreter;
import ch.elexis.core.data.lock.LocalLockService;
import ch.elexis.core.data.preferences.CorePreferenceInitializer;
import ch.elexis.core.data.server.ElexisServerEventService;
import ch.elexis.core.data.service.OrderService;
import ch.elexis.core.data.service.StockCommissioningSystemService;
import ch.elexis.core.data.service.StockService;
import ch.elexis.core.lock.ILocalLockService;
import ch.elexis.core.services.IOrderService;
import ch.elexis.core.services.IStockCommissioningSystemService;
import ch.elexis.data.Anwender;
import ch.elexis.data.Kontakt;
import ch.elexis.data.Mandant;
import ch.elexis.data.PersistentObject;
import ch.elexis.data.PersistentObjectFactory;
import ch.elexis.data.Query;
import ch.elexis.data.User;
import ch.rgw.io.LockFile;
import ch.rgw.io.Settings;
import ch.rgw.io.SqlSettings;
import ch.rgw.io.SysSettings;
import ch.rgw.tools.Log;
import ch.rgw.tools.StringTool;
import ch.rgw.tools.VersionInfo;
/**
* @since 3.0.0
*/
public class CoreHub implements BundleActivator {
public static final String PLUGIN_ID = "ch.elexis.core.data";
/*
* This version is needed to compare the DB
*/
public static String Version = "3.2.0.qualifier"; //$NON-NLS-1$
public static final String APPLICATION_NAME = "Elexis Core"; //$NON-NLS-1$
static final String neededJRE = "1.8.0"; //$NON-NLS-1$
public static final String DBVersion = "3.2.7"; //$NON-NLS-1$
protected static Logger log = LoggerFactory.getLogger(CoreHub.class.getName());
private static String LocalCfgFile = null;
private BundleContext context;
/** Das Singleton-Objekt dieser Klasse */
public static CoreHub plugin;
private static List<ShutdownJob> shutdownJobs = new LinkedList<ShutdownJob>();
/** Factory für interne PersistentObjects */
public static final PersistentObjectFactory poFactory = new PersistentObjectFactory();
/** Heartbeat */
public static Heartbeat heart;
/**
* Beschreibbares Verzeichnis für userspezifische Konfigurationsdaten etc. Achtung: "User" meint
* hier: den eingeloggten Betriebssystem-User, nicht den Elexis-User. In Windows wird userDir
* meist %USERPROFILE%\elexis sein, in Linux ~./elexis. Es kann mit getWritableUserDir() geholt
* werden.
*/
static File userDir;
/** Globale Einstellungen (Werden in der Datenbank gespeichert) */
public static Settings globalCfg;
/** Lokale Einstellungen (Werden in der Registry bzw. ~/.java gespeichert) */
public static Settings localCfg;
/** Anwenderspezifische Einstellungen (Werden in der Datenbank gespeichert) */
public static Settings userCfg;
/** Mandantspezifische EInstellungen (Werden in der Datenbank gespeichert) */
public static Settings mandantCfg;
public static Anwender actUser; // TODO set
/**
* @deprecated please use {@link ElexisEventDispatcher#getSelectedMandator()} to retrieve
* current mandator
*/
public static Mandant actMandant;
private static boolean tooManyInstances;
/** Der Initialisierer für die Voreinstellungen */
public static final CorePreferenceInitializer pin = new CorePreferenceInitializer();
/** Die zentrale Zugriffskontrolle */
public static final AbstractAccessControl acl = new RoleBasedAccessControl();
/** Lock Service **/
private static ILocalLockService localLockService;
/** Stock Service **/
private static final StockService stockService = new StockService();
/** Order Service **/
private static final IOrderService orderService = new OrderService();
/** Event Service to transport Events to the Elexis Server */
private static ElexisServerEventService elexisServerEventService;
/**
* Stock commissioning system service
*/
private static final IStockCommissioningSystemService stockCommissioningSystemService =
new StockCommissioningSystemService();
/**
* The listener for patient events
*/
private final PatientEventListener eeli_pat = new PatientEventListener();
public static boolean isTooManyInstances(){
return tooManyInstances;
}
/**
* get the base directory of this currently running elexis application
*
* @return the topmost directory of this application or null if this information could not be
* retrieved
*/
public static String getBasePath(){
return FrameworkUtil.getBundle(CoreHub.class).getEntry("/").toString();
}
/**
* Return a directory suitable for temporary files. Most probably this will be a default tempdir
* provided by the os. If none such exists, it will be the user dir.
*
* @return always a valid and writable directory.
*/
public static File getTempDir(){
File ret = null;
String temp = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
if (!StringTool.isNothing(temp)) {
ret = new File(temp);
if (ret.exists() && ret.isDirectory()) {
return ret;
} else {
if (ret.mkdirs()) {
return ret;
}
}
}
return getWritableUserDir();
}
/**
* return a directory suitable for plugin specific configuration data. If no such dir exists, it
* will be created. If it could not be created, the application will refuse to start.
*
* @return a directory that exists always and is always writable and readable for plugins of the
* currently running elexis instance. Caution: this directory is not necessarily shared
* among different OS-Users. In Windows it is normally %USERPROFILE%\elexis, in Linux
* ~./elexis
*/
public static File getWritableUserDir(){
if (userDir == null) {
String userhome = null;
if (localCfg != null) {
userhome = localCfg.get("elexis-userDir", null); //$NON-NLS-1$
}
if (userhome == null) {
userhome = System.getProperty("user.home"); //$NON-NLS-1$
}
if (StringTool.isNothing(userhome)) {
userhome = System.getProperty("java.io.tempdir"); //$NON-NLS-1$
}
userDir = new File(userhome, "elexis"); //$NON-NLS-1$
}
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
System.err.print("fatal: could not create Userdir"); //$NON-NLS-1$
MessageEvent.fireLoggedError("Panic exit",
"could not create userdir " + userDir.getAbsolutePath());
System.exit(-5);
}
}
return userDir;
}
@Override
public void start(BundleContext context) throws Exception{
this.context = context;
log.debug("Starting " + CoreHub.class.getName());
plugin = this;
localLockService = new LocalLockService();
elexisServerEventService = new ElexisServerEventService();
startUpBundle();
setUserDir(userDir);
heart = Heartbeat.getInstance();
ElexisEventDispatcher.getInstance().addListeners(eeli_pat);
// add core ClassLoader to default Script Interpreter
Interpreter.classLoaders.add(CoreHub.class.getClassLoader());
if (!ElexisSystemPropertyConstants.RUN_MODE_FROM_SCRATCH
.equals(System.getProperty(ElexisSystemPropertyConstants.RUN_MODE)))
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run(){
SysSettings localCfg = (SysSettings) CoreHub.localCfg;
localCfg.write_xml(LocalCfgFile);
}
});
}
/*
* We use maven resources filtering replace in rsc/version.properties the property
* ${pom.version} by the current build version and place it into the file /version.properties of
* each jar file.
*
* See http://maven.apache.org /plugins/maven-resources-plugin/examples/filter.html
*/
public static String readElexisBuildVersion(){
Properties prop = new Properties();
String elexis_version = "Developer";
String url_name = "platform:/plugin/ch.elexis.core.data/version.properties";
try {
URL url;
url = new URL(url_name);
InputStream inputStream = url.openConnection().getInputStream();
if (inputStream != null) {
prop.load(inputStream);
elexis_version = prop.getProperty("elexis.version");
}
} catch (IOException e) {
log.warn("Error reading build version information from " + url_name);
}
return elexis_version.replace("-SNAPSHOT", "");
}
@Override
public void stop(BundleContext context) throws Exception{
log.debug("Stopping " + CoreHub.class.getName());
getLocalLockService().releaseAllLocks();
getLocalLockService().shutdown();
CoreHub.logoffAnwender();
PersistentObject.disconnect();
ElexisEventDispatcher.getInstance().removeListeners(eeli_pat);
ElexisEventDispatcher.getInstance().dump();
globalCfg = null;
heart.stop();
plugin = null;
this.context = null;
}
private void startUpBundle(){
String[] args = CommandLineArgs.getApplicationArgs();
String config = "default"; //$NON-NLS-1$
for (String s : args) {
if (s.startsWith("--use-config=")) { //$NON-NLS-1$
String[] c = s.split("="); //$NON-NLS-1$
config = c[1];
}
}
if (ElexisSystemPropertyConstants.RUN_MODE_FROM_SCRATCH
.equals(System.getProperty(ElexisSystemPropertyConstants.RUN_MODE))) {
config = UUID.randomUUID().toString();
}
loadLocalCfg(config);
// Damit Anfragen auf userCfg und mandantCfg bei nicht eingeloggtem User
// keine NPE werfen
userCfg = localCfg;
mandantCfg = localCfg;
// Java Version prüfen
VersionInfo vI = new VersionInfo(System.getProperty("java.version", "0.0.0")); //$NON-NLS-1$ //$NON-NLS-2$
log.info(getId() + "; Java: " + vI.version() + "\nencoding: "
+ System.getProperty("file.encoding"));
if (vI.isOlder(neededJRE)) {
MessageEvent.fireLoggedError("Invalid Java version",
"Your Java version is older than " + neededJRE + ", please update.");
}
log.info("Basepath: " + getBasePath());
pin.initializeDefaultPreferences();
heart = Heartbeat.getInstance();
initializeLock();
}
private static void initializeLock(){
final int timeoutSeconds = 600;
try {
final LockFile lockfile = new LockFile(userDir, "elexislock", 4, timeoutSeconds); //$NON-NLS-1$
final int n = lockfile.lock();
if (n == 0) {
MessageEvent.fireLoggedError("Too many instances",
"Too many concurrent instances of Elexis running. Check elexislock files in "
+ userDir);
tooManyInstances = true;
} else {
tooManyInstances = false;
HeartListener lockListener = new HeartListener() {
long timeSet;
public void heartbeat(){
long now = System.currentTimeMillis();
if ((now - timeSet) > timeoutSeconds) {
lockfile.updateLock(n);
timeSet = now;
}
}
};
heart.addListener(lockListener, Heartbeat.FREQUENCY_LOW);
}
} catch (IOException ex) {
log.error("Can not aquire lock file in " + userDir + "; " + ex.getMessage()); //$NON-NLS-1$
}
}
public static String getId(){
StringBuilder sb = new StringBuilder();
sb.append(APPLICATION_NAME).append(" v.").append(Version).append("\n")
.append(CoreHubHelper.getRevision(true, plugin)).append("\n")
.append(System.getProperty("os.name")).append(StringConstants.SLASH)
.append(System.getProperty("os.version")).append(StringConstants.SLASH)
.append(System.getProperty("os.arch")); //$NON-NLS-1$
return sb.toString();
}
private void loadLocalCfg(String branch){
LocalCfgFile = CoreHubHelper.getWritableUserDir() + "/localCfg_" + branch + ".xml";
String msg = "loadLocalCfg: Loading branch " + branch + " from " + LocalCfgFile;
System.out.println(msg);
log.debug(msg);
SysSettings cfg = new SysSettings(SysSettings.USER_SETTINGS, Desk.class);
cfg.read_xml(LocalCfgFile);
CoreHub.localCfg = cfg;
}
public static void setMandant(Mandant newMandant){
if (actMandant != null && mandantCfg != null) {
mandantCfg.flush();
}
if (newMandant == null) {
mandantCfg = userCfg;
} else {
mandantCfg = getUserSetting(newMandant);
}
actMandant = newMandant;
ElexisEventDispatcher.getInstance()
.fire(new ElexisEvent(newMandant, Mandant.class, ElexisEvent.EVENT_MANDATOR_CHANGED));
}
public static Settings getUserSetting(Kontakt user){
if (StringConstants.ONE.equals(user.get(Kontakt.FLD_IS_USER))) {
Settings settings = new SqlSettings(PersistentObject.getConnection(), "USERCONFIG",
"Param", "Value", "UserID=" + user.getWrappedId());
return settings;
}
return null;
}
public Bundle getBundle(){
return context.getBundle();
}
/**
* get a list of all mandators known to this system
*/
public static List<Mandant> getMandantenList(){
Query<Mandant> qbe = new Query<Mandant>(Mandant.class);
return qbe.execute();
}
/**
* get a list of all users known to this system
*/
public static List<Anwender> getUserList(){
Query<Anwender> qbe = new Query<Anwender>(Anwender.class);
return qbe.execute();
}
/**
* Return the name of a config instance, the user chose. This is just the valuie of the
* -Dconfig=xx runtime value or "default" if no -Dconfig was set
*/
public static String getCfgVariant(){
String config = System.getProperty("config");
return config == null ? "default" : config;
}
public void setUserDir(File dir){
userDir = dir;
localCfg.set("elexis-userDir", dir.getAbsolutePath()); //$NON-NLS-1$
}
/**
* Add a ShutdownJob to the list of jobs that has to be done after the Elexis workbench was shut
* down.
*
* @param job
*/
public static void addShutdownJob(final ShutdownJob job){
if (!shutdownJobs.contains(job)) {
shutdownJobs.add(job);
}
}
public static int getSystemLogLevel(){
return localCfg.get(Preferences.ABL_LOGLEVEL, Log.ERRORS);
}
/**
* Perform the required tasks to log off the current {@link Anwender}
*
* @since 3.1 moved from {@link Anwender} class
*/
public static void logoffAnwender(){
if (CoreHub.actUser == null)
return;
if (CoreHub.userCfg != null) {
CoreHub.userCfg.flush();
}
getLocalLockService().releaseAllLocks();
CoreHub.setMandant(null);
CoreHub.heart.suspend();
CoreHub.actUser = null;
ElexisEventDispatcher.getInstance()
.fire(new ElexisEvent(null, Anwender.class, ElexisEvent.EVENT_USER_CHANGED));
ElexisEventDispatcher.getInstance()
.fire(new ElexisEvent(null, User.class, ElexisEvent.EVENT_DESELECTED));
CoreHub.userCfg = CoreHub.localCfg;
}
public static ILocalLockService getLocalLockService(){
return localLockService;
}
public static StockService getStockService(){
return stockService;
}
public static IStockCommissioningSystemService getStockCommissioningSystemService(){
return stockCommissioningSystemService;
}
public static IOrderService getOrderService(){
return orderService;
}
public static ElexisServerEventService getElexisServerEventService(){
return elexisServerEventService;
}
}