/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.util;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.util.ManagedSerializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* An {@code AdaptiveField} is an abstraction over the traditional Java field
* that allows a field to be stored either locally (as a normal field would be)
* or remotely in the data store, while still allowing common operations as
* well as the ability to dynamically switch where the field's value is stored.
*
* <p>
*
* A {@code ManagedObject} will deserialize all of its non-transient fields,
* which at times is undesirable for latency (e.g. when the field is large, or
* will not be used). An {@code AdaptiveField} allow the developer finer
* control over whether a field needs to be included in the serialization graph
* by providing a common interface for interacting with the field regardless of
* how it is stored.
*
* <p>
*
* A local {@code AdaptiveField} has its value stored just like any other Java
* field. When a {@code ManagedObject} deserializes from the data store, the
* field will be included in its serialization graph. For this reason a {@code
* ManagedObject} <i>cannot</i> be stored in an {@code AdaptiveField}. If the
* developer wants to change the field so that it is stored in the data store,
* the {@link AdaptiveField#makeManaged AdaptiveField.makeManaged} call can be
* used. This causes the field's value to no longer be deserialized when the
* containing {@code ManagedObject} is deserialized.
*
* @param <T> the type of the referenced object
* @see ManagedReference
* @see com.sun.sgs.app.ManagedObject
*/
public final class AdaptiveField<T> implements Serializable {
/** The version of the serialized form. */
private static final long serialVersionUID = 0x1L;
/**
* The field if is being stored locally, else null.
*
* @serial
*/
private T local;
/**
* The reference to the field if it is current being managed by the data
* store, else null.
*
* @serial
*/
private ManagedReference<ManagedSerializable<T>> ref;
/**
* A local cache of the object if it is being managed by the data store but
* has accessed during this transaction, else null. This allows subsequent
* calls to return immediately instead of having to go to the {@code
* ManagedReference} cache.
*/
private transient T remoteCache;
/**
* Whether the field is currently maintained with a local reference or is
* being stored in the data store.
*
* @serial
*/
private boolean isLocal;
/**
* Constructs this {@code AdaptiveField} as a local reference with the
* provided value.
*
* @param value the value of this field
*
* @throws IllegalArgumentException if the provided value is a {@code
* ManagedObject}
*/
public AdaptiveField(T value) {
this(value, true);
}
/**
* Constructs this {@code AdaptiveField} with the provided value, and
* stores it as specified.
*
* @param value the value of this field
* @param isLocal whether this field should be kept with a local reference
* or stored in the data store.
*
* @throws IllegalArgumentException if the provided value is a {@code
* ManagedObject}
*/
public AdaptiveField(T value, boolean isLocal) {
this.isLocal = isLocal;
if (isLocal) {
local = value;
} else {
remoteCache = value;
ref = AppContext.getDataManager().createReference(
new ManagedSerializable<T>(value));
}
}
/**
* Returns the value of the field. Note that if the field is stored
* locally and changes, the caller should call {@link
* com.sun.sgs.app.DataManager#markForUpdate DataManager.markForUpdate} on
* the {@code ManagedObject} that contains this field since its state has
* been changed. The values of fields that are not local are cached after
* the initial call to the {@code DataManager}, so subsequent calls to
* {@code get} will act as if they are local.
*
* @return the value of the field
*/
public T get() {
if (isLocal) {
return local;
}
if (remoteCache == null && ref != null) {
remoteCache = ref.get().get();
}
return remoteCache;
}
/**
* Returns the value of the field and if remotely stored, marks the value
* for update. Note that if the field is stored locally and changes, the
* caller should call {@link com.sun.sgs.app.DataManager#markForUpdate
* DataManager.markForUpdate} on the {@code ManagedObject} that contains
* this field since its state has been changed.
*
* @return the value of the field
*/
public T getForUpdate() {
if (isLocal) {
return local;
}
if (ref != null) {
remoteCache = ref.getForUpdate().get();
}
return remoteCache;
}
/**
* Returns {@code true} if this field is stored as a local reference.
* Otherwise, the field is persisted with the {@code DataManager} and is
* excluded from the serialization graph of the object that contains this
* {@code AdaptiveField}.
*
* @return {@code true} if this field is stored as a local reference
*/
public boolean isLocal() {
return isLocal;
}
/**
* Move this field from being stored as a {@code ManagedObject} in the data
* store to being kept as a local reference. This field will now be
* included in the serialization graph of the object that contains this
* field.
*/
public void makeLocal() {
if (!isLocal) {
if (ref != null) {
ManagedSerializable<T> m = ref.get();
// REMINDER: if we can ever remote the object without having to
// call get(), then we could use to localCache to speed this
// next call up a bit.
local = m.get();
AppContext.getDataManager().removeObject(m);
} else {
// If the field was stored remotely and ref == null, then the
// value should be null, as we remove the ManagedSerializable
// from the data store when a remotely managed object is set to
// null
local = null;
}
ref = null;
remoteCache = null;
isLocal = true;
}
}
/**
* Move this field from being a local Java reference to being stored as a
* {@code ManagedObject} in the data store. This field will no longer be
* included in the serialization graph of the object that contains this
* field.
*/
public void makeManaged() {
if (isLocal) {
ref = AppContext.getDataManager().
createReference(new ManagedSerializable<T>(local));
remoteCache = local;
local = null;
isLocal = false;
}
}
/**
* If this field is not stored locally, marks the field for update.
*/
public void markForUpdate() {
if (!isLocal && ref != null) {
AppContext.getDataManager().markForUpdate(ref.get());
}
}
/**
* Sets the value of this field.
*
* @param value the new value of the field
*/
public void set(T value) {
set(value, isLocal);
}
/**
* Sets the value of this field and updates its locality based on {@code
* isLocal}.
*
* @param value the new value of the field
* @param isLocal whether this field should be kept with a local reference
* or stored in the data store.
*/
public void set(T value, boolean isLocal) {
if (isLocal) {
local = value;
// check whether it was previously managed object and if so, remove
// it
if (!this.isLocal) {
this.isLocal = true;
if (ref != null) {
AppContext.getDataManager().removeObject(ref.get());
ref = null;
remoteCache = null;
}
}
} else {
// invalidate the local copy and move this field into the data
// store
if (this.isLocal) {
this.isLocal = false;
local = null;
// null remote values are never stored in the data store, and
// the ref should already be null at this point
if (value != null) {
ref = AppContext.getDataManager().createReference(
new ManagedSerializable<T>(value));
}
} else {
// the value was already remotely managed, so attempt to reuse
// the old ManagedSerializable
if (ref == null) {
if (value != null) {
// if the previously the remote value was null, we need
// to create a new managed reference
ref = AppContext.getDataManager().createReference(
new ManagedSerializable<T>(value));
}
} else if (value != null) {
// we can reuse the previous ManagedSerializable to hold
// the value
ref.get().set(value);
} else {
// else, the value is null but we had an old value stored
// by reference, so remove it from the data store
AppContext.getDataManager().removeObject(ref.get());
ref = null;
}
}
remoteCache = value;
}
}
/** Make sure that the cached value is cleared. */
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
remoteCache = null;
}
}