/* (c) 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geogig.geoserver.rest; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.rest.GeoServerServletConverter; import org.geoserver.rest.PageInfo; import org.geotools.util.logging.Logging; import org.locationtech.geogig.rest.RestletException; import org.locationtech.geogig.rest.TaskResultDownloadResource; import org.locationtech.geogig.rest.TaskStatusResource; import org.locationtech.geogig.rest.postgis.PGRouter; import org.locationtech.geogig.rest.repository.CommandResource; import org.locationtech.geogig.rest.repository.FixedEncoder; import org.locationtech.geogig.rest.repository.RepositoryProvider; import org.locationtech.geogig.rest.repository.RepositoryRouter; import org.locationtech.geogig.rest.repository.UploadCommandResource; import org.locationtech.geogig.web.api.CommandSpecException; import org.locationtech.geogig.web.api.index.IndexCommandResource; import org.restlet.Context; import org.restlet.Restlet; import org.restlet.Router; import org.restlet.data.MediaType; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.resource.Representation; import org.springframework.beans.BeansException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.noelios.restlet.application.Decoder; import com.noelios.restlet.ext.servlet.ServletConverter; /** * Simple AbstractController implementation that does the translation between Spring requests and * Restlet requests. */ public class GeogigDispatcher extends AbstractController { /** HTTP method "PUT" */ public static final String METHOD_PUT = "PUT"; /** HTTP method "DELETE" */ public static final String METHOD_DELETE = "DELETE"; /** * logger */ static Logger LOG = Logging.getLogger(GeogigDispatcher.class); private GeoServerRepositoryProvider repositoryProvider; /** * converter for turning servlet requests into restlet requests. */ private ServletConverter converter; /** * the root restlet router */ private Restlet root; public GeogigDispatcher() { this.repositoryProvider = new GeoServerRepositoryProvider(); setSupportedMethods( new String[] { METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_DELETE, METHOD_HEAD }); } @Override protected void initApplicationContext() throws BeansException { super.initApplicationContext(); converter = new GeoServerServletConverter(getServletContext()); Router router = createInboundRoot(); org.restlet.Context context = null;// getContext(); FixedEncoder encoder = new _FixedEncoder(context); // needed for the Encoder to wrap the incoming requests if they come with // "Content-Type: gzip" encoder.setEncodeRequest(false); encoder.setEncodeResponse(true); encoder.setNext(router); Decoder decoder = new Decoder(context); decoder.setDecodeRequest(true); decoder.setDecodeResponse(false); decoder.setNext(encoder); root = decoder; converter.setTarget(root); } public Router createInboundRoot() { Router router = createRoot(); router.attach("/repos", RepositoryListResource.class); router.attach("/repos.{extension}", RepositoryListResource.class); router.attach("/repos/", RepositoryListResource.class); router.attach("/repos.{extension}/", RepositoryListResource.class); router.attach("/repos/{repository}/importExistingRepo", ImportRepoCommandResource.class); router.attach("/repos/{repository}/importExistingRepo.{extension}", ImportRepoCommandResource.class); router.attach("/tasks.{extension}", TaskStatusResource.class); router.attach("/tasks", TaskStatusResource.class); router.attach("/tasks/{taskId}.{extension}", TaskStatusResource.class); router.attach("/tasks/{taskId}", TaskStatusResource.class); router.attach("/tasks/{taskId}/download", TaskResultDownloadResource.class); Router postgis = new PGRouter(); router.attach("/repos/{repository}/postgis", postgis); // GET and DELETE requests are handled in the same resource router.attach("/repos/{repository}.{extension}", RepositoryResource.class); router.attach("/repos/{repository}", RepositoryResource.class); router.attach("/repos/{repository}/repo", makeRepoRouter()); router.attach("/repos/{repository}/import.{extension}", UploadCommandResource.class); router.attach("/repos/{repository}/import", UploadCommandResource.class); router.attach("/repos/{repository}/init.{extension}", InitCommandResource.class); router.attach("/repos/{repository}/init", InitCommandResource.class); router.attach("/repos/{repository}/{command}.{extension}", CommandResource.class); router.attach("/repos/{repository}/{command}", CommandResource.class); router.attach("/repos/{repository}/index/{command}.{extension}", IndexCommandResource.class); router.attach("/repos/{repository}/index/{command}", IndexCommandResource.class); return router; } public static Router makeRepoRouter() { return new RepositoryRouter(); } @Override public ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception { try { converter.service(req, resp); } catch (Exception e) { if (e instanceof CommandSpecException) { String msg = e.getMessage(); resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); if (msg != null) { resp.getOutputStream().write(msg.getBytes(Charsets.UTF_8)); } return null; } RestletException re = null; if (e instanceof RestletException) { re = (RestletException) e; } if (re == null && e.getCause() instanceof RestletException) { re = (RestletException) e.getCause(); } if (re != null) { resp.setStatus(re.getStatus().getCode()); String reStr = re.getRepresentation().getText(); if (reStr != null) { LOG.severe(reStr); resp.setContentType("text/plain"); resp.getOutputStream().write(reStr.getBytes()); } // log the full exception at a higher level LOG.log(Level.SEVERE, "", re); } else { LOG.log(Level.SEVERE, "", e); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); if (e.getMessage() != null) { resp.getOutputStream().write(e.getMessage().getBytes()); } } resp.getOutputStream().flush(); } return null; } public Router createRoot() { Router router = new Router() { @Override protected synchronized void init(Request request, Response response) { super.init(request, response); if (!isStarted()) { return; } request.getAttributes().put(RepositoryProvider.KEY, repositoryProvider); // set the page uri's // http://host:port/appName String baseURL = request.getRootRef().getParentRef().toString(); String rootPath = request.getRootRef().toString().substring(baseURL.length()); String pagePath = request.getResourceRef().toString().substring(baseURL.length()); String basePath = null; if (request.getResourceRef().getBaseRef() != null) { basePath = request.getResourceRef().getBaseRef().toString() .substring(baseURL.length()); } // strip off the extension String extension = ResponseUtils.getExtension(pagePath); if (extension != null) { pagePath = pagePath.substring(0, pagePath.length() - extension.length() - 1); } // trim leading slash if (pagePath.endsWith("/")) { pagePath = pagePath.substring(0, pagePath.length() - 1); } // create a page info object and put it into a request attribute PageInfo pageInfo = new PageInfo(); pageInfo.setBaseURL(baseURL); pageInfo.setRootPath(rootPath); pageInfo.setBasePath(basePath); pageInfo.setPagePath(pagePath); pageInfo.setExtension(extension); request.getAttributes().put(PageInfo.KEY, pageInfo); } }; return router; } private static class _FixedEncoder extends FixedEncoder { public _FixedEncoder(Context context) { super(context); } @Override public boolean canEncode(Representation representation) { // In the context of GeoServer, an applicationContext filter will automatically apply GZIP encoding for // certain Representations by default. We don't want to double encode them // from GeoServer's applicationContext, GZIP compression will be applied to: // text/* // *xml* // application/json // application/x-javascript if (representation != null) { final MediaType mediaType = representation.getMediaType(); final String mainType = Strings.nullToEmpty(mediaType.getMainType()); final String subType = Strings.nullToEmpty(mediaType.getSubType()); if ("text".equals(mainType) || mainType.contains("xml") || subType.contains("xml") || ("application".equals(mainType) && ("json".equals(subType) || "x-javascript".equals(subType)))) { return false; } } // just return the super impl return super.canEncode(representation); } } }