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