001    package org.apache.ojb.broker.cache;
002    
003    /* Copyright 2004-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.Map;
021    
022    import org.apache.ojb.broker.Identity;
023    import org.apache.ojb.broker.util.logging.Logger;
024    import org.apache.ojb.broker.util.logging.LoggerFactory;
025    
026    /**
027     * A wrapper class for {@link ObjectCache} implementations used to materialize object graphs and
028     * push the fully materialized object to the real object cache.
029     * To avoid passing of partial materialized objects to cache this class act as a temporary storage
030     * for unmaterialized (new read or refreshed) objects.
031     *
032     * @author <a href="mailto:arminw@apache.org">Armin Waibel</a>
033     * @version $Id: MaterializationCache.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
034     */
035    public class MaterializationCache implements ObjectCacheInternal
036    {
037        private static Logger log = LoggerFactory.getLogger(MaterializationCache.class);
038    
039        private CacheDistributor cacheDistributor;
040        private HashMap objectBuffer;
041        private boolean enabledReadCache;
042        private int invokeCounter;
043    
044        MaterializationCache(CacheDistributor cache)
045        {
046            this.cacheDistributor = cache;
047            this.objectBuffer = new HashMap();
048            enabledReadCache = false;
049        }
050    
051        /**
052         * Returns <em>true</em> if the materialisation cache is enabled, otherwise <em>false</em>.
053         */
054        public boolean isEnabledMaterialisationCache()
055        {
056            return enabledReadCache;
057        }
058    
059        /**
060         * For internal use only! Helper method to guarantee that only full materialized objects
061         * will be pushed to the application cache regardless if an local PB transaction
062         * is running or not. When a complex object is materialized there will be
063         * nested calls to the same PB instance methods, e.g. materialization of a referenced
064         * object which itself have several references, ...
065         * <br/>
066         * This method and {@link #disableMaterializationCache()} are used to delimit nested calls
067         * and to detect the end of an object materialization and avoid endless loops on circular
068         * references.
069         * <br/>
070         * If an code block with 'enabledMaterializationCache' throws an exception, in catch-block
071         * method {@link #doLocalClear()} have to be called.
072         */
073        public void enableMaterializationCache()
074        {
075            ++invokeCounter;
076            enabledReadCache = true;
077        }
078    
079        /**
080         * @see #enableMaterializationCache()
081         */
082        public void disableMaterializationCache()
083        {
084            if(!enabledReadCache) return;
085    
086            --invokeCounter;
087            /*
088            if materialization of the requested object was completed, the
089            counter represents '0' and we push the object
090            to the application cache
091            */
092            if(invokeCounter == 0)
093            {
094                try
095                {
096                    if(log.isDebugEnabled())
097                    {
098                        log.debug("Materialisation of object is finished, push "
099                                + objectBuffer.size() + "objects to cache");
100                    }
101                    pushObjects();
102                }
103                finally
104                {
105                    doLocalClear();
106                }
107            }
108        }
109    
110        public void doInternalCache(Identity oid, Object obj, int type)
111        {
112            // if OJB try to build an object graph put objects in local cache
113            // else use the application cache
114            if(enabledReadCache)
115            {
116                doLocalCache(oid, obj, type);
117            }
118            else
119            {
120                cacheDistributor.doInternalCache(oid, obj, type);
121            }
122        }
123    
124        public void cache(Identity oid, Object obj)
125        {
126            doInternalCache(oid, obj, TYPE_UNKNOWN);
127        }
128    
129        /**
130         * @see ObjectCacheInternal#cacheIfNew(org.apache.ojb.broker.Identity, Object)
131         */ 
132        public boolean cacheIfNew(Identity oid, Object obj)
133        {
134            boolean result = cacheDistributor.cacheIfNew(oid, obj);
135            if(enabledReadCache)
136            {
137                doLocalCache(oid, obj, TYPE_CACHED_READ);
138            }
139            return result;
140        }
141    
142        public Object lookup(Identity oid)
143        {
144            Object result = null;
145            if(enabledReadCache)
146            {
147                result = doLocalLookup(oid);
148            }
149            if(result == null)
150            {
151                result = cacheDistributor.lookup(oid);
152            }
153            return result;
154        }
155    
156        public Object doLocalLookup(Identity oid)
157        {
158            ObjectEntry entry = (ObjectEntry) objectBuffer.get(oid);
159            return entry != null ? entry.obj : null;
160        }
161    
162        public void remove(Identity oid)
163        {
164            doLocalRemove(oid);
165            cacheDistributor.remove(oid);
166        }
167    
168        public void doLocalRemove(Identity oid)
169        {
170            objectBuffer.remove(oid);
171        }
172    
173        /**
174         * Clears the internal used cache for object materialization.
175         */
176        public void doLocalClear()
177        {
178            if(log.isDebugEnabled()) log.debug("Clear materialization cache");
179            invokeCounter = 0;
180            enabledReadCache = false;
181            objectBuffer.clear();
182        }
183    
184        public void clear()
185        {
186            if(log.isDebugEnabled()) log.debug("Clear used caches");
187            doLocalClear();
188            cacheDistributor.clear();
189        }
190    
191        private void doLocalCache(Identity oid, Object obj, int type)
192        {
193            objectBuffer.put(oid, new ObjectEntry(obj, type));
194        }
195    
196        private void pushObjects()
197        {
198            Iterator it = objectBuffer.entrySet().iterator();
199            Map.Entry entry;
200            ObjectEntry oe;
201            while(it.hasNext())
202            {
203                entry = (Map.Entry) it.next();
204                oe = (ObjectEntry) entry.getValue();
205                /*
206                never push temporary object to a higher level cache
207                */
208                if(oe.type != TYPE_TEMP)
209                {
210                    if(log.isDebugEnabled()) log.debug("Push to cache: " + entry.getKey());
211                    cacheDistributor.doInternalCache((Identity) entry.getKey(), oe.obj, oe.type);
212                }
213            }
214        }
215    
216        //===========================================================
217        // inner class
218        //===========================================================
219    
220        static final class ObjectEntry
221        {
222            Object obj;
223            int type;
224    
225            public ObjectEntry(Object obj, int type)
226            {
227                this.obj = obj;
228                this.type = type;
229            }
230        }
231    }