/** * Copyright 2011 the original author or authors. * * 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. */ package org.bricket.web; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; import java.util.jar.Manifest; import javax.jcr.ImportUUIDBehavior; import javax.servlet.http.HttpServletRequest; import org.apache.wicket.Application; import org.apache.wicket.Component; import org.apache.wicket.Component.IVisitor; import org.apache.wicket.IRequestTarget; import org.apache.wicket.Page; import org.apache.wicket.Request; import org.apache.wicket.RequestCycle; import org.apache.wicket.ResourceReference; import org.apache.wicket.Response; import org.apache.wicket.RestartResponseAtInterceptPageException; import org.apache.wicket.Session; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.authentication.AuthenticatedWebSession; import org.apache.wicket.authorization.Action; import org.apache.wicket.authorization.IAuthorizationStrategy; import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener; import org.apache.wicket.authorization.UnauthorizedInstantiationException; import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadWebRequest; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.protocol.http.WebRequest; import org.apache.wicket.protocol.http.WebRequestCycle; import org.apache.wicket.request.IRequestCycleProcessor; import org.apache.wicket.request.RequestParameters; import org.apache.wicket.request.target.basic.URIRequestTargetUrlCodingStrategy; import org.apache.wicket.spring.injection.annot.SpringComponentInjector; import org.apache.wicket.util.resource.FileResourceStream; import org.bricket.plugin.authentication.web.LoginPage; import org.bricket.plugin.picture.domain.Picture; import org.bricket.plugin.picture.service.PictureModificationService; import org.bricket.plugin.picture.service.PictureService; import org.bricket.plugin.role.service.RoleService.Roles; import org.bricket.service.BricketService; import org.bricket.web.brixadmin.AdminPage; import org.odlabs.wiquery.ui.themes.IThemableApplication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import brix.Brix; import brix.BrixNodeModel; import brix.Path; import brix.config.BrixConfig; import brix.config.PrefixUriMapper; import brix.config.UriMapper; import brix.jcr.ThreadLocalSessionFactory; import brix.jcr.api.JcrSession; import brix.jcr.exception.JcrException; import brix.jcr.wrapper.BrixNode; import brix.plugin.site.SitePlugin; import brix.plugins.springsecurity.AuthorizationStrategyImpl; import brix.web.BrixRequestCycleProcessor; import brix.web.nodepage.BrixNodePageUrlCodingStrategy; import brix.web.nodepage.BrixNodeRequestTarget; import brix.workspace.Workspace; import brix.workspace.WorkspaceManager; /** * @author Ingo Renner * @author Henning Teek */ public class BricketApplication extends AbstractBricketApplication implements BricketAwareApplication, IThemableApplication { public static final String BRIX_SITE = "/brix:root/brix:web/brix:site"; private final Logger log = LoggerFactory.getLogger(BricketApplication.class); private Brix brix; private AuthorizationStrategyImpl authorizationStrategy; @Autowired private PictureModificationService pictureModificationService; @Autowired private PictureService pictureService; public static final String THUMBNAIL_IMAGE_PATH = "thumb"; public static final String PREVIEW_IMAGE_PATH = "perview"; public static final String LAYOUT_IMAGE_PATH = "layout"; public static final String IMAGE_PAGE_PATH = "image"; private static final String UNKNOWN_VERSION = "unknown version"; public static BricketApplication get() { Application application = Application.get(); if (!(application instanceof BricketApplication)) { throw new WicketRuntimeException("The application attached to the current thread is not a " + BricketApplication.class.getSimpleName()); } return (BricketApplication) application; } @Override protected void init() { super.init(); addSpringInjector(); final ThreadLocalSessionFactory sf = getJcrSessionFactory(); final WorkspaceManager wm = getWorkspaceManager(); try { // create brix instance and attach it to this application BrixConfig brixConfig = createBrixConfig(sf, wm); registerBricketPlugins(brixConfig); createBrix(brixConfig); initializeRepository(); initDefaultWorkspace(); } catch (Exception e) { log.error("Exception in WicketApplication init()", e); } finally { // since we accessed session factory we also have to perform cleanup cleanupSessionFactory(); } // mount admin page mount(new QueryStringHybridUrlCodingStrategy("/admin", AdminPage.class)); getApplicationSettings().setAccessDeniedPage(LoginPage.class); getSecuritySettings().setAuthorizationStrategy(new IAuthorizationStrategy() { @Override public boolean isActionAuthorized(Component component, Action action) { return true; } @Override public <T extends Component> boolean isInstantiationAuthorized(Class<T> componentClass) { boolean result = true; if (componentClass.equals(AdminPage.class)) { List<ConfigAttribute> attrs = new ArrayList<ConfigAttribute>(); attrs.add(new AuthorizationStrategyImpl.ConfigAttributeImpl(Roles.ROLE_SUPERUSER.name())); result = authorizationStrategy.userHasAuthority(componentClass, attrs); } return result; } }); getSecuritySettings().setUnauthorizedComponentInstantiationListener( new BricketUnauthorizedComponentInstantiationListener()); initializeBricketServices(); // Access to Images by url mount(createImageURIRequestTargetUrlCodingStrategy()); mount(createLayoutURIRequestTargetUrlCodingStrategy()); mount(createThumbnailURIRequestTargetUrlCodingStrategy()); log.info("bricket {} initialized.", getVersion()); } protected void createBrix(BrixConfig brixConfig) { brix = getWicketBrix(brixConfig, authorizationStrategy); brix.attachTo(this); } protected BrixConfig createBrixConfig(final ThreadLocalSessionFactory sf, final WorkspaceManager wm) { // create uri mapper for the cms // we are mounting the cms on the root, and getting the workspace // name from the // application properties UriMapper mapper = new PrefixUriMapper(Path.ROOT) { @Override public Workspace getWorkspaceForRequest(WebRequestCycle requestCycle, Brix brix) { final String name = getProperties().getJcrDefaultWorkspace(); final SitePlugin sitePlugin = SitePlugin.get(brix); return sitePlugin.getSiteWorkspace(name, getProperties().getWorkspaceDefaultState()); } }; BrixConfig config = new BrixConfig(sf, wm, mapper); config.setHttpPort(getProperties().getHttpPort()); config.setHttpsPort(getProperties().getHttpsPort()); return config; } protected void initializeBricketServices() { // initialize bricket services from spring context Map<String, BricketService> bricketServices = getApplicationContext().getBeansOfType(BricketService.class); log.info("bricket services = {}", (bricketServices == null ? "no services found." : bricketServices.keySet())); if (bricketServices != null) { for (BricketService bricketService : bricketServices.values()) { try { bricketService.init(); } catch (Exception e) { log.error("failed to initialize service", e); } } } } protected void registerBricketPlugins(final BrixConfig config) { // register bricket plugins from spring context Map<String, BricketPlugin> bricketPlugins = getApplicationContext().getBeansOfType(BricketPlugin.class); log.info("bricket plugins = {}", (bricketPlugins == null ? "no plugins found." : bricketPlugins.keySet())); if (bricketPlugins != null) { for (BricketPlugin bricketPlugin : bricketPlugins.values()) { bricketPlugin.register(config.getRegistry()); } } } protected void addSpringInjector() { addComponentInstantiationListener(new SpringComponentInjector(this)); } private URIRequestTargetUrlCodingStrategy createImageURIRequestTargetUrlCodingStrategy() { return new URIRequestTargetUrlCodingStrategy("/" + IMAGE_PAGE_PATH) { @Override public IRequestTarget decode(RequestParameters requestParameters) { // Get request URI String uri = getURI(requestParameters); Long id; try { id = Long.valueOf(uri); } catch (NumberFormatException e) { id = 0L; } final File imageFile; final String name; if (id > 0L) { Picture pic = pictureService.loadPicture(id); imageFile = pictureModificationService.getImage(pic.getDigest()); name = pic.getFilename(); } else { imageFile = pictureModificationService.getImage(PictureModificationService.FILE_NOT_FOUND_DIGEST); name = PictureModificationService.FILE_NOT_FOUND_NAME; } return new ImageResourceStreamRequestTarget(new FileResourceStream( new org.apache.wicket.util.file.File(imageFile)), 0) { @Override public String getFileName() { return name; } }; } @Override public boolean matches(IRequestTarget requestTarget) { if (requestTarget instanceof ImageResourceStreamRequestTarget) { return ((ImageResourceStreamRequestTarget) requestTarget).getImageTypeId() == 0; } return false; } }; } private URIRequestTargetUrlCodingStrategy createLayoutURIRequestTargetUrlCodingStrategy() { return new URIRequestTargetUrlCodingStrategy("/" + LAYOUT_IMAGE_PATH) { @Override public IRequestTarget decode(RequestParameters requestParameters) { // Get request URI String uri = getURI(requestParameters); Long id; try { id = Long.valueOf(uri); } catch (NumberFormatException e) { id = 0L; } final File imageFile; final String name; if (id > 0L) { Picture pic = pictureService.loadPicture(id); imageFile = pictureModificationService.getLayoutImage(pic.getDigest()); name = pic.getFilename(); } else { imageFile = pictureModificationService .getLayoutImage(PictureModificationService.FILE_NOT_FOUND_DIGEST); name = PictureModificationService.FILE_NOT_FOUND_NAME; } return new ImageResourceStreamRequestTarget(new FileResourceStream( new org.apache.wicket.util.file.File(imageFile)), 1) { @Override public String getFileName() { return name + ".png"; } }; } @Override public boolean matches(IRequestTarget requestTarget) { if (requestTarget instanceof ImageResourceStreamRequestTarget) { return ((ImageResourceStreamRequestTarget) requestTarget).getImageTypeId() == 1; } return false; } }; } private URIRequestTargetUrlCodingStrategy createThumbnailURIRequestTargetUrlCodingStrategy() { return new URIRequestTargetUrlCodingStrategy("/" + THUMBNAIL_IMAGE_PATH) { @Override public IRequestTarget decode(RequestParameters requestParameters) { // Get request URI String uri = getURI(requestParameters); Long id; try { id = Long.valueOf(uri); } catch (NumberFormatException e) { id = 0L; } final File imageFile; final String name; if (id > 0L) { Picture pic = pictureService.loadPicture(id); imageFile = pictureModificationService.getThumbnailImage(pic.getDigest()); name = pic.getFilename(); } else { imageFile = pictureModificationService .getThumbnailImage(PictureModificationService.FILE_NOT_FOUND_DIGEST); name = PictureModificationService.FILE_NOT_FOUND_NAME; } return new ImageResourceStreamRequestTarget(new FileResourceStream( new org.apache.wicket.util.file.File(imageFile)), 2) { @Override public String getFileName() { return name + ".png"; } }; } @Override public boolean matches(IRequestTarget requestTarget) { if (requestTarget instanceof ImageResourceStreamRequestTarget) { return ((ImageResourceStreamRequestTarget) requestTarget).getImageTypeId() == 2; } return false; } }; } protected Bricket getWicketBrix(BrixConfig config, AuthorizationStrategyImpl auth) { return new Bricket(config, auth); } public Brix getBrix() { return brix; } public void setAuthorizationStrategy(AuthorizationStrategyImpl authorizationStrategy) { this.authorizationStrategy = authorizationStrategy; } private void initDefaultWorkspace() { final String defaultState = getProperties().getWorkspaceDefaultState(); final String wn = getProperties().getJcrDefaultWorkspace(); final SitePlugin sp = SitePlugin.get(brix); if (!sp.siteExists(wn, defaultState)) { Workspace w = sp.createSite(wn, defaultState); JcrSession session = brix.getCurrentSession(w.getId()); session.importXML("/", getWorkspaceXml(), ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); brix.initWorkspace(w, session); session.save(); } } protected InputStream getWorkspaceXml() { return BricketApplication.class.getResourceAsStream("workspace.xml"); } /** * Allow Brix to perform repository initialization */ private void initializeRepository() { try { brix.initRepository(); } finally { // cleanup any sessions we might have created cleanupSessionFactory(); } } public String getThumbnailUrl(long id, RequestCycle rc) { HttpServletRequest req = ((WebRequest) rc.getRequest()).getHttpServletRequest(); return req.getContextPath() + "/" + BricketApplication.THUMBNAIL_IMAGE_PATH + "/" + id; } public String getPreviewUrl(long id, RequestCycle rc) { HttpServletRequest req = ((WebRequest) rc.getRequest()).getHttpServletRequest(); return req.getContextPath() + "/" + BricketApplication.PREVIEW_IMAGE_PATH + "/" + id; } public String getLayoutUrl(long id, RequestCycle rc) { HttpServletRequest req = ((WebRequest) rc.getRequest()).getHttpServletRequest(); return req.getContextPath() + "/" + BricketApplication.LAYOUT_IMAGE_PATH + "/" + id; } @Override public BrixNodeModel getBrixLink(String path) { try { return new BrixNodeModel(BRIX_SITE + path, getWorkspaceId()); } catch (JcrException e) { log.error("error creating brix node model for: " + path, e); } return null; } @Override public String getBrixLinkUrl(String path, RequestCycle rc) { try { return getBrixLinkUrl(new BrixNodeModel(path, getWorkspaceId()), rc); } catch (JcrException e) { log.error("error creating brix node model for: " + path, e); } return null; } @Override public String getBrixLinkUrl(BrixNode node, RequestCycle rc) { try { return getBrixLinkUrl(new BrixNodeModel(node), rc); } catch (JcrException e) { log.error("error creating brix node model for node: " + node, e); } return null; } @Override public String getBrixLinkUrl(BrixNodeModel nodeModel, RequestCycle rc) { try { return rc.urlFor(new BrixNodeRequestTarget(nodeModel)).toString(); } catch (JcrException e) { log.error("error creating brix node request target for node model: " + nodeModel, e); } return null; } @Override public BrixNodeModel getI18nBrixLink(Session session, String path) { assert (session instanceof BricketWebSession); return new BrixNodeModel(BRIX_SITE + ((BricketWebSession) session).getI18nLink(path), getWorkspaceId()); } @Override public String getVersion() { Class<BricketApplication> clazz = BricketApplication.class; String classPath = clazz.getResource(clazz.getSimpleName() + ".class").toString(); if (!classPath.startsWith("jar")) { // class not from JAR return UNKNOWN_VERSION; } try { String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; Manifest manifest = new Manifest(new URL(manifestPath).openStream()); Attributes attr = manifest.getMainAttributes(); String version = attr.getValue(Name.IMPLEMENTATION_VERSION); return version != null ? version : UNKNOWN_VERSION; } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return UNKNOWN_VERSION; } @Override protected IRequestCycleProcessor newRequestCycleProcessor() { /* * install brix request cycle processor * * this will allow brix to take over part of wicket's url space and * handle requests */ return new BrixRequestCycleProcessor(brix); } @Override public Class<? extends Page> getHomePage() { /* * use special class so that the URL coding strategy knows we want to go * home it is not possible to just return null here because some pages * (e.g. expired page) rely on knowing the home page */ return BrixNodePageUrlCodingStrategy.HomePage.class; } @Override public Session newSession(Request request, Response response) { return new BricketWebSession(request); } @Override protected Class<? extends WebPage> getSignInPageClass() { return LoginPage.class; } @Override protected Class<? extends AuthenticatedWebSession> getWebSessionClass() { return BricketWebSession.class; } @Override protected WebRequest newWebRequest(HttpServletRequest servletRequest) { return new UploadWebRequest(servletRequest); } /** * Search the FeedbackPanel on the given page. * * @param page * @return null or FeedbackPanel */ public BricketFeedbackPanel getBricketFeedbackPanel(Page page) { return (BricketFeedbackPanel) page.visitChildren(new FeedbackPanelVisitor()); } @Override public ResourceReference getTheme(Session session) { return new ResourceReference(BricketApplication.class, "theme.css"); } private static final class BricketUnauthorizedComponentInstantiationListener implements IUnauthorizedComponentInstantiationListener { @Override public void onUnauthorizedInstantiation(Component component) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { throw new RestartResponseAtInterceptPageException(LoginPage.class); } else { throw new UnauthorizedInstantiationException(component.getClass()); } } } private static final class FeedbackPanelVisitor implements IVisitor<Component> { private final Set<Component> visited = new HashSet<Component>(); @Override public Object component(Component component) { if (!visited.contains(component)) { visited.add(component); if (component instanceof BricketFeedbackPanel) { return component; } } return IVisitor.CONTINUE_TRAVERSAL; } } }