package org.apache.commons.jcs.auxiliary.remote.http.server; /* * 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 java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.jcs.access.exception.CacheException; import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator; import org.apache.commons.jcs.auxiliary.remote.http.behavior.IRemoteHttpCacheConstants; import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest; import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse; import org.apache.commons.jcs.engine.behavior.ICacheElement; import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal; import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager; import org.apache.commons.jcs.engine.control.CompositeCacheManager; import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger; import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware; import org.apache.commons.jcs.utils.config.PropertySetter; import org.apache.commons.jcs.utils.serialization.StandardSerializer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This servlet simply reads and writes objects. The requests are packaged in a general wrapper. The * processor works on the wrapper object and returns a response wrapper. */ public class RemoteHttpCacheServlet extends HttpServlet { /** Don't change. */ private static final long serialVersionUID = 8752849397531933346L; /** The Logger. */ private static final Log log = LogFactory.getLog( RemoteHttpCacheServlet.class ); /** The cache manager */ private static CompositeCacheManager cacheMgr; /** The service that does the work. */ private static ICacheServiceNonLocal<Serializable, Serializable> remoteCacheService; /** This needs to be standard, since the other side is standard */ private final StandardSerializer serializer = new StandardSerializer(); /** Number of service calls. */ private int serviceCalls = 0; /** The interval at which we will log the count. */ private final int logInterval = 100; /** * Initializes the cache. * <p> * This provides an easy extension point. Simply extend this servlet and override the init * method to change the way the properties are loaded. * @param config * @throws ServletException */ @Override public void init( ServletConfig config ) throws ServletException { try { cacheMgr = CompositeCacheManager.getInstance(); } catch (CacheException e) { throw new ServletException(e); } remoteCacheService = createRemoteHttpCacheService( cacheMgr ); super.init( config ); } /** * Read the request, call the processor, write the response. * <p> * @param request * @param response * @throws ServletException * @throws IOException */ @Override public void service( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { incrementServiceCallCount(); if ( log.isDebugEnabled() ) { log.debug( "Servicing a request. " + request ); } RemoteCacheRequest<Serializable, Serializable> remoteRequest = readRequest( request ); RemoteCacheResponse<Object> cacheResponse = processRequest( remoteRequest ); writeResponse( response, cacheResponse ); } /** * Read the request from the input stream. * <p> * @param request * @return RemoteHttpCacheRequest */ protected RemoteCacheRequest<Serializable, Serializable> readRequest( HttpServletRequest request ) { RemoteCacheRequest<Serializable, Serializable> remoteRequest = null; try { InputStream inputStream = request.getInputStream(); if ( log.isDebugEnabled() ) { log.debug( "After getting input stream and before reading it" ); } remoteRequest = readRequestFromStream( inputStream ); } catch ( Exception e ) { log.error( "Could not get a RemoteHttpCacheRequest object from the input stream.", e ); } return remoteRequest; } /** * Reads the response from the stream and then closes it. * <p> * @param inputStream * @return RemoteHttpCacheRequest * @throws IOException * @throws ClassNotFoundException */ protected RemoteCacheRequest<Serializable, Serializable> readRequestFromStream( InputStream inputStream ) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( inputStream, null ); @SuppressWarnings("unchecked") // Need to cast from Object RemoteCacheRequest<Serializable, Serializable> remoteRequest = (RemoteCacheRequest<Serializable, Serializable>) ois.readObject(); ois.close(); return remoteRequest; } /** * Write the response to the output stream. * <p> * @param response * @param cacheResponse */ protected void writeResponse( HttpServletResponse response, RemoteCacheResponse<Object> cacheResponse ) { try { response.setContentType( "application/octet-stream" ); byte[] responseAsByteAray = serializer.serialize( cacheResponse ); response.setContentLength( responseAsByteAray.length ); OutputStream outputStream = response.getOutputStream(); if ( log.isDebugEnabled() ) { log.debug( "Opened output stream. Response size: " + responseAsByteAray.length ); } // WRITE outputStream.write( responseAsByteAray ); outputStream.flush(); outputStream.close(); } catch ( IOException e ) { log.error( "Problem writing response. " + cacheResponse, e ); } } /** * Processes the request. It will call the appropriate method on the service * <p> * @param request * @return RemoteHttpCacheResponse, never null */ protected RemoteCacheResponse<Object> processRequest( RemoteCacheRequest<Serializable, Serializable> request ) { RemoteCacheResponse<Object> response = new RemoteCacheResponse<Object>(); if ( request == null ) { String message = "The request is null. Cannot process"; log.warn( message ); response.setSuccess( false ); response.setErrorMessage( message ); } else { try { switch ( request.getRequestType() ) { case GET: ICacheElement<Serializable, Serializable> element = remoteCacheService.get( request.getCacheName(), request.getKey(), request.getRequesterId() ); response.setPayload(element); break; case GET_MULTIPLE: Map<Serializable, ICacheElement<Serializable, Serializable>> elementMap = remoteCacheService.getMultiple( request.getCacheName(), request.getKeySet(), request.getRequesterId() ); if ( elementMap != null ) { Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<Serializable, ICacheElement<Serializable, Serializable>>(); map.putAll(elementMap); response.setPayload(map); } break; case GET_MATCHING: Map<Serializable, ICacheElement<Serializable, Serializable>> elementMapMatching = remoteCacheService.getMatching( request.getCacheName(), request.getPattern(), request.getRequesterId() ); if ( elementMapMatching != null ) { Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<Serializable, ICacheElement<Serializable, Serializable>>(); map.putAll(elementMapMatching); response.setPayload(map); } break; case REMOVE: remoteCacheService.remove( request.getCacheName(), request.getKey(), request.getRequesterId() ); break; case REMOVE_ALL: remoteCacheService.removeAll( request.getCacheName(), request.getRequesterId() ); break; case UPDATE: remoteCacheService.update( request.getCacheElement(), request.getRequesterId() ); break; case ALIVE_CHECK: case DISPOSE: response.setSuccess( true ); // DO NOTHING break; case GET_KEYSET: Set<Serializable> keys = remoteCacheService.getKeySet( request.getCacheName() ); response.setPayload( keys ); break; default: String message = "Unknown event type. Cannot process " + request; log.warn( message ); response.setSuccess( false ); response.setErrorMessage( message ); break; } } catch ( IOException e ) { String message = "Problem processing request. " + request + " Error: " + e.getMessage(); log.error( message, e ); response.setSuccess( false ); response.setErrorMessage( message ); } } return response; } /** * Configures the attributes and the event logger and constructs a service. * <p> * @param cacheManager * @return RemoteHttpCacheService */ protected <K, V> RemoteHttpCacheService<K, V> createRemoteHttpCacheService( ICompositeCacheManager cacheManager ) { Properties props = cacheManager.getConfigurationProperties(); ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props ); RemoteHttpCacheServerAttributes attributes = configureRemoteHttpCacheServerAttributes( props ); RemoteHttpCacheService<K, V> service = new RemoteHttpCacheService<K, V>( cacheManager, attributes, cacheEventLogger ); if ( log.isInfoEnabled() ) { log.info( "Created new RemoteHttpCacheService " + service ); } return service; } /** * Tries to get the event logger. * <p> * @param props * @return ICacheEventLogger */ protected ICacheEventLogger configureCacheEventLogger( Properties props ) { ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator .parseCacheEventLogger( props, IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_PREFIX ); return cacheEventLogger; } /** * Configure. * <p> * jcs.remotehttpcache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE * <p> * @param prop * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes */ protected RemoteHttpCacheServerAttributes configureRemoteHttpCacheServerAttributes( Properties prop ) { RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes(); // configure automatically PropertySetter.setProperties( rcsa, prop, IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." ); return rcsa; } /** * @param rcs the remoteCacheService to set */ protected void setRemoteCacheService(ICacheServiceNonLocal<Serializable, Serializable> rcs) { remoteCacheService = rcs; } /** * Log some details. */ private void incrementServiceCallCount() { // not thread safe, but it doesn't have to be accurate serviceCalls++; if ( log.isInfoEnabled() ) { if ( serviceCalls % logInterval == 0 ) { log.info( "serviceCalls = " + serviceCalls ); } } } /** Release the cache manager. */ @Override public void destroy() { if ( log.isInfoEnabled() ) { log.info( "Servlet Destroyed, shutting down JCS." ); } cacheMgr.shutDown(); } /** * Get servlet information * <p> * @return basic info */ @Override public String getServletInfo() { return "RemoteHttpCacheServlet"; } }