/**
* 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.brixcms.web;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.DefaultMapperContext;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.IPageProvider;
import org.apache.wicket.core.request.handler.IPageRequestHandler;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
import org.apache.wicket.core.request.handler.PageAndComponentProvider;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.core.request.mapper.AbstractComponentMapper;
import org.apache.wicket.core.request.mapper.IPageSource;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.https.HttpsConfig;
import org.apache.wicket.protocol.https.HttpsMapper;
import org.apache.wicket.protocol.https.Scheme;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.Url.QueryParameter;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.http.handler.RedirectRequestHandler;
import org.apache.wicket.request.mapper.info.ComponentInfo;
import org.apache.wicket.request.mapper.info.PageComponentInfo;
import org.apache.wicket.request.mapper.info.PageInfo;
import org.apache.wicket.request.mapper.parameter.INamedParameters;
import org.apache.wicket.request.mapper.parameter.INamedParameters.NamedPair;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
import org.brixcms.Brix;
import org.brixcms.BrixNodeModel;
import org.brixcms.Path;
import org.brixcms.config.BrixConfig;
import org.brixcms.jcr.api.JcrSession;
import org.brixcms.jcr.exception.JcrException;
import org.brixcms.jcr.wrapper.BrixNode;
import org.brixcms.plugin.site.SiteNodePlugin;
import org.brixcms.plugin.site.SitePlugin;
import org.brixcms.plugin.site.page.AbstractSitePagePlugin;
import org.brixcms.plugin.site.page.PageRenderingPage;
import org.brixcms.web.nodepage.BrixNodePageRequestHandler;
import org.brixcms.web.nodepage.BrixNodeRequestHandler;
import org.brixcms.web.nodepage.BrixNodeWebPage;
import org.brixcms.web.nodepage.BrixPageParameters;
import org.brixcms.web.nodepage.PageParametersAware;
import org.brixcms.workspace.WorkspaceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BrixRequestMapper extends AbstractComponentMapper {
private static final Logger log = LoggerFactory.getLogger(BrixRequestMapper.class);
private final Brix brix;
private final HttpsConfig config;
public BrixRequestMapper(Brix brix, HttpsConfig config) {
this.config = config;
this.brix = brix;
}
@Override
public IRequestHandler mapRequest(Request request) {
final Url url = request.getClientUrl();
if (isInternalWicket(request)) {
return null;
}
// TODO: This is just a quick fix
if (url.getSegments().size() > 0) {
if (url.getSegments().get(0).equals("webdav") || url.getSegments().get(0).equals("jcrwebdav")) {
return null;
}
}
// index.brix handling
if (url.toString().endsWith(SitePlugin.BRIX_INDEX_PAGE)) {
url.getSegments().remove(SitePlugin.BRIX_INDEX_PAGE);
return new RedirectRequestHandler("/" + url.getPath());
}
Path path = new Path("/" + url.getPath());
//fix for ROOT AJAX requests...-> to index.brix
if(path.isRoot() && ((WebRequest)request).isAjax()) {
path = new Path("/" + SitePlugin.BRIX_INDEX_PAGE);
}
// root path handling
if (path.isRoot()) {
final BrixNode node = getNodeForUriPath(path);
return SitePlugin.get().getNodePluginForNode(node).respond(new BrixNodeModel(node),
new BrixPageParameters(request.getRequestParameters()));
}
IRequestHandler handler = null;
try {
while (handler == null) {
final BrixNode node = getNodeForUriPath(path);
if (node != null) {
SiteNodePlugin plugin = SitePlugin.get().getNodePluginForNode(node);
if (plugin instanceof AbstractSitePagePlugin) {
handler = SitePlugin.get().getNodePluginForNode(node).respond(new BrixNodeModel(node),
createBrixPageParams(request.getUrl(), path));
} else {
handler = SitePlugin.get().getNodePluginForNode(node).respond(new BrixNodeModel(node),
new BrixPageParameters(request.getRequestParameters()));
}
}
if (handler != null || path.toString().equals(".")) {
break;
}
path = path.parent();
if (path.isRoot()) {
break;
}
}
} catch (JcrException e) {
log.warn("JcrException caught due to incorrect url", e);
}
final PageComponentInfo info = getPageComponentInfo(request.getUrl());
if (info != null) {
Integer renderCount = info.getComponentInfo() != null ? info.getComponentInfo().getRenderCount() : null;
if (info.getComponentInfo() == null) {
PageProvider provider;
if (handler instanceof BrixNodePageRequestHandler) {
provider = new PageProvider(info.getPageInfo().getPageId(), BrixNodeWebPage.class, renderCount);
BrixNodePageRequestHandler brixNodePageRequestHandler = (BrixNodePageRequestHandler) handler;
final IPageProvider pageProviderAdapter = brixNodePageRequestHandler.getPageProvider();
provider.setPageSource(new IPageSource() {
@Override
public IRequestablePage getPageInstance(int pageId) {
IRequestablePage page = null;
Integer existingPageId = pageProviderAdapter.getPageId();
if (existingPageId != null && pageId == existingPageId) {
page = pageProviderAdapter.getPageInstance();
}
return page;
}
@Override
public IRequestablePage newPageInstance(Class<? extends IRequestablePage> pageClass,
PageParameters pageParameters) {
IRequestablePage page = pageProviderAdapter.getPageInstance();
page.getPageParameters().set(info.toString(), "");
return page;
}
});
} else {
provider = new PageProvider(info.getPageInfo().getPageId(), renderCount);
provider.setPageSource(getContext());
}
// render page
return new RenderPageRequestHandler(provider);
} else {
ComponentInfo componentInfo = info.getComponentInfo();
final Path finalPath = path;
if (componentInfo != null) {
renderCount = componentInfo.getRenderCount();
}
PageAndComponentProvider provider = new PageAndComponentProvider(info.getPageInfo().getPageId(), PageRenderingPage.class,
new BrixPageParameters(request.getRequestParameters()), renderCount, componentInfo.getComponentPath());
provider.setPageSource(new DefaultMapperContext() {
@Override
public IRequestablePage newPageInstance(Class<? extends IRequestablePage> pageClass, PageParameters pageParameters) {
return new PageRenderingPage(new BrixNodeModel(getNodeForUriPath(finalPath)),
new BrixPageParameters(pageParameters));
}
});
return new ListenerRequestHandler(provider, componentInfo.getBehaviorId());
}
}
Scheme desired = getDesiredSchemeFor(handler);
Scheme current = getSchemeOf(request);
if (!desired.isCompatibleWith(current)) {
// we are currently on the wrong scheme for this handler
// construct a url for the handler on the correct scheme
String urlChange = createRedirectUrl(handler, request, desired);
// replace handler with one that will redirect to the created url
handler = createRedirectHandler(urlChange);
}
return handler;
}
private BrixPageParameters createBrixPageParams(Url url, Path path) {
BrixPageParameters parameters = new BrixPageParameters();
Path nodePath = path;
Path requestPath = new Path("/" + url.getPath());
if (nodePath.isAncestorOf(requestPath)) {
Path remaining = new Path(requestPath.toString(), false).toRelative(nodePath);
int i = 0;
for (String s : remaining) {
parameters.set(i, urlDecode(s));
++i;
}
}
for (QueryParameter parameter : url.getQueryParameters()) {
String parameterName = parameter.getName();
if (PageComponentInfo.parse(parameterName) == null) {
parameters.add(parameterName, parameter.getValue());
}
}
return parameters;
}
@Override
public int getCompatibilityScore(Request request) {
if (isInternalWicket(request)) {
return 0;
}
// bluff we can parse all segments - makes sure we run first
return request.getUrl().getSegments().size();
}
private boolean isInternalWicket(Request request) {
Url url = request.getUrl();
if (url.getSegments().size() > 0) {
if (url.getSegments().get(0).equals((Application.get().getMapperContext().getNamespace()))) {
// starts with wicket namespace - is an internal wicket url
return true;
}
}
return false;
}
/**
* Returns a decoded value of the given value
*
* @param value
* @return Decodes the value
*/
private static String urlDecode(String value) {
try {
value = URLDecoder.decode(value, Application.get().getRequestCycleSettings().getResponseRequestEncoding());
} catch (UnsupportedEncodingException ex) {
log.error("error decoding parameter", ex);
}
return value;
}
private static boolean isNumber(String string) {
if (string == null || string.length() == 0) {
return false;
}
for (int i = 0; i < string.length(); ++i) {
if (Character.isDigit(string.charAt(i)) == false) {
return false;
}
}
return true;
}
@Override
public Url mapHandler(IRequestHandler requestHandler) {
if (requestHandler instanceof BrixNodeRequestHandler) {
BrixNodeRequestHandler handler = (BrixNodeRequestHandler) requestHandler;
String nodeURL = handler.getNodeURL();
return encode(nodeURL, handler.getPageParameters(), null);
} else if (requestHandler instanceof ListenerRequestHandler) {
ListenerRequestHandler handler = (ListenerRequestHandler) requestHandler;
if (handler.getPage() instanceof BrixNodeWebPage) {
BrixNodeWebPage page = (BrixNodeWebPage) handler.getPage();
String componentPath = handler.getComponentPath();
PageInfo pageInfo = new PageInfo(page.getPageId());
ComponentInfo componentInfo = new ComponentInfo(page.getRenderCount(), componentPath, handler.getBehaviorIndex());
PageComponentInfo info = new PageComponentInfo(pageInfo, componentInfo);
Url url = encode(page);
encodePageComponentInfo(url, info);
return url;
} else {
return null;
}
} else if (requestHandler instanceof RenderPageRequestHandler) {
RenderPageRequestHandler handler = (RenderPageRequestHandler) requestHandler;
if (handler.getPage() instanceof BrixNodeWebPage) {
BrixNodeWebPage page = (BrixNodeWebPage) handler.getPage();
PageInfo i = new PageInfo(page.getPageId());
PageComponentInfo info = new PageComponentInfo(i, null);
Url url = encode(page);
encodePageComponentInfo(url, info);
url.getSegments().remove(SitePlugin.BRIX_INDEX_PAGE);
return url;
} else {
return null;
}
} else if (requestHandler instanceof BookmarkableListenerRequestHandler) {
BookmarkableListenerRequestHandler handler = (BookmarkableListenerRequestHandler) requestHandler;
if (handler.getPage() instanceof BrixNodeWebPage) {
BrixNodeWebPage page = (BrixNodeWebPage) handler.getPage();
Integer renderCount = null;
if (handler.includeRenderCount()) {
renderCount = handler.getRenderCount();
}
PageInfo pageInfo = getPageInfo(handler);
ComponentInfo componentInfo = new ComponentInfo(renderCount, handler.getComponentPath(), handler.getBehaviorIndex());
PageComponentInfo info = new PageComponentInfo(pageInfo, componentInfo);
Url url = encode(page);
encodePageComponentInfo(url, info);
return url;
} else {
return null;
}
} else {
return null;
}
}
protected final PageInfo getPageInfo(IPageRequestHandler handler) {
Args.notNull(handler, "handler");
Integer pageId = null;
if (handler.isPageInstanceCreated()) {
IRequestablePage page = handler.getPage();
if (page.isPageStateless() == false) {
pageId = page.getPageId();
}
}
return new PageInfo(pageId);
}
private Url encode(BrixNodeWebPage page) {
BrixNode node = page.getModelObject();
// This is a URL for redirect. Allow components to contribute state to
// URL if they want to
final BrixPageParameters parameters = page.getBrixPageParameters();
Iterator<NamedPair> it = page.getBrixPageParameters().getAllNamed().iterator();
while (it.hasNext()) {
INamedParameters.NamedPair namedPair = it.next();
if (isNumber(namedPair.getKey())) {
parameters.remove(namedPair.getKey());
}
}
page.visitChildren(PageParametersAware.class, new IVisitor<Component, PageParametersAware>() {
@Override
public void component(Component component, IVisit<PageParametersAware> iVisit) {
((PageParametersAware) component).contributeToPageParameters(parameters);
}
});
return encode(node, parameters, null);
}
private Url encode(BrixNode node, PageParameters parameters, PageInfo info) {
return encode(getUriPathForNode(node).toString(), parameters, info);
}
private Url encode(String nodeURL, PageParameters parameters, PageInfo info) {
StringBuilder builder = new StringBuilder();
if (nodeURL.startsWith("/") && nodeURL.length() > 1) {
nodeURL = nodeURL.substring(1);
}
builder.append(urlEncodePath(new Path(nodeURL, false)));
boolean skipFirstSlash = builder.length() > 1 && builder.charAt(builder.length() - 1) == '/';
for (int i = 0; i < parameters.getIndexedCount(); ++i) {
if (!skipFirstSlash) {
builder.append('/');
} else {
skipFirstSlash = false;
}
final StringValue value = parameters.get(i);
final String s = value.toString();
if (s != null) {
builder.append(urlEncode(s));
}
}
Set<String> keys = parameters.getNamedKeys();
if (info != null || !keys.isEmpty()) {
builder.append("?");
}
if (info != null) {
builder.append(info.toString());
}
boolean first = (info == null);
for (String key : keys) {
List<StringValue> values = parameters.getValues(key);
for (StringValue value : values) {
if (first) {
first = false;
} else {
builder.append("&");
}
builder.append(urlEncode(key));
builder.append("=");
builder.append(urlEncode(value.toString()));
}
}
return Url.parse(builder.toString());
}
private String urlEncodePath(Path path) {
StringBuilder res = new StringBuilder(path.size());
boolean first = true;
for (String s : path) {
if (first) {
first = false;
} else {
res.append("/");
}
res.append(urlEncode(s));
}
return res.toString();
}
/**
* Url encodes a string
*
* @param string
* string to be encoded
* @return encoded string
*/
public static String urlEncode(String string) {
try {
return URLEncoder.encode(string, Application.get().getRequestCycleSettings().getResponseRequestEncoding());
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
return string;
}
}
/**
* Resolves uri path to a {@link BrixNode}. By default this method uses
* {@link BrixConfig#getMapper()} to map the uri to a node path.
*
* @param uriPath
* uri path
* @return node that maps to the <code>uriPath</code> or <code>null</code>
* if none
*/
public BrixNode getNodeForUriPath(final Path uriPath) {
BrixNode node = null;
// create desired nodepath
final Path nodePath = brix.getConfig().getMapper().getNodePathForUriPath(uriPath.toAbsolute(), brix);
if (nodePath != null) {
// allow site plugin to translate the node path into an actual jcr
// path
final String jcrPath = SitePlugin.get().toRealWebNodePath(nodePath.toString());
// retrieve jcr session
final String workspace = WorkspaceUtils.getWorkspace();
final JcrSession session = brix.getCurrentSession(workspace);
if (session.itemExists(jcrPath)) {
// node exists, return it
node = (BrixNode) session.getItem(jcrPath);
}
}
return node;
}
/**
* Creates a uri path for the specified <code>node</code> By default this
* method uses {@link BrixConfig#getMapper()} to map node path to a uri
* path.
*
* @param node
* node to create uri path for
* @return uri path that represents the node
*/
public Path getUriPathForNode(final BrixNode node) {
// allow site plugin to translate jcr path into node path
final String jcrPath = SitePlugin.get().fromRealWebNodePath(node.getPath());
final Path nodePath = new Path(jcrPath);
// use urimapper to create the uri
return brix.getConfig().getMapper().getUriPathForNode(nodePath, brix);
}
/**
* Creates the {@link IRequestHandler} that will be responsible for the
* redirect
*
* @param url
* @return request handler
*/
protected IRequestHandler createRedirectHandler(String url) {
return new HttpsMapper.RedirectHandler(url, config);
}
/**
* Construts a redirect url that should switch the user to the specified
* {@code scheme}
*
* @param handler
* request handler being accessed
* @param request
* current request
* @param scheme
* desired scheme for the redirect url
* @return url
*/
protected String createRedirectUrl(IRequestHandler handler, Request request, Scheme scheme) {
HttpServletRequest req = (HttpServletRequest) ((WebRequest) request).getContainerRequest();
String url = scheme.urlName() + "://";
url += req.getServerName();
if (!scheme.usesStandardPort(config)) {
url += ":" + scheme.getPort(config);
}
url += req.getRequestURI();
if (req.getQueryString() != null) {
url += "?" + req.getQueryString();
}
return url;
}
/**
* Figures out which {@link org.apache.wicket.protocol.https.Scheme} should
* be used to access the request handler
*
* @param handler
* request handler
* @return {@link org.apache.wicket.protocol.https.Scheme}
*/
protected Scheme getDesiredSchemeFor(IRequestHandler handler) {
if (handler instanceof BrixNodePageRequestHandler) {
BrixNode.Protocol protocol = ((BrixNodePageRequestHandler) handler).getPage().getPageNode().getRequiredProtocol();
switch (protocol) {
case HTTP:
return Scheme.HTTP;
case HTTPS:
return Scheme.HTTPS;
/**
* could be seen as not really correct as PRESERVE_CURRENT could
* also relate to "not change the scheme", however, Brix
* traditionally respected this as "NON SSL" like described on the
* SSL Page in the brix-demo:
*
* "... The URL will revert back to a non-secure one once the user navigates to a page that does not require SSL. "
*
*/
case PRESERVE_CURRENT:
return Scheme.HTTP;
}
}
return Scheme.ANY;
}
/**
* Determines the {@link Scheme} of the request
*
* @param request
* @return {@link Scheme#HTTPS} or {@link Scheme#HTTP}
*/
protected Scheme getSchemeOf(Request request) {
HttpServletRequest req = (HttpServletRequest) request.getContainerRequest();
if ("https".equalsIgnoreCase(req.getScheme())) {
return Scheme.HTTPS;
} else if ("http".equalsIgnoreCase(req.getScheme())) {
return Scheme.HTTP;
} else {
throw new IllegalStateException("Could not resolve protocol for request: " + req);
}
}
public static final class HomePage extends WebPage {
}
}