1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.kernel;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.apache.openjpa.lib.util.Localizer;
32 import org.apache.openjpa.lib.util.ReferenceHashSet;
33 import org.apache.openjpa.util.Exceptions;
34 import org.apache.openjpa.util.InternalException;
35 import org.apache.openjpa.util.UserException;
36
37 /**
38 * Cache of managed objects. Must be static for serialization reasons.
39 */
40 class ManagedCache implements Serializable {
41
42 private static final Localizer _loc =
43 Localizer.forPackage(ManagedCache.class);
44
45 private Map _main; // oid -> sm
46 private Map _conflicts = null; // conflict oid -> new sm
47 private Map _news = null; // tmp id -> new sm
48 private Collection _embeds = null; // embedded/non-persistent sms
49 private Collection _untracked = null; // hard refs to untracked sms
50 private BrokerImpl broker;
51
52 /**
53 * Constructor; supply primary cache map.
54 */
55 ManagedCache(BrokerImpl broker) {
56 this.broker = broker;
57 _main = broker.newManagedObjectCache();
58 }
59
60 /**
61 * Return the instance for the given oid, optionally allowing
62 * new instances.
63 */
64 public StateManagerImpl getById(Object oid, boolean allowNew) {
65 if (oid == null)
66 return null;
67
68 // check main cache for oid
69 StateManagerImpl sm = (StateManagerImpl) _main.get(oid);
70 StateManagerImpl sm2;
71 if (sm != null) {
72 // if it's a new instance, we know it's the only match, because
73 // other pers instances override new instances in _cache
74 if (sm.isNew() && !sm.isDeleted())
75 return (allowNew) ? sm : null;
76 if (!allowNew || !sm.isDeleted())
77 return sm;
78
79 // sm is deleted; check conflict cache
80 if (_conflicts != null) {
81 sm2 = (StateManagerImpl) _conflicts.get(oid);
82 if (sm2 != null)
83 return sm2;
84 }
85 }
86
87 // at this point sm is null or deleted; check the new cache for
88 // any matches. this allows us to match app id objects to new
89 // instances without permanant oids
90 if (allowNew && _news != null && !_news.isEmpty()) {
91 sm2 = (StateManagerImpl) _news.get(oid);
92 if (sm2 != null)
93 return sm2;
94 }
95 return sm;
96 }
97
98 /**
99 * Call this method when a new state manager initializes itself.
100 */
101 public void add(StateManagerImpl sm) {
102 if (!sm.isIntercepting()) {
103 if (_untracked == null)
104 _untracked = new HashSet();
105 _untracked.add(sm);
106 }
107
108 if (!sm.isPersistent() || sm.isEmbedded()) {
109 if (_embeds == null)
110 _embeds = new ReferenceHashSet(ReferenceHashSet.WEAK);
111 _embeds.add(sm);
112 return;
113 }
114
115 // initializing new instance; put in new cache because won't have
116 // permanent oid yet
117 if (sm.isNew()) {
118 if (_news == null)
119 _news = new HashMap();
120 _news.put(sm.getId(), sm);
121 return;
122 }
123
124 // initializing persistent instance; put in main cache
125 StateManagerImpl orig = (StateManagerImpl) _main.put
126 (sm.getObjectId(), sm);
127 if (orig != null) {
128 _main.put(sm.getObjectId(), orig);
129 throw new UserException(_loc.get("dup-load", sm.getObjectId(),
130 Exceptions.toString(orig.getManagedInstance())))
131 .setFailedObject(sm.getManagedInstance());
132 }
133 }
134
135 /**
136 * Remove the given state manager from the cache when it transitions
137 * to transient.
138 */
139 public void remove(Object id, StateManagerImpl sm) {
140 // if it has a permanent oid, remove from main / conflict cache,
141 // else remove from embedded/nontrans cache, and if not there
142 // remove from new cache
143 Object orig;
144 if (sm.getObjectId() != null) {
145 orig = _main.remove(id);
146 if (orig != sm) {
147 if (orig != null)
148 _main.put(id, orig); // put back
149 if (_conflicts != null) {
150 orig = _conflicts.remove(id);
151 if (orig != null && orig != sm)
152 _conflicts.put(id, orig); // put back
153 }
154 }
155 } else if ((_embeds == null || !_embeds.remove(sm))
156 && _news != null) {
157 orig = _news.remove(id);
158 if (orig != null && orig != sm)
159 _news.put(id, orig); // put back
160 }
161
162 if (_untracked != null)
163 _untracked.remove(sm);
164 }
165
166 /**
167 * An embedded or nonpersistent managed instance has been persisted.
168 */
169 public void persist(StateManagerImpl sm) {
170 if (_embeds != null)
171 _embeds.remove(sm);
172 }
173
174 /**
175 * A new instance has just been assigned a permanent oid.
176 */
177 public void assignObjectId(Object id, StateManagerImpl sm) {
178 // if assigning oid, remove from new cache and put in primary; may
179 // not be in new cache if another new instance had same id
180 StateManagerImpl orig = null;
181 if (_news != null) {
182 orig = (StateManagerImpl) _news.remove(id);
183 if (orig != null && orig != sm)
184 _news.put(id, orig); // put back
185 }
186
187 // put in main cache, but make sure we don't replace another
188 // instance with the same oid
189 orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm);
190 if (orig != null) {
191 _main.put(sm.getObjectId(), orig);
192 if (!orig.isDeleted())
193 throw new UserException(_loc.get("dup-oid-assign",
194 sm.getObjectId(),
195 Exceptions.toString(sm.getManagedInstance())))
196 .setFailedObject(sm.getManagedInstance());
197
198 // same oid as deleted instance; put in conflict cache
199 if (_conflicts == null)
200 _conflicts = new HashMap();
201 _conflicts.put(sm.getObjectId(), sm);
202 }
203 }
204
205 /**
206 * A new instance has committed; recache under permanent oid.
207 */
208 public void commitNew(Object id, StateManagerImpl sm) {
209 // if the id didn't change, the instance was already assigned an
210 // id, but it could have been in conflict cache
211 StateManagerImpl orig;
212 if (sm.getObjectId() == id) {
213 orig = (_conflicts == null) ? null
214 : (StateManagerImpl) _conflicts.remove(id);
215 if (orig == sm) {
216 orig = (StateManagerImpl) _main.put(id, sm);
217 if (orig != null && !orig.isDeleted()) {
218 _main.put(sm.getObjectId(), orig);
219 throw new UserException(_loc.get("dup-oid-assign",
220 sm.getObjectId(), Exceptions.toString(
221 sm.getManagedInstance())))
222 .setFailedObject(sm.getManagedInstance())
223 .setFatal(true);
224 }
225 }
226 return;
227 }
228
229 // oid changed, so it must previously have been a new instance
230 // without an assigned oid. remove it from the new cache; ok if
231 // we end up removing another instance with same id
232 if (_news != null)
233 _news.remove(id);
234
235 // and put into main cache now that id is asssigned
236 orig = (StateManagerImpl) _main.put(sm.getObjectId(), sm);
237 if (orig != null && orig != sm && !orig.isDeleted()) {
238 // put back orig and throw error
239 _main.put(sm.getObjectId(), orig);
240 throw new UserException(_loc.get("dup-oid-assign",
241 sm.getObjectId(), Exceptions.toString(sm.getManagedInstance())))
242 .setFailedObject(sm.getManagedInstance()).setFatal(true);
243 }
244 }
245
246 /**
247 * Return a copy of all cached persistent objects.
248 */
249 public Collection copy() {
250 // proxies not included here because the state manager is always
251 // present in other caches too
252
253 int size = _main.size();
254 if (_conflicts != null)
255 size += _conflicts.size();
256 if (_news != null)
257 size += _news.size();
258 if (_embeds != null)
259 size += _embeds.size();
260 if (size == 0)
261 return Collections.EMPTY_LIST;
262
263 List copy = new ArrayList(size);
264 for (Iterator itr = _main.values().iterator(); itr.hasNext();)
265 copy.add(itr.next());
266 if (_conflicts != null && !_conflicts.isEmpty())
267 for (Iterator itr = _conflicts.values().iterator();
268 itr.hasNext();)
269 copy.add(itr.next());
270 if (_news != null && !_news.isEmpty())
271 for (Iterator itr = _news.values().iterator(); itr.hasNext();)
272 copy.add(itr.next());
273 if (_embeds != null && !_embeds.isEmpty())
274 for (Iterator itr = _embeds.iterator(); itr.hasNext();)
275 copy.add(itr.next());
276 return copy;
277 }
278
279 /**
280 * Clear the cache.
281 */
282 public void clear() {
283 _main = broker.newManagedObjectCache();
284 if (_conflicts != null)
285 _conflicts = null;
286 if (_news != null)
287 _news = null;
288 if (_embeds != null)
289 _embeds = null;
290 if (_untracked != null)
291 _untracked = null;
292 }
293
294 /**
295 * Clear new instances without permanent oids.
296 */
297 public void clearNew() {
298 if (_news != null)
299 _news = null;
300 }
301
302 void dirtyCheck() {
303 if (_untracked == null)
304 return;
305
306 for (Iterator iter = _untracked.iterator(); iter.hasNext(); )
307 ((StateManagerImpl) iter.next()).dirtyCheck();
308 }
309 }