001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdo;
012:
013: import com.versant.core.common.Debug;
014: import com.versant.core.metadata.ClassMetaData;
015: import com.versant.core.metadata.FetchGroup;
016: import com.versant.core.metadata.MDStatics;
017: import com.versant.core.metadata.parser.JdoExtension;
018: import com.versant.core.metadata.parser.JdoExtensionKeys;
019: import com.versant.core.metadata.parser.JdoQuery;
020: import com.versant.core.util.CharBuf;
021: import com.versant.core.util.IntArray;
022:
023: import javax.jdo.Extent;
024: import java.io.Externalizable;
025: import java.io.IOException;
026: import java.io.ObjectInput;
027: import java.io.ObjectOutput;
028: import java.util.Collection;
029:
030: import com.versant.core.common.BindingSupportImpl;
031:
032: /**
033: * All the information required to compile a query.
034: */
035: public final class QueryDetails implements Externalizable,
036: ParamDeclarationParser.Handler {
037:
038: private static final int DEFAULT_BATCH_SIZE = 50;
039:
040: public static final int LANGUAGE_JDOQL = 1;
041: public static final int LANGUAGE_SQL = 2;
042: public static final int LANGUAGE_OQL = 3;
043: public static final int LANGUAGE_EJBQL = 4;
044:
045: public static final int NOT_SET = 0;
046: public static final int FALSE = 1;
047: public static final int TRUE = 2;
048:
049: private int language;
050: private Class candidateClass;
051: private boolean subClasses = true;
052: private transient Collection col;
053: private String filter;
054: private String imports;
055: private String variables;
056: private String ordering;
057: private String result;
058: private String grouping;
059: private int unique;
060: private boolean ignoreCache;
061: private boolean useIgnoreCacheFromPM;
062:
063: private int paramCount;
064: private String[] paramTypes;
065: private String[] paramNames;
066: // If the query parameters were declared with the jdoGenieOptions parameter
067: // then this is its index in the parameter list.
068: private int optionsParamIndex = -1;
069:
070: private int fetchGroupIndex;
071: private boolean randomAccess;
072: private boolean countOnSize;
073: private boolean bounded;
074:
075: private int resultBatchSize = -1;
076: private int maxResultCount = -1;
077:
078: private int[] extraEvictClasses; // indexes of extra eviction trigger classes
079: private int cacheable; // cache results override (NOT_SET, FALSE, TRUE)
080:
081: public QueryDetails() {
082: }
083:
084: /**
085: * Create from a query definition in the meta data.
086: */
087: public QueryDetails(ClassMetaData cmd, JdoQuery q) {
088: if (q.sql != null) {
089: language = LANGUAGE_SQL;
090: filter = q.sql;
091: } else {
092: language = LANGUAGE_JDOQL;
093: filter = q.filter;
094: }
095: candidateClass = cmd.cls;
096: subClasses = q.includeSubclasses != MDStatics.FALSE;
097: if (useIgnoreCacheFromPM = q.ignoreCache == MDStatics.NOT_SET) {
098: ignoreCache = false;
099: } else {
100: ignoreCache = q.ignoreCache == MDStatics.TRUE;
101: }
102: imports = q.imports;
103: variables = q.variables;
104: ordering = q.ordering;
105: result = q.result;
106: grouping = q.grouping;
107: unique = q.unique;
108: if (q.extensions != null) {
109: for (int i = 0; i < q.extensions.length; i++) {
110: JdoExtension e = q.extensions[i];
111: switch (e.key) {
112: case JdoExtensionKeys.FETCH_GROUP:
113: FetchGroup fg = cmd.getFetchGroup(e.getString());
114: if (fg == null) {
115: throw BindingSupportImpl.getInstance().runtime(
116: "Query fetch group " + "not found on "
117: + cmd.qname + ": " + e + "\n"
118: + e.getContext());
119: }
120: fetchGroupIndex = fg.index;
121: break;
122: case JdoExtensionKeys.RANDOM_ACCESS:
123: randomAccess = e.getBoolean();
124: break;
125: case JdoExtensionKeys.COUNT_STAR_ON_SIZE:
126: countOnSize = e.getBoolean();
127: break;
128: case JdoExtensionKeys.MAX_ROWS:
129: maxResultCount = e.getInt();
130: break;
131: case JdoExtensionKeys.FETCH_SIZE:
132: resultBatchSize = e.getInt();
133: break;
134: case JdoExtensionKeys.BOUNDED:
135: bounded = e.getBoolean();
136: break;
137: case JdoExtensionKeys.EVICTION_CLASSES:
138: extraEvictClasses = processEvictionClassesExt(cmd,
139: e);
140: break;
141: case JdoExtensionKeys.CACHEABLE:
142: setCacheable(e.getBoolean());
143: break;
144: case JdoExtension.QUERY_PARAM_VALUES:
145: //ignore
146: break;
147: default:
148: throw createExtNotAllowed(e);
149: }
150: }
151: }
152: try {
153: declareParameters(q.parameters);
154: } catch (RuntimeException e) {
155: if (BindingSupportImpl.getInstance().isOwnException(e)) {
156: throw BindingSupportImpl.getInstance().runtime(
157: "Invalid parameter declaration: " + e + "\n"
158: + q.getContext(), e);
159: } else {
160: throw e;
161: }
162: }
163: }
164:
165: public int getLanguage() {
166: return language;
167: }
168:
169: public void setLanguage(int language) {
170: this .language = language;
171: }
172:
173: public String getLanguageStr() {
174: switch (language) {
175: case LANGUAGE_JDOQL:
176: return "JDOQL";
177: case LANGUAGE_SQL:
178: return "SQL";
179: case LANGUAGE_OQL:
180: return "OQL";
181: case LANGUAGE_EJBQL:
182: return "EJBQL";
183: }
184: return "UNKNOWN(" + language + ")";
185: }
186:
187: public int getUnique() {
188: return unique;
189: }
190:
191: public void setUnique(boolean unique) {
192: this .unique = unique ? TRUE : FALSE;
193: }
194:
195: public String getGrouping() {
196: return grouping;
197: }
198:
199: public void setGrouping(String grouping) {
200: this .grouping = grouping;
201: }
202:
203: public String getResult() {
204: return result;
205: }
206:
207: public void setResult(String result) {
208: this .result = result;
209: }
210:
211: private RuntimeException createExtNotAllowed(JdoExtension e) {
212: return BindingSupportImpl.getInstance().runtime(
213: "Extension not allowed here: " + e + "\n"
214: + e.getContext());
215: }
216:
217: private int[] processEvictionClassesExt(ClassMetaData cmd,
218: JdoExtension ext) {
219: boolean includeSubclasses = ext.getBoolean();
220: if (ext.nested == null)
221: return null;
222: IntArray ans = new IntArray();
223: for (int i = 0; i < ext.nested.length; i++) {
224: JdoExtension e = ext.nested[i];
225: if (e.key != JdoExtensionKeys.CLASS)
226: throw createExtNotAllowed(e);
227: String name = e.getString();
228: ClassMetaData target = cmd.jmd.getClassMetaData(cmd, name);
229: if (target == null) {
230: throw BindingSupportImpl.getInstance().runtime(
231: "Class does not exist or "
232: + "is not persistent: " + e + "\n"
233: + e.getContext());
234: }
235: if (includeSubclasses) {
236: addIndexesForHeirachy(target, ans);
237: } else {
238: ans.add(target.index);
239: }
240: }
241: return ans.toArray();
242: }
243:
244: private void addIndexesForHeirachy(ClassMetaData root, IntArray ans) {
245: ans.add(root.index);
246: if (root.pcSubclasses == null)
247: return;
248: for (int i = 0; i < root.pcSubclasses.length; i++) {
249: addIndexesForHeirachy(root.pcSubclasses[i], ans);
250: }
251: }
252:
253: public boolean includeSubClasses() {
254: return subClasses;
255: }
256:
257: public void setSubClasses(boolean subClasses) {
258: this .subClasses = subClasses;
259: }
260:
261: public QueryDetails(QueryDetails qParams) {
262: this .fillFrom(qParams);
263: }
264:
265: public int getResultBatchSize() {
266: return resultBatchSize;
267: }
268:
269: /**
270: * This will return true if the maxResults is smaller of equal to the batch
271: * size.
272: * <p/>
273: * This method is used to determine if all the results can be fetched at once.
274: *
275: * @return
276: */
277: public boolean prefetchAll() {
278: return maxResultCount == resultBatchSize;
279: }
280:
281: public void setResultBatchSize(int value) {
282: if (value <= 0) {
283: throw BindingSupportImpl.getInstance().invalidOperation(
284: "The batch size must be greater than zero");
285: }
286: this .resultBatchSize = value;
287: }
288:
289: public int getMaxResultCount() {
290: return maxResultCount;
291: }
292:
293: /**
294: * Set the max results returned for this query.
295: * Setting it to zero is the same as unsetting it(no limit).
296: *
297: * @param value
298: */
299: public void setMaxResultCount(int value) {
300: if (value < 0) {
301: throw BindingSupportImpl
302: .getInstance()
303: .invalidOperation(
304: "The query max result count must be greater or equal to zero");
305: }
306: this .maxResultCount = value;
307: }
308:
309: /**
310: * This is called just before the query is executed. It updates the
311: * batch and max result counts.
312: */
313: public void updateCounts() {
314: if (resultBatchSize == -1) {
315: if (maxResultCount == -1) {
316: //nothing set by user
317: resultBatchSize = DEFAULT_BATCH_SIZE;
318: } else {
319: //user only set the maxResults therefore update the batch size to
320: //be the same
321: resultBatchSize = maxResultCount;
322: }
323: } else {
324: //the user did set the batch size
325: //if the maxResults is set and it is smaller than the batch
326: //then update the batch size to be the same as the max.
327: if (maxResultCount != -1
328: && maxResultCount < resultBatchSize) {
329: resultBatchSize = maxResultCount;
330: }
331: }
332: }
333:
334: /**
335: * Take into acount 'parallelCollectionFetch', 'randomAccess' and 'fg.canUseParallelFetch'
336: * to determine if parallel collection fetch should happen.
337: */
338: public static boolean enableParallelCollectionFetch(
339: QueryDetails qp, FetchGroup fg) {
340: return qp.bounded && !qp.randomAccess
341: && fg.canUseParallelFetch();
342: }
343:
344: public boolean isBounded() {
345: return bounded;
346: }
347:
348: public void setBounded(boolean value) {
349: bounded = value;
350: }
351:
352: public Class getCandidateClass() {
353: return candidateClass;
354: }
355:
356: public void setCandidateClass(Class candidateClass) {
357: this .candidateClass = candidateClass;
358: }
359:
360: public void setExtent(Extent extent) {
361: if (Debug.DEBUG) {
362: if (extent == null) {
363: throw BindingSupportImpl.getInstance().internal(
364: "Setting extent to null is not supported");
365: }
366: }
367: candidateClass = extent.getCandidateClass();
368: subClasses = extent.hasSubclasses();
369: col = null;
370: }
371:
372: public Collection getCol() {
373: return col;
374: }
375:
376: public void setCol(Collection col) {
377: this .col = col;
378: subClasses = true;
379: }
380:
381: public String getFilter() {
382: return filter;
383: }
384:
385: public void setFilter(String filter) {
386: this .filter = checkString(filter);
387: }
388:
389: private boolean checkString(String lString, String oString) {
390: if (lString == null) {
391: return oString == null;
392: }
393: return lString.equals(oString);
394: }
395:
396: private final String checkString(String val) {
397: if (val == null || val.equals("")) {
398: return null;
399: }
400: return val;
401: }
402:
403: public String getImports() {
404: return imports;
405: }
406:
407: public void setImports(String imports) {
408: this .imports = checkString(imports);
409: }
410:
411: public void declareParameters(String params) {
412: optionsParamIndex = -1;
413: paramTypes = null;
414: paramNames = null;
415: paramCount = 0;
416: ParamDeclarationParser.parse(checkString(params), this );
417: }
418:
419: public int hashCode() {
420: int result;
421: result = (candidateClass != null ? candidateClass.getName()
422: .hashCode() : 0);
423: result = 29 * result + (filter != null ? filter.hashCode() : 0);
424: result = 29 * result
425: + (ordering != null ? ordering.hashCode() : 0);
426: return result;
427: }
428:
429: public boolean equals(Object o) {
430: if (o == this )
431: return true;
432: if (o instanceof QueryDetails) {
433: QueryDetails other = (QueryDetails) o;
434: // do not include useIgnoreCacheFromPM here
435: return randomAccess == other.randomAccess
436: && countOnSize == other.countOnSize
437: && bounded == other.bounded
438: && fetchGroupIndex == other.fetchGroupIndex
439: && subClasses == other.subClasses
440: && maxResultCount == other.maxResultCount
441: && resultBatchSize == other.resultBatchSize
442: && ignoreCache == other.ignoreCache
443: && unique == other.unique
444: && language == other.language
445: && cacheable == other.cacheable
446: && checkString(filter, other.filter)
447: && checkString(result, other.result)
448: && checkString(grouping, other.grouping)
449: && checkCandidate(other)
450: && checkString(ordering, other.ordering)
451: && checkExtraEvictClasses(other);
452: } else {
453: return false;
454: }
455: }
456:
457: private boolean checkExtraEvictClasses(QueryDetails other) {
458: if (extraEvictClasses != null) {
459: int[] a = other.extraEvictClasses;
460: if (a == null)
461: return false;
462: if (a.length != extraEvictClasses.length)
463: return false;
464: for (int i = a.length - 1; i >= 0; i--) {
465: if (a[i] != extraEvictClasses[i])
466: return false;
467: }
468: return true;
469: } else {
470: return other.extraEvictClasses == null;
471: }
472: }
473:
474: private boolean checkCandidate(QueryDetails other) {
475: if (candidateClass != null) {
476: if (candidateClass != other.candidateClass) {
477: return false;
478: }
479: } else {
480: if (other.candidateClass != null) {
481: return false;
482: }
483: }
484: return true;
485: }
486:
487: public void parameterParsed(int index, String type, String name) {
488: if (VersantQuery.VERSANT_OPTIONS.equals(name)
489: || VersantQuery.JDO_GENIE_OPTIONS.equals(name)) {
490: if (optionsParamIndex >= 0) {
491: throw BindingSupportImpl
492: .getInstance()
493: .runtime(
494: "The "
495: + VersantQuery.VERSANT_OPTIONS
496: + " parameter may only appear once in the parameter declarations");
497: }
498: optionsParamIndex = index;
499: } else {
500: if (paramTypes == null) {
501: paramTypes = new String[3];
502: paramNames = new String[3];
503: } else if (paramCount == paramTypes.length) {
504: int len = paramCount;
505: int n = len * 2;
506: String[] a = new String[n];
507: System.arraycopy(paramTypes, 0, a, 0, len);
508: paramTypes = a;
509: a = new String[n];
510: System.arraycopy(paramNames, 0, a, 0, len);
511: paramNames = a;
512: }
513: paramTypes[paramCount] = type;
514: paramNames[paramCount++] = name;
515: }
516: }
517:
518: /**
519: * Get the number of parameters excluding the jdoGenieOptions parameter
520: * (if any). This returns -1 if the number of parameters is not known
521: * e.g. EJBQL query or single string JDOQL query.
522: */
523: public int getParamCount() {
524: if (language == LANGUAGE_EJBQL) {
525: return -1;
526: } else {
527: return paramCount;
528: }
529: }
530:
531: /**
532: * Get the total number of parameters including the jdoGenieOptions parameter
533: * (if any). This returns -1 if the number of parameters is not known
534: * e.g. EJBQL query or single string JDOQL query.
535: */
536: public int getTotalParamCount() {
537: if (language == LANGUAGE_EJBQL) {
538: return -1;
539: } else {
540: if (optionsParamIndex >= 0)
541: return paramCount + 1;
542: return paramCount;
543: }
544: }
545:
546: /**
547: * Get the parameter types or null if there are none. The length of this
548: * array may exceed getParamCount(). This array does not contain the
549: * jdoGenieOptions parameter (if any).
550: */
551: public String[] getParamTypes() {
552: return paramTypes;
553: }
554:
555: /**
556: * Get the parameter names or null if there are none. The length of this
557: * array may exceed getParamCount(). This array does not contain the
558: * jdoGenieOptions parameter (if any).
559: */
560: public String[] getParamNames() {
561: return paramNames;
562: }
563:
564: /**
565: * Get a comma list of all the parameters or null if none. This is a
566: * tempory fix until the code that uses this can be refactored to use
567: * the already parsed types and names.
568: */
569: public String getParameters() {
570: int n = getParamCount();
571: if (n == 0)
572: return null;
573: CharBuf s = new CharBuf();
574: for (int i = 0; i < n; i++) {
575: if (i > 0)
576: s.append(',');
577: s.append(paramTypes[i]);
578: s.append(' ');
579: s.append(paramNames[i]);
580: }
581: return s.toString();
582: }
583:
584: public String getVariables() {
585: return variables;
586: }
587:
588: public void setVariables(String variables) {
589: this .variables = checkString(variables);
590: }
591:
592: public String getOrdering() {
593: return ordering;
594: }
595:
596: public void setOrdering(String ordering) {
597: this .ordering = checkString(ordering);
598: }
599:
600: public boolean isIgnoreCache() {
601: return ignoreCache;
602: }
603:
604: /**
605: * If true then any query created from these params should set its
606: * ignoreCache flag to the current setting for the PM.
607: */
608: public boolean isUseIgnoreCacheFromPM() {
609: return useIgnoreCacheFromPM;
610: }
611:
612: public void setIgnoreCache(boolean ignoreCache) {
613: this .ignoreCache = ignoreCache;
614: }
615:
616: public int getOptionsParamIndex() {
617: return optionsParamIndex;
618: }
619:
620: public void setOptionsParamIndex(int optionsParamIndex) {
621: this .optionsParamIndex = optionsParamIndex;
622: }
623:
624: public boolean hasJdoGenieOptions() {
625: return optionsParamIndex >= 0;
626: }
627:
628: public int getFetchGroupIndex() {
629: return fetchGroupIndex;
630: }
631:
632: public void setFetchGroupIndex(int fetchGroupIndex) {
633: this .fetchGroupIndex = fetchGroupIndex;
634: }
635:
636: public boolean isRandomAccess() {
637: return randomAccess;
638: }
639:
640: public void setRandomAccess(boolean randomAccess) {
641: this .randomAccess = randomAccess;
642: }
643:
644: public boolean isCountOnSize() {
645: return countOnSize;
646: }
647:
648: public void setCountOnSize(boolean countOnSize) {
649: this .countOnSize = countOnSize;
650: }
651:
652: public int[] getExtraEvictClasses() {
653: return extraEvictClasses;
654: }
655:
656: public void setExtraEvictClasses(int[] extraEvictClasses) {
657: this .extraEvictClasses = extraEvictClasses;
658: }
659:
660: public void fillFrom(QueryDetails qd) {
661: // do not include useIgnoreCacheFromPM here
662: language = qd.language;
663: candidateClass = qd.candidateClass;
664: col = qd.col;
665: subClasses = qd.subClasses;
666: filter = qd.filter;
667: result = qd.result;
668: grouping = qd.grouping;
669: ignoreCache = qd.ignoreCache;
670: unique = qd.unique;
671: imports = qd.imports;
672: variables = qd.variables;
673: ordering = qd.ordering;
674: paramCount = qd.paramCount;
675: paramTypes = qd.paramTypes;
676: paramNames = qd.paramNames;
677: optionsParamIndex = qd.optionsParamIndex;
678: fetchGroupIndex = qd.fetchGroupIndex;
679: randomAccess = qd.randomAccess;
680: countOnSize = qd.countOnSize;
681: maxResultCount = qd.maxResultCount;
682: resultBatchSize = qd.resultBatchSize;
683: extraEvictClasses = qd.extraEvictClasses;
684: cacheable = qd.cacheable;
685: bounded = qd.bounded;
686: }
687:
688: /**
689: * Clear the extent and col variables.
690: */
691: public void clearExtentAndCol() {
692: this .col = null;
693: }
694:
695: public void readExternal(ObjectInput s) throws IOException,
696: ClassNotFoundException {
697: language = s.read();
698: candidateClass = (Class) s.readObject();
699: filter = (String) s.readObject();
700: result = (String) s.readObject();
701: grouping = (String) s.readObject();
702: unique = s.readInt();
703: ignoreCache = s.readBoolean();
704: useIgnoreCacheFromPM = s.readBoolean();
705: imports = (String) s.readObject();
706: variables = (String) s.readObject();
707: ordering = (String) s.readObject();
708: subClasses = s.readBoolean();
709:
710: paramCount = s.readShort();
711: paramTypes = new String[paramCount];
712: paramNames = new String[paramCount];
713: for (int i = 0; i < paramCount; i++) {
714: paramTypes[i] = s.readUTF();
715: paramNames[i] = s.readUTF();
716: }
717: optionsParamIndex = s.readShort();
718:
719: fetchGroupIndex = s.readShort();
720: randomAccess = s.readBoolean();
721: countOnSize = s.readBoolean();
722: maxResultCount = s.readInt();
723: resultBatchSize = s.readInt();
724: bounded = s.readBoolean();
725:
726: int n = s.readShort();
727: if (n > 0) {
728: extraEvictClasses = new int[n];
729: for (int i = 0; i < n; i++)
730: extraEvictClasses[i] = s.readShort();
731: }
732:
733: cacheable = s.readByte();
734: }
735:
736: public void writeExternal(ObjectOutput s) throws IOException {
737: s.writeByte(language);
738: s.writeObject(candidateClass);
739: s.writeObject(filter);
740: s.writeObject(result);
741: s.writeObject(grouping);
742: s.writeInt(unique);
743: s.writeBoolean(ignoreCache);
744: s.writeBoolean(useIgnoreCacheFromPM);
745: s.writeObject(imports);
746: s.writeObject(variables);
747: s.writeObject(ordering);
748: s.writeBoolean(subClasses);
749:
750: s.writeShort(paramCount);
751: for (int i = 0; i < paramCount; i++) {
752: s.writeUTF(paramTypes[i]);
753: s.writeUTF(paramNames[i]);
754: }
755: s.writeShort(optionsParamIndex);
756:
757: s.writeShort(fetchGroupIndex);
758: s.writeBoolean(randomAccess);
759: s.writeBoolean(countOnSize);
760: s.writeInt(maxResultCount);
761: s.writeInt(resultBatchSize);
762: s.writeBoolean(bounded);
763:
764: if (extraEvictClasses != null) {
765: int n = extraEvictClasses.length;
766: s.writeShort(n);
767: for (int i = 0; i < n; i++)
768: s.writeShort(extraEvictClasses[i]);
769: } else {
770: s.writeShort(0);
771: }
772:
773: s.writeByte(cacheable);
774: }
775:
776: public void dump() {
777: StringBuffer sb = new StringBuffer(">>>>> QueryDetails: ");
778: sb.append("\n language = " + language);
779: sb.append("\n candidateClass = " + candidateClass);
780: sb.append("\n filter = " + filter);
781: sb.append("\n result = " + result);
782: sb.append("\n grouping = " + grouping);
783: sb.append("\n unique = " + unique);
784: sb.append("\n ignoreCache = " + ignoreCache);
785: sb
786: .append("\n useIgnoreCacheFromPM = "
787: + useIgnoreCacheFromPM);
788: sb.append("\n imports = " + imports);
789: sb.append("\n variables = " + variables);
790: sb.append("\n ordering = " + ordering);
791: sb.append("\n paramCount = " + paramCount);
792: for (int i = 0; i < paramCount; i++) {
793: sb.append("\n param[");
794: sb.append(i);
795: sb.append("] = '");
796: sb.append(paramTypes[i]);
797: sb.append("' '");
798: sb.append(paramNames[i]);
799: sb.append('\'');
800: }
801: sb.append("\n optionsParamIndex = " + optionsParamIndex);
802: sb.append("\n fetchGroupIndex = " + fetchGroupIndex);
803: sb.append("\n randomAccess = " + randomAccess);
804: sb.append("\n countOnSize = " + countOnSize);
805: sb.append("\n maxRows = " + maxResultCount);
806: sb.append("\n resultBatchSize = " + resultBatchSize);
807: sb.append("\n parallelCollectionFetch = " + bounded);
808: sb.append("\n cacheable = " + cacheable);
809: System.out.println(sb.toString());
810: }
811:
812: public void setCacheable(boolean on) {
813: cacheable = on ? TRUE : FALSE;
814: }
815:
816: /**
817: * Return the cacheable tri-state (NOT_SET, FALSE, TRUE).
818: */
819: public int getCacheable() {
820: return cacheable;
821: }
822:
823: }
|