001package org.apache.commons.jcs.auxiliary.remote;
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.net.UnknownHostException;
024
025import org.apache.commons.jcs.access.exception.CacheException;
026import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
027import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
028import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
029import org.apache.commons.jcs.engine.behavior.ICacheElement;
030import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
031import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
032import org.apache.commons.jcs.engine.behavior.IElementSerializer;
033import org.apache.commons.jcs.engine.control.CompositeCacheManager;
034import org.apache.commons.jcs.utils.net.HostNameUtil;
035import org.apache.commons.jcs.utils.serialization.SerializationConversionUtil;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038
039/** Shared listener base. */
040public abstract class AbstractRemoteCacheListener<K, V>
041    implements IRemoteCacheListener<K, V>
042{
043    /** The logger */
044    private static final Log log = LogFactory.getLog( AbstractRemoteCacheListener.class );
045
046    /** The cached name of the local host. The remote server gets this for logging purposes. */
047    private static String localHostName = null;
048
049    /**
050     * The cache manager used to put items in different regions. This is set lazily and should not
051     * be sent to the remote server.
052     */
053    private ICompositeCacheManager cacheMgr;
054
055    /** The remote cache configuration object. */
056    private final IRemoteCacheAttributes irca;
057
058    /** This is set by the remote cache server. */
059    private long listenerId = 0;
060
061    /** Custom serializer. */
062    private IElementSerializer elementSerializer;
063
064    /**
065     * Only need one since it does work for all regions, just reference by multiple region names.
066     * <p>
067     * The constructor exports this object, making it available to receive incoming calls. The
068     * callback port is anonymous unless a local port value was specified in the configuration.
069     * <p>
070     * @param irca cache configuration
071     * @param cacheMgr the cache hub
072     * @param elementSerializer a custom serializer
073     */
074    public AbstractRemoteCacheListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
075    {
076        this.irca = irca;
077        this.cacheMgr = cacheMgr;
078        this.elementSerializer = elementSerializer;
079    }
080
081    /**
082     * Let the remote cache set a listener_id. Since there is only one listener for all the regions
083     * and every region gets registered? the id shouldn't be set if it isn't zero. If it is we
084     * assume that it is a reconnect.
085     * <p>
086     * @param id The new listenerId value
087     * @throws IOException
088     */
089    @Override
090    public void setListenerId( long id )
091        throws IOException
092    {
093        listenerId = id;
094        if ( log.isInfoEnabled() )
095        {
096            log.info( "set listenerId = [" + id + "]" );
097        }
098    }
099
100    /**
101     * Gets the listenerId attribute of the RemoteCacheListener object. This is stored in the
102     * object. The RemoteCache object contains a reference to the listener and get the id this way.
103     * <p>
104     * @return The listenerId value
105     * @throws IOException
106     */
107    @Override
108    public long getListenerId()
109        throws IOException
110    {
111        if ( log.isDebugEnabled() )
112        {
113            log.debug( "get listenerId = [" + listenerId + "]" );
114        }
115        return listenerId;
116
117    }
118
119    /**
120     * Gets the remoteType attribute of the RemoteCacheListener object
121     * <p>
122     * @return The remoteType value
123     * @throws IOException
124     */
125    @Override
126    public RemoteType getRemoteType()
127        throws IOException
128    {
129        if ( log.isDebugEnabled() )
130        {
131            log.debug( "getRemoteType = [" + irca.getRemoteType() + "]" );
132        }
133        return irca.getRemoteType();
134    }
135
136    /**
137     * If this is configured to remove on put, then remove the element since it has been updated
138     * elsewhere. cd should be incomplete for faster transmission. We don't want to pass data only
139     * invalidation. The next time it is used the local cache will get the new version from the
140     * remote store.
141     * <p>
142     * If remove on put is not configured, then update the item.
143     * @param cb
144     * @throws IOException
145     */
146    @Override
147    public void handlePut( ICacheElement<K, V> cb )
148        throws IOException
149    {
150        if ( irca.getRemoveUponRemotePut() )
151        {
152            if ( log.isDebugEnabled() )
153            {
154                log.debug( "PUTTING ELEMENT FROM REMOTE, (  invalidating ) " );
155            }
156            handleRemove( cb.getCacheName(), cb.getKey() );
157        }
158        else
159        {
160            if ( log.isDebugEnabled() )
161            {
162                log.debug( "PUTTING ELEMENT FROM REMOTE, ( updating ) " );
163                log.debug( "cb = " + cb );
164            }
165
166            // Eventually the instance of will not be necessary.
167            if ( cb instanceof ICacheElementSerialized )
168            {
169                if ( log.isDebugEnabled() )
170                {
171                    log.debug( "Object needs to be deserialized." );
172                }
173                try
174                {
175                    cb = SerializationConversionUtil.getDeSerializedCacheElement(
176                            (ICacheElementSerialized<K, V>) cb, this.elementSerializer );
177                    if ( log.isDebugEnabled() )
178                    {
179                        log.debug( "Deserialized result = " + cb );
180                    }
181                }
182                catch ( IOException e )
183                {
184                    throw e;
185                }
186                catch ( ClassNotFoundException e )
187                {
188                    log.error( "Received a serialized version of a class that we don't know about.", e );
189                }
190            }
191
192            getCacheManager().<K, V>getCache( cb.getCacheName() ).localUpdate( cb );
193        }
194    }
195
196    /**
197     * Calls localRemove on the CompositeCache.
198     * <p>
199     * @param cacheName
200     * @param key
201     * @throws IOException
202     */
203    @Override
204    public void handleRemove( String cacheName, K key )
205        throws IOException
206    {
207        if ( log.isDebugEnabled() )
208        {
209            log.debug( "handleRemove> cacheName=" + cacheName + ", key=" + key );
210        }
211
212        getCacheManager().<K, V>getCache( cacheName ).localRemove( key );
213    }
214
215    /**
216     * Calls localRemoveAll on the CompositeCache.
217     * <p>
218     * @param cacheName
219     * @throws IOException
220     */
221    @Override
222    public void handleRemoveAll( String cacheName )
223        throws IOException
224    {
225        if ( log.isDebugEnabled() )
226        {
227            log.debug( "handleRemoveAll> cacheName=" + cacheName );
228        }
229
230        getCacheManager().<K, V>getCache( cacheName ).localRemoveAll();
231    }
232
233    /**
234     * @param cacheName
235     * @throws IOException
236     */
237    @Override
238    public void handleDispose( String cacheName )
239        throws IOException
240    {
241        if ( log.isDebugEnabled() )
242        {
243            log.debug( "handleDispose> cacheName=" + cacheName );
244        }
245        // TODO consider what to do here, we really don't want to
246        // dispose, we just want to disconnect.
247        // just allow the cache to go into error recovery mode.
248        // getCacheManager().freeCache( cacheName, true );
249    }
250
251    /**
252     * Gets the cacheManager attribute of the RemoteCacheListener object. This is one of the few
253     * places that force the cache to be a singleton.
254     */
255    protected ICompositeCacheManager getCacheManager()
256    {
257        if ( cacheMgr == null )
258        {
259            try
260            {
261                cacheMgr = CompositeCacheManager.getInstance();
262
263                if ( log.isDebugEnabled() )
264                {
265                    log.debug( "had to get cacheMgr" );
266                    log.debug( "cacheMgr = " + cacheMgr );
267                }
268            }
269            catch (CacheException e)
270            {
271                log.error( "Could not get cacheMgr", e );
272            }
273        }
274        else
275        {
276            if ( log.isDebugEnabled() )
277            {
278                log.debug( "already got cacheMgr = " + cacheMgr );
279            }
280        }
281
282        return cacheMgr;
283    }
284
285    /**
286     * This is for debugging. It allows the remote server to log the address of clients.
287     * <p>
288     * @return String
289     * @throws IOException
290     */
291    @Override
292    public synchronized String getLocalHostAddress()
293        throws IOException
294    {
295        if ( localHostName == null )
296        {
297            try
298            {
299                localHostName = HostNameUtil.getLocalHostAddress();
300            }
301            catch ( UnknownHostException uhe )
302            {
303                localHostName = "unknown";
304            }
305        }
306        return localHostName;
307    }
308
309    /**
310     * For easier debugging.
311     * <p>
312     * @return Basic info on this listener.
313     */
314    @Override
315    public String toString()
316    {
317        StringBuilder buf = new StringBuilder();
318        buf.append( "\n AbstractRemoteCacheListener: " );
319        buf.append( "\n RemoteHost = " + irca.getRemoteLocation().toString() );
320        buf.append( "\n ListenerId = " + listenerId );
321        return buf.toString();
322    }
323}