001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object;
006:
007: import bsh.EvalError;
008: import bsh.Interpreter;
009: import bsh.ParseException;
010:
011: import com.tc.lang.TCThreadGroup;
012: import com.tc.logging.CustomerLogging;
013: import com.tc.logging.TCLogger;
014: import com.tc.logging.TCLogging;
015: import com.tc.object.bytecode.Manageable;
016: import com.tc.object.bytecode.TransparentAccess;
017: import com.tc.object.dna.api.DNA;
018: import com.tc.object.dna.api.DNAException;
019: import com.tc.object.dna.api.DNAWriter;
020: import com.tc.object.field.TCField;
021: import com.tc.util.Assert;
022: import com.tc.util.Conversion;
023: import com.tc.util.Util;
024:
025: import gnu.trove.TLinkable;
026:
027: import java.io.IOException;
028: import java.lang.ref.ReferenceQueue;
029: import java.lang.ref.WeakReference;
030:
031: /**
032: * Implementation of TCObject interface.
033: * <p>
034: */
035: public abstract class TCObjectImpl implements TCObject {
036: private static final TCLogger logger = TCLogging
037: .getLogger(TCObjectImpl.class);
038:
039: private static final int ACCESSED_OFFSET = 1;
040: private static final int IS_NEW_OFFSET = 2;
041: private static final int AUTOLOCKS_DISABLED_OFFSET = 4;
042: private static final int EVICTION_IN_PROGRESS_OFFSET = 8;
043: private static final int NEW_DEHYDRATE_IN_PROGRESS_OFFSET = 16;
044:
045: // XXX::This initial negative version number is important since GID is assigned in the server from 0.
046: private long version = -1;
047:
048: private final ObjectID objectID;
049: protected final TCClass tcClazz;
050: private WeakReference peerObject;
051: private TLinkable next;
052: private TLinkable previous;
053: private byte flags = 0;
054: private static final TCLogger consoleLogger = CustomerLogging
055: .getConsoleLogger();
056:
057: protected TCObjectImpl(ReferenceQueue queue, ObjectID id,
058: Object peer, TCClass clazz) {
059: this .objectID = id;
060: this .tcClazz = clazz;
061: setPeerObject(new WeakObjectReference(id, peer, queue));
062: }
063:
064: public boolean isShared() {
065: return true;
066: }
067:
068: public boolean isNull() {
069: return peerObject == null || getPeerObject() == null;
070: }
071:
072: public ObjectID getObjectID() {
073: return objectID;
074: }
075:
076: protected ClientObjectManager getObjectManager() {
077: return tcClazz.getObjectManager();
078: }
079:
080: public Object getPeerObject() {
081: return peerObject.get();
082: }
083:
084: protected void setPeerObject(WeakReference pojo) {
085: this .peerObject = pojo;
086: Object realPojo;
087: if ((realPojo = peerObject.get()) instanceof Manageable) {
088: Manageable m = (Manageable) realPojo;
089: m.__tc_managed(this );
090: }
091: }
092:
093: public TCClass getTCClass() {
094: return tcClazz;
095: }
096:
097: /**
098: * Reconstitutes the object using the data in the DNA strand. XXX: We may need to signal (via a different signature or
099: * args) that the hydration is intended to initialize the object from scratch or if it's a delta. We must avoid
100: * creating a new instance of the peer object if the strand is just a delta.
101: *
102: * @throws ClassNotFoundException
103: */
104: public void hydrate(DNA from, boolean force)
105: throws ClassNotFoundException {
106: synchronized (getResolveLock()) {
107: boolean isNewLoad = isNull();
108: createPeerObjectIfNecessary(from);
109:
110: Object po = getPeerObject();
111: if (po == null)
112: return;
113: try {
114: tcClazz.hydrate(this , from, po, force);
115: if (isNewLoad)
116: performOnLoadActionIfNecessary(po);
117: } catch (ClassNotFoundException e) {
118: logger.warn("Re-throwing Exception: ", e);
119: throw e;
120: } catch (IOException e) {
121: logger.warn("Re-throwing Exception: ", e);
122: throw new DNAException(e);
123: }
124: }
125: }
126:
127: private void performOnLoadActionIfNecessary(Object pojo) {
128: TCClass tcc = getTCClass();
129: if (tcc.hasOnLoadExecuteScript() || tcc.hasOnLoadMethod()) {
130: String eval = tcc.hasOnLoadExecuteScript() ? tcc
131: .getOnLoadExecuteScript() : "self."
132: + tcc.getOnLoadMethod() + "()";
133: resolveAllReferences();
134:
135: final ClassLoader prevLoader = Thread.currentThread()
136: .getContextClassLoader();
137: final boolean adjustTCL = TCThreadGroup
138: .currentThreadInTCThreadGroup();
139:
140: if (adjustTCL) {
141: ClassLoader newTCL = pojo.getClass().getClassLoader();
142: if (newTCL == null)
143: newTCL = ClassLoader.getSystemClassLoader();
144: Thread.currentThread().setContextClassLoader(newTCL);
145: }
146:
147: try {
148: Interpreter i = new Interpreter();
149: i.setClassLoader(tcc.getPeerClass().getClassLoader());
150: i.set("self", pojo);
151: i.eval("setAccessibility(true)");
152: i.eval(eval);
153: } catch (ParseException e) {
154: // Error Parsing script. Use e.getMessage() instead of e.getErrorText() when there is a ParseException because
155: // expectedTokenSequences in ParseException could be null and thus, may throw a NullPointerException when
156: // calling
157: // e.getErrorText().
158: consoleLogger.error("Unable to parse OnLoad script: "
159: + pojo.getClass() + " error: " + e.getMessage()
160: + " stack: " + e.getScriptStackTrace());
161: logger.error("Unable to parse OnLoad script: "
162: + pojo.getClass() + " error: " + e.getMessage()
163: + " line: " + " stack: "
164: + e.getScriptStackTrace());
165: } catch (EvalError e) {
166: // General Error evaluating script
167: consoleLogger
168: .error("OnLoad execute script failed for: "
169: + pojo.getClass() + " error: "
170: + e.getErrorText() + " line: "
171: + e.getErrorLineNumber() + "; "
172: + e.getMessage() + "; stack: "
173: + e.getScriptStackTrace());
174: logger.error("OnLoad execute script failed for: "
175: + pojo.getClass() + " error: "
176: + e.getErrorText() + " line: "
177: + e.getErrorLineNumber() + "; "
178: + e.getMessage() + "; stack: "
179: + e.getScriptStackTrace());
180: } finally {
181: if (adjustTCL)
182: Thread.currentThread().setContextClassLoader(
183: prevLoader);
184: }
185: }
186: }
187:
188: protected synchronized void setFlag(int offset, boolean value) {
189: flags = Conversion.setFlag(flags, offset, value);
190: }
191:
192: private synchronized boolean getFlag(int offset) {
193: return Conversion.getFlag(flags, offset);
194: }
195:
196: private void createPeerObjectIfNecessary(DNA from) {
197: if (isNull()) {
198: // TODO: set created and modified version id
199: setPeerObject(getObjectManager().createNewPeer(tcClazz,
200: from));
201: }
202: }
203:
204: public ObjectID setReference(String fieldName, ObjectID id) {
205: throw new AssertionError("shouldn't be called");
206: }
207:
208: public void setArrayReference(int index, ObjectID id) {
209: throw new AssertionError("shouldn't be called");
210: }
211:
212: public void setValue(String fieldName, Object obj) {
213: try {
214: TransparentAccess ta = (TransparentAccess) getPeerObject();
215: if (ta == null) {
216: // Object was GC'd so return which should lead to a re-retrieve
217: return;
218: }
219: clearReference(fieldName);
220: TCField field = getTCClass().getField(fieldName);
221: if (field == null) {
222: logger
223: .warn("Data for field:"
224: + fieldName
225: + " was recieved but that field does not exist in class:");
226: return;
227: }
228: if (obj instanceof ObjectID) {
229: setReference(fieldName, (ObjectID) obj);
230: if (!field.isFinal()) {
231: ta.__tc_setfield(field.getName(), null);
232: }
233: } else {
234: // clean this up
235: ta.__tc_setfield(field.getName(), obj);
236: }
237: } catch (Exception e) {
238: // TODO: More elegant exception handling.
239: throw new com.tc.object.dna.api.DNAException(e);
240: }
241: }
242:
243: public int clearReferences(int toClear) {
244: synchronized (getResolveLock()) {
245: try {
246: Object po = getPeerObject();
247: Assert.assertFalse(isNew()); // Shouldnt clear new Objects
248: if (po == null)
249: return 0;
250: return clearReferences(po, toClear);
251: } finally {
252: setEvictionInProgress(false);
253: }
254: }
255: }
256:
257: protected abstract int clearReferences(Object pojo, int toClear);
258:
259: public final Object getResolveLock() {
260: return objectID; // Save a field by using this one as the lock
261: }
262:
263: public void resolveArrayReference(int index) {
264: throw new AssertionError("shouldn't be called");
265: }
266:
267: public ArrayIndexOutOfBoundsException checkArrayIndex(int index) {
268: throw new AssertionError("shouldn't be called");
269: }
270:
271: public void clearArrayReference(int index) {
272: clearReference(Integer.toString(index));
273: }
274:
275: public void clearReference(String fieldName) {
276: // do nothing
277: }
278:
279: public void resolveReference(String fieldName) {
280: // do nothing
281: }
282:
283: public void resolveAllReferences() {
284: // override me
285: }
286:
287: public void literalValueChanged(Object newValue, Object oldValue) {
288: throw new UnsupportedOperationException();
289: }
290:
291: public void setLiteralValue(Object newValue) {
292: throw new UnsupportedOperationException();
293: }
294:
295: /**
296: * Writes the data in the object to the DNA writer supplied.
297: */
298: public boolean dehydrateIfNew(DNAWriter writer) throws DNAException {
299: final boolean dehydrate;
300:
301: // We use 2 flags here, we can't hold the lock on "this"
302: // while dehydrating (CDV-479). Introducing another lock would work, but would
303: // require adding a field to TCObjectImpl (which is a no-no memory wise)
304: synchronized (this ) {
305: dehydrate = isNew()
306: && !getFlag(NEW_DEHYDRATE_IN_PROGRESS_OFFSET);
307:
308: if (dehydrate) {
309: setFlag(NEW_DEHYDRATE_IN_PROGRESS_OFFSET, true);
310: }
311: }
312:
313: // Flipping the "new" flag must occur AFTER dehydrate -- otherwise the client
314: // memory manager might start nulling field values! (see canEvict() dependency on isNew() condition)
315: if (dehydrate) {
316: tcClazz.dehydrate(this , writer, getPeerObject());
317: setFlag(IS_NEW_OFFSET, false);
318: setFlag(NEW_DEHYDRATE_IN_PROGRESS_OFFSET, false);
319: }
320:
321: return dehydrate;
322: }
323:
324: public synchronized void setVersion(long version) {
325: this .version = version;
326: }
327:
328: public synchronized long getVersion() {
329: return this .version;
330: }
331:
332: public String toString() {
333: return getClass().getName() + "@"
334: + System.identityHashCode(this ) + "[objectID="
335: + objectID + ", TCClass=" + tcClazz + "]";
336: }
337:
338: public void objectFieldChanged(String classname, String fieldname,
339: Object newValue, int index) {
340: try {
341: this .markAccessed();
342: if (index == NULL_INDEX) {
343: // Assert.eval(fieldname.indexOf('.') >= 0);
344: clearReference(fieldname);
345: } else {
346: clearArrayReference(index);
347: }
348: getObjectManager().getTransactionManager().fieldChanged(
349: this , classname, fieldname, newValue, index);
350: } catch (Throwable t) {
351: Util.printLogAndRethrowError(t, logger);
352: }
353: }
354:
355: public void objectFieldChangedByOffset(String classname,
356: long fieldOffset, Object newValue, int index) {
357: String fieldname = tcClazz.getFieldNameByOffset(fieldOffset);
358: objectFieldChanged(classname, fieldname, newValue, index);
359: }
360:
361: public boolean isFieldPortableByOffset(long fieldOffset) {
362: return tcClazz.isPortableField(fieldOffset);
363: }
364:
365: public String getFieldNameByOffset(long fieldOffset) {
366: return tcClazz.getFieldNameByOffset(fieldOffset);
367: }
368:
369: public void booleanFieldChanged(String classname, String fieldname,
370: boolean newValue, int index) {
371: objectFieldChanged(classname, fieldname, new Boolean(newValue),
372: index);
373: }
374:
375: public void byteFieldChanged(String classname, String fieldname,
376: byte newValue, int index) {
377: objectFieldChanged(classname, fieldname, new Byte(newValue),
378: index);
379: }
380:
381: public void charFieldChanged(String classname, String fieldname,
382: char newValue, int index) {
383: objectFieldChanged(classname, fieldname,
384: new Character(newValue), index);
385: }
386:
387: public void doubleFieldChanged(String classname, String fieldname,
388: double newValue, int index) {
389: objectFieldChanged(classname, fieldname, new Double(newValue),
390: index);
391: }
392:
393: public void floatFieldChanged(String classname, String fieldname,
394: float newValue, int index) {
395: objectFieldChanged(classname, fieldname, new Float(newValue),
396: index);
397: }
398:
399: public void intFieldChanged(String classname, String fieldname,
400: int newValue, int index) {
401: objectFieldChanged(classname, fieldname, new Integer(newValue),
402: index);
403: }
404:
405: public void longFieldChanged(String classname, String fieldname,
406: long newValue, int index) {
407: objectFieldChanged(classname, fieldname, new Long(newValue),
408: index);
409: }
410:
411: public void shortFieldChanged(String classname, String fieldname,
412: short newValue, int index) {
413: objectFieldChanged(classname, fieldname, new Short(newValue),
414: index);
415: }
416:
417: public void objectArrayChanged(int startPos, Object[] array,
418: int length) {
419: this .markAccessed();
420: for (int i = 0; i < length; i++) {
421: clearArrayReference(startPos + i);
422: }
423: getObjectManager().getTransactionManager().arrayChanged(this ,
424: startPos, array, length);
425: }
426:
427: public void primitiveArrayChanged(int startPos, Object array,
428: int length) {
429: this .markAccessed();
430: getObjectManager().getTransactionManager().arrayChanged(this ,
431: startPos, array, length);
432: }
433:
434: public void setNext(TLinkable link) {
435: this .next = link;
436: }
437:
438: public void setPrevious(TLinkable link) {
439: this .previous = link;
440: }
441:
442: public TLinkable getNext() {
443: return this .next;
444: }
445:
446: public TLinkable getPrevious() {
447: return this .previous;
448: }
449:
450: public void markAccessed() {
451: setFlag(ACCESSED_OFFSET, true);
452: }
453:
454: public void clearAccessed() {
455: setFlag(ACCESSED_OFFSET, false);
456: }
457:
458: public boolean recentlyAccessed() {
459: return getFlag(ACCESSED_OFFSET);
460: }
461:
462: public int accessCount(int factor) {
463: // TODO:: Implement when needed
464: throw new UnsupportedOperationException();
465: }
466:
467: public synchronized void setIsNew() {
468: if (getFlag(IS_NEW_OFFSET)) {
469: throw new IllegalStateException("new flag already set");
470: }
471: setFlag(IS_NEW_OFFSET, true);
472: }
473:
474: public boolean isNew() {
475: return getFlag(IS_NEW_OFFSET);
476: }
477:
478: // These autlocking disable methods are checked in ManagerImpl. The one known use case
479: // is the Hashtable used to hold sessions. We need local synchronization,
480: // but we don't ever want autolocks for that particular instance
481: public void disableAutoLocking() {
482: setFlag(AUTOLOCKS_DISABLED_OFFSET, true);
483: }
484:
485: public boolean autoLockingDisabled() {
486: return getFlag(AUTOLOCKS_DISABLED_OFFSET);
487: }
488:
489: private void setEvictionInProgress(boolean value) {
490: setFlag(EVICTION_IN_PROGRESS_OFFSET, value);
491: }
492:
493: private boolean isEvictionInProgress() {
494: return getFlag(EVICTION_IN_PROGRESS_OFFSET);
495: }
496:
497: public synchronized boolean canEvict() {
498: boolean canEvict = isEvictable()
499: && !(isNew() || isEvictionInProgress());
500: if (canEvict) {
501: setEvictionInProgress(true);
502: }
503: return canEvict;
504: }
505:
506: protected abstract boolean isEvictable();
507:
508: }
|