/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package ro.nextreports.server.web;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.protocol.HTTP;
import org.apache.wicket.Page;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.Session;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.authorization.strategies.page.SimplePageAuthorizationStrategy;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.core.request.mapper.StalePageException;
import org.apache.wicket.devutils.DevUtilsPage;
import org.apache.wicket.markup.html.pages.RedirectPage;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.apache.wicket.util.encoding.UrlEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.support.WebApplicationContextUtils;
import ro.nextreports.server.ReleaseInfo;
import ro.nextreports.server.StorageConstants;
import ro.nextreports.server.dao.StorageDao;
import ro.nextreports.server.domain.Entity;
import ro.nextreports.server.domain.IFrameSettings;
import ro.nextreports.server.domain.SchedulerJob;
import ro.nextreports.server.domain.Settings;
import ro.nextreports.server.domain.User;
import ro.nextreports.server.exception.MaintenanceException;
import ro.nextreports.server.schedule.QuartzJobHandler;
import ro.nextreports.server.schedule.UserSynchronizerJob;
import ro.nextreports.server.service.StorageService;
import ro.nextreports.server.web.common.misc.NoVersionMountMapper;
import ro.nextreports.server.web.core.ErrorPage;
import ro.nextreports.server.web.core.HomePage;
import ro.nextreports.server.web.core.MaintenancePage;
import ro.nextreports.server.web.core.SecurePage;
import ro.nextreports.server.web.core.settings.LogoResourceReference;
import ro.nextreports.server.web.dashboard.WidgetWebPage;
import ro.nextreports.server.web.debug.Info;
import ro.nextreports.server.web.debug.InfoUtil;
import ro.nextreports.server.web.debug.SystemInfoPage;
import ro.nextreports.server.web.debug.SystemLogPage;
import ro.nextreports.server.web.integration.DashboardWebPage;
import ro.nextreports.server.web.integration.DashboardsPage;
import ro.nextreports.server.web.integration.ReportsPage;
import ro.nextreports.server.web.security.LoginPage;
import ro.nextreports.server.web.security.SecurityUtil;
import ro.nextreports.server.web.security.cas.CasLoginErrorPage;
import ro.nextreports.server.web.security.cas.CasLoginPage;
import ro.nextreports.server.web.security.cas.CasUtil;
import ro.nextreports.server.web.security.recover.ForgotPasswordPage;
import ro.nextreports.server.web.security.recover.ResetPasswordPage;
import ro.nextreports.server.web.themes.ThemesManager;
/**
* @author Decebal Suiu
*/
public class NextServerApplication extends WebApplication {
public final static String NEXT_CHARTS_JS = "nextcharts-1.5.min.js";
private static volatile boolean maintenance = false;
private static final Logger LOG = LoggerFactory.getLogger(NextServerApplication.class);
public NextServerApplication() {
super();
}
public static NextServerApplication get() {
return (NextServerApplication) WebApplication.get();
}
@Override
public void init() {
super.init();
// log system info
logSystemInfo();
// spring
addSpringInjection();
// markup settings
getMarkupSettings().setStripWicketTags(true);
getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
// application settings
if (CasUtil.isCasUsed()) {
getApplicationSettings().setPageExpiredErrorPage(CasLoginPage.class);
// getApplicationSettings().setInternalErrorPage(CasLoginErrorPage.class);
getApplicationSettings().setAccessDeniedPage(CasLoginPage.class);
} else {
getApplicationSettings().setPageExpiredErrorPage(LoginPage.class);
// getApplicationSettings().setInternalErrorPage(LoginErrorPage.class);
getApplicationSettings().setAccessDeniedPage(LoginPage.class);
}
// show internal error page rather than default developer page
// getExceptionSettings().setUnexpectedExceptionDisplay(IExceptionSettings.SHOW_INTERNAL_ERROR_PAGE);
// exception settings
getResourceSettings().setThrowExceptionOnMissingResource(false);
// security settings
addSecurityAuthorization();
// request cycle settings
// getRequestCycleSettings().addResponseFilter(new
// ServerAndClientTimeFilter());
// remove this so meta content is first in head title
// getRequestCycleSettings().addResponseFilter(new
// AjaxServerAndClientTimeFilter());
// debug
// getDebugSettings().setAjaxDebugModeEnabled(false);
getDebugSettings().setDevelopmentUtilitiesEnabled(true);
// activate some options only in "DEVELOPMENT" mode
if (usesDevelopmentConfig()) {
// enable request logger
getRequestLoggerSettings().setRequestLoggerEnabled(true);
getRequestLoggerSettings().setRequestsWindowSize(3000);
// locate where wicket markup comes from your browser's source view
getDebugSettings().setOutputMarkupContainerClassName(true);
}
// mount
// new
// AnnotatedMountScanner().scanPackage(NextServerApplication.class.getPackage().getName()).mount(this);
mount(new NoVersionMountMapper("/home", HomePage.class));
if (CasUtil.isCasUsed()) {
// mountPage("/login", CasLoginPage.class);
mount(new NoVersionMountMapper("/login", CasLoginPage.class));
// this matches the value set in securityCas.xml
// mountPage("/cas/error", CasLoginErrorPage.class);
mount(new NoVersionMountMapper("/cas/error", CasLoginErrorPage.class));
} else {
// mountPage("/login", LoginPage.class);
mount(new NoVersionMountMapper("/login", LoginPage.class));
}
mountPage("/debug", DevUtilsPage.class);
mountPage("/sysinfo", SystemInfoPage.class);
mountPage("/syslog", SystemLogPage.class);
// mountPage("/addFolders", AddFoldersPage.class); // for development
// mountPage("/pivot", PivotPage.class); // for development
mountPage("/forgot", ForgotPasswordPage.class);
mountPage("/reset", ResetPasswordPage.class);
mountPage("/dashboards", DashboardsPage.class);
mountPage("/reports", ReportsPage.class);
// load all jobs from repository to scheduler
addJobsInScheduler();
StorageService storageService = (StorageService) getSpringBean("storageService");
if (storageService.getSettings() != null) {
if (storageService.getSettings().getSynchronizer().isRunOnStartup()) {
runUserSynchronizerJob();
}
IFrameSettings iframeSettings = storageService.getSettings().getIframe();
if ((iframeSettings != null) && iframeSettings.isEnable()) {
mount(new NoVersionMountMapper("/widget", WidgetWebPage.class));
mount(new NoVersionMountMapper("/dashboard", DashboardWebPage.class));
}
// set the current color theme at startup
ThemesManager.getInstance().setTheme(storageService.getSettings().getColorTheme());
// need to have a static url to view logo in maintenance page
mountResource("/../themes/" + storageService.getSettings().getColorTheme() + "/images/Nextreports-logo.png",
new LogoResourceReference());
}
getRequestCycleListeners().add(new ExceptionRequestCycleListener());
getRequestCycleListeners().add(new LoggingRequestCycleListener());
getRequestCycleListeners().add(new MaintenanceRequestCycleListener());
logSettings(storageService.getSettings());
LOG.info("NextReports Server " + ReleaseInfo.getVersion() + " started.");
}
@Override
public Class<? extends Page> getHomePage() {
return HomePage.class;
}
@Override
public Session newSession(Request request, Response response) {
return new NextServerSession(request);
}
public Object getSpringBean(String beanName) {
ApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
if (!applicationContext.containsBean(beanName)) {
return null;
}
return applicationContext.getBean(beanName);
}
protected void addSpringInjection() {
getComponentInstantiationListeners().add(new SpringComponentInjector(this));
}
protected void addSecurityAuthorization() {
Class<? extends Page> signInPageClass = LoginPage.class;
if (CasUtil.isCasUsed()) {
signInPageClass = CasLoginPage.class;
}
IAuthorizationStrategy authStrategy = new SimplePageAuthorizationStrategy(SecurePage.class, signInPageClass) {
@Override
protected boolean isAuthorized() {
boolean b = NextServerSession.get().isSignedIn();
if (!b) {
if (CasUtil.isCasUsed()) {
LOG.debug("Checking if context contains CAS authentication");
b = NextServerSession.get().checkForSignIn();
if (!b) {
String serviceUrl = CasUtil.getServiceProperties().getService();
String loginUrl = CasUtil.getLoginUrl();
LOG.debug("cas authentication: service URL: " + serviceUrl);
String redirectUrl = loginUrl + "?service=" + serviceUrl;
LOG.debug("attempting to redirect to: " + redirectUrl);
throw new RestartResponseAtInterceptPageException(new RedirectPage(redirectUrl));
}
}
}
return b;
}
};
getSecuritySettings().setAuthorizationStrategy(authStrategy);
}
protected void addJobsInScheduler() {
if (LOG.isDebugEnabled()) {
LOG.debug("Add jobs in scheduler...");
}
long t = System.currentTimeMillis();
SchedulerJob[] schedulerJobs = getSchedulerJobs();
Object o = getSpringBean("quartzJobHandler");
if (o != null) {
QuartzJobHandler quartzJobHandler = (QuartzJobHandler) o;
if (schedulerJobs != null) {
for (SchedulerJob schedulerJob : schedulerJobs) {
try {
quartzJobHandler.addJob(schedulerJob);
} catch (Exception e) {
// TODO
e.printStackTrace();
LOG.error(e.getMessage(), e);
}
}
}
}
if (LOG.isDebugEnabled()) {
t = System.currentTimeMillis() - t;
LOG.debug("Added jobs in scheduler in " + t + " ms");
}
}
private SchedulerJob[] getSchedulerJobs() {
PlatformTransactionManager transactionManager = (PlatformTransactionManager) getSpringBean(
"transactionManager");
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
SchedulerJob[] schedulerJobs = transactionTemplate.execute(new TransactionCallback<SchedulerJob[]>() {
public SchedulerJob[] doInTransaction(TransactionStatus transactionStatus) {
StorageDao storageDao = (StorageDao) getSpringBean("storageDao");
try {
Entity[] entities = storageDao.getEntitiesByClassName(StorageConstants.SCHEDULER_ROOT,
SchedulerJob.class.getName());
SchedulerJob[] schedulerJobs = new SchedulerJob[entities.length];
System.arraycopy(entities, 0, schedulerJobs, 0, entities.length);
return schedulerJobs;
} catch (Exception e) {
// TODO
e.printStackTrace();
transactionStatus.setRollbackOnly();
return null;
}
}
});
return schedulerJobs;
}
private void runUserSynchronizerJob() {
if (LOG.isDebugEnabled()) {
LOG.debug("Run user synchronizer job ...");
}
long t = System.currentTimeMillis();
// JobDetail userSynchronizerJob = (JobDetail)
// getSpringBean("userSynchronizerJob");
ProviderManager authenticationManager = (ProviderManager) getSpringBean("authenticationManager");
UserSynchronizerJob userSynchronizerJob = new UserSynchronizerJob();
userSynchronizerJob.setAuthenticationManager(authenticationManager);
userSynchronizerJob.setStorageService((StorageService) getSpringBean("storageService"));
userSynchronizerJob.syncUsers();
if (LOG.isDebugEnabled()) {
t = System.currentTimeMillis() - t;
LOG.debug("Users synchronized in " + t + " ms");
}
}
public static boolean isMaintenance() {
return maintenance;
}
public static void setMaintenance(boolean maintenance) {
NextServerApplication.maintenance = maintenance;
}
private void logSystemInfo() {
LOG.info("############ S Y S T E M P R O P E R T I E S ############");
List<String> names = InfoUtil.getSystemProperties();
for (String name : names) {
LOG.info(String.format("%-40s", name) + " : " + System.getProperty(name));
}
LOG.info("############ J V M A R G U M E N T S ############");
List<String> arguments = InfoUtil.getJVMArguments();
for (String argument : arguments) {
LOG.info(argument);
}
LOG.info("############ G E N E R A L J V M I N F O ############");
List<Info> infos = InfoUtil.getGeneralJVMInfo();
for (Info info : infos) {
LOG.info(String.format("%-20s", info.getDisplayName()) + " : " + info.getValue());
}
LOG.info("############ E N D S Y S T E M I N F O ############");
}
private void logSettings(Settings settings) {
LOG.info("############ S E R V E R S E T T I N G S ############");
List<Info> infos = InfoUtil.getServerSettings(settings);
for (Info info : infos) {
LOG.info(String.format("%-40s", info.getDisplayName()) + " : " + info.getValue());
}
LOG.info("############ E N D S E R V E R S E T T I N G S ############");
}
private class ExceptionRequestCycleListener extends AbstractRequestCycleListener {
@Override
public IRequestHandler onException(RequestCycle cycle, Exception e) {
if (e instanceof PageExpiredException) {
LOG.error("Page expired", e); // !?
return null; // see
// getApplicationSettings().setPageExpiredErrorPage
}
if (e instanceof MaintenanceException) {
return new RenderPageRequestHandler(new PageProvider(MaintenancePage.class));
}
if (e instanceof StalePageException) {
return null;
}
String errorCode = String.valueOf(System.currentTimeMillis());
LOG.error("Error with code " + errorCode, e);
PageParameters parameters = new PageParameters();
parameters.add("errorCode", errorCode);
parameters.add("errorMessage", UrlEncoder.QUERY_INSTANCE.encode(e.getMessage(), HTTP.ISO_8859_1));
return new RenderPageRequestHandler(new PageProvider(ErrorPage.class, parameters));
}
}
private class LoggingRequestCycleListener extends AbstractRequestCycleListener {
@Override
public void onBeginRequest(RequestCycle cycle) {
String username = "";
if (NextServerSession.get().isSignedIn()) {
username = NextServerSession.get().getUsername();
}
Session session = NextServerSession.get();
String sessionId = NextServerSession.get().getId();
if (sessionId == null) {
session.bind();
sessionId = session.getId();
}
HttpServletRequest request = ((ServletWebRequest) RequestCycle.get().getRequest()).getContainerRequest();
String ip = request.getHeader("X-Forwarded-For");
if (ip == null) {
ip = request.getRemoteHost();
}
MDC.put("username", username);
MDC.put("session", sessionId);
MDC.put("ip", ip);
}
@Override
public void onEndRequest(RequestCycle cycle) {
MDC.remove("username");
MDC.remove("session");
MDC.remove("ip");
}
}
private class MaintenanceRequestCycleListener extends AbstractRequestCycleListener {
@Override
public void onBeginRequest(RequestCycle cycle) {
if (isMaintenance()) {
User user = SecurityUtil.getLoggedUser();
if ((user == null) || ((user != null) && user.isAdmin())) {
super.onBeginRequest(cycle);
return;
}
throw new MaintenanceException();
}
}
}
}