View Javadoc
1   package org.apache.commons.jcs.auxiliary.remote.http.server;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.ObjectInputStream;
25  import java.io.OutputStream;
26  import java.io.Serializable;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.Set;
31  
32  import javax.servlet.ServletConfig;
33  import javax.servlet.ServletException;
34  import javax.servlet.http.HttpServlet;
35  import javax.servlet.http.HttpServletRequest;
36  import javax.servlet.http.HttpServletResponse;
37  
38  import org.apache.commons.jcs.access.exception.CacheException;
39  import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
40  import org.apache.commons.jcs.auxiliary.remote.http.behavior.IRemoteHttpCacheConstants;
41  import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
42  import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
43  import org.apache.commons.jcs.engine.behavior.ICacheElement;
44  import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
45  import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
46  import org.apache.commons.jcs.engine.control.CompositeCacheManager;
47  import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
48  import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
49  import org.apache.commons.jcs.utils.config.PropertySetter;
50  import org.apache.commons.jcs.utils.serialization.StandardSerializer;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  /**
55   * This servlet simply reads and writes objects. The requests are packaged in a general wrapper. The
56   * processor works on the wrapper object and returns a response wrapper.
57   */
58  public class RemoteHttpCacheServlet
59      extends HttpServlet
60  {
61      /** Don't change. */
62      private static final long serialVersionUID = 8752849397531933346L;
63  
64      /** The Logger. */
65      private static final Log log = LogFactory.getLog( RemoteHttpCacheServlet.class );
66  
67      /** The cache manager */
68      private static CompositeCacheManager cacheMgr;
69  
70      /** The service that does the work. */
71      private static ICacheServiceNonLocal<Serializable, Serializable> remoteCacheService;
72  
73      /** This needs to be standard, since the other side is standard */
74      private final StandardSerializer serializer = new StandardSerializer();
75  
76      /** Number of service calls. */
77      private int serviceCalls = 0;
78  
79      /** The interval at which we will log the count. */
80      private final int logInterval = 100;
81  
82      /**
83       * Initializes the cache.
84       * <p>
85       * This provides an easy extension point. Simply extend this servlet and override the init
86       * method to change the way the properties are loaded.
87       * @param config
88       * @throws ServletException
89       */
90      @Override
91      public void init( ServletConfig config )
92          throws ServletException
93      {
94          try
95          {
96              cacheMgr = CompositeCacheManager.getInstance();
97          }
98          catch (CacheException e)
99          {
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 }