/**
* 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;
}
}
}