Package ch.ethz.inf.vs.californium.proxy

Source Code of ch.ethz.inf.vs.californium.proxy.ProxyCacheResource

/*******************************************************************************
* Copyright (c) 2014, Institute for Pervasive Computing, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Californium (Cf) CoAP framework.
******************************************************************************/

package ch.ethz.inf.vs.californium.proxy;

import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import ch.ethz.inf.vs.californium.coap.CoAP.ResponseCode;
import ch.ethz.inf.vs.californium.coap.MediaTypeRegistry;
import ch.ethz.inf.vs.californium.coap.OptionNumberRegistry;
import ch.ethz.inf.vs.californium.coap.Request;
import ch.ethz.inf.vs.californium.coap.Response;
import ch.ethz.inf.vs.californium.network.config.NetworkConfig;
import ch.ethz.inf.vs.californium.network.config.NetworkConfigDefaults;
import ch.ethz.inf.vs.californium.server.resources.CoapExchange;
import ch.ethz.inf.vs.californium.server.resources.ResourceBase;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.primitives.Ints;

/**
* Resource to handle the caching in the proxy.
*
* @author Francesco Corazza
*
*/
public class ProxyCacheResource extends ResourceBase implements CacheResource {
 
  /**
   * The time after which an entry is removed. Since it is not possible to set
   * the expiration for the single instances, this constant represent the
   * upper bound for the cache. The real lifetime will be handled explicitely
   * with the max-age option.
   */
  private static final int CACHE_RESPONSE_MAX_AGE =
      NetworkConfig.getStandard().getInt(NetworkConfigDefaults.HTTP_CACHE_RESPONSE_MAX_AGE);

  /**
   * Maximum size for the cache.
   */
  private static final long CACHE_SIZE =
      NetworkConfig.getStandard().getInt(NetworkConfigDefaults.HTTP_CACHE_SIZE);

  /**
   * The cache. http://code.google.com/p/guava-libraries/wiki/CachesExplained
   */
  private final LoadingCache<CacheKey, Response> responseCache;

  private boolean enabled = false;

  /**
   * Instantiates a new proxy cache resource.
   */
  public ProxyCacheResource() {
    this(false);
  }
 
  /**
   * Instantiates a new proxy cache resource.
   */
  public ProxyCacheResource(boolean enabled) {
    super("cache");
    this.enabled = enabled;

    // builds a new cache that:
    // - has a limited size of CACHE_SIZE entries
    // - removes entries after CACHE_RESPONSE_MAX_AGE seconds from the last
    // write
    // - record statistics
    responseCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).recordStats().expireAfterWrite(CACHE_RESPONSE_MAX_AGE, TimeUnit.SECONDS).build(new CacheLoader<CacheKey, Response>() {
      @Override
      public Response load(CacheKey request) throws NullPointerException {
        // retreive the response from the incoming request, no
        // exceptions are thrown
        Response cachedResponse = request.getResponse();

        // check for null and raise an exception that clients must
        // handle
        if (cachedResponse == null) {
          throw new NullPointerException();
        }

        return cachedResponse;
      }
    });
  }

  /**
   * Puts in cache an entry or, if already present, refreshes it. The method
   * first checks the response code, only the 2.xx codes are cached by coap.
   * In case of 2.01, 2.02, and 2.04 response codes it invalidates the
   * possibly present response. In case of 2.03 it updates the freshness of
   * the response with the max-age option provided. In case of 2.05 it creates
   * the key and caches the response if the max-age option is higher than
   * zero.
   */
  @Override
  public void cacheResponse(Request request, Response response) {
    // enable or disable the caching (debug purposes)
    if (!enabled) {
      return;
    }

    // only the response with success codes should be cached
    ResponseCode code = response.getCode();
    if (ResponseCode.isSuccess(code)) {
      // get the request
//      Request request = response.getRequest();
      CacheKey cacheKey = null;
      try {
        cacheKey = CacheKey.fromContentTypeOption(request);
      } catch (URISyntaxException e) {
        LOGGER.warning("Cannot create the cache key: " + e.getMessage());
      }

      if (code == ResponseCode.CREATED || code == ResponseCode.DELETED || code == ResponseCode.CHANGED) {
        // the stored response should be invalidated if the response has
        // codes: 2.01, 2.02, 2.04.
        invalidateRequest(cacheKey);
      } else if (code == ResponseCode.VALID) {
        // increase the max-age value according to the new response
//        Option maxAgeOption = response.getFirstOption(OptionNumberRegistry.MAX_AGE);
        Long maxAgeOption = response.getOptions().getMaxAge();
        if (maxAgeOption != null) {
          // get the cached response
          Response cachedResponse = responseCache.getUnchecked(cacheKey);

          // calculate the new parameters
          long newCurrentTime = response.getTimestamp();
          int newMaxAge = maxAgeOption.intValue();

          // set the new parameters
//          cachedResponse.getFirstOption(OptionNumberRegistry.MAX_AGE).setIntValue(newMaxAge);
          cachedResponse.getOptions().setMaxAge(newMaxAge);
          cachedResponse.setTimestamp(newCurrentTime);

          LOGGER.finer("Updated cached response");
        } else {
          LOGGER.warning("No max-age option set in response: " + response);
        }
      } else if (code == ResponseCode.CONTENT) {
        // set max-age if not set
//        Option maxAgeOption = response.getFirstOption(OptionNumberRegistry.MAX_AGE);
        Long maxAgeOption = response.getOptions().getMaxAge();
        if (maxAgeOption == null) {
          response.getOptions().setMaxAge(OptionNumberRegistry.DEFAULT_MAX_AGE);
        }

        if (maxAgeOption > 0) {
          // cache the request
          try {
            // Caches loaded by a CacheLoader will call
            // CacheLoader.load(K) to load new values into the cache
            // when used the get method.
            Response responseInserted = responseCache.get(cacheKey);
            if (responseInserted != null) {
//              if (Bench_Help.DO_LOG)
                LOGGER.finer("Cached response");
            } else {
              LOGGER.warning("Failed to insert the response in the cache");
            }
          } catch (Exception e) {
            // swallow
            LOGGER.log(Level.WARNING, "Exception while inserting the response in the cache", e);
          }
        } else {
          // if the max-age option is set to 0, then the response
          // should be invalidated
          invalidateRequest(request);
        }
      } else {
        // this code should not be reached
        LOGGER.severe("Code not recognized: " + code);
      }
    }
  }

  @Override
  public CacheStats getCacheStats() {
    return responseCache.stats();
  }

  /**
   * Retrieves the response in the cache that matches the request passed, null
   * otherwise. The method creates the key for the cache starting from the
   * request and checks if the cache contains it. If present, the method
   * updates the max-age of the linked response to consider the time passed in
   * the cache (according to the freshness model) and returns it. On the
   * contrary, if the response has passed its expiration time, it is
   * invalidated and the method returns null.
   */
  @Override
  public Response getResponse(Request request) {
    if (!enabled) {
      return null;
    }

    // search the desired representation
    Response response = null;
    CacheKey cacheKey = null;
    try {
      for (CacheKey acceptKey : CacheKey.fromAcceptOptions(request)) {
        response = responseCache.getIfPresent(acceptKey);
        cacheKey = acceptKey;

        if (response != null) {
          break;
        }
      }
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    // if the response is not null, manage the cached response
    if (response != null) {
      LOGGER.finer("Cache hit");

      // check if the response is expired
      long currentTime = System.nanoTime();
      int nanosLeft = getRemainingLifetime(response, currentTime);
      if (nanosLeft > 0) {
        // if the response can be used, then update its max-age to
        // consider the aging of the response while in the cache
        response.getOptions().setMaxAge(nanosLeft);
        // set the current time as the response timestamp
        response.setTimestamp(currentTime);
      } else {
        LOGGER.finer("Expired response");

        // try to validate the response
        response = validate(cacheKey);
        if (response != null) {
          LOGGER.finer("Validation successful");
        } else {
          invalidateRequest(cacheKey);
        }
      }
    }

    return response;
  }

  /*
   * (non-Javadoc)
   * @see ch.ethz.inf.vs.californium.endpoint.resources.CacheResource#
   * invalidateResponse(ch.ethz.inf.vs.californium.coap.Response)
   */
  @Override
  public void invalidateRequest(Request request) {
    try {
      invalidateRequest(CacheKey.fromAcceptOptions(request));
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    LOGGER.finer("Invalidated request");
  }

  @Override
  public void handleDELETE(CoapExchange exchange) {
    responseCache.invalidateAll();
    exchange.respond(ResponseCode.DELETED);
  }

  @Override
  public void handleGET(CoapExchange exchange) {
    StringBuilder builder = new StringBuilder();
    builder.append("Available commands:\n - GET: show cached values\n - DELETE: empty the cache\n - POST: enable/disable caching\n");

    // get cache values
    builder.append("\nCached values:\n");
    for (CacheKey cachedRequest : responseCache.asMap().keySet()) {
      Response response = responseCache.asMap().get(cachedRequest);

      builder.append(cachedRequest.getProxyUri().toString() + " (" +
          MediaTypeRegistry.toString(cachedRequest.getMediaType()) + ") > " + getRemainingLifetime(response) + " seconds | (" + cachedRequest.getMediaType() + ")\n");
    }

    exchange.respond(ResponseCode.CONTENT, builder.toString());
  }

  @Override
  public void handlePOST(CoapExchange exchange) {
    enabled = !enabled;
    String content = enabled ? "Enabled" : "Disabled";
    exchange.respond(ResponseCode.CHANGED, content);
  }

  private int getRemainingLifetime(Response response) {
    return getRemainingLifetime(response, System.nanoTime());
  }

  /**
   * Method that checks if the lifetime allowed for the response if expired.
   * The result is calculated with the initial timestamp (when the response
   * has been received) and the max-age option compared against the current
   * timestamp. If the max-age option is not specified, it will be assumed the
   * default (60 seconds).
   *
   * @param response
   *            the response
   * @param currentTime
   * @return true, if is expired
   */
  private int getRemainingLifetime(Response response, long currentTime) {
    // get the timestamp
    long arriveTime = response.getTimestamp();

//    Option maxAgeOption = response.getFirstOption(OptionNumberRegistry.MAX_AGE);
    Long maxAgeOption = response.getOptions().getMaxAge();
    int oldMaxAge = OptionNumberRegistry.DEFAULT_MAX_AGE;
    if (maxAgeOption != null) {
      oldMaxAge = maxAgeOption.intValue();
    }

    // calculate the time that the response has spent in the cache
    double secondsInCache = TimeUnit.NANOSECONDS.toSeconds(currentTime - arriveTime);
    int cacheTime = Ints.checkedCast(Math.round(secondsInCache));
    return oldMaxAge - cacheTime;
  }

  private void invalidateRequest(CacheKey cacheKey) {
    responseCache.invalidate(cacheKey);
  }

  private void invalidateRequest(List<CacheKey> cacheKeys) {
    responseCache.invalidateAll(cacheKeys);
  }

  private Response validate(CacheKey cachedRequest) {
    // TODO
    return null;
  }

  /**
   * Nested class that normalizes the variable fields of the coap requests to
   * be used as a key for the cache. The class tries to handle also the
   * different requests that must refer to the same response (e.g., requests
   * that with or without the accept options produce the same response).
   *
   * @author Francesco Corazza
   */
  private static final class CacheKey {
    private final String proxyUri;
    private final int mediaType;
    private Response response;
    private final byte[] payload;

    /**
     * Creates a list of keys for the cache from a request with multiple
     * accept options set. Method needed to search for content-type
     * wildcards in the cache (text/* means: text/plain, text/html,
     * text/xml, text/csv, etc.). If the accept option is not set, it simply
     * gives back the keys for every representation.
     *
     * @param request
     * @return
     * @throws URISyntaxException
     */
    private static List<CacheKey> fromAcceptOptions(Request request) throws URISyntaxException {
      if (request == null) {
        throw new IllegalArgumentException("request == null");
      }

      List<CacheKey> cacheKeys = new LinkedList<ProxyCacheResource.CacheKey>();
      String proxyUri = request.getOptions().getProxyURI();
      try {
        proxyUri = URLEncoder.encode(proxyUri, "ISO-8859-1");
      } catch (UnsupportedEncodingException e) {
        LOGGER.warning("ISO-8859-1 do not support this encoding: " + e.getMessage());
        throw new URISyntaxException("ISO-8859-1 do not support this encoding", e.getMessage());
      }
      byte[] payload = request.getPayload();
     
      // Implementation in new Cf (Only one accept option allowed)
      Integer accept = request.getOptions().getAccept();
      if (accept != null) {
        int mediaType = accept.intValue();
        CacheKey cacheKey = new CacheKey(proxyUri, mediaType, payload);
        cacheKeys.add(cacheKey);
      } else {
        // if the accept options are not set, simply set all media types
        // FIXME not efficient
        for (Integer acceptType : MediaTypeRegistry.getAllMediaTypes()) {
          CacheKey cacheKey = new CacheKey(proxyUri, acceptType, payload);
          cacheKeys.add(cacheKey);
        }
      }

      return cacheKeys;
    }

    /**
     * Create a key for the cache starting from a request and the
     * content-type of the corresponding response.
     *
     * @param request
     * @return
     * @throws URISyntaxException
     */
    private static CacheKey fromContentTypeOption(Request request) throws URISyntaxException {
      if (request == null) {
        throw new IllegalArgumentException("request == null");
      }

      Response response = request.getResponse();
      if (response == null) {
        return fromAcceptOptions(request).get(0);
      }

      String proxyUri = request.getOptions().getProxyURI();
      Integer mediaType = response.getOptions().getContentFormat();
      if (mediaType == null)
        mediaType = MediaTypeRegistry.TEXT_PLAIN;
      byte[] payload = request.getPayload();

      // create the new cacheKey
      CacheKey cacheKey = new CacheKey(proxyUri, mediaType, payload);
      cacheKey.setResponse(response);

      return cacheKey;
    }

    public CacheKey(String proxyUri, int mediaType, byte[] payload) {
      this.proxyUri = proxyUri;
      this.mediaType = mediaType;
      this.payload = payload;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (getClass() != obj.getClass()) {
        return false;
      }
      CacheKey other = (CacheKey) obj;
      if (mediaType != other.mediaType) {
        return false;
      }
      if (!Arrays.equals(payload, other.payload)) {
        return false;
      }
      if (proxyUri == null) {
        if (other.proxyUri != null) {
          return false;
        }
      } else if (!proxyUri.equals(other.proxyUri)) {
        return false;
      }
      return true;
    }

    /**
     * @return the mediaType
     */
    public int getMediaType() {
      return mediaType;
    }

    /**
     * @return the proxyUri
     */
    public String getProxyUri() {
      return proxyUri;
    }

    /**
     * @return the response
     */
    public Response getResponse() {
      return response;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + mediaType;
      result = prime * result + Arrays.hashCode(payload);
      result = prime * result + (proxyUri == null ? 0 : proxyUri.hashCode());
      return result;
    }

    private void setResponse(Response response) {
      this.response = response;

    }
  }

  public boolean isEnabled() {
    return enabled;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }
}
TOP

Related Classes of ch.ethz.inf.vs.californium.proxy.ProxyCacheResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.