001package org.apache.commons.jcs.auxiliary.remote.http.server;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.ObjectInputStream;
025import java.io.OutputStream;
026import java.io.Serializable;
027import java.util.HashMap;
028import java.util.Map;
029import java.util.Properties;
030import java.util.Set;
031
032import javax.servlet.ServletConfig;
033import javax.servlet.ServletException;
034import javax.servlet.http.HttpServlet;
035import javax.servlet.http.HttpServletRequest;
036import javax.servlet.http.HttpServletResponse;
037
038import org.apache.commons.jcs.access.exception.CacheException;
039import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
040import org.apache.commons.jcs.auxiliary.remote.http.behavior.IRemoteHttpCacheConstants;
041import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
042import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
043import org.apache.commons.jcs.engine.behavior.ICacheElement;
044import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
045import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
046import org.apache.commons.jcs.engine.control.CompositeCacheManager;
047import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
048import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
049import org.apache.commons.jcs.utils.config.PropertySetter;
050import org.apache.commons.jcs.utils.serialization.StandardSerializer;
051import org.apache.commons.logging.Log;
052import org.apache.commons.logging.LogFactory;
053
054/**
055 * This servlet simply reads and writes objects. The requests are packaged in a general wrapper. The
056 * processor works on the wrapper object and returns a response wrapper.
057 */
058public class RemoteHttpCacheServlet
059    extends HttpServlet
060{
061    /** Don't change. */
062    private static final long serialVersionUID = 8752849397531933346L;
063
064    /** The Logger. */
065    private static final Log log = LogFactory.getLog( RemoteHttpCacheServlet.class );
066
067    /** The cache manager */
068    private static CompositeCacheManager cacheMgr;
069
070    /** The service that does the work. */
071    private static ICacheServiceNonLocal<Serializable, Serializable> remoteCacheService;
072
073    /** This needs to be standard, since the other side is standard */
074    private final StandardSerializer serializer = new StandardSerializer();
075
076    /** Number of service calls. */
077    private int serviceCalls = 0;
078
079    /** The interval at which we will log the count. */
080    private final int logInterval = 100;
081
082    /**
083     * Initializes the cache.
084     * <p>
085     * This provides an easy extension point. Simply extend this servlet and override the init
086     * method to change the way the properties are loaded.
087     * @param config
088     * @throws ServletException
089     */
090    @Override
091    public void init( ServletConfig config )
092        throws ServletException
093    {
094        try
095        {
096            cacheMgr = CompositeCacheManager.getInstance();
097        }
098        catch (CacheException e)
099        {
100            throw new ServletException(e);
101        }
102
103        remoteCacheService = createRemoteHttpCacheService( cacheMgr );
104
105        super.init( config );
106    }
107
108    /**
109     * Read the request, call the processor, write the response.
110     * <p>
111     * @param request
112     * @param response
113     * @throws ServletException
114     * @throws IOException
115     */
116    @Override
117    public void service( HttpServletRequest request, HttpServletResponse response )
118        throws ServletException, IOException
119    {
120        incrementServiceCallCount();
121        if ( log.isDebugEnabled() )
122        {
123            log.debug( "Servicing a request. " + request );
124        }
125
126        RemoteCacheRequest<Serializable, Serializable> remoteRequest = readRequest( request );
127        RemoteCacheResponse<Object> cacheResponse = processRequest( remoteRequest );
128
129        writeResponse( response, cacheResponse );
130    }
131
132    /**
133     * Read the request from the input stream.
134     * <p>
135     * @param request
136     * @return RemoteHttpCacheRequest
137     */
138    protected RemoteCacheRequest<Serializable, Serializable> readRequest( HttpServletRequest request )
139    {
140        RemoteCacheRequest<Serializable, Serializable> remoteRequest = null;
141        try
142        {
143            InputStream inputStream = request.getInputStream();
144            if ( log.isDebugEnabled() )
145            {
146                log.debug( "After getting input stream and before reading it" );
147            }
148
149            remoteRequest = readRequestFromStream( inputStream );
150        }
151        catch ( Exception e )
152        {
153            log.error( "Could not get a RemoteHttpCacheRequest object from the input stream.", e );
154        }
155        return remoteRequest;
156    }
157
158    /**
159     * Reads the response from the stream and then closes it.
160     * <p>
161     * @param inputStream
162     * @return RemoteHttpCacheRequest
163     * @throws IOException
164     * @throws ClassNotFoundException
165     */
166    protected RemoteCacheRequest<Serializable, Serializable> readRequestFromStream( InputStream inputStream )
167        throws IOException, ClassNotFoundException
168    {
169        ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( inputStream, null );
170
171        @SuppressWarnings("unchecked") // Need to cast from Object
172        RemoteCacheRequest<Serializable, Serializable> remoteRequest
173            = (RemoteCacheRequest<Serializable, Serializable>) ois.readObject();
174        ois.close();
175        return remoteRequest;
176    }
177
178    /**
179     * Write the response to the output stream.
180     * <p>
181     * @param response
182     * @param cacheResponse
183     */
184    protected void writeResponse( HttpServletResponse response, RemoteCacheResponse<Object> cacheResponse )
185    {
186        try
187        {
188            response.setContentType( "application/octet-stream" );
189
190            byte[] responseAsByteAray = serializer.serialize( cacheResponse );
191            response.setContentLength( responseAsByteAray.length );
192
193            OutputStream outputStream = response.getOutputStream();
194            if ( log.isDebugEnabled() )
195            {
196                log.debug( "Opened output stream.  Response size: " + responseAsByteAray.length );
197            }
198            // WRITE
199            outputStream.write( responseAsByteAray );
200            outputStream.flush();
201            outputStream.close();
202        }
203        catch ( IOException e )
204        {
205            log.error( "Problem writing response. " + cacheResponse, e );
206        }
207    }
208
209    /**
210     * Processes the request. It will call the appropriate method on the service
211     * <p>
212     * @param request
213     * @return RemoteHttpCacheResponse, never null
214     */
215    protected RemoteCacheResponse<Object> processRequest( RemoteCacheRequest<Serializable, Serializable> request )
216    {
217        RemoteCacheResponse<Object> response = new RemoteCacheResponse<Object>();
218
219        if ( request == null )
220        {
221            String message = "The request is null. Cannot process";
222            log.warn( message );
223            response.setSuccess( false );
224            response.setErrorMessage( message );
225        }
226        else
227        {
228            try
229            {
230                switch ( request.getRequestType() )
231                {
232                    case GET:
233                        ICacheElement<Serializable, Serializable> element =
234                            remoteCacheService.get( request.getCacheName(), request.getKey(), request.getRequesterId() );
235                        response.setPayload(element);
236                        break;
237                    case GET_MULTIPLE:
238                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMap =
239                            remoteCacheService.getMultiple( request.getCacheName(), request.getKeySet(), request.getRequesterId() );
240                        if ( elementMap != null )
241                        {
242                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<Serializable, ICacheElement<Serializable, Serializable>>();
243                            map.putAll(elementMap);
244                            response.setPayload(map);
245                        }
246                        break;
247                    case GET_MATCHING:
248                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMapMatching =
249                            remoteCacheService.getMatching( request.getCacheName(), request.getPattern(), request.getRequesterId() );
250                        if ( elementMapMatching != null )
251                        {
252                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<Serializable, ICacheElement<Serializable, Serializable>>();
253                            map.putAll(elementMapMatching);
254                            response.setPayload(map);
255                        }
256                        break;
257                    case REMOVE:
258                        remoteCacheService.remove( request.getCacheName(), request.getKey(), request.getRequesterId() );
259                        break;
260                    case REMOVE_ALL:
261                        remoteCacheService.removeAll( request.getCacheName(), request.getRequesterId() );
262                        break;
263                    case UPDATE:
264                        remoteCacheService.update( request.getCacheElement(), request.getRequesterId() );
265                        break;
266                    case ALIVE_CHECK:
267                    case DISPOSE:
268                        response.setSuccess( true );
269                        // DO NOTHING
270                        break;
271                    case GET_KEYSET:
272                        Set<Serializable> keys = remoteCacheService.getKeySet( request.getCacheName() );
273                        response.setPayload( keys );
274                        break;
275                    default:
276                        String message = "Unknown event type.  Cannot process " + request;
277                        log.warn( message );
278                        response.setSuccess( false );
279                        response.setErrorMessage( message );
280                        break;
281                }
282            }
283            catch ( IOException e )
284            {
285                String message = "Problem processing request. " + request + " Error: " + e.getMessage();
286                log.error( message, e );
287                response.setSuccess( false );
288                response.setErrorMessage( message );
289            }
290        }
291
292        return response;
293    }
294
295    /**
296     * Configures the attributes and the event logger and constructs a service.
297     * <p>
298     * @param cacheManager
299     * @return RemoteHttpCacheService
300     */
301    protected <K, V> RemoteHttpCacheService<K, V> createRemoteHttpCacheService( ICompositeCacheManager cacheManager )
302    {
303        Properties props = cacheManager.getConfigurationProperties();
304        ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
305        RemoteHttpCacheServerAttributes attributes = configureRemoteHttpCacheServerAttributes( props );
306
307        RemoteHttpCacheService<K, V> service = new RemoteHttpCacheService<K, V>( cacheManager, attributes, cacheEventLogger );
308        if ( log.isInfoEnabled() )
309        {
310            log.info( "Created new RemoteHttpCacheService " + service );
311        }
312        return service;
313    }
314
315    /**
316     * Tries to get the event logger.
317     * <p>
318     * @param props
319     * @return ICacheEventLogger
320     */
321    protected ICacheEventLogger configureCacheEventLogger( Properties props )
322    {
323        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
324            .parseCacheEventLogger( props, IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_PREFIX );
325
326        return cacheEventLogger;
327    }
328
329    /**
330     * Configure.
331     * <p>
332     * jcs.remotehttpcache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
333     * <p>
334     * @param prop
335     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
336     */
337    protected RemoteHttpCacheServerAttributes configureRemoteHttpCacheServerAttributes( Properties prop )
338    {
339        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
340
341        // configure automatically
342        PropertySetter.setProperties( rcsa, prop,
343                                      IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
344
345        return rcsa;
346    }
347
348    /**
349     * @param rcs the remoteCacheService to set
350     */
351    protected void setRemoteCacheService(ICacheServiceNonLocal<Serializable, Serializable> rcs)
352    {
353        remoteCacheService = rcs;
354    }
355
356    /**
357     * Log some details.
358     */
359    private void incrementServiceCallCount()
360    {
361        // not thread safe, but it doesn't have to be accurate
362        serviceCalls++;
363        if ( log.isInfoEnabled() )
364        {
365            if ( serviceCalls % logInterval == 0 )
366            {
367                log.info( "serviceCalls = " + serviceCalls );
368            }
369        }
370    }
371
372    /** Release the cache manager. */
373    @Override
374    public void destroy()
375    {
376        if ( log.isInfoEnabled() )
377        {
378            log.info( "Servlet Destroyed, shutting down JCS." );
379        }
380
381        cacheMgr.shutDown();
382    }
383
384    /**
385     * Get servlet information
386     * <p>
387     * @return basic info
388     */
389    @Override
390    public String getServletInfo()
391    {
392        return "RemoteHttpCacheServlet";
393    }
394}