001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.kernel;
020:
021: import java.io.Serializable;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Set;
032:
033: import org.apache.commons.lang.StringUtils;
034: import org.apache.openjpa.conf.OpenJPAConfiguration;
035: import org.apache.openjpa.lib.rop.EagerResultList;
036: import org.apache.openjpa.lib.rop.ListResultObjectProvider;
037: import org.apache.openjpa.lib.rop.ResultList;
038: import org.apache.openjpa.lib.rop.ResultObjectProvider;
039: import org.apache.openjpa.lib.rop.SimpleResultList;
040: import org.apache.openjpa.lib.rop.WindowResultList;
041: import org.apache.openjpa.lib.util.Localizer;
042: import org.apache.openjpa.meta.ClassMetaData;
043: import org.apache.openjpa.meta.FetchGroup;
044: import org.apache.openjpa.meta.FieldMetaData;
045: import org.apache.openjpa.meta.JavaTypes;
046: import org.apache.openjpa.util.ImplHelper;
047: import org.apache.openjpa.util.InternalException;
048: import org.apache.openjpa.util.NoTransactionException;
049: import org.apache.openjpa.util.UserException;
050:
051: /**
052: * Allows configuration and optimization of how objects are loaded from
053: * the data store.
054: *
055: * @since 0.3.0
056: * @author Abe White
057: * @author Pinaki Poddar
058: * @nojavadoc
059: */
060: public class FetchConfigurationImpl implements FetchConfiguration,
061: Cloneable {
062:
063: private static final Localizer _loc = Localizer
064: .forPackage(FetchConfigurationImpl.class);
065:
066: /**
067: * Configurable state shared throughout a traversal chain.
068: */
069: protected static class ConfigurationState implements Serializable {
070: public transient StoreContext ctx = null;
071: public int fetchBatchSize = 0;
072: public int maxFetchDepth = 1;
073: public boolean queryCache = true;
074: public int flushQuery = 0;
075: public int lockTimeout = -1;
076: public int readLockLevel = LOCK_NONE;
077: public int writeLockLevel = LOCK_NONE;
078: public Set fetchGroups = null;
079: public Set fields = null;
080: public Set rootClasses;
081: public Set rootInstances;
082: public Map hints = null;
083: }
084:
085: private final ConfigurationState _state;
086: private FetchConfigurationImpl _parent;
087: private String _fromField;
088: private Class _fromType;
089: private String _directRelationOwner;
090: private boolean _load = true;
091: private int _availableRecursion;
092: private int _availableDepth;
093:
094: public FetchConfigurationImpl() {
095: this (null);
096: }
097:
098: protected FetchConfigurationImpl(ConfigurationState state) {
099: _state = (state == null) ? new ConfigurationState() : state;
100: _availableDepth = _state.maxFetchDepth;
101: }
102:
103: public StoreContext getContext() {
104: return _state.ctx;
105: }
106:
107: public void setContext(StoreContext ctx) {
108: // can't reset non-null context to another context
109: if (ctx != null && _state.ctx != null && ctx != _state.ctx)
110: throw new InternalException();
111: _state.ctx = ctx;
112: if (ctx == null)
113: return;
114:
115: // initialize to conf info
116: OpenJPAConfiguration conf = ctx.getConfiguration();
117: setFetchBatchSize(conf.getFetchBatchSize());
118: setFlushBeforeQueries(conf.getFlushBeforeQueriesConstant());
119: setLockTimeout(conf.getLockTimeout());
120: clearFetchGroups();
121: addFetchGroups(Arrays.asList(conf.getFetchGroupsList()));
122: setMaxFetchDepth(conf.getMaxFetchDepth());
123: }
124:
125: /**
126: * Clone this instance.
127: */
128: public Object clone() {
129: FetchConfigurationImpl clone = newInstance(null);
130: clone._state.ctx = _state.ctx;
131: clone._parent = _parent;
132: clone._fromField = _fromField;
133: clone._fromType = _fromType;
134: clone._directRelationOwner = _directRelationOwner;
135: clone._load = _load;
136: clone._availableRecursion = _availableRecursion;
137: clone._availableDepth = _availableDepth;
138: clone.copy(this );
139: return clone;
140: }
141:
142: /**
143: * Return a new hollow instance.
144: */
145: protected FetchConfigurationImpl newInstance(
146: ConfigurationState state) {
147: return new FetchConfigurationImpl(state);
148: }
149:
150: public void copy(FetchConfiguration fetch) {
151: setFetchBatchSize(fetch.getFetchBatchSize());
152: setMaxFetchDepth(fetch.getMaxFetchDepth());
153: setQueryCacheEnabled(fetch.getQueryCacheEnabled());
154: setFlushBeforeQueries(fetch.getFlushBeforeQueries());
155: setLockTimeout(fetch.getLockTimeout());
156: clearFetchGroups();
157: addFetchGroups(fetch.getFetchGroups());
158: clearFields();
159: addFields(fetch.getFields());
160:
161: // don't use setters because require active transaction
162: _state.readLockLevel = fetch.getReadLockLevel();
163: _state.writeLockLevel = fetch.getWriteLockLevel();
164: }
165:
166: public int getFetchBatchSize() {
167: return _state.fetchBatchSize;
168: }
169:
170: public FetchConfiguration setFetchBatchSize(int fetchBatchSize) {
171: if (fetchBatchSize == DEFAULT && _state.ctx != null)
172: fetchBatchSize = _state.ctx.getConfiguration()
173: .getFetchBatchSize();
174: if (fetchBatchSize != DEFAULT)
175: _state.fetchBatchSize = fetchBatchSize;
176: return this ;
177: }
178:
179: public int getMaxFetchDepth() {
180: return _state.maxFetchDepth;
181: }
182:
183: public FetchConfiguration setMaxFetchDepth(int depth) {
184: if (depth == DEFAULT && _state.ctx != null)
185: depth = _state.ctx.getConfiguration().getMaxFetchDepth();
186: if (depth != DEFAULT) {
187: _state.maxFetchDepth = depth;
188: if (_parent == null)
189: _availableDepth = depth;
190: }
191: return this ;
192: }
193:
194: public boolean getQueryCacheEnabled() {
195: return _state.queryCache;
196: }
197:
198: public FetchConfiguration setQueryCacheEnabled(boolean cache) {
199: _state.queryCache = cache;
200: return this ;
201: }
202:
203: public int getFlushBeforeQueries() {
204: return _state.flushQuery;
205: }
206:
207: public FetchConfiguration setFlushBeforeQueries(int flush) {
208: if (flush == DEFAULT && _state.ctx != null)
209: _state.flushQuery = _state.ctx.getConfiguration()
210: .getFlushBeforeQueriesConstant();
211: else if (flush != DEFAULT)
212: _state.flushQuery = flush;
213: return this ;
214: }
215:
216: public Set getFetchGroups() {
217: return (_state.fetchGroups == null) ? Collections.EMPTY_SET
218: : _state.fetchGroups;
219: }
220:
221: public boolean hasFetchGroup(String group) {
222: return _state.fetchGroups != null
223: && (_state.fetchGroups.contains(group) || _state.fetchGroups
224: .contains(FetchGroup.NAME_ALL));
225: }
226:
227: public FetchConfiguration addFetchGroup(String name) {
228: if (StringUtils.isEmpty(name))
229: throw new UserException(_loc.get("null-fg"));
230:
231: lock();
232: try {
233: if (_state.fetchGroups == null)
234: _state.fetchGroups = new HashSet();
235: _state.fetchGroups.add(name);
236: } finally {
237: unlock();
238: }
239: return this ;
240: }
241:
242: public FetchConfiguration addFetchGroups(Collection groups) {
243: if (groups == null || groups.isEmpty())
244: return this ;
245: for (Iterator itr = groups.iterator(); itr.hasNext();)
246: addFetchGroup((String) itr.next());
247: return this ;
248: }
249:
250: public FetchConfiguration removeFetchGroup(String group) {
251: lock();
252: try {
253: if (_state.fetchGroups != null)
254: _state.fetchGroups.remove(group);
255: } finally {
256: unlock();
257: }
258: return this ;
259: }
260:
261: public FetchConfiguration removeFetchGroups(Collection groups) {
262: lock();
263: try {
264: if (_state.fetchGroups != null)
265: _state.fetchGroups.removeAll(groups);
266: } finally {
267: unlock();
268: }
269: return this ;
270: }
271:
272: public FetchConfiguration clearFetchGroups() {
273: lock();
274: try {
275: if (_state.fetchGroups != null)
276: _state.fetchGroups.clear();
277: } finally {
278: unlock();
279: }
280: return this ;
281: }
282:
283: public FetchConfiguration resetFetchGroups() {
284: clearFetchGroups();
285: if (_state.ctx != null)
286: addFetchGroups(Arrays.asList(_state.ctx.getConfiguration()
287: .getFetchGroupsList()));
288: return this ;
289: }
290:
291: public Set getFields() {
292: return (_state.fields == null) ? Collections.EMPTY_SET
293: : _state.fields;
294: }
295:
296: public boolean hasField(String field) {
297: return _state.fields != null && _state.fields.contains(field);
298: }
299:
300: public FetchConfiguration addField(String field) {
301: if (StringUtils.isEmpty(field))
302: throw new UserException(_loc.get("null-field"));
303:
304: lock();
305: try {
306: if (_state.fields == null)
307: _state.fields = new HashSet();
308: _state.fields.add(field);
309: } finally {
310: unlock();
311: }
312: return this ;
313: }
314:
315: public FetchConfiguration addFields(Collection fields) {
316: if (fields == null || fields.isEmpty())
317: return this ;
318:
319: lock();
320: try {
321: if (_state.fields == null)
322: _state.fields = new HashSet();
323: _state.fields.addAll(fields);
324: } finally {
325: unlock();
326: }
327: return this ;
328: }
329:
330: public FetchConfiguration removeField(String field) {
331: lock();
332: try {
333: if (_state.fields != null)
334: _state.fields.remove(field);
335: } finally {
336: unlock();
337: }
338: return this ;
339: }
340:
341: public FetchConfiguration removeFields(Collection fields) {
342: lock();
343: try {
344: if (_state.fields != null)
345: _state.fields.removeAll(fields);
346: } finally {
347: unlock();
348: }
349: return this ;
350: }
351:
352: public FetchConfiguration clearFields() {
353: lock();
354: try {
355: if (_state.fields != null)
356: _state.fields.clear();
357: } finally {
358: unlock();
359: }
360: return this ;
361: }
362:
363: public int getLockTimeout() {
364: return _state.lockTimeout;
365: }
366:
367: public FetchConfiguration setLockTimeout(int timeout) {
368: if (timeout == DEFAULT && _state.ctx != null)
369: _state.lockTimeout = _state.ctx.getConfiguration()
370: .getLockTimeout();
371: else if (timeout != DEFAULT)
372: _state.lockTimeout = timeout;
373: return this ;
374: }
375:
376: public int getReadLockLevel() {
377: return _state.readLockLevel;
378: }
379:
380: public FetchConfiguration setReadLockLevel(int level) {
381: if (_state.ctx == null)
382: return this ;
383:
384: lock();
385: try {
386: assertActiveTransaction();
387: if (level == DEFAULT)
388: _state.readLockLevel = _state.ctx.getConfiguration()
389: .getReadLockLevelConstant();
390: else
391: _state.readLockLevel = level;
392: } finally {
393: unlock();
394: }
395: return this ;
396: }
397:
398: public int getWriteLockLevel() {
399: return _state.writeLockLevel;
400: }
401:
402: public FetchConfiguration setWriteLockLevel(int level) {
403: if (_state.ctx == null)
404: return this ;
405:
406: lock();
407: try {
408: assertActiveTransaction();
409: if (level == DEFAULT)
410: _state.writeLockLevel = _state.ctx.getConfiguration()
411: .getWriteLockLevelConstant();
412: else
413: _state.writeLockLevel = level;
414: } finally {
415: unlock();
416: }
417: return this ;
418: }
419:
420: public ResultList newResultList(ResultObjectProvider rop) {
421: if (rop instanceof ListResultObjectProvider)
422: return new SimpleResultList(rop);
423: if (_state.fetchBatchSize < 0)
424: return new EagerResultList(rop);
425: if (rop.supportsRandomAccess())
426: return new SimpleResultList(rop);
427: return new WindowResultList(rop);
428: }
429:
430: /**
431: * Throw an exception if no transaction is active.
432: */
433: private void assertActiveTransaction() {
434: if (_state.ctx != null && !_state.ctx.isActive())
435: throw new NoTransactionException(_loc.get("not-active"));
436: }
437:
438: public void setHint(String name, Object value) {
439: lock();
440: try {
441: if (_state.hints == null)
442: _state.hints = new HashMap();
443: _state.hints.put(name, value);
444: } finally {
445: unlock();
446: }
447: }
448:
449: public Object getHint(String name) {
450: return (_state.hints == null) ? null : _state.hints.get(name);
451: }
452:
453: public Set getRootClasses() {
454: return (_state.rootClasses == null) ? Collections.EMPTY_SET
455: : _state.rootClasses;
456: }
457:
458: public FetchConfiguration setRootClasses(Collection classes) {
459: lock();
460: try {
461: if (_state.rootClasses != null)
462: _state.rootClasses.clear();
463: if (classes != null && !classes.isEmpty()) {
464: if (_state.rootClasses == null)
465: _state.rootClasses = new HashSet(classes);
466: else
467: _state.rootClasses.addAll(classes);
468: }
469: } finally {
470: unlock();
471: }
472: return this ;
473: }
474:
475: public Set getRootInstances() {
476: return (_state.rootInstances == null) ? Collections.EMPTY_SET
477: : _state.rootInstances;
478: }
479:
480: public FetchConfiguration setRootInstances(Collection instances) {
481: lock();
482: try {
483: if (_state.rootInstances != null)
484: _state.rootInstances.clear();
485: if (instances != null && !instances.isEmpty()) {
486: if (_state.rootInstances == null)
487: _state.rootInstances = new HashSet(instances);
488: else
489: _state.rootInstances.addAll(instances);
490: }
491: } finally {
492: unlock();
493: }
494: return this ;
495: }
496:
497: public void lock() {
498: if (_state.ctx != null)
499: _state.ctx.lock();
500: }
501:
502: public void unlock() {
503: if (_state.ctx != null)
504: _state.ctx.unlock();
505: }
506:
507: /////////////
508: // Traversal
509: /////////////
510:
511: public int requiresFetch(FieldMetaData fm) {
512: if (!includes(fm))
513: return FETCH_NONE;
514:
515: Class type = getRelationType(fm);
516: if (type == null)
517: return FETCH_LOAD;
518: if (_availableDepth == 0)
519: return FETCH_NONE;
520:
521: // we can skip calculating recursion depth if this is a top-level conf:
522: // the field is in our fetch groups, so can't possibly not select
523: if (_parent == null)
524: return FETCH_LOAD;
525:
526: int rdepth = getAvailableRecursionDepth(fm, type, false);
527: if (rdepth != FetchGroup.DEPTH_INFINITE && rdepth <= 0)
528: return FETCH_NONE;
529:
530: if (StringUtils.equals(_directRelationOwner, fm.getFullName()))
531: return FETCH_REF;
532: return FETCH_LOAD;
533: }
534:
535: public boolean requiresLoad() {
536: return _load;
537: }
538:
539: public FetchConfiguration traverse(FieldMetaData fm) {
540: Class type = getRelationType(fm);
541: if (type == null)
542: return this ;
543:
544: FetchConfigurationImpl clone = newInstance(_state);
545: clone._parent = this ;
546: clone._availableDepth = reduce(_availableDepth);
547: clone._fromField = fm.getFullName(false);
548: clone._fromType = type;
549: clone._availableRecursion = getAvailableRecursionDepth(fm,
550: type, true);
551: if (StringUtils.equals(_directRelationOwner, fm.getFullName()))
552: clone._load = false;
553: else
554: clone._load = _load;
555:
556: FieldMetaData owner = fm.getMappedByMetaData();
557: if (owner != null && owner.getTypeCode() == JavaTypes.PC)
558: clone._directRelationOwner = owner.getFullName();
559:
560: return clone;
561: }
562:
563: /**
564: * Whether our configuration state includes the given field.
565: */
566: private boolean includes(FieldMetaData fmd) {
567: if ((fmd.isInDefaultFetchGroup() && hasFetchGroup(FetchGroup.NAME_DEFAULT))
568: || hasFetchGroup(FetchGroup.NAME_ALL)
569: || hasField(fmd.getFullName(false)))
570: return true;
571: String[] fgs = fmd.getCustomFetchGroups();
572: for (int i = 0; i < fgs.length; i++)
573: if (hasFetchGroup(fgs[i]))
574: return true;
575: return false;
576: }
577:
578: /**
579: * Return the available recursion depth via the given field for the
580: * given type.
581: *
582: * @param traverse whether we're traversing the field
583: */
584: private int getAvailableRecursionDepth(FieldMetaData fm,
585: Class type, boolean traverse) {
586: // see if there's a previous limit
587: int avail = Integer.MIN_VALUE;
588: for (FetchConfigurationImpl f = this ; f != null; f = f._parent) {
589: if (ImplHelper.isAssignable(f._fromType, type)) {
590: avail = f._availableRecursion;
591: if (traverse)
592: avail = reduce(avail);
593: break;
594: }
595: }
596: if (avail == 0)
597: return 0;
598:
599: // calculate fetch groups max
600: ClassMetaData meta = fm.getDefiningMetaData();
601: int max = Integer.MIN_VALUE;
602: if (fm.isInDefaultFetchGroup())
603: max = meta.getFetchGroup(FetchGroup.NAME_DEFAULT)
604: .getRecursionDepth(fm);
605: String[] groups = fm.getCustomFetchGroups();
606: int cur;
607: for (int i = 0; max != FetchGroup.DEPTH_INFINITE
608: && i < groups.length; i++) {
609: // ignore custom groups that are inactive in this configuration
610: if (!this .hasFetchGroup(groups[i]))
611: continue;
612: cur = meta.getFetchGroup(groups[i]).getRecursionDepth(fm);
613: if (cur == FetchGroup.DEPTH_INFINITE || cur > max)
614: max = cur;
615: }
616: // reduce max if we're traversing a self-type relation
617: if (traverse
618: && max != Integer.MIN_VALUE
619: && ImplHelper.isAssignable(meta.getDescribedType(),
620: type))
621: max = reduce(max);
622:
623: // take min/defined of previous avail and fetch group max
624: if (avail == Integer.MIN_VALUE && max == Integer.MIN_VALUE) {
625: int def = FetchGroup.RECURSION_DEPTH_DEFAULT;
626: return (traverse && ImplHelper.isAssignable(meta
627: .getDescribedType(), type)) ? def - 1 : def;
628: }
629: if (avail == Integer.MIN_VALUE
630: || avail == FetchGroup.DEPTH_INFINITE)
631: return max;
632: if (max == Integer.MIN_VALUE
633: || max == FetchGroup.DEPTH_INFINITE)
634: return avail;
635: return Math.min(max, avail);
636: }
637:
638: /**
639: * Return the relation type of the given field.
640: */
641: private static Class getRelationType(FieldMetaData fm) {
642: if (fm.isDeclaredTypePC())
643: return fm.getDeclaredType();
644: if (fm.getElement().isDeclaredTypePC())
645: return fm.getElement().getDeclaredType();
646: if (fm.getKey().isDeclaredTypePC())
647: return fm.getKey().getDeclaredType();
648: return null;
649: }
650:
651: /**
652: * Reduce the given logical depth by 1.
653: */
654: private static int reduce(int d) {
655: if (d == 0)
656: return 0;
657: if (d != FetchGroup.DEPTH_INFINITE)
658: d--;
659: return d;
660: }
661:
662: /////////////////
663: // Debug methods
664: /////////////////
665:
666: FetchConfiguration getParent() {
667: return _parent;
668: }
669:
670: boolean isRoot() {
671: return _parent == null;
672: }
673:
674: FetchConfiguration getRoot() {
675: return (isRoot()) ? this : _parent.getRoot();
676: }
677:
678: int getAvailableFetchDepth() {
679: return _availableDepth;
680: }
681:
682: int getAvailableRecursionDepth() {
683: return _availableRecursion;
684: }
685:
686: String getTraversedFromField() {
687: return _fromField;
688: }
689:
690: Class getTraversedFromType() {
691: return _fromType;
692: }
693:
694: List getPath() {
695: if (isRoot())
696: return Collections.EMPTY_LIST;
697: return trackPath(new ArrayList());
698: }
699:
700: List trackPath(List path) {
701: if (_parent != null)
702: _parent.trackPath(path);
703: path.add(this );
704: return path;
705: }
706:
707: public String toString() {
708: return "FetchConfiguration@" + System.identityHashCode(this )
709: + " (" + _availableDepth + ")" + getPathString();
710: }
711:
712: private String getPathString() {
713: List path = getPath();
714: if (path.isEmpty())
715: return "";
716: StringBuffer buf = new StringBuffer().append(": ");
717: for (Iterator itr = path.iterator(); itr.hasNext();) {
718: buf.append(((FetchConfigurationImpl) itr.next())
719: .getTraversedFromField());
720: if (itr.hasNext())
721: buf.append("->");
722: }
723: return buf.toString();
724: }
725: }
|