/*
* Copyright 2013-2017 Erudika. https://erudika.com
*
* 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.
*
* For issues and patches go to: https://github.com/erudika
*/
package com.erudika.para;
import com.erudika.para.cache.Cache;
import com.erudika.para.core.App;
import com.erudika.para.persistence.DAO;
import com.erudika.para.queue.Queue;
import com.erudika.para.rest.CustomResourceHandler;
import com.erudika.para.search.Search;
import com.erudika.para.utils.Config;
import com.erudika.para.utils.VersionInfo;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.util.Modules;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the main utility class and entry point.
* Dependency injection is initialized with the provided modules.
* @author Alex Bogdanovski [alex@erudika.com]
*/
public final class Para {
/**
* The ASCII logo.
*/
public static final String LOGO;
static {
boolean printVer = Config.getConfigBoolean("print_version", true);
String[] logo = {"",
" ____ ___ _ ____ ___ _ ",
" / __ \\/ __` / ___/ __` /",
" / /_/ / /_/ / / / /_/ / ",
" / .___/\\__,_/_/ \\__,_/ " + (printVer ? "v" + getVersion() : ""),
" /_/ ", ""
};
StringBuilder sb = new StringBuilder();
for (String line : logo) {
sb.append(line).append("\n");
}
LOGO = sb.toString();
}
private static final Logger logger = LoggerFactory.getLogger(Para.class);
private static final List<DestroyListener> DESTROY_LISTENERS = new ArrayList<DestroyListener>();
private static final List<InitializeListener> INIT_LISTENERS = new ArrayList<InitializeListener>();
private static final List<IOListener> IO_LISTENERS = new ArrayList<IOListener>();
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(Config.EXECUTOR_THREADS);
private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(Config.EXECUTOR_THREADS);
private static Injector injector;
private static ClassLoader paraClassLoader;
/**
* No-args constructor.
*/
private Para() { }
/**
* Initializes the Para core modules and allows the user to override them. Call this method first.
*
* @param modules a list of modules that override the main modules
*/
public static void initialize(Module... modules) {
if (injector == null) {
printLogo();
try {
logger.info("--- Para.initialize() [{}] ---", Config.ENVIRONMENT);
Stage stage = Config.IN_PRODUCTION ? Stage.PRODUCTION : Stage.DEVELOPMENT;
List<Module> coreModules = Arrays.asList(modules);
List<Module> externalModules = getExternalModules();
if (coreModules.isEmpty() && externalModules.isEmpty()) {
logger.warn("No implementing modules found. Aborting...");
destroy();
return;
}
if (!externalModules.isEmpty()) {
injector = Guice.createInjector(stage, Modules.override(coreModules).with(externalModules));
} else {
injector = Guice.createInjector(stage, coreModules);
}
for (InitializeListener initListener : INIT_LISTENERS) {
if (initListener != null) {
injectInto(initListener);
initListener.onInitialize();
logger.debug("Executed {}.onInitialize().", initListener.getClass().getName());
}
}
// this enables the "River" feature - polls the deault queue for objects and imports them into Para
if (Config.getConfigBoolean("queue_link_enabled", false)) {
injector.getInstance(Queue.class).startPolling();
}
logger.info("Instance #{} initialized.", Config.WORKER_ID);
} catch (Exception e) {
logger.error(null, e);
}
}
}
/**
* Calls all registered listeners on exit. Call this method last.
*/
public static void destroy() {
try {
if (injector != null) {
logger.info("--- Para.destroy() ---");
for (DestroyListener destroyListener : DESTROY_LISTENERS) {
if (destroyListener != null) {
injectInto(destroyListener);
destroyListener.onDestroy();
logger.debug("Executed {}.onDestroy().", destroyListener.getClass().getName());
}
}
injector = null;
}
if (!EXECUTOR.isShutdown()) {
EXECUTOR.shutdown();
EXECUTOR.awaitTermination(60, TimeUnit.SECONDS);
}
if (!SCHEDULER.isShutdown()) {
SCHEDULER.shutdown();
SCHEDULER.awaitTermination(60, TimeUnit.SECONDS);
}
} catch (Exception e) {
logger.error(null, e);
}
}
/**
* Inject dependencies into a given object.
*
* @param obj the object we inject into
*/
public static void injectInto(Object obj) {
if (obj == null) {
return;
}
if (injector == null) {
handleNotInitializedError();
}
injector.injectMembers(obj);
}
/**
* Return an instance of some class if it has been wired through DI.
* @param <T> any type
* @param type any type
* @return an object
*/
public static <T> T getInstance(Class<T> type) {
if (injector == null) {
handleNotInitializedError();
}
return injector.getInstance(type);
}
/**
* @return an instance of the core persistence class.
* @see DAO
*/
public static DAO getDAO() {
return getInstance(DAO.class);
}
/**
* @return an instance of the core search class.
* @see Search
*/
public static Search getSearch() {
return getInstance(Search.class);
}
/**
* @return an instance of the core cache class.
* @see Cache
*/
public static Cache getCache() {
return getInstance(Cache.class);
}
/**
* Registers a new initialization listener.
*
* @param il the listener
*/
public static void addInitListener(InitializeListener il) {
if (il != null) {
INIT_LISTENERS.add(il);
}
}
/**
* Registers a new destruction listener.
*
* @param dl the listener
*/
public static void addDestroyListener(DestroyListener dl) {
if (dl != null) {
DESTROY_LISTENERS.add(dl);
}
}
/**
* Registers a new Para I/O listener.
*
* @param iol the listener
*/
public static void addIOListener(IOListener iol) {
if (iol != null) {
IO_LISTENERS.add(iol);
}
}
/**
* Returns a list of I/O listeners (callbacks).
* @return the list of registered listeners
*/
public static List<IOListener> getIOListeners() {
return IO_LISTENERS;
}
/**
* Returns the Para executor service.
* @return a fixed thread executor service
*/
public static ExecutorService getExecutorService() {
return EXECUTOR;
}
/**
* Returns the Para scheduled executor service.
* @return a scheduled executor service
*/
public static ScheduledExecutorService getScheduledExecutorService() {
return SCHEDULER;
}
/**
* Executes a {@link java.lang.Runnable} asynchronously.
* @param runnable a task
*/
public static void asyncExecute(Runnable runnable) {
if (runnable != null) {
try {
Para.getExecutorService().execute(runnable);
} catch (RejectedExecutionException ex) {
logger.warn(ex.getMessage());
try {
runnable.run();
} catch (Exception e) {
logger.error(null, e);
}
}
}
}
/**
* Executes a {@link java.lang.Runnable} at a fixed interval, asynchronously.
* @param task a task
* @param delay run after
* @param interval run at this interval of time
* @param t time unit
* @return a Future
*/
public static ScheduledFuture<?> asyncExecutePeriodically(Runnable task, long delay, long interval, TimeUnit t) {
if (task != null) {
try {
return Para.getScheduledExecutorService().scheduleAtFixedRate(task, delay, interval, t);
} catch (RejectedExecutionException ex) {
logger.warn(ex.getMessage());
}
}
return null;
}
/**
* Try loading external {@link com.erudika.para.rest.CustomResourceHandler} classes.
* These will handle custom API requests.
* via {@link java.util.ServiceLoader#load(java.lang.Class)}.
* @return a loaded list of ServletContextListener class.
*/
public static List<CustomResourceHandler> getCustomResourceHandlers() {
ServiceLoader<CustomResourceHandler> loader = ServiceLoader.
load(CustomResourceHandler.class, Para.getParaClassLoader());
List<CustomResourceHandler> externalResources = new ArrayList<CustomResourceHandler>();
for (CustomResourceHandler handler : loader) {
if (handler != null) {
injectInto(handler);
externalResources.add(handler);
}
}
return externalResources;
}
private static List<Module> getExternalModules() {
ServiceLoader<Module> moduleLoader = ServiceLoader.load(Module.class, Para.getParaClassLoader());
List<Module> externalModules = new ArrayList<Module>();
for (Module module : moduleLoader) {
externalModules.add(module);
}
return externalModules;
}
/**
* Returns the {@link URLClassLoader} classloader for Para.
* Used for loading JAR files from 'lib/*.jar'.
* @return a classloader
*/
public static ClassLoader getParaClassLoader() {
if (paraClassLoader == null) {
try {
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
List<URL> jars = new ArrayList<URL>();
File lib = new File(Config.getConfigParam("plugin_folder", "lib/"));
if (lib.exists() && lib.isDirectory()) {
for (File file : FileUtils.listFiles(lib, new String[]{"jar"}, false)) {
jars.add(file.toURI().toURL());
}
}
paraClassLoader = new URLClassLoader(jars.toArray(new URL[0]), currentClassLoader);
// Thread.currentThread().setContextClassLoader(paraClassLoader);
} catch (Exception e) {
logger.error(null, e);
}
}
return paraClassLoader;
}
private static void handleNotInitializedError() {
throw new IllegalStateException("Call Para.initialize() first!");
}
/**
* Creates the root application and returns the credentials for it.
* @return credentials for the root app
*/
public static Map<String, String> setup() {
return setup(Config.APP_NAME_NS, Config.APP_NAME, false);
}
/**
* Creates a new application and returns the credentials for it.
* @param appid the app identifier
* @param name the full name of the app
* @param shared false if the app should have its own index
* @return credentials for the root app
*/
public static Map<String, String> setup(String appid, String name, boolean shared) {
Map<String, String> creds = new TreeMap<String, String>();
creds.put("message", "All set!");
if (StringUtils.isBlank(appid)) {
return creds;
}
App app = new App(appid);
if (!app.exists()) {
app.setName(name);
app.setSharingIndex(shared);
app.setActive(true);
app.create();
logger.info("Created new {}app '{}'. Make sure to create a table and index for it.",
shared ? "'shared' " : "", app.getAppIdentifier());
creds.putAll(app.getCredentials());
creds.put("message", "Save the secret key - it is shown only once!");
}
return creds;
}
/**
* Prints the Para logo to System.out.
*/
public static void printLogo() {
if (Config.getConfigBoolean("print_logo", true)) {
System.out.print(LOGO);
}
}
/**
* The current version of Para.
* @return version string, from pom.xml
*/
public static String getVersion() {
return VersionInfo.getVersion();
}
}