blob: 3eb1085661f5bcc5dc46da416df751dcd3698b5c [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jdo.impl.fostore;
import java.io.DataInput;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.jdo.model.jdo.PersistenceModifier;
import org.apache.jdo.pm.PersistenceManagerInternal;
import org.apache.jdo.sco.SCOCollection;
import org.apache.jdo.sco.SCOMap;
import org.apache.jdo.state.FieldManager;
import org.apache.jdo.state.StateManagerInternal;
/**
* Represents a request to write a new object to the datastore.
*
* @author Dave Bristor
* @version 1.0.1
*/
//
// This is client-side code. It does not need to live in the server.
//
class InsertRequest extends AbstractFieldRequest {
// Provisional object id of object being inserted by this request.
private final OID oid;
// Metadata for this object.
private final FOStoreModel model;
private final Class cls;
// Set of CLID's of the object instances referenced by this object's
// fields (note that Strings are omitted). This is sent to the store
// along with the object values, as an appendix, so that when
// reconstituting the object, information about the referenced classes is
// available. Of course, metadata for the object is available (it was or
// will be sent to the store in an ActivateClassRequest), but that field
// information is about the type of referenced objects, not about their
// class. See storeObjectField().
private Set clids = null;
// Set of int arrays of object references in this object which are
// provisional, as offsets of OID's in the request buffer. See the end
// of doRequestBody, and storeObjectField(). Each field may add one
// entry to the ArrayList; each field may have any number of offsets
// within it. totNumOffsets is the total number of offsets, across all
// arrays in the ArrayList.
private ArrayList oidOffsets = new ArrayList();
private int numOidOffsets = 0;
private int clidOffsets[];
private int numClidOffsets;
InsertRequest(StateManagerInternal sm, Message m, FOStorePMF pmf) {
super(sm, m, pmf);
this.oid = (OID)sm.getInternalObjectId();
this.model = pmf.getModel();
this.cls = sm.getPCClass();
}
//
// Methods from AbstractRequest
//
/**
* Provides detail about data being inserted in an InsertRequest.
* The format of this request is (aside from the request header):
* <pre>
* oid: OID
* length of data block containing classname, field values, CLID info: int
* className: String (fully qualified name of class of object)
* fostoreSchemaUID for class
* length of the field values part of this block: int
* fieldValue: Object... (repeats numFields times)
* number of [CLID, classname] pairs for Object fields of this object
* [CLID, classname]...
* (the following are not in the above noted length bytes of this block)
* number of provisional OID's written in this request
* OID offset...
* number of the above written CLIDs that are provisional
* CLID offset...
* </pre>
*
* In the case of both the OID and CLID offsets, the offset is from
* the beginning of the block. In the case of the CLIDs and the OID
* and CLID offsets, the size of each set written may be zero but
* that size is always written.
* <p>
* Note that the number of CLID/classname pairs and the CLID/classname
* pairs themselves are part of the data block that is intended to be
* stored by the database server (and hence later returned upon
* fetch/getExtent). That is, the size written includes those pairs.
* The offsets, however, are not part of the datablock; they are used
* by the database server and discarded.
* @see AbstractRequest#doRequestBody
*/
protected void doRequestBody() throws IOException {
oid.write(out);
// Write out the values of all the fields.
int startPos = writeBlock(jdoClass.getPersistentFieldNumbers(), false);
// Now write the oidOffsets, so that the store can turn provisional
// OID's into datastore OID's.
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.dRB: numoidOffsets=" + // NOI18N
numOidOffsets);
}
out.writeInt(numOidOffsets);
int size = oidOffsets.size();
for (int i = 0; i < size; i++) {
int fieldOffsets[] = (int[])(oidOffsets.get(i));
int numOffsets = fieldOffsets.length;
for (int j = 0; j < numOffsets; j++) {
// Write out offset relative to start of the data block.
out.writeInt(fieldOffsets[j] - startPos);
if (logger.isDebugEnabled()) {
logger.debug(
"InsertRequest.dRB: " + oid // NOI18N
+ " rawOffset=" + fieldOffsets[j] // NOI18N
+ " offset=" + // NOI18N
(fieldOffsets[j] - startPos));
}
}
}
// Now write the offsets of the provisional CLID's
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.dRB: numClidOffsets=" + // NOI18N
numClidOffsets);
}
out.writeInt(numClidOffsets);
for (int i = 0; i < numClidOffsets; i++) {
out.writeInt(clidOffsets[i]);
}
}
//
// Methods from Request
//
/**
* Reads the oid, and notifies the persistence manager and state manager
* of the new oid.
* @see Request#handleReply
*/
public void handleReply(Status status, DataInput in, int length)
throws IOException {
OID replyOid = OID.read(in);
// We don't have to update the cache, because we will when the
// instance was made persistent, we did a CreateOIDRequest, and it's
// reply updated the cache.
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.hR: " + status + ", " + // NOI18N
oid + " " + // NOI18N
Tester.toHex(oid.oid, 16) +
" -> " + replyOid + " " + // NOI18N
Tester.toHex(replyOid.oid, 16));
}
}
//
// Methods from FieldManager
//
/**
* @see org.apache.jdo.state.FieldManager#storeBooleanField(int fieldNum,
* boolean value)
*/
public void storeBooleanField(int fieldNum, boolean value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeBoolean(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeBooleanField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeCharField(int fieldNum,
* char value)
*/
public void storeCharField(int fieldNum, char value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeChar(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeCharField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeByteField(int fieldNum,
* byte value)
*/
public void storeByteField(int fieldNum, byte value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeByte(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeByteField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeShortField(int fieldNum,
* short value)
*/
public void storeShortField(int fieldNum, short value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeShort(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeShortField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeIntField(int fieldNum, int value)
*/
public void storeIntField(int fieldNum, int value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeInt(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeIntField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeLongField(int fieldNum,
* long value)
*/
public void storeLongField(int fieldNum, long value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeLong(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeLongField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeFloatField(int fieldNum,
* float value)
*/
public void storeFloatField(int fieldNum, float value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeFloat(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeFloatField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeDoubleField(int fieldNum,
* double value)
*/
public void storeDoubleField(int fieldNum, double value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
t.storeDouble(value, out);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeDoubleField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeStringField(int fieldNum,
* String value)
*/
public void storeStringField(int fieldNum, String value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
// Though it is a String, we need to preserve pm value
// in the ObjectTranscriber in case this is request
// to fetch a PC (embedded or because its field is accessed.
PersistenceManagerInternal pm =
(PersistenceManagerInternal)sm.getPersistenceManager();
t.storeObject(value, out, pm);
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeStringField", ex); // NOI18N
}
}
/**
* @see org.apache.jdo.state.FieldManager#storeObjectField(int fieldNum,
* Object value)
*/
public void storeObjectField(int fieldNum, Object value) {
FOStoreTranscriber t = model.getTranscriber(cls, fieldNum);
try {
PersistenceManagerInternal pm =
(PersistenceManagerInternal)sm.getPersistenceManager();
int fieldOffsets[] = t.storeObject(value, out, pm);
// Add the field's object's CLID.
addCLID(value);
// If value is Collection, Map, or array then need to add the
// Class's of its elements.
if (null != value) {
if (value instanceof Collection) {
addCollectionCLIDs((Collection)value);
} else if (value instanceof Map) {
addMapCLIDs((Map)value);
} else {
Class cls = value.getClass().getComponentType();
if (null != cls) {
// We have an array. We have to add the CLIDs for all
// its elements, not just for the component type,
// because the class of the elements could be a
// subclass or implementation of the component type.
addArrayCLIDs(value);
}
}
}
// Check for and store offsets.
if (null != fieldOffsets) {
oidOffsets.add(fieldOffsets);
numOidOffsets += fieldOffsets.length;
if (logger.isDebugEnabled()) {
logger.debug(
"InsertRequest.sOF: fieldNum=" + fieldNum // NOI18N
+ " numOffsets=" + fieldOffsets.length); // NOI18N
}
}
} catch (IOException ex) {
throw new FOStoreFatalIOException(
this.getClass(), "storeObjectField", ex); // NOI18N
}
}
//
// Implementation detail
//
protected OID getOID() {
return oid;
}
/**
* Writes a data block, which consists of the values of the specified
* fields, plus the CLID's and corresponding class names of referenced
* objects.
* @param fields Field numbers of the fields to be written
* @param identifying If true, write before/flushed image fields,
* otherwise write current fields.
* @return Position in output stream at which data block starts
*/
protected int writeBlock(int fields[], boolean identifying) throws IOException {
// Save and overwrite the position where we later write the length of
// the data.
int dataLengthPos = out.getPos();
out.writeInt(LENGTH_COOKIE);
// Save start of data block
int startPos = out.getPos();
// Initialize list of CLIDs used by this instance
clids = new HashSet();
// Fully qualified class name and FOStoreSchemaUID
if (logger.isDebugEnabled()) {
logger.debug("IR: className=" + jdoClass.getName() + // NOI18N
", fsuid=" + fsuid); // NOI18N
}
out.writeUTF(jdoClass.getName());
fsuid.write(out);
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.writeBlock: classname=" + // NOI18N
jdoClass.getName());
}
insertFields(fields, identifying);
// Write the CLID's and classnames. Some might be provisional, so as
// with the OID's we must write their offsets so they can be turned
// into real CLID's in the store. Here we save the offsets, to write
// them later, after the data block.
int size = clids.size();
clidOffsets = new int[size]; // Max num of possible provisionals
numClidOffsets = 0;
if (logger.isDebugEnabled()) {
logger.debug(
"InsertRequest.writeBlock: numCLID's=" + // NOI18N
size);
}
out.writeInt(size);
for (Iterator i = clids.iterator(); i.hasNext();) {
CLID c = (CLID)i.next();
if (c.isProvisional()) {
clidOffsets[numClidOffsets++] = out.getPos() - startPos;
}
c.write(out);
Class clz = CLID.getKnownType(c);
if (null == clz) {
clz = model.getClass(c);
}
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.writeBlock: " + c); // NOI18N
}
String className = clz.getName();
if (logger.isDebugEnabled()) {
logger.debug(
"InsertRequest.writeBlock: field type " + // NOI18N
className);
}
out.writeUTF(className);
FOStoreSchemaUID fldUID = FOStoreSchemaUID.lookup(clz, model);
fldUID.write(out);
}
// Write the length of the block (data + CLID's)
int currentPos = out.getPos();
int length = currentPos - startPos;
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.dRB: length=" + length); // NOI18N
}
out.setPos(dataLengthPos);
out.writeInt(length);
out.setPos(currentPos);
return startPos;
}
/**
* Writes values of the specified fields. If identifying is true, writes
* identifying fields, otherwise writes current fields.
*/
private void insertFields(int[] fields, boolean identifying)
throws IOException {
int fieldLengthPos = out.getPos();
out.writeInt(LENGTH_COOKIE);
int fieldPos = out.getPos();
sm.provideFields(fields, this, identifying);
// Write the length of the field values part of the block.
int currentPos = out.getPos();
int length = currentPos - fieldPos;
out.setPos(fieldLengthPos);
out.writeInt(length);
out.setPos(currentPos);
if (logger.isDebugEnabled()) {
logger.debug("InsertRequest.dRB: field values length=" + length); // NOI18N
}
}
private void addCollectionCLIDs(Collection c) {
Iterator i;
if (c instanceof SCOCollection) {
SCOCollection sco = (SCOCollection)c;
i = sco.eitherIterator();
} else {
i = c.iterator();
}
while(i.hasNext()) {
addCLID(i.next());
}
}
private void addMapCLIDs(Map m) {
Iterator i = null;
if (m instanceof SCOMap) {
i = ((SCOMap)m).eitherIterator();
} else {
i = m.entrySet().iterator();
}
while (i.hasNext()) {
Map.Entry entry = (Map.Entry)i.next();
addCLID(entry.getKey());
addCLID(entry.getValue());
}
}
private void addArrayCLIDs(Object arr) {
try {
int length = Array.getLength(arr);
for (int i = 0; i < length; i++) {
Object o = Array.get(arr, i);
addCLID(o);
}
} catch (RuntimeException ex) {
throw new FOStoreFatalInternalException(
this.getClass(), "addArrayCLIDs", ex); // NOI18N
}
}
private void addCLID(Object o) {
if (null != o) {
Class cls = o.getClass();
if (! CLID.isKnown(cls)) {
clids.add(model.getCLID(cls));
}
}
}
}