001: /*
002:
003: Derby - Class org.apache.derby.impl.store.access.PropertyConglomerate
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: 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, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.store.access;
023:
024: import org.apache.derby.iapi.reference.Attribute;
025: import org.apache.derby.iapi.reference.Property;
026: import org.apache.derby.iapi.reference.SQLState;
027:
028: import org.apache.derby.iapi.types.UserType;
029: import org.apache.derby.impl.store.access.UTF;
030: import org.apache.derby.impl.store.access.UTFQualifier;
031: import org.apache.derby.iapi.services.io.FormatableBitSet;
032: import org.apache.derby.iapi.services.io.FormatableHashtable;
033: import org.apache.derby.iapi.services.locks.ShExLockable;
034: import org.apache.derby.iapi.services.locks.ShExQual;
035: import org.apache.derby.iapi.services.daemon.Serviceable;
036: import org.apache.derby.iapi.services.locks.C_LockFactory;
037: import org.apache.derby.iapi.services.locks.Latch;
038: import org.apache.derby.iapi.services.locks.LockFactory;
039: import org.apache.derby.iapi.services.property.PropertyUtil;
040:
041: import org.apache.derby.iapi.services.monitor.Monitor;
042: import org.apache.derby.iapi.services.sanity.SanityManager;
043: import org.apache.derby.iapi.services.io.Formatable;
044: import org.apache.derby.iapi.error.StandardException;
045: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
046: import org.apache.derby.iapi.store.access.conglomerate.TransactionManager;
047: import org.apache.derby.iapi.store.access.AccessFactory;
048: import org.apache.derby.iapi.store.access.AccessFactoryGlobals;
049: import org.apache.derby.iapi.store.access.ConglomerateController;
050: import org.apache.derby.iapi.services.property.PropertyFactory;
051: import org.apache.derby.iapi.services.property.PropertySetCallback;
052: import org.apache.derby.iapi.store.access.Qualifier;
053: import org.apache.derby.iapi.store.access.ScanController;
054: import org.apache.derby.iapi.store.access.TransactionController;
055: import org.apache.derby.iapi.store.raw.RawStoreFactory;
056: import org.apache.derby.iapi.types.DataValueDescriptor;
057:
058: import java.io.Serializable;
059: import java.util.Dictionary;
060: import java.util.Enumeration;
061: import java.util.Hashtable;
062: import java.util.Properties;
063:
064: /**
065: Stores properties in a congolmerate with complete transactional support.
066: <p>
067: The PropertyConglomerate contains one row with 2 columns per property.
068: Column 0 is the UTF key, and column 1 is the data.
069: <p>
070:
071: <p>
072: The property conglomerate manages the storage of database properties
073: and thier defaults. Each property is stored as a row in the
074: PropertyConglomerate
075: <OL>
076: <LI>Column 0 is the UTF key,
077: <LI>Column 1 is the data.
078: </OL>
079: All the property defaults are stored in a single row of the Property
080: Conglomerate:
081: <OL>
082: <LI>Column 0 is the UTF key "derby.defaultPropertyName".
083: <LI>Column 1 is a FormatableProperties object with one
084: row per default property.
085: </OL>
086: <p>
087: In general a propery default defines it value if the property
088: itself is not defined.
089:
090: <p>
091: Because the properties conglomerate is stored in a conglomerate
092: the information it contains is not available before the raw store
093: runs recovery. To make a small number of properties (listed in
094: servicePropertyList) available during early boot, this copies
095: them to services.properties.
096: **/
097: class PropertyConglomerate {
098: protected long propertiesConglomId;
099: protected Properties serviceProperties;
100: private LockFactory lf;
101: private Dictionary cachedSet;
102: private CacheLock cachedLock;
103:
104: private PropertyFactory pf;
105:
106: /* Constructors for This class: */
107:
108: PropertyConglomerate(TransactionController tc, boolean create,
109: Properties serviceProperties, PropertyFactory pf)
110: throws StandardException {
111: this .pf = pf;
112:
113: if (!create) {
114: String id = serviceProperties
115: .getProperty(Property.PROPERTIES_CONGLOM_ID);
116: if (id == null) {
117: create = true;
118: } else {
119: try {
120: propertiesConglomId = Long.valueOf(id).longValue();
121: } catch (NumberFormatException nfe) {
122: throw Monitor.exceptionStartingModule(nfe);
123: }
124: }
125: }
126:
127: if (create) {
128: DataValueDescriptor[] template = makeNewTemplate();
129:
130: Properties conglomProperties = new Properties();
131:
132: conglomProperties.put(Property.PAGE_SIZE_PARAMETER,
133: RawStoreFactory.PAGE_SIZE_STRING);
134:
135: conglomProperties.put(
136: RawStoreFactory.PAGE_RESERVED_SPACE_PARAMETER,
137: RawStoreFactory.PAGE_RESERVED_ZERO_SPACE_STRING);
138:
139: propertiesConglomId = tc
140: .createConglomerate(AccessFactoryGlobals.HEAP,
141: template, null, conglomProperties,
142: TransactionController.IS_DEFAULT);
143:
144: serviceProperties.put(Property.PROPERTIES_CONGLOM_ID, Long
145: .toString(propertiesConglomId));
146: }
147:
148: this .serviceProperties = serviceProperties;
149:
150: lf = ((RAMTransaction) tc).getAccessManager().getLockFactory();
151: cachedLock = new CacheLock(this );
152:
153: PC_XenaVersion softwareVersion = new PC_XenaVersion();
154: if (create)
155: setProperty(tc,
156: DataDictionary.PROPERTY_CONGLOMERATE_VERSION,
157: softwareVersion, true);
158: else
159: softwareVersion
160: .upgradeIfNeeded(tc, this , serviceProperties);
161:
162: getCachedDbProperties(tc);
163: }
164:
165: /* Private/Protected methods of This class: */
166:
167: /**
168: * Create a new PropertyConglomerate row, with values in it.
169: **/
170: private DataValueDescriptor[] makeNewTemplate(String key,
171: Serializable value) {
172: DataValueDescriptor[] template = new DataValueDescriptor[2];
173:
174: template[0] = new UTF(key);
175: template[1] = new UserType(value);
176:
177: return (template);
178: }
179:
180: /**
181: * Create a new empty PropertyConglomerate row, to fetch values into.
182: **/
183: private DataValueDescriptor[] makeNewTemplate() {
184: DataValueDescriptor[] template = new DataValueDescriptor[2];
185:
186: template[0] = new UTF();
187: template[1] = new UserType();
188:
189: return (template);
190: }
191:
192: /**
193: * Open a scan on the properties conglomerate looking for "key".
194: * <p>
195: * Open a scan on the properties conglomerate qualified to
196: * find the row with value key in column 0. Both column 0
197: * and column 1 are included in the scan list.
198: *
199: * @return an open ScanController on the PropertyConglomerate.
200: *
201: * @param tc The transaction to do the Conglomerate work under.
202: * @param key The "key" of the property that is being requested.
203: * @param open_mode Whether we are setting or getting the property.
204: *
205: * @exception StandardException Standard exception policy.
206: **/
207: private ScanController openScan(TransactionController tc,
208: String key, int open_mode) throws StandardException {
209: Qualifier[][] qualifiers = null;
210:
211: if (key != null) {
212: // Set up qualifier to look for the row with key value in column[0]
213: qualifiers = new Qualifier[1][];
214: qualifiers[0] = new Qualifier[1];
215: qualifiers[0][0] = new UTFQualifier(0, key);
216: }
217:
218: // open the scan, clients will do the fetches and close.
219: ScanController scan = tc.openScan(
220: propertiesConglomId,
221: false, // don't hold over the commit
222: open_mode, TransactionController.MODE_TABLE,
223: TransactionController.ISOLATION_SERIALIZABLE,
224: (FormatableBitSet) null,
225: (DataValueDescriptor[]) null, // start key
226: ScanController.NA, qualifiers,
227: (DataValueDescriptor[]) null, // stop key
228: ScanController.NA);
229:
230: return (scan);
231: }
232:
233: /* Package Methods of This class: */
234:
235: /**
236: Set a property in the conglomerate.
237:
238: @param key The key used to lookup this property.
239: @param value The value to be associated with this key. If null, delete the
240: property from the properties list.
241: */
242:
243: /**
244: * Set the default for a property.
245: * @exception StandardException Standard exception policy.
246: */
247: void setPropertyDefault(TransactionController tc, String key,
248: Serializable value) throws StandardException {
249: lockProperties(tc);
250: Serializable valueToSave = null;
251: //
252: //If the default is visible we validate apply and map.
253: //otherwise we just map.
254: if (propertyDefaultIsVisible(tc, key)) {
255: valueToSave = validateApplyAndMap(tc, key, value, false);
256: } else {
257: synchronized (this ) {
258: Hashtable defaults = new Hashtable();
259: getProperties(tc, defaults, false/*!stringsOnly*/,
260: true/*defaultsOnly*/);
261: validate(key, value, defaults);
262: valueToSave = map(key, value, defaults);
263: }
264: }
265: savePropertyDefault(tc, key, valueToSave);
266: }
267:
268: boolean propertyDefaultIsVisible(TransactionController tc,
269: String key) throws StandardException {
270: lockProperties(tc);
271: return (readProperty(tc, key) == null);
272: }
273:
274: void saveProperty(TransactionController tc, String key,
275: Serializable value) throws StandardException {
276: if (saveServiceProperty(key, value))
277: return;
278:
279: // Do a scan to see if the property already exists in the Conglomerate.
280: ScanController scan = this .openScan(tc, key,
281: TransactionController.OPENMODE_FORUPDATE);
282:
283: DataValueDescriptor[] row = makeNewTemplate();
284:
285: if (scan.fetchNext(row)) {
286: if (value == null) {
287: // A null input value means that we should delete the row
288:
289: scan.delete();
290: } else {
291: // a value already exists, just replace the second columm
292:
293: row[1] = new UserType(value);
294:
295: scan.replace(row, (FormatableBitSet) null);
296: }
297:
298: scan.close();
299: } else {
300: // The value does not exist in the Conglomerate.
301:
302: scan.close();
303: scan = null;
304:
305: if (value != null) {
306: // not a delete request, so insert the new property.
307:
308: row = makeNewTemplate(key, value);
309:
310: ConglomerateController cc = tc.openConglomerate(
311: propertiesConglomId, false,
312: TransactionController.OPENMODE_FORUPDATE,
313: TransactionController.MODE_TABLE,
314: TransactionController.ISOLATION_SERIALIZABLE);
315:
316: cc.insert(row);
317:
318: cc.close();
319: }
320: }
321: }
322:
323: private boolean saveServiceProperty(String key, Serializable value) {
324: if (PropertyUtil.isServiceProperty(key)) {
325: if (value != null)
326: serviceProperties.put(key, value);
327: else
328: serviceProperties.remove(key);
329: return true;
330: } else {
331: return false;
332: }
333: }
334:
335: void savePropertyDefault(TransactionController tc, String key,
336: Serializable value) throws StandardException {
337: if (saveServiceProperty(key, value))
338: return;
339:
340: Dictionary defaults = (Dictionary) readProperty(tc,
341: AccessFactoryGlobals.DEFAULT_PROPERTY_NAME);
342: if (defaults == null)
343: defaults = new FormatableHashtable();
344: if (value == null)
345: defaults.remove(key);
346: else
347: defaults.put(key, value);
348: if (defaults.size() == 0)
349: defaults = null;
350: saveProperty(tc, AccessFactoryGlobals.DEFAULT_PROPERTY_NAME,
351: (Serializable) defaults);
352: }
353:
354: private Serializable validateApplyAndMap(TransactionController tc,
355: String key, Serializable value, boolean dbOnlyProperty)
356: throws StandardException {
357: Dictionary d = new Hashtable();
358: getProperties(tc, d, false/*!stringsOnly*/, false/*!defaultsOnly*/);
359: Serializable mappedValue = pf.doValidateApplyAndMap(tc, key,
360: value, d, dbOnlyProperty);
361: //
362: // RESOLVE: log device cannot be changed on the fly right now
363: if (key.equals(Attribute.LOG_DEVICE)) {
364: throw StandardException
365: .newException(SQLState.RAWSTORE_CANNOT_CHANGE_LOGDEVICE);
366: }
367:
368: if (mappedValue == null)
369: return value;
370: else
371: return mappedValue;
372: }
373:
374: /**
375: Call the property set callbacks to map a proposed property value
376: to a value to save.
377: <P>
378: The caller must run this in a block synchronized on this
379: to serialize validations with changes to the set of
380: property callbacks
381: */
382: private Serializable map(String key, Serializable value,
383: Dictionary set) throws StandardException {
384: return pf.doMap(key, value, set);
385: }
386:
387: /**
388: Call the property set callbacks to validate a property change
389: against the property set provided.
390: <P>
391: The caller must run this in a block synchronized on this
392: to serialize validations with changes to the set of
393: property callbacks
394: */
395:
396: private void validate(String key, Serializable value, Dictionary set)
397: throws StandardException {
398: pf.validateSingleProperty(key, value, set);
399: }
400:
401: private boolean bootPasswordChange(TransactionController tc,
402: String key, Serializable value) throws StandardException {
403: // first check for boot password change - we don't put boot password
404: // in the servicePropertyList because if we do, then we expose the
405: // boot password in clear text
406: if (key.equals(Attribute.BOOT_PASSWORD)) {
407: // The user is trying to change the secret key.
408: // The secret key is never stored in clear text, but we
409: // store the encrypted form in the services.properties
410: // file. Swap the secret key with the encrypted form and
411: // put that in the services.properties file.
412: AccessFactory af = ((TransactionManager) tc)
413: .getAccessManager();
414:
415: RawStoreFactory rsf = (RawStoreFactory) Monitor
416: .findServiceModule(af, RawStoreFactory.MODULE);
417:
418: // remove secret key from properties list if possible
419: serviceProperties.remove(Attribute.BOOT_PASSWORD);
420:
421: value = rsf.changeBootPassword(serviceProperties, value);
422: serviceProperties.put(RawStoreFactory.ENCRYPTED_KEY, value);
423: return true;
424: } else {
425: return false;
426: }
427: }
428:
429: /**
430: * Sets the Serializable object associated with a property key.
431: * <p>
432: * This implementation turns the setProperty into an insert into the
433: * PropertyConglomerate conglomerate.
434: * <p>
435: * See the discussion of getProperty().
436: * <p>
437: * The value stored may be a Formatable object or a Serializable object
438: * whose class name starts with java.*. This stops arbitary objects being
439: * stored in the database by class name, which will cause problems in
440: * obfuscated/non-obfuscated systems.
441: *
442: * @param tc The transaction to do the Conglomerate work under.
443: * @param key The key used to lookup this property.
444: * @param value The value to be associated with this key. If null,
445: * delete the property from the properties list.
446: *
447: * @exception StandardException Standard exception policy.
448: **/
449: void setProperty(TransactionController tc, String key,
450: Serializable value, boolean dbOnlyProperty)
451: throws StandardException {
452: if (SanityManager.DEBUG) {
453:
454: if (!((value == null) || (value instanceof Formatable))) {
455: if (!(value.getClass().getName().startsWith("java."))) {
456: SanityManager
457: .THROWASSERT("Non-formattable, non-java class - "
458: + value.getClass().getName());
459: }
460: }
461: }
462:
463: lockProperties(tc);
464: Serializable valueToValidateAndApply = value;
465: //
466: //If we remove a property we validate and apply its default.
467: if (value == null)
468: valueToValidateAndApply = getPropertyDefault(tc, key);
469: Serializable valueToSave = validateApplyAndMap(tc, key,
470: valueToValidateAndApply, dbOnlyProperty);
471:
472: //
473: //if this is a bootPasswordChange we save it in
474: //a special way.
475: if (bootPasswordChange(tc, key, value))
476: return;
477:
478: //
479: //value==null means we are removing a property.
480: //To remove the property we call saveProperty with
481: //a null value. Note we avoid saving the mapped
482: //DEFAULT value returned by validateAndApply.
483: else if (value == null)
484: saveProperty(tc, key, null);
485: //
486: //value != null means we simply save the possibly
487: //mapped value of the property returned by
488: //validateAndApply.
489: else
490: saveProperty(tc, key, valueToSave);
491: }
492:
493: private Serializable readProperty(TransactionController tc,
494: String key) throws StandardException {
495: // scan the table for a row with matching "key"
496: ScanController scan = openScan(tc, key, 0);
497:
498: DataValueDescriptor[] row = makeNewTemplate();
499:
500: // did we find at least one row?
501: boolean isThere = scan.fetchNext(row);
502:
503: scan.close();
504:
505: if (!isThere)
506: return null;
507:
508: return (Serializable) (((UserType) row[1]).getObject());
509: }
510:
511: private Serializable getCachedProperty(TransactionController tc,
512: String key) throws StandardException {
513: //
514: //Get the cached set of properties.
515: Dictionary dbProps = getCachedDbProperties(tc);
516:
517: //
518: //Return the value if it is defined.
519: if (dbProps.get(key) != null)
520: return (Serializable) dbProps.get(key);
521: else
522: return getCachedPropertyDefault(tc, key, dbProps);
523: }
524:
525: private Serializable getCachedPropertyDefault(
526: TransactionController tc, String key, Dictionary dbProps)
527: throws StandardException {
528: //
529: //Get the cached set of properties.
530: if (dbProps == null)
531: dbProps = getCachedDbProperties(tc);
532: //
533: //return the default for the value if it is defined.
534: Dictionary defaults = (Dictionary) dbProps
535: .get(AccessFactoryGlobals.DEFAULT_PROPERTY_NAME);
536: if (defaults == null)
537: return null;
538: else
539: return (Serializable) defaults.get(key);
540: }
541:
542: /**
543: * Gets the de-serialized object associated with a property key.
544: * <p>
545: * The Store provides a transaction protected list of database properties.
546: * Higher levels of the system can store and retrieve these properties
547: * once Recovery has finished. Each property is a serializable object
548: * and is stored/retrieved using a String key.
549: * <p>
550: * In this implementation a lookup is done on the PropertyConglomerate
551: * conglomerate, using a scan with "key" as the qualifier.
552: * <p>
553: * @param tc The transaction to do the Conglomerate work under.
554: * @param key The "key" of the property that is being requested.
555: *
556: * @return object The object associated with property key. n
557: * ull means no such key-value pair.
558: *
559: * @exception StandardException Standard exception policy.
560: **/
561: Serializable getProperty(TransactionController tc, String key)
562: throws StandardException {
563: //
564: //Try service properties first.
565: if (PropertyUtil.isServiceProperty(key))
566: return serviceProperties.getProperty(key);
567:
568: // See if I'm the exclusive owner. If so I cannot populate
569: // the cache as it would contain my uncommitted changes.
570: if (iHoldTheUpdateLock(tc)) {
571: //
572: //Return the property value if it is defined.
573: Serializable v = readProperty(tc, key);
574: if (v != null)
575: return v;
576:
577: return getPropertyDefault(tc, key);
578: } else {
579: return getCachedProperty(tc, key);
580: }
581: }
582:
583: /**
584: * Get the default for a property.
585: * @exception StandardException Standard exception policy.
586: */
587: Serializable getPropertyDefault(TransactionController tc, String key)
588: throws StandardException {
589: // See if I'm the exclusive owner. If so I cannot populate
590: // the cache as it would contain my uncommitted changes.
591: if (iHoldTheUpdateLock(tc)) {
592: //
593: //Return the property default value (may be null) if
594: //defined.
595: Dictionary defaults = (Dictionary) readProperty(tc,
596: AccessFactoryGlobals.DEFAULT_PROPERTY_NAME);
597: if (defaults == null)
598: return null;
599: else
600: return (Serializable) defaults.get(key);
601: } else {
602: return getCachedPropertyDefault(tc, key, null);
603: }
604: }
605:
606: private Dictionary copyValues(Dictionary to, Dictionary from,
607: boolean stringsOnly) {
608: if (from == null)
609: return to;
610: for (Enumeration keys = from.keys(); keys.hasMoreElements();) {
611: String key = (String) keys.nextElement();
612: Object value = from.get(key);
613: if ((value instanceof String) || !stringsOnly)
614: to.put(key, value);
615: }
616: return to;
617: }
618:
619: /**
620: Fetch the set of properties as a Properties object. This means
621: that only keys that have String values will be included.
622: */
623: Properties getProperties(TransactionController tc)
624: throws StandardException {
625: Properties p = new Properties();
626: getProperties(tc, p, true/*stringsOnly*/, false/*!defaultsOnly*/);
627: return p;
628: }
629:
630: public void getProperties(TransactionController tc, Dictionary d,
631: boolean stringsOnly, boolean defaultsOnly)
632: throws StandardException {
633: // See if I'm the exclusive owner. If so I cannot populate
634: // the cache as it would contain my uncommitted changes.
635: if (iHoldTheUpdateLock(tc)) {
636: Dictionary dbProps = readDbProperties(tc);
637: Dictionary defaults = (Dictionary) dbProps
638: .get(AccessFactoryGlobals.DEFAULT_PROPERTY_NAME);
639: copyValues(d, defaults, stringsOnly);
640: if (!defaultsOnly)
641: copyValues(d, dbProps, stringsOnly);
642: } else {
643: Dictionary dbProps = getCachedDbProperties(tc);
644: Dictionary defaults = (Dictionary) dbProps
645: .get(AccessFactoryGlobals.DEFAULT_PROPERTY_NAME);
646: copyValues(d, defaults, stringsOnly);
647: if (!defaultsOnly)
648: copyValues(d, dbProps, stringsOnly);
649: }
650: }
651:
652: void resetCache() {
653: cachedSet = null;
654: }
655:
656: /** Read the database properties and add in the service set. */
657: private Dictionary readDbProperties(TransactionController tc)
658: throws StandardException {
659: Dictionary set = new Hashtable();
660:
661: // scan the table for a row with no matching "key"
662: ScanController scan = openScan(tc, (String) null, 0);
663:
664: DataValueDescriptor[] row = makeNewTemplate();
665:
666: while (scan.fetchNext(row)) {
667:
668: Object key = ((UserType) row[0]).getObject();
669: Object value = ((UserType) row[1]).getObject();
670: if (SanityManager.DEBUG) {
671: if (!(key instanceof String))
672: SanityManager.THROWASSERT("Key is not a string "
673: + key.getClass().getName());
674: }
675: set.put(key, value);
676: }
677: scan.close();
678:
679: // add the known properties from the service properties set
680: for (int i = 0; i < PropertyUtil.servicePropertyList.length; i++) {
681: String value = serviceProperties
682: .getProperty(PropertyUtil.servicePropertyList[i]);
683: if (value != null)
684: set.put(PropertyUtil.servicePropertyList[i], value);
685: }
686: return set;
687: }
688:
689: private Dictionary getCachedDbProperties(TransactionController tc)
690: throws StandardException {
691: Dictionary dbProps = cachedSet;
692: //Get the cached set of properties.
693: if (dbProps == null) {
694: dbProps = readDbProperties(tc);
695: cachedSet = dbProps;
696: }
697:
698: return dbProps;
699: }
700:
701: /** Lock the database properties for an update. */
702: void lockProperties(TransactionController tc)
703: throws StandardException {
704: // lock the property set until the transaction commits.
705: // This allows correct operation of the cache. The cache remains
706: // valid for all transactions except the one that is modifying
707: // it. Thus readers see the old uncommited values. When this
708: // thread releases its exclusive lock the cached is cleared
709: // and the next reader will re-populate the cache.
710: Object csGroup = tc.getLockObject();
711: lf.lockObject(csGroup, csGroup, cachedLock, ShExQual.EX,
712: C_LockFactory.TIMED_WAIT);
713: }
714:
715: /**
716: Return true if the caller holds the exclusive update lock on the
717: property conglomerate.
718: */
719: private boolean iHoldTheUpdateLock(TransactionController tc)
720: throws StandardException {
721: Object csGroup = tc.getLockObject();
722: return lf.isLockHeld(csGroup, csGroup, cachedLock, ShExQual.EX);
723: }
724: }
725:
726: /**
727: Only used for exclusive lock purposes.
728: */
729: class CacheLock extends ShExLockable {
730:
731: private PropertyConglomerate pc;
732:
733: CacheLock(PropertyConglomerate pc) {
734: this .pc = pc;
735: }
736:
737: public void unlockEvent(Latch lockInfo) {
738: super.unlockEvent(lockInfo);
739: pc.resetCache();
740: }
741: }
|