blob: 48c3fce1d7becebe79c4e60ad379bc62efd6a250 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.ignite.internal.binary.builder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.ignite.binary.BinaryInvalidTypeException;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.binary.BinaryObjectBuilder;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryType;
import org.apache.ignite.internal.binary.BinaryContext;
import org.apache.ignite.internal.binary.BinaryEnumObjectImpl;
import org.apache.ignite.internal.binary.BinaryFieldMetadata;
import org.apache.ignite.internal.binary.BinaryMetadata;
import org.apache.ignite.internal.binary.BinaryObjectImpl;
import org.apache.ignite.internal.binary.BinaryObjectOffheapImpl;
import org.apache.ignite.internal.binary.BinarySchema;
import org.apache.ignite.internal.binary.BinarySchemaRegistry;
import org.apache.ignite.internal.binary.BinaryUtils;
import org.apache.ignite.internal.binary.BinaryWriterExImpl;
import org.apache.ignite.internal.binary.GridBinaryMarshaller;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.thread.IgniteThread;
import org.jetbrains.annotations.Nullable;
/**
*
*/
public class BinaryObjectBuilderImpl implements BinaryObjectBuilder {
/** */
private static final Object REMOVED_FIELD_MARKER = new Object();
/** */
private final BinaryContext ctx;
/** */
private final int typeId;
/** May be null. */
private String typeName;
/** May be null. */
private String clsNameToWrite;
/** */
private boolean registeredType = true;
/** */
private Map<String, Object> assignedVals;
/** */
private Map<Integer, Object> readCache;
/** Position of object in source array, or -1 if object is not created from BinaryObject. */
private final int start;
/** Flags. */
private final short flags;
/** Total header length */
private final int hdrLen;
/** Context of BinaryObject reading process. Or {@code null} if object is not created from BinaryObject. */
private final BinaryBuilderReader reader;
/** Affinity key field name. */
private String affFieldName;
/**
* @param clsName Class name.
* @param ctx Binary context.
*/
public BinaryObjectBuilderImpl(BinaryContext ctx, String clsName) {
this(ctx, ctx.typeId(clsName), ctx.userTypeName(clsName));
}
/**
* @param typeName Type name.
* @param ctx Context.
* @param typeId Type id.
*/
public BinaryObjectBuilderImpl(BinaryContext ctx, int typeId, String typeName) {
this.typeId = typeId;
this.typeName = typeName;
this.ctx = ctx;
start = -1;
flags = -1;
reader = null;
hdrLen = GridBinaryMarshaller.DFLT_HDR_LEN;
readCache = Collections.emptyMap();
}
/**
* @param obj Object to wrap.
*/
public BinaryObjectBuilderImpl(BinaryObjectImpl obj) {
this(new BinaryBuilderReader(obj), obj.start());
reader.registerObject(this);
}
/**
* @param reader ctx
* @param start Start.
*/
BinaryObjectBuilderImpl(BinaryBuilderReader reader, int start) {
assert reader != null;
this.reader = reader;
this.start = start;
this.flags = reader.readShortPositioned(start + GridBinaryMarshaller.FLAGS_POS);
byte ver = reader.readBytePositioned(start + GridBinaryMarshaller.PROTO_VER_POS);
BinaryUtils.checkProtocolVersion(ver);
int typeId = reader.readIntPositioned(start + GridBinaryMarshaller.TYPE_ID_POS);
ctx = reader.binaryContext();
if (typeId == GridBinaryMarshaller.UNREGISTERED_TYPE_ID) {
int mark = reader.position();
reader.position(start + GridBinaryMarshaller.DFLT_HDR_LEN);
clsNameToWrite = reader.readString();
Class cls;
try {
cls = U.forName(clsNameToWrite, ctx.configuration().getClassLoader());
}
catch (ClassNotFoundException e) {
throw new BinaryInvalidTypeException("Failed to load the class: " + clsNameToWrite, e);
}
this.typeId = ctx.registerClass(cls, true, false).typeId();
registeredType = false;
hdrLen = reader.position() - mark;
reader.position(mark);
}
else {
this.typeId = typeId;
hdrLen = GridBinaryMarshaller.DFLT_HDR_LEN;
}
}
/** {@inheritDoc} */
@Override public BinaryObject build() {
try (BinaryWriterExImpl writer = new BinaryWriterExImpl(ctx)) {
Thread curThread = Thread.currentThread();
if (curThread instanceof IgniteThread)
writer.failIfUnregistered(((IgniteThread)curThread).isForbiddenToRequestBinaryMetadata());
writer.typeId(typeId);
BinaryBuilderSerializer serializationCtx = new BinaryBuilderSerializer();
serializationCtx.registerObjectWriting(this, 0);
serializeTo(writer, serializationCtx);
byte[] arr = writer.array();
return new BinaryObjectImpl(ctx, arr, 0);
}
}
/**
* @param writer Writer.
* @param serializer Serializer.
*/
void serializeTo(BinaryWriterExImpl writer, BinaryBuilderSerializer serializer) {
try {
writer.preWrite(registeredType ? null : clsNameToWrite);
Set<Integer> remainsFlds = null;
BinaryType meta = ctx.metadata(typeId);
Map<String, BinaryFieldMetadata> fieldsMeta = null;
if (reader != null && BinaryUtils.hasSchema(flags)) {
BinarySchema schema = reader.schema();
Map<Integer, Object> assignedFldsById;
if (assignedVals != null) {
assignedFldsById = U.newHashMap(assignedVals.size());
for (Map.Entry<String, Object> entry : assignedVals.entrySet()) {
String name = entry.getKey();
Object val = entry.getValue();
int fieldId = ctx.fieldId(typeId, name);
assignedFldsById.put(fieldId, val);
if (val != REMOVED_FIELD_MARKER)
fieldsMeta = checkMetadata(meta, fieldsMeta, val, name, fieldId);
}
remainsFlds = assignedFldsById.keySet();
}
else
assignedFldsById = Collections.emptyMap();
// Get footer details.
int fieldIdLen = BinaryUtils.fieldIdLength(flags);
int fieldOffsetLen = BinaryUtils.fieldOffsetLength(flags);
IgniteBiTuple<Integer, Integer> footer = BinaryUtils.footerAbsolute(reader, start);
int footerPos = footer.get1();
int footerEnd = footer.get2();
// Get raw position.
int rawPos = BinaryUtils.rawOffsetAbsolute(reader, start);
// Position reader on data.
reader.position(start + hdrLen);
int idx = 0;
while (reader.position() < rawPos) {
int fieldId = schema.fieldId(idx++);
int fieldLen =
fieldPositionAndLength(footerPos, footerEnd, rawPos, fieldIdLen, fieldOffsetLen).get2();
int postPos = reader.position() + fieldLen; // Position where reader will be placed afterwards.
footerPos += fieldIdLen + fieldOffsetLen;
if (assignedFldsById.containsKey(fieldId)) {
Object assignedVal = assignedFldsById.remove(fieldId);
if (assignedVal != REMOVED_FIELD_MARKER) {
writer.writeFieldId(fieldId);
serializer.writeValue(writer, assignedVal);
}
}
else {
int type = fieldLen != 0 ? reader.readByte(0) : 0;
if (fieldLen != 0 && !BinaryUtils.isPlainArrayType(type) && BinaryUtils.isPlainType(type)) {
writer.writeFieldId(fieldId);
writer.write(reader.array(), reader.position(), fieldLen);
}
else {
writer.writeFieldId(fieldId);
Object val;
if (fieldLen == 0)
val = null;
else if (readCache == null) {
val = reader.parseValue();
assert reader.position() == postPos;
}
else
val = readCache.get(fieldId);
serializer.writeValue(writer, val);
}
}
reader.position(postPos);
}
}
if (assignedVals != null && (remainsFlds == null || !remainsFlds.isEmpty())) {
for (Map.Entry<String, Object> entry : assignedVals.entrySet()) {
Object val = entry.getValue();
if (val == REMOVED_FIELD_MARKER)
continue;
String name = entry.getKey();
int fieldId = ctx.fieldId(typeId, name);
if (remainsFlds != null && !remainsFlds.contains(fieldId))
continue;
writer.writeFieldId(fieldId);
serializer.writeValue(writer, val);
if (reader == null)
// Metadata has already been checked.
fieldsMeta = checkMetadata(meta, fieldsMeta, val, name, fieldId);
}
}
if (reader != null) {
// Write raw data if any.
int rawOff = BinaryUtils.rawOffsetAbsolute(reader, start);
int footerStart = BinaryUtils.footerStartAbsolute(reader, start);
if (rawOff < footerStart) {
writer.rawWriter();
writer.write(reader.array(), rawOff, footerStart - rawOff);
}
// Shift reader to the end of the object.
reader.position(start + BinaryUtils.length(reader, start));
}
writer.postWrite(true, registeredType);
// Update metadata if needed.
int schemaId = writer.schemaId();
BinarySchemaRegistry schemaReg = ctx.schemaRegistry(typeId);
if (schemaReg.schema(schemaId) == null) {
String typeName = this.typeName;
if (typeName == null) {
assert meta != null;
typeName = meta.typeName();
}
BinarySchema curSchema = writer.currentSchema();
String affFieldName0 = affFieldName;
if (affFieldName0 == null)
affFieldName0 = ctx.affinityKeyFieldName(typeId);
ctx.registerUserClassName(typeId, typeName, writer.failIfUnregistered(), false);
ctx.updateMetadata(typeId, new BinaryMetadata(typeId, typeName, fieldsMeta, affFieldName0,
Collections.singleton(curSchema), false, null), writer.failIfUnregistered());
schemaReg.addSchema(curSchema.schemaId(), curSchema);
}
// Update hash code after schema is written.
writer.postWriteHashCode(registeredType ? null : clsNameToWrite);
}
finally {
writer.popSchema();
}
}
/**
* Checks metadata when a BinaryObject is being serialized.
*
* @param meta Current metadata.
* @param fieldsMeta Map holding metadata information that has to be updated.
* @param newVal Field value being serialized.
* @param name Field name.
* @param fieldId Field ID.
*/
private Map<String, BinaryFieldMetadata> checkMetadata(BinaryType meta, Map<String, BinaryFieldMetadata> fieldsMeta,
Object newVal, String name, int fieldId) {
String oldFldTypeName = meta == null ? null : meta.fieldTypeName(name);
boolean nullFieldVal = false;
int newFldTypeId;
if (newVal instanceof BinaryValueWithType) {
newFldTypeId = ((BinaryValueWithType)newVal).typeId();
if (((BinaryValueWithType)newVal).value() == null)
nullFieldVal = true;
}
// Detect Enum and Enum array type.
else if (newVal instanceof BinaryEnumObjectImpl)
newFldTypeId = GridBinaryMarshaller.ENUM;
else if (newVal.getClass().isArray() && BinaryEnumObjectImpl.class.isAssignableFrom(newVal.getClass().getComponentType()))
newFldTypeId = GridBinaryMarshaller.ENUM_ARR;
else if (newVal.getClass().isArray() && BinaryObject.class.isAssignableFrom(newVal.getClass().getComponentType()))
newFldTypeId = GridBinaryMarshaller.OBJ_ARR;
else
newFldTypeId = BinaryUtils.typeByClass(newVal.getClass());
if (oldFldTypeName == null) {
// It's a new field, we have to add it to metadata.
if (fieldsMeta == null) {
if (BinaryUtils.FIELDS_SORTED_ORDER)
fieldsMeta = new TreeMap<>();
else
fieldsMeta = new LinkedHashMap<>();
}
fieldsMeta.put(name, new BinaryFieldMetadata(newFldTypeId, fieldId));
}
else if (!nullFieldVal) {
String newFldTypeName = BinaryUtils.fieldTypeName(newFldTypeId);
if (!F.eq(newFldTypeName, oldFldTypeName) &&
!oldFldTypeName.equals(BinaryUtils.fieldTypeName(GridBinaryMarshaller.OBJ))) {
throw new BinaryObjectException(
"Wrong value has been set [" +
"typeName=" + (typeName == null ? meta.typeName() : typeName) +
", fieldName=" + name +
", fieldType=" + oldFldTypeName +
", assignedValueType=" + newFldTypeName + ']'
);
}
}
return fieldsMeta;
}
/**
* Get field position and length.
*
* @param footerPos Field position inside the footer (absolute).
* @param footerEnd Footer end (absolute).
* @param rawPos Raw data position (absolute).
* @param fieldIdLen Field ID length.
* @param fieldOffsetLen Field offset length.
* @return Tuple with field position and length.
*/
private IgniteBiTuple<Integer, Integer> fieldPositionAndLength(int footerPos, int footerEnd, int rawPos,
int fieldIdLen, int fieldOffsetLen) {
// Get field offset first.
int fieldOffset = BinaryUtils.fieldOffsetRelative(reader, footerPos + fieldIdLen, fieldOffsetLen);
int fieldPos = start + fieldOffset;
// Get field length.
int fieldLen;
if (footerPos + fieldIdLen + fieldOffsetLen == footerEnd)
// This is the last field, compare to raw offset.
fieldLen = rawPos - fieldPos;
else {
// Field is somewhere in the middle, get difference with the next offset.
int nextFieldOffset = BinaryUtils.fieldOffsetRelative(reader,
footerPos + fieldIdLen + fieldOffsetLen + fieldIdLen, fieldOffsetLen);
fieldLen = nextFieldOffset - fieldOffset;
}
return F.t(fieldPos, fieldLen);
}
/**
* Initialize read cache if needed.
*/
private void ensureReadCacheInit() {
assert reader != null;
if (readCache == null) {
int fieldIdLen = BinaryUtils.fieldIdLength(flags);
int fieldOffsetLen = BinaryUtils.fieldOffsetLength(flags);
BinarySchema schema = reader.schema();
Map<Integer, Object> readCache = new HashMap<>();
IgniteBiTuple<Integer, Integer> footer = BinaryUtils.footerAbsolute(reader, start);
int footerPos = footer.get1();
int footerEnd = footer.get2();
int rawPos = BinaryUtils.rawOffsetAbsolute(reader, start);
int idx = 0;
while (footerPos + fieldIdLen < footerEnd) {
int fieldId = schema.fieldId(idx++);
IgniteBiTuple<Integer, Integer> posAndLen =
fieldPositionAndLength(footerPos, footerEnd, rawPos, fieldIdLen, fieldOffsetLen);
Object val = reader.getValueQuickly(posAndLen.get1(), posAndLen.get2());
readCache.put(fieldId, val);
// Shift current footer position.
footerPos += fieldIdLen + fieldOffsetLen;
}
this.readCache = readCache;
}
}
/**
* If value of {@link #assignedVals} is null, set it according to {@link BinaryUtils#FIELDS_SORTED_ORDER}.
*/
private Map<String, Object> assignedValues() {
if (assignedVals == null) {
if (BinaryUtils.FIELDS_SORTED_ORDER)
assignedVals = new TreeMap<>();
else
assignedVals = new LinkedHashMap<>();
}
return assignedVals;
}
/** {@inheritDoc} */
@Override public <T> T getField(String name) {
Object val;
if (assignedVals != null && assignedVals.containsKey(name)) {
val = assignedVals.get(name);
if (val == REMOVED_FIELD_MARKER)
return null;
}
else {
ensureReadCacheInit();
int fldId = ctx.fieldId(typeId, name);
val = readCache.get(fldId);
}
return (T)BinaryUtils.unwrapLazy(val);
}
/** {@inheritDoc} */
@Override public BinaryObjectBuilder setField(String name, Object val0) {
Object val = assignedValues().get(name);
if (val instanceof BinaryValueWithType)
((BinaryValueWithType)val).value(val0);
else {
Class valCls = (val == null) ? Object.class : val.getClass();
val = val0 == null ? new BinaryValueWithType(BinaryUtils.typeByClass(valCls), null) : val0;
}
assignedValues().put(name, val);
return this;
}
/** {@inheritDoc} */
@Override public <T> BinaryObjectBuilder setField(String name, @Nullable T val, Class<? super T> type) {
byte typeId;
if (Collection.class.equals(type))
typeId = GridBinaryMarshaller.COL;
else if (Map.class.equals(type))
typeId = GridBinaryMarshaller.MAP;
else
typeId = BinaryUtils.typeByClass(type);
assignedValues().put(name, new BinaryValueWithType(typeId, val));
return this;
}
/** {@inheritDoc} */
@Override public BinaryObjectBuilder setField(String name, @Nullable BinaryObjectBuilder builder) {
return setField(name, (Object)builder);
}
/**
* Removes field from binary object.
*
* @param name Field name.
* @return {@code this} instance for chaining.
*/
@Override public BinaryObjectBuilderImpl removeField(String name) {
assignedValues().put(name, REMOVED_FIELD_MARKER);
return this;
}
/**
* Creates builder initialized by specified binary object.
*
* @param obj Binary object to initialize builder.
* @return New builder.
*/
public static BinaryObjectBuilderImpl wrap(BinaryObject obj) {
BinaryObjectImpl heapObj;
if (obj instanceof BinaryObjectOffheapImpl)
heapObj = (BinaryObjectImpl)((BinaryObjectOffheapImpl)obj).heapCopy();
else
heapObj = (BinaryObjectImpl)obj;
return new BinaryObjectBuilderImpl(heapObj);
}
/**
* @return Object start position in source array.
*/
int start() {
return start;
}
/**
* @return Object type id.
*/
public int typeId() {
return typeId;
}
/**
* Set known affinity key field name.
*
* @param affFieldName Affinity key field name.
*/
public void affinityFieldName(String affFieldName) {
this.affFieldName = affFieldName;
}
}