package org.orienteer.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IApplicationListener;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.core.request.mapper.BookmarkableMapper;
import org.apache.wicket.core.request.mapper.HomePageMapper;
import org.apache.wicket.core.request.mapper.MountedMapper;
import org.apache.wicket.datetime.DateConverter;
import org.apache.wicket.datetime.StyleDateConverter;
import org.apache.wicket.guice.GuiceInjectorHolder;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.SharedResourceReference;
import org.apache.wicket.settings.RequestCycleSettings;
import org.joda.time.DateTimeZone;
import org.orienteer.core.component.meta.WicketPropertyResolver;
import org.orienteer.core.component.visualizer.UIVisualizersRegistry;
import org.orienteer.core.hook.CalculablePropertiesHook;
import org.orienteer.core.hook.CallbackHook;
import org.orienteer.core.hook.ReferencesConsistencyHook;
import org.orienteer.core.module.*;
import org.orienteer.core.service.IOClassIntrospector;
import org.orienteer.core.tasks.console.OConsoleTasksModule;
import org.orienteer.core.web.BasePage;
import org.orienteer.core.web.HomePage;
import org.orienteer.core.web.LoginPage;
import org.orienteer.core.web.UnauthorizedPage;
import org.orienteer.core.widget.IWidgetTypesRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabase.ATTRIBUTES;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.metadata.security.OUser;
import de.agilecoders.wicket.webjars.WicketWebjars;
import de.agilecoders.wicket.webjars.settings.IWebjarsSettings;
import ru.ydn.wicket.wicketorientdb.EmbeddOrientDbApplicationListener;
import ru.ydn.wicket.wicketorientdb.IOrientDbSettings;
import ru.ydn.wicket.wicketorientdb.LazyAuthorizationRequestCycleListener;
import ru.ydn.wicket.wicketorientdb.OrientDbSettings;
import ru.ydn.wicket.wicketorientdb.OrientDbWebApplication;
import ru.ydn.wicket.wicketorientdb.OrientDbWebSession;
import ru.ydn.wicket.wicketorientdb.utils.DBClosure;
/**
* Main {@link WebApplication} for Orienteer bases applications
*/
public class OrienteerWebApplication extends OrientDbWebApplication
{
private static final Logger LOG = LoggerFactory.getLogger(OrienteerWebApplication.class);
public static final DateConverter DATE_CONVERTER = new StyleDateConverter("M-", false);
public static final DateConverter DATE_TIME_CONVERTER = new StyleDateConverter("MM", true);
private LinkedHashMap<String, IOrienteerModule> registeredModules = new LinkedHashMap<String, IOrienteerModule>();
private boolean registeredModulesSorted = false;
@Inject
private IWebjarsSettings webjarSettings;
@Inject
@Named("orientdb.embedded")
private boolean embedded;
@Inject
@Named("orienteer.authenticatelazy")
private boolean authenticateLazy;
@Inject(optional=true)
@Named("wicket.render.strategy")
private RequestCycleSettings.RenderStrategy renderStrategy;
@Inject
@Named("orienteer.image.logo")
private String imageLogoPath;
@Inject(optional=true)
public OrienteerWebApplication setConfigurationType(@Named("orienteer.production") boolean production) {
setConfigurationType(production?RuntimeConfigurationType.DEPLOYMENT:RuntimeConfigurationType.DEVELOPMENT);
return this;
}
@Inject
@Override
public void setOrientDbSettings(IOrientDbSettings orientDbSettings) {
super.setOrientDbSettings(orientDbSettings);
}
public static OrienteerWebApplication get()
{
return (OrienteerWebApplication) WebApplication.get();
}
public static OrienteerWebApplication lookupApplication()
{
return lookupApplication(OrienteerWebApplication.class);
}
@Override
public Class<? extends WebPage> getHomePage()
{
return HomePage.class;
}
@Override
protected Class<? extends OrienteerWebSession> getWebSessionClass()
{
return OrienteerWebSession.class;
}
/**
* @see org.apache.wicket.Application#init()
*/
@Override
public void init()
{
super.init();
if(embedded)
{
getApplicationListeners().add(new EmbeddOrientDbApplicationListener(OrienteerWebApplication.class.getResource("db.config.xml"))
{
@Override
public void onAfterServerStartupAndActivation(OrientDbWebApplication app)
throws Exception {
IOrientDbSettings settings = app.getOrientDbSettings();
ODatabaseDocumentTx db = new ODatabaseDocumentTx(settings.getDBUrl());
if(!db.exists()) {
db = db.create();
onDbCreated(db, settings);
}
if(db.isClosed()) db.open(settings.getAdminUserName(), settings.getAdminPassword());
db.getMetadata().load();
db.close();
}
private void onDbCreated(ODatabaseDocumentTx db, IOrientDbSettings settings) {
if(OrientDbSettings.ADMIN_DEFAULT_USERNAME.equals(settings.getAdminUserName())
&& !OrientDbSettings.ADMIN_DEFAULT_PASSWORD.equals(settings.getAdminPassword())) {
OUser admin = db.getMetadata().getSecurity().getUser(OrientDbSettings.ADMIN_DEFAULT_USERNAME);
admin.setPassword(settings.getAdminPassword());
admin.save();
}
if(OrientDbSettings.READER_DEFAULT_USERNAME.equals(settings.getGuestUserName())
&& !OrientDbSettings.READER_DEFAULT_PASSWORD.equals(settings.getGuestPassword())) {
OUser reader = db.getMetadata().getSecurity().getUser(OrientDbSettings.READER_DEFAULT_USERNAME);
reader.setPassword(settings.getGuestPassword());
reader.save();
}
}
});
}
WicketWebjars.install(this, webjarSettings);
mountPages("org.orienteer.core.web");
getResourceBundles().addCssBundle(BasePage.class, "orienteer.css", BasePage.SB_ADMIN_CSS, BasePage.ORIENTEER_CSS);
mountResource("logo.png", new SharedResourceReference(imageLogoPath));
getMarkupSettings().setStripWicketTags(true);
getResourceSettings().setThrowExceptionOnMissingResource(false);
getApplicationListeners().add(new ModuledDataInstallator());
getApplicationListeners().add(new IApplicationListener() {
@Override
public void onAfterInitialized(Application application) {
new DBClosure<Boolean>() {
@Override
protected Boolean execute(ODatabaseDocument db) {
String timeZoneId = (String) db.get(ATTRIBUTES.TIMEZONE);
TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId));
DateTimeZone.setDefault(DateTimeZone.forID(timeZoneId));
return true;
}
}.execute();
}
@Override
public void onBeforeDestroyed(Application application) {/*NOP*/}
});
getPageSettings().addComponentResolver(new WicketPropertyResolver());
//Remove default BookmarkableMapper to disallow direct accessing of pages through /wicket/bookmarkable/<class>
for(IRequestMapper mapper : getRootRequestMapperAsCompound()){
if(mapper instanceof BookmarkableMapper) {
getRootRequestMapperAsCompound().remove(mapper);
break;
}
}
registerModule(OrienteerLocalizationModule.class);
registerModule(UpdateDefaultSchemaModule.class);
registerModule(PerspectivesModule.class);
registerModule(OWidgetsModule.class);
registerModule(UserOnlineModule.class);
registerModule(TaskManagerModule.class);
registerModule(OConsoleTasksModule.class);
getOrientDbSettings().getORecordHooks().add(CalculablePropertiesHook.class);
getOrientDbSettings().getORecordHooks().add(ReferencesConsistencyHook.class);
getOrientDbSettings().getORecordHooks().add(CallbackHook.class);
mountOrientDbRestApi();
if(authenticateLazy) getRequestCycleListeners().add(new LazyAuthorizationRequestCycleListener());
registerWidgets("org.orienteer.core.component.widget");
if(renderStrategy!=null) getRequestCycleSettings().setRenderStrategy(renderStrategy);
}
@Override
protected Class<? extends WebPage> getSignInPageClass() {
return LoginPage.class;
}
public Injector getInjector()
{
return getMetaData(GuiceInjectorHolder.INJECTOR_KEY).getInjector();
}
public <T> T getServiceInstance(Class<T> serviceType)
{
return getInjector().getInstance(serviceType);
}
public ODatabaseDocument getDatabase()
{
return OrientDbWebSession.get().getDatabase();
}
public synchronized List<IOrienteerModule> getRegisteredModules() {
if(!registeredModulesSorted){
LinkedHashMap<String, IOrienteerModule> sorted = new LinkedHashMap<String, IOrienteerModule>();
LinkedHashMap<String, IOrienteerModule> unsorted = new LinkedHashMap<>(registeredModules);
Set<String> toRemove = new HashSet<>();
while(!unsorted.isEmpty()) {
for (Map.Entry<String, IOrienteerModule> entry : unsorted.entrySet()) {
Set<String> dependencies = entry.getValue().getDependencies();
if(dependencies==null || dependencies.isEmpty() || sorted.keySet().containsAll(dependencies)) {
sorted.put(entry.getKey(), entry.getValue());
toRemove.add(entry.getKey());
}
}
if(!toRemove.isEmpty()) {
for (String keyToRemove : toRemove) {
unsorted.remove(keyToRemove);
}
} else {
LOG.error("Modules without satisfied dependencies: "+unsorted.keySet());
sorted.putAll(unsorted);
break;
}
}
registeredModules = sorted;
registeredModulesSorted = true;
}
return new ArrayList<>(registeredModules.values());
}
public synchronized <M extends IOrienteerModule> M registerModule(Class<M> moduleClass)
{
M module = getServiceInstance(moduleClass);
registeredModules.put(module.getName(), getServiceInstance(moduleClass));
registeredModulesSorted = false;
return module;
}
public IOrienteerModule getModuleByName(String name)
{
return registeredModules.get(name);
}
public UIVisualizersRegistry getUIVisualizersRegistry()
{
return getServiceInstance(UIVisualizersRegistry.class);
}
public IOClassIntrospector getOClassIntrospector()
{
return getServiceInstance(IOClassIntrospector.class);
}
public void mountPages(String packageName) {
mountPages(packageName, OrienteerWebApplication.class.getClassLoader());
}
public void mountPages(String packageName, ClassLoader classLoader) {
mountOrUnmountPages(packageName, classLoader, true);
}
public void unmountPages(String packageName) {
unmountPages(packageName, OrienteerWebApplication.class.getClassLoader());
}
public void unmountPages(String packageName, ClassLoader classLoader) {
mountOrUnmountPages(packageName, classLoader, false);
}
private void mountOrUnmountPages(String packageName, ClassLoader classLoader, boolean mount) {
ClassPath classPath;
try {
classPath = ClassPath.from(classLoader);
} catch (IOException e) {
throw new WicketRuntimeException("Can't scan classpath", e);
}
for(ClassInfo classInfo : classPath.getTopLevelClassesRecursive(packageName)) {
Class<?> clazz = classInfo.load();
MountPath mountPath = clazz.getAnnotation(MountPath.class);
if(mountPath!=null) {
if(!IRequestablePage.class.isAssignableFrom(clazz))
throw new WicketRuntimeException("@"+MountPath.class.getSimpleName()+" should be only on pages");
Class<? extends IRequestablePage> pageClass = (Class<? extends IRequestablePage>) clazz;
String mainPath = mountPath.value();
String[] alt = mountPath.alt();
for(int i=alt.length-1;i>=-1;i--)
{
String path = i<0?mainPath:alt[i];
if(mount) {
if ("/".equals(path)) {
mount(new HomePageMapper(pageClass));
}
mount(new MountedMapper(path, pageClass));
} else {
unmount(path);
}
}
}
}
}
public void registerWidgets(String packageName) {
IWidgetTypesRegistry registry = getServiceInstance(IWidgetTypesRegistry.class);
registry.register(packageName);
}
public void unregisterWidgets(String packageName) {
IWidgetTypesRegistry registry = getServiceInstance(IWidgetTypesRegistry.class);
registry.unregister(packageName);
}
@Override
public void restartResponseAtSignInPage() {
//This is required because home page is dynamic and depends on assigned perspective.
if(RequestCycle.get().getRequest().getQueryParameters().getParameterValue(HomePage.FROM_HOME_PARAM).toBoolean(false)) {
throw new RestartResponseException(getSignInPageClass());
} else super.restartResponseAtSignInPage();
}
@Override
protected void onUnauthorizedPage(Component page) {
throw new RestartResponseException(UnauthorizedPage.class);
}
}