/* (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.gwc.dispatch;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.PublishedInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.gwc.layer.CatalogConfiguration;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.DispatcherCallback;
import org.geoserver.ows.LocalPublished;
import org.geoserver.ows.LocalWorkspace;
import org.geoserver.ows.Request;
import org.geoserver.platform.ServiceException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* Adapts plain incoming requests to be resolved to the GWC proxy service.
* <p>
* The GeoServer {@link Dispatcher} will call {@link #init(Request)} as the first step before
* processing the request. This callback will set the {@link Request}'s service, version, and
* request properties to the "fake" gwc service (service=gwc, version=1.0.0, request=dispatch), so
* that when the {@link Dispatcher} looks up for the actual service bean to process the request it
* finds out the {@link GwcServiceProxy} instance that's configured to handle such a service
* request.
* <p>
* See the package documentation for more insights on how these all fit together.
*
*/
public class GwcServiceDispatcherCallback extends AbstractDispatcherCallback implements
DispatcherCallback {
// contains the current gwc operation
public static final ThreadLocal<String> GWC_OPERATION = new ThreadLocal<>();
private static final Pattern GWC_WS_VIRTUAL_SERVICE_PATTERN = Pattern.compile("([^/]+)/gwc/service");
private static final Pattern GWC_LAYER_VIRTUAL_SERVICE_PATTERN = Pattern.compile("([^/]+)/([^/]+)/gwc/service");
private final Catalog catalog;
public GwcServiceDispatcherCallback(Catalog catalog) {
this.catalog = catalog;
}
@Override
public void finished(Request request) {
// cleaning the current thread local operation
GWC_OPERATION.remove();
}
@Override
public Request init(Request request) {
String context = request.getContext();
if (context == null || !isGwcServiceTargeted(context)) {
return null;
}
// storing the current operation
GWC_OPERATION.set((String) request.getKvp().get("REQUEST"));
Map<String, String> kvp = new HashMap<>();
kvp.put("service", "gwc");
kvp.put("version", "1.0.0");
kvp.put("request", "dispatch");
// if we are in the presence of virtual service we need to adapt the request
WorkspaceInfo localWorkspace = LocalWorkspace.get();
PublishedInfo localPublished = LocalPublished.get();
if (localWorkspace != null) {
// this is a virtual service request
String layerName = (String) request.getKvp().get("LAYER");
if (layerName == null) {
layerName = (String) request.getKvp().get("layer");
}
if (layerName != null) {
// we have a layer name as parameter we need to adapt it (gwc doesn't care about workspaces)
layerName = CatalogConfiguration.removeWorkspacePrefix(layerName, catalog);
layerName = localWorkspace.getName() + ":" + layerName;
// we set the layer parameter with GWC expected name
kvp.put("LAYER", layerName);
}
String localPublishedName = localPublished != null ? localPublished.getName() : null;
// we need to setup a proper context path (gwc doesn't expect the workspace to be part of the URL)
request.setHttpRequest(new VirtualServiceRequest(request.getHttpRequest(), localWorkspace.getName(), localPublishedName, layerName));
} else if(localPublished != null) {
request.setHttpRequest(new VirtualServiceRequest(request.getHttpRequest(), localPublished.getName(), null, null));
}
request.setKvp(kvp);
request.setRawKvp(kvp);
return request;
}
/**
* Helper method that checks if the GWC service is targeted based on the request context.
*/
private boolean isGwcServiceTargeted(String context) {
if (context.startsWith("gwc/service")) {
// is gwc is targeted
return true;
}
// we may be in the context of a workspace or group specific
Matcher matcher = GWC_WS_VIRTUAL_SERVICE_PATTERN.matcher(context);
if (matcher.matches()) {
// this is a virtual service, let's see if we have a valid workspace
if(LocalWorkspace.get() == null && !(LocalPublished.get() instanceof LayerGroupInfo)) {
// the workspace name has to be valid
throw new ServiceException("No such workspace '" + matcher.group(1) + "'");
}
// the local workspace is set so we have a valid workspace
return true;
}
matcher = GWC_LAYER_VIRTUAL_SERVICE_PATTERN.matcher(context);
if (matcher.matches()) {
// this is a laye specific virtual service, let's see if we have a valid workspace
if(LocalPublished.get() == null) {
// the workspace name has to be valid
throw new ServiceException("No such layer or layer group '" + matcher.group(2) + "'");
}
// the local workspace is set so we have a valid workspace
return true;
}
// this request is not targeting gwc service
return false;
}
/**
* Helper wrapper that allow to match GWC expectations. GWC doesn't have the concept of workspaces,
* so he always expect a layer name to be prefixed by is workspace.
*/
private final class VirtualServiceRequest extends HttpServletRequestWrapper {
private final String localWorkspaceName;
private String localPublishedName;
private final String layerName;
private final Map<String, String[]> parameters;
public VirtualServiceRequest(HttpServletRequest request, String localWorkspaceName, String localPublishedName, String layerName) {
super(request);
this.localWorkspaceName = localWorkspaceName;
this.localPublishedName = localPublishedName;
this.layerName = layerName;
parameters = new HashMap<>(request.getParameterMap());
if (layerName != null) {
parameters.put("layer", new String[]{layerName});
}
}
@Override
public String getContextPath() {
// to GWC the workspace is part of the request context
if(localPublishedName == null) {
return super.getContextPath() + "/" + localWorkspaceName;
} else {
return super.getContextPath() + "/" + localWorkspaceName + "/" + localPublishedName;
}
}
@Override
public String getParameter(String name) {
if (layerName != null && name.equalsIgnoreCase("layer")) {
return layerName;
}
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return parameters;
}
@Override
public String[] getParameterValues(String name) {
if (layerName != null && name.equalsIgnoreCase("layer")) {
return new String[]{layerName};
}
return super.getParameterValues(name);
}
}
}