com.sector91.wit.responders.ResourceResponder.java Source code

Java tutorial

Introduction

Here is the source code for com.sector91.wit.responders.ResourceResponder.java

Source

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ //

////   ///   /// ///       
//////  ////   // ////  //// 
/// ////  ////  //  ////  //// 
///  //// /////  //        ///  
///  //// ///// //  //// ////// 
//   /// /////  //  ////  ////  
// //// ///// //  ////  ////   
/////////////  ////  ////   
////////////   ///   ///    
///// /////   ////  ////    
///// /////   //// ///// // 
////  ////    /// ///// //  
///// /////   ////////////   
////  ////     ////  ////    

// The Web framework with class.

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ //

// Copyright (c) 2013 Adam R. Nelson
//
// 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 com.sector91.wit.responders;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;

import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.Status;

import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;
import com.sector91.wit.Responder;
import com.sector91.wit.http.CompressionDecider;
import com.sector91.wit.http.ContentTypes;
import com.sector91.wit.http.HttpException;
import com.sector91.wit.params.One;
import com.sector91.wit.resources.ResourceLoader;
import com.sector91.wit.resources.ResourceNotFoundException;

public class ResourceResponder implements Responder<One<String>> {

    private static final String TAG = ResourceResponder.class.getSimpleName();

    public static final long DEFAULT_CACHE_CAPACITY = 256;

    private final long timestamp;
    final ClassLoader context;
    final String basePath;

    private final ResourceLoader<byte[]> loader;
    final CacheBuilder<? super String, ? super byte[]> cacheBuilder;
    Predicate<String> gzipIf = CompressionDecider.DEFAULT;

    public ResourceResponder(Class<?> context, String basePath) {
        this(context.getClassLoader(), basePath);
    }

    public ResourceResponder(Class<?> context, String basePath,
            CacheBuilder<? super String, ? super byte[]> cacheBuilder) {
        this(context.getClassLoader(), basePath, cacheBuilder);
    }

    public ResourceResponder(ClassLoader context, String basePath) {
        this(context, basePath, CacheBuilder.newBuilder().maximumSize(DEFAULT_CACHE_CAPACITY));
    }

    public ResourceResponder(ClassLoader context, String basePath,
            CacheBuilder<? super String, ? super byte[]> cacheBuilder) {
        this.context = context;
        this.basePath = basePath;
        this.timestamp = (System.currentTimeMillis() / 1000) * 1000; // Floor to seconds.
        this.cacheBuilder = cacheBuilder;
        this.loader = new GZippingResourceLoader();
    }

    public ResourceResponder gzipIf(Predicate<String> decider) {
        gzipIf = decider;
        return this;
    }

    @Override
    public void respond(One<String> param, Request request, Response response) throws IOException, HttpException {
        Log.debug(TAG, "Starting ResourceResponder for path " + basePath);

        // Read basic information from the request.
        final String path = param.first();
        Log.trace(TAG, "Responding with resource: " + path);
        final String mimetype = ContentTypes.forPath(path);
        final boolean gzipSupported = request.getValue(HttpHeaders.ACCEPT_ENCODING).contains("gzip");
        final boolean shouldCompressFile = gzipIf.apply(mimetype);

        // EDGE CASE: If we're caching a compressed version of the resource, but
        // the client doesn't support compression, then don't bother caching, and
        // just read the resource directly.
        if (!gzipSupported && shouldCompressFile) {
            Log.debug(TAG, "Resource '" + basePath + path + "' is cached in gzipped form, but client does"
                    + " not support gzip. Skipping cache.");
            final InputStream stream = context.getResourceAsStream(basePath + path);
            if (stream == null)
                throw new HttpException(Status.NOT_FOUND);
            response.setContentType(mimetype);
            response.setDate(HttpHeaders.LAST_MODIFIED, timestamp);
            try (OutputStream out = response.getOutputStream()) {
                ByteStreams.copy(stream, out);
            }
            return;
        }

        // Retrieve the resource from the cache, loading it if necessary
        final byte[] data;
        try {
            data = loader.load(path);
        } catch (ResourceNotFoundException ex) {
            throw new HttpException(Status.NOT_FOUND);
        }

        // Return a 304 Not Modified if the client already has a cached copy.
        response.setDate(HttpHeaders.LAST_MODIFIED, timestamp);
        final long ifModifiedSince = request.getDate(HttpHeaders.IF_MODIFIED_SINCE);
        if (ifModifiedSince >= timestamp) {
            Log.trace(TAG, "Resource not modified: " + path);
            response.setStatus(Status.NOT_MODIFIED);
            response.getOutputStream().close();
            return;
        }

        // Otherwise, write the response headers and data.
        response.setContentType(mimetype);
        if (shouldCompressFile)
            response.setValue(HttpHeaders.CONTENT_ENCODING, "gzip");
        response.setContentLength(data.length);
        try (OutputStream out = response.getOutputStream()) {
            out.write(data);
        }
    }

    private class GZippingResourceLoader extends ResourceLoader<byte[]> {

        GZippingResourceLoader() {
            super(context, basePath, cacheBuilder);
        }

        @Override
        protected byte[] convert(String path, InputStream in) throws IOException {
            final String mimetype = ContentTypes.forPath(path);
            final boolean shouldCompress = gzipIf.apply(mimetype);
            if (shouldCompress) {
                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try (final GZIPOutputStream gzip = new GZIPOutputStream(baos)) {
                    ByteStreams.copy(in, gzip);
                }
                return baos.toByteArray();
            } else
                return ByteStreams.toByteArray(in);
        }
    }
}