BaseResource.java
/*
* Copyright (c) 2007-2017 MetaSolutions AB
*
* 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.entrystore.rest.resources;
import com.google.common.collect.Sets;
import org.entrystore.ContextManager;
import org.entrystore.Entry;
import org.entrystore.PrincipalManager;
import org.entrystore.harvester.Harvester;
import org.entrystore.impl.RepositoryManagerImpl;
import org.entrystore.repository.RepositoryManager;
import org.entrystore.repository.config.Settings;
import org.entrystore.rest.EntryStoreApplication;
import org.entrystore.rest.auth.UserTempLockoutCache;
import org.entrystore.rest.util.CORSUtil;
import org.entrystore.rest.util.JSONErrorMessages;
import org.entrystore.rest.util.Util;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.ServerInfo;
import org.restlet.data.Status;
import org.restlet.ext.json.JsonRepresentation;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.Options;
import org.restlet.resource.ServerResource;
import org.restlet.util.Series;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
*<p> Base resource class that supports common behaviours or attributes shared by
* all resources.</p>
*
* @author Eric Johansson
* @author Hannes Ebner
*/
public abstract class BaseResource extends ServerResource {
protected HashMap<String, String> parameters;
MediaType format;
protected String contextId;
protected String entryId;
protected org.entrystore.Context context;
protected Entry entry;
private static final Logger log = LoggerFactory.getLogger(BaseResource.class);
private static ServerInfo serverInfo;
@Override
public void init(Context c, Request request, Response response) {
parameters = Util.parseRequest(request.getResourceRef().getRemainingPart());
super.init(c, request, response);
// we set a custom Server header in the HTTP response
setServerInfo(this.getServerInfo());
contextId = (String) request.getAttributes().get("context-id");
if (getCM() != null && contextId != null) {
if (getReservedNames().contains(contextId.toLowerCase())) {
log.error("Context ID is a reserved term and must not be used: \"{}\". This error is likely to be caused by an error in the REST routing.", contextId);
} else {
context = getCM().getContext(contextId);
if (context == null) {
log.info("There is no context {}", contextId);
}
}
}
entryId = (String) request.getAttributes().get("entry-id");
if (context != null && entryId != null) {
entry = context.get(entryId);
if (entry == null) {
log.info("There is no entry {} in context {}", entryId, contextId);
}
}
String format = parameters.get("format");
if (format != null) {
// workaround for URL-decoded pluses (space) in MIME-type names, e.g. ld+json
format = format.replace(' ', '+');
this.format = new MediaType(format);
}
Util.handleIfUnmodifiedSince(entry, getRequest());
}
// TODO move this into a ServerInfoFilter that processes before the authentication mechanism
@Override
public ServerInfo getServerInfo() {
if (serverInfo == null) {
ServerInfo si = super.getServerInfo();
si.setAgent(getRM().getConfiguration().getString(Settings.HTTP_HEADER_SERVER, "EntryStore/" + EntryStoreApplication.getVersion()));
serverInfo = si;
}
return serverInfo;
}
/**
* Sends a response with CORS headers according to the configuration.
*/
@Options
public Representation preflightCORS() {
if ("off".equalsIgnoreCase(getRM().getConfiguration().getString(Settings.CORS, "off"))) {
log.info("Received CORS preflight request but CORS support is disabled");
setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
return null;
}
getResponse().setEntity(new EmptyRepresentation());
Series reqHeaders = (Series) getRequest().getAttributes().get("org.restlet.http.headers");
String origin = reqHeaders.getFirstValue("Origin", true);
if (origin != null) {
CORSUtil cors = CORSUtil.getInstance(getRM().getConfiguration());
if (!cors.isValidOrigin(origin)) {
log.info("Received CORS preflight request with disallowed origin");
//setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
return new EmptyRepresentation();
}
getResponse().setAccessControlAllowOrigin(origin);
getResponse().setAccessControlAllowMethods(Sets.newHashSet(Method.HEAD, Method.GET, Method.PUT, Method.POST, Method.DELETE, Method.OPTIONS));
getResponse().setAccessControlAllowCredentials(true);
if (cors.getAllowedHeaders() != null) {
getResponse().setAccessControlAllowHeaders(cors.getAllowedHeaders());
getResponse().setAccessControlExposeHeaders(cors.getAllowedHeaders());
}
if (cors.getMaxAge() > -1) {
getResponse().setAccessControlMaxAge(cors.getMaxAge());
}
}
return getResponse().getEntity();
}
/**
* Gets the current {@link ContextManager}
* @return The current {@link ContextManager} for the contexts.
*/
public ContextManager getCM() {
return getEntryStoreApplication().getCM();
}
/**
* Gets the current {@link PrincipalManager}
* @return The current {@link PrincipalManager} for the contexts.
*/
public PrincipalManager getPM() {
return getEntryStoreApplication().getPM();
}
/**
* Gets the current {@link RepositoryManager}.
* @return the current {@link RepositoryManager}.
*/
public RepositoryManagerImpl getRM() {
return getEntryStoreApplication().getRM();
}
public ArrayList<Harvester> getHarvesters() {
return getEntryStoreApplication().getHarvesters();
}
public Set<String> getReservedNames() {
return getEntryStoreApplication().getReservedNames();
}
public UserTempLockoutCache getUserTempLockoutCache() {
return getEntryStoreApplication().getUserTempLockoutCache();
}
public EntryStoreApplication getEntryStoreApplication() {
return (EntryStoreApplication) getContext().getAttributes().get(EntryStoreApplication.KEY);
}
public Representation unauthorizedHEAD() {
log.info("Unauthorized HEAD");
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
return new EmptyRepresentation();
}
public Representation unauthorizedGET() {
log.info("Unauthorized GET");
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
MediaType preferredMediaType = getRequest().getClientInfo().getPreferredMediaType(supportedMediaTypes);
if (MediaType.APPLICATION_JSON.equals(preferredMediaType)) {
return new JsonRepresentation(JSONErrorMessages.unauthorizedGET);
} else {
return new EmptyRepresentation();
}
}
public void unauthorizedDELETE() {
log.info("Unauthorized DELETE");
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
if (MediaType.APPLICATION_JSON.equals(getRequest().getEntity().getMediaType())) {
getResponse().setEntity(new JsonRepresentation(JSONErrorMessages.unauthorizedDELETE));
}
}
public void unauthorizedPOST() {
log.info("Unauthorized POST");
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
if (MediaType.APPLICATION_JSON.equals(getRequest().getEntity().getMediaType())) {
getResponse().setEntity(new JsonRepresentation(JSONErrorMessages.unauthorizedPOST));
}
}
public void unauthorizedPUT() {
log.info("Unauthorized PUT");
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
if (MediaType.APPLICATION_JSON.equals(getRequest().getEntity().getMediaType())) {
getResponse().setEntity(new JsonRepresentation(JSONErrorMessages.unauthorizedPUT));
}
}
protected Representation createEmptyRepresentationWithLastModified(Date modificationDate) {
Representation result = new EmptyRepresentation();
if (modificationDate != null) {
result.setModificationDate(modificationDate);
result.setTag(Util.createTag(modificationDate));
} else {
log.warn("Last-Modified header could not be set because the entry does not have a modification date: {}", entry.getEntryURI());
}
return result;
}
}