/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.everrest; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import org.eclipse.che.dto.server.JsonSerializable; import org.everrest.core.ApplicationContext; import org.everrest.core.Filter; import org.everrest.core.GenericContainerResponse; import org.everrest.core.ResponseFilter; import org.everrest.core.impl.ApplicationContextImpl; import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import java.nio.charset.Charset; import java.util.List; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.JSON_SERIALIZABLE; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.STRING; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.UNKNOWN; /** * Filter implementing {@link org.everrest.core.ResponseFilter} in order to generate ETag for clients that want to use conditional * requests. * It is applying on GET method and JSON content type only. * * @author Florent Benoit */ @Filter public class ETagResponseFilter implements ResponseFilter { public enum EntityType { JSON_SERIALIZABLE, STRING, UNKNOWN } /** * Filter the given container response * * @param containerResponse * the reponse to use */ public void doFilter(GenericContainerResponse containerResponse) { // get entity of the response Object entity = containerResponse.getEntity(); // no entity, skip if (entity == null) { return; } // Only handle JSON content if (!MediaType.APPLICATION_JSON_TYPE.equals(containerResponse.getContentType())) { return; } // Get the request ApplicationContext applicationContext = ApplicationContextImpl.getCurrent(); Request request = applicationContext.getRequest(); // manage only GET requests if (!"GET".equals(request.getMethod())) { return; } // calculate hash with MD5 HashFunction hashFunction = Hashing.md5(); Hasher hasher = hashFunction.newHasher(); boolean hashingSuccess = true; // Manage a list if (entity instanceof List) { List<?> entities = (List)entity; for (Object simpleEntity : entities) { hashingSuccess = addHash(simpleEntity, hasher); if (!hashingSuccess) { break; } } } else { hashingSuccess = addHash(entity, hasher); } // if we're able to handle the hash if (hashingSuccess) { // get result of the hash HashCode hashCode = hasher.hash(); // Create the entity tag EntityTag entityTag = new EntityTag(hashCode.toString()); // Check the etag Response.ResponseBuilder builder = request.evaluatePreconditions(entityTag); // not modified ? if (builder != null) { containerResponse.setResponse(builder.tag(entityTag).build()); } else { // it has been changed, so send response with new ETag and entity Response.ResponseBuilder responseBuilder = Response.fromResponse(containerResponse.getResponse()).tag(entityTag); containerResponse.setResponse(responseBuilder.build()); } } } /** * Helper method to add entity to hash. If there is an invalid entity type it will return false * * @param entity * the entity object to analyze and extract JSON for hashing it * @param hasher * the hasher used to add the hashes */ protected boolean addHash(Object entity, Hasher hasher) { // get entity type EntityType entityType = getElementType(entity); // check if (entityType == UNKNOWN) { // unknown entity type, cannot perform hash return false; } // add hash if all is OK try { hasher.putString(getJson(entity, entityType), Charset.defaultCharset()); } catch (RuntimeException e) { return false; } return true; } /** * Helper method to retrieving the JSON content based on the entity type * * @param entity * the object to analyze * @return the JSON string or null if it's an unknown type */ protected String getJson(Object entity, EntityType entityType) { switch (entityType) { case JSON_SERIALIZABLE: return ((JsonSerializable)entity).toJson(); case STRING: return (String)entity; default: return null; } } /** * Helper method for getting the type of the JSON entity * * @param entity * the entity object * @return the type of the element */ protected EntityType getElementType(Object entity) { if (JsonSerializable.class.isAssignableFrom(entity.getClass())) { return JSON_SERIALIZABLE; } if (String.class.isAssignableFrom(entity.getClass())) { return STRING; } return UNKNOWN; } }