blob: ac316bae95043999ff25100ba52e51b427a3b744 [file] [log] [blame]
package org.apache.directmemory.server.services;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import org.apache.commons.lang.StringUtils;
import org.apache.directmemory.DirectMemory;
import org.apache.directmemory.cache.CacheService;
import org.apache.directmemory.memory.Pointer;
import org.apache.directmemory.server.commons.DirectMemoryException;
import org.apache.directmemory.server.commons.DirectMemoryHttpConstants;
import org.apache.directmemory.server.commons.DirectMemoryRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static java.lang.Integer.getInteger;
import static org.apache.directmemory.DirectMemory.DEFAULT_CONCURRENCY_LEVEL;
import static org.apache.directmemory.DirectMemory.DEFAULT_INITIAL_CAPACITY;
/**
* TODO add some listener plugin mechanism to store figures/statistics on cache access
*
* @author Olivier Lamy
*/
public class DirectMemoryServlet
extends HttpServlet
{
private Logger log = LoggerFactory.getLogger( getClass() );
private CacheService<Object, Object> cacheService;
private Map<String, ContentTypeHandler> contentTypeHandlers;
@Override
public void init( ServletConfig config )
throws ServletException
{
super.init( config );
// TODO some configuration for cacheService.init( .... ); different from sysproperties
//int numberOfBuffers, int size, int initialCapacity, int concurrencyLevel
cacheService = new DirectMemory<Object, Object>().setNumberOfBuffers(
getInteger( "directMemory.numberOfBuffers", 10 ) ).setSize(
getInteger( "directMemory.size", 1000 ) ).setInitialCapacity(
getInteger( "directMemory.initialCapacity", DEFAULT_INITIAL_CAPACITY ) ).setConcurrencyLevel(
getInteger( "directMemory.concurrencyLevel", DEFAULT_CONCURRENCY_LEVEL ) ).newCacheService();
//
contentTypeHandlers = new HashMap<String, ContentTypeHandler>( 2 );
contentTypeHandlers.put( MediaType.APPLICATION_JSON, new JsonContentTypeHandler() );
contentTypeHandlers.put( DirectMemoryHttpConstants.JAVA_SERIALIZED_OBJECT_CONTENT_TYPE_HEADER,
new JavaSerializedContentTypeHandler() );
contentTypeHandlers.put( MediaType.TEXT_PLAIN, new TextPlainContentTypeHandler() );
log.info( "DirectMemoryServlet initialized" );
}
@Override
public void destroy()
{
super.destroy();
}
@Override
protected void doPost( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
this.doPut( req, resp );
}
@Override
protected void doPut( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
//TODO check request content to send HttpServletResponse.SC_BAD_REQUEST
// if missing parameter in json request
String path = req.getPathInfo();
String servletPath = req.getServletPath();
String key = retrieveKeyFromPath( path );
DirectMemoryRequest request = null;
ContentTypeHandler contentTypeHandler = findPutCacheContentTypeHandler( req, resp );
if ( contentTypeHandler == null )
{
String contentType = req.getContentType();
log.error( "No content type handler for content type {}", contentType );
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Content-Type '" + contentType + "' not supported" );
return;
}
try
{
request = contentTypeHandler.handlePut( req, resp );
}
catch ( DirectMemoryException e )
{
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage() );
return;
}
//if exists free first ?
//if ( cacheService.retrieveByteArray( key ) == null )
byte[] bytes = request.getCacheContent();
Pointer p = cacheService.putByteArray( key, bytes, request.getExpiresIn() );
if ( p == null )
{
resp.sendError( HttpServletResponse.SC_NO_CONTENT, "Content not put in cache for key: " + key );
return;
}
log.debug( "put content for key {} size {}", key, bytes.length );
resp.addHeader( DirectMemoryHttpConstants.EXPIRES_SERIALIZE_SIZE, Integer.toString( bytes.length ) );
}
protected ContentTypeHandler findPutCacheContentTypeHandler( HttpServletRequest req, HttpServletResponse response )
{
String contentType = req.getContentType();
if ( StringUtils.startsWith( contentType, MediaType.APPLICATION_JSON ) )
{
// application/json
return contentTypeHandlers.get( MediaType.APPLICATION_JSON );
}
else if ( StringUtils.startsWith( contentType, MediaType.TEXT_PLAIN ) )
{
// text/plain
return contentTypeHandlers.get( MediaType.TEXT_PLAIN );
}
return contentTypeHandlers.get( contentType );
}
@Override
protected void doDelete( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
String path = req.getPathInfo();
String servletPath = req.getServletPath();
String key = retrieveKeyFromPath( path );
Pointer pointer = cacheService.getPointer( key );
if ( pointer == null )
{
resp.sendError( HttpServletResponse.SC_NO_CONTENT, "No content for key: " + key );
return;
}
cacheService.free( pointer );
log.debug( "free content of key: {}", key );
}
@Override
protected void doGet( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
// url format = /cache/key so get the key from path
String path = req.getPathInfo();
String servletPath = req.getServletPath();
String key = retrieveKeyFromPath( path );
if ( StringUtils.isEmpty( key ) )
{
resp.sendError( HttpServletResponse.SC_BAD_REQUEST, "key missing in path" );
return;
}
String acceptContentType = req.getHeader( "Accept" );
if ( StringUtils.isEmpty( acceptContentType ) )
{
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"you must specify Accept with Content-Type you want in the response" );
return;
}
ContentTypeHandler contentTypeHandler = findGetCacheContentTypeHandler( req, resp );
if ( contentTypeHandler == null )
{
String contentType = req.getContentType();
log.error( "No content type handler for content type {}", acceptContentType );
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Content-Type: " + acceptContentType + " not supported" );
return;
}
byte[] bytes = cacheService.retrieveByteArray( key );
log.debug( "return content size {} for key {}", ( bytes == null ? "null" : bytes.length ), key );
if ( bytes == null || bytes.length == 0 )
{
resp.sendError( HttpServletResponse.SC_NO_CONTENT, "No content for key: " + key );
return;
}
try
{
byte[] respBytes =
contentTypeHandler.handleGet( new DirectMemoryRequest().setKey( key ), bytes, resp, req );
resp.getOutputStream().write( respBytes );
}
catch ( DirectMemoryException e )
{
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage() );
}
}
protected ContentTypeHandler findGetCacheContentTypeHandler( HttpServletRequest req, HttpServletResponse response )
{
String acceptContentType = req.getHeader( "Accept" );
if ( StringUtils.contains( acceptContentType, MediaType.APPLICATION_JSON ) )
{
// application/json
return contentTypeHandlers.get( MediaType.APPLICATION_JSON );
}
else if ( StringUtils.startsWith( acceptContentType, MediaType.TEXT_PLAIN ) )
{
return contentTypeHandlers.get( MediaType.TEXT_PLAIN );
}
return contentTypeHandlers.get( acceptContentType );
}
/**
* protected for unit test reason
*
* @param path
* @return
*/
protected String retrieveKeyFromPath( String path )
{
if ( StringUtils.endsWith( path, "/" ) )
{
return StringUtils.substringAfterLast( StringUtils.substringBeforeLast( path, "/" ), "/" );
}
return StringUtils.substringAfterLast( path, "/" );
}
}