/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.web; import static org.apache.wicket.RuntimeConfigurationType.DEPLOYMENT; import java.io.File; import java.net.URI; import java.net.URL; import java.util.List; import java.util.Locale; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import org.apache.wicket.Application; import org.apache.wicket.ConverterLocator; import org.apache.wicket.DefaultExceptionMapper; import org.apache.wicket.IConverterLocator; import org.apache.wicket.RuntimeConfigurationType; import org.apache.wicket.Session; import org.apache.wicket.core.request.handler.IPageRequestHandler; import org.apache.wicket.core.request.handler.PageProvider; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.protocol.http.WebSession; import org.apache.wicket.protocol.http.servlet.ServletWebRequest; import org.apache.wicket.request.IExceptionMapper; import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.IRequestHandlerDelegate; import org.apache.wicket.request.Request; import org.apache.wicket.request.Response; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.cycle.AbstractRequestCycleListener; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.resource.loader.IStringResourceLoader; import org.apache.wicket.settings.RequestCycleSettings.RenderStrategy; import org.apache.wicket.util.IProvider; import org.geoserver.catalog.Catalog; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerInfo.WebUIMode; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.security.GeoServerSecurityManager; import org.geoserver.web.spring.security.GeoServerSession; import org.geoserver.web.util.DataDirectoryConverterLocator; import org.geoserver.web.util.GeoToolsConverterAdapter; import org.geoserver.web.util.converters.StringBBoxConverter; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.measure.Measure; import org.geotools.util.MeasureConverterFactory; import org.geotools.util.logging.Logging; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; /** * The GeoServer application, the main entry point for any Wicket application. In particular, this one sets up, among the others, custom resource * loader, custom localizers, and custom converters (wrapping the GeoTools ones), as well as providing some convenience methods to access the * GeoServer Spring context and principal GeoServer objects. * * @author Andrea Aaime, The Open Planning Project * @author Justin Deoliveira, The Open Planning Project */ public class GeoServerApplication extends WebApplication implements ApplicationContextAware, ApplicationListener<ApplicationEvent> { /** * logger for web application */ public static Logger LOGGER = Logging.getLogger("org.geoserver.web"); public static boolean DETECT_BROWSER = Boolean .valueOf(System.getProperty("org.geoserver.web.browser.detect", "true")); ApplicationContext applicationContext; /** * Default redirect mode. Determines whether default webUIMode setting means redirect or not (default is true). */ protected boolean defaultIsRedirect = true; /** * Get default redirect mode. * * @return default redirect mode. */ public boolean isDefaultIsRedirect() { return defaultIsRedirect; } /** * Set default redirect mode. * (must be called before init method, usually by Spring PropertyOverriderConfigurer) * * @param defaultIsRedirect */ public void setDefaultIsRedirect(boolean defaultIsRedirect) { this.defaultIsRedirect = defaultIsRedirect; } /** * The {@link GeoServerHomePage}. */ public Class<GeoServerHomePage> getHomePage() { return GeoServerHomePage.class; } public static GeoServerApplication get() { return (GeoServerApplication) Application.get(); } /** * Returns the spring application context. */ public ApplicationContext getApplicationContext() { return applicationContext; } /** * Returns the geoserver configuration instance. */ public GeoServer getGeoServer() { return getBeanOfType(GeoServer.class); } /** * Returns the catalog. */ public Catalog getCatalog() { return getGeoServer().getCatalog(); } /** * Returns the security manager. */ public GeoServerSecurityManager getSecurityManager() { return getBeanOfType(GeoServerSecurityManager.class); } /** * Returns the geoserver resource loader. */ public GeoServerResourceLoader getResourceLoader() { return getBeanOfType(GeoServerResourceLoader.class); } /** * Loads a bean from the spring application context of a specific type. * <p> * If there are multiple beans of the specfied type in the context an exception is thrown. * </p> * * @param type The class of the bean to return. */ public <T> T getBeanOfType(Class<T> type) { return GeoServerExtensions.bean(type, getApplicationContext()); } /** * Loads a bean from the spring application context with a specific name. * * @param type The class of the bean to return. */ public Object getBean(String name) { return GeoServerExtensions.bean(name); } /** * Loads beans from the spring application context of a specific type. * * @param type The type of beans to return. * * @return A list of objects of the specified type, possibly empty. * @see {@link GeoServerExtensions#extensions(Class, ApplicationContext)} */ public <T> List<T> getBeansOfType(Class<T> type) { return GeoServerExtensions.extensions(type, getApplicationContext()); } /** * Clears all the wicket caches so that resources and localization files will be re-read */ public void clearWicketCaches() { getResourceSettings().getPropertiesFactory().clearCache(); getResourceSettings().getLocalizer().clearCache(); } /** * Initialization override which sets up a locator for i18n resources. */ protected void init() { // enable GeoServer custom resource locators getResourceSettings().setUseMinifiedResources(false); getResourceSettings().setResourceStreamLocator(new GeoServerResourceStreamLocator()); /* * The order string resource loaders are added to IResourceSettings is of importance so we need to add any contributed loader prior to the * standard ones so it takes precedence. Otherwise it won't be hit due to GeoServerStringResourceLoader never resolving to null but falling * back to the default language */ List<IStringResourceLoader> alternateResourceLoaders = getBeansOfType( IStringResourceLoader.class); for (IStringResourceLoader loader : alternateResourceLoaders) { LOGGER.info("Registering alternate resource loader: " + loader); getResourceSettings().getStringResourceLoaders().add(loader); } getResourceSettings().getStringResourceLoaders().add(0, new GeoServerStringResourceLoader()); getDebugSettings().setAjaxDebugModeEnabled(false); getApplicationSettings().setPageExpiredErrorPage(GeoServerExpiredPage.class); // generates infinite redirections, commented out for the moment // getSecuritySettings().setCryptFactory(GeoserverWicketEncrypterFactory.get()); // theoretically, this replaces the old GeoServerRequestEncodingStrategy // by making the URLs encrypted at will GeoServerSecurityManager securityManager = getBeanOfType(GeoServerSecurityManager.class); setRootRequestMapper(new DynamicCryptoMapper(getRootRequestMapper(), securityManager, this)); getRequestCycleListeners().add(new CallbackRequestCycleListener(this)); WebUIMode webUIMode = getGeoServer().getGlobal().getWebUIMode(); if (webUIMode == null) { webUIMode = WebUIMode.DEFAULT; } switch (webUIMode) { case DO_NOT_REDIRECT: getRequestCycleSettings().setRenderStrategy(RenderStrategy.ONE_PASS_RENDER); break; case REDIRECT: getRequestCycleSettings().setRenderStrategy(RenderStrategy.REDIRECT_TO_BUFFER); break; case DEFAULT: getRequestCycleSettings().setRenderStrategy(defaultIsRedirect ? RenderStrategy.REDIRECT_TO_BUFFER : RenderStrategy.ONE_PASS_RENDER); } } @Override public IProvider<IExceptionMapper> getExceptionMapperProvider() { // IProvider is functional, remove a bit of boilerplate return () -> new DefaultExceptionMapper() { @Override protected IRequestHandler mapUnexpectedExceptions(Exception e, Application application) { return createPageRequestHandler(new PageProvider(new GeoServerErrorPage(e))); } }; } @Override public RuntimeConfigurationType getConfigurationType() { String config = GeoServerExtensions.getProperty("wicket." + Application.CONFIGURATION, getApplicationContext()); if (config == null) { return DEPLOYMENT; } else if (!"DEPLOYMENT".equalsIgnoreCase(config) && !"DEVELOPMENT".equalsIgnoreCase(config)) { LOGGER.warning("Unknown Wicket configuration value '" + config + "', defaulting to DEPLOYMENT"); return DEPLOYMENT; } else { return RuntimeConfigurationType.valueOf(config.toUpperCase()); } } @Override public Session newSession(Request request, Response response) { Session s = new GeoServerSession(request); if (s.getLocale() == null) s.setLocale(Locale.ENGLISH); return s; } /* * Overrides to return a custom converter locator which loads converters from the GeoToools converter subsystem. */ protected IConverterLocator newConverterLocator() { // TODO: load converters from application context ConverterLocator locator = new ConverterLocator(); locator.set(ReferencedEnvelope.class, new GeoToolsConverterAdapter(new StringBBoxConverter(), ReferencedEnvelope.class)); DataDirectoryConverterLocator dd = new DataDirectoryConverterLocator(getResourceLoader()); locator.set(File.class, dd.getConverter(File.class)); locator.set(URI.class, dd.getConverter(URI.class)); locator.set(URL.class, dd.getConverter(URL.class)); locator.set(Measure.class, new GeoToolsConverterAdapter(MeasureConverterFactory.CONVERTER, Measure.class)); return locator; } // static class RequestCycleProcessor extends WebRequestCycleProcessor { // // public IRequestTarget resolve(RequestCycle requestCycle, // RequestParameters requestParameters) { // IRequestTarget target = super.resolve(requestCycle, // requestParameters); // if (target != null) { // return target; // } // // STILL HAVE TO FIGURE OUT HOW TO SEND THE USER BACK TO THE HOME PAGE IN THE NEW WICKET // return resolveHomePageTarget(requestCycle, requestParameters); // } // // // } static class CallbackRequestCycleListener extends AbstractRequestCycleListener { private List<WicketCallback> callbacks; public CallbackRequestCycleListener(GeoServerApplication app) { callbacks = app.getBeansOfType(WicketCallback.class); } @Override public void onBeginRequest(org.apache.wicket.request.cycle.RequestCycle cycle) { for (WicketCallback callback : callbacks) { callback.onBeginRequest(); } } @Override public void onEndRequest(org.apache.wicket.request.cycle.RequestCycle cycle) { for (WicketCallback callback : callbacks) { callback.onEndRequest(); } } @Override public void onDetach(org.apache.wicket.request.cycle.RequestCycle cycle) { for (WicketCallback callback : callbacks) { callback.onAfterTargetsDetached(); } } @Override public void onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handler) { processHandler(cycle, handler); } private void processHandler(RequestCycle cycle, IRequestHandler handler) { if(handler instanceof IPageRequestHandler) { IPageRequestHandler pageHandler = (IPageRequestHandler) handler; Class<? extends IRequestablePage> pageClass = pageHandler.getPageClass(); for (WicketCallback callback : callbacks) { callback.onRequestTargetSet(cycle, pageClass); } } else if(handler instanceof IRequestHandlerDelegate) { IRequestHandlerDelegate delegator = (IRequestHandlerDelegate) handler; processHandler(cycle, delegator.getDelegateHandler()); } } @Override public void onRequestHandlerResolved(org.apache.wicket.request.cycle.RequestCycle cycle, IRequestHandler handler) { processHandler(cycle, handler); } @Override public IRequestHandler onException(org.apache.wicket.request.cycle.RequestCycle cycle, Exception ex) { for (WicketCallback callback : callbacks) { callback.onRuntimeException(cycle, ex); } return null; } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * Convenience method to get the underlying servlet request backing the current wicket request. * <p> * The request is obtained from the current RequestCycle. * </p> */ public HttpServletRequest servletRequest() { RequestCycle cycle = RequestCycle.get(); if (cycle == null) { throw new IllegalStateException("Method must be called from a wicket request thread"); } return servletRequest(cycle.getRequest()); } /** * Convenience method to get the underlying servlet request backing the current wicket request. */ public HttpServletRequest servletRequest(Request req) { if (req == null || !(req instanceof ServletWebRequest)) { throw new IllegalStateException("Request not of type ServletWebRequest, was: " + req.getClass().getName()); } return ((ServletWebRequest) req).getContainerRequest(); } public void onApplicationEvent(ApplicationEvent event) { if(event instanceof AuthenticationSuccessEvent || event instanceof InteractiveAuthenticationSuccessEvent) { if(Session.exists()) { WebSession.get().replaceSession(); } } } }