package com.linkedin.databus.core; /* * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. */ import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import org.apache.log4j.Logger; public abstract class DbusEventFactory { // We define V1 as 0 because the magic byte in the DbusEvent is treated as version // and has to be zero for the first version of DbusEvent. public static final byte DBUS_EVENT_V1 = 0; public static final byte DBUS_EVENT_V2 = 2; public static final byte DBUS_EVENT_CURLY_BRACE = 123; // JSON-encoded exceptions look like "version 123" public static final Logger LOG = Logger.getLogger("DbusEventFactory"); private final ByteOrder _byteOrder; protected DbusEventFactory(ByteOrder byteOrder) { _byteOrder = byteOrder; } public abstract byte getVersion(); public ByteOrder getByteOrder() { return _byteOrder; } /** * @deprecated DO NOT USE THIS METHOD */ @Deprecated public static void setByteOrder(ByteOrder byteOrderForBackwardCompatiblity) { LOG.warn("This method is deprecated and should not be used. This call has been ignored."); } /** * Creates a writable but empty DbusEvent; for internal use by DbusEventBuffer only. * The empty event can be initialized via its reset() method. * * TODO What is the purpose of this API? TBD... # [currently used for iterators; expect to switch to createReadOnlyDbusEvent() below in upcoming change] * * @return DbusEventInternalWritable object */ public abstract DbusEventInternalWritable createWritableDbusEvent(); /** * Serializes an event into the provided ByteBuffer at its current position. * Does not change the position of the ByteBuffer after serialization. * * @param key Key of the event to be serialized. * @param serializationBuffer ByteBuffer into which the event is to be serialized. * @param dbusEventInfo Parameters describing the event to be serialized. * * @return The size of the serialized event. * @throws KeyTypeNotImplementedException if the key type specified is not supported by the underlying event format. */ public static int serializeEvent(DbusEventKey key, ByteBuffer serializationBuffer, DbusEventInfo dbusEventInfo) throws KeyTypeNotImplementedException { byte version = dbusEventInfo.getEventSerializationVersion(); if (version == DBUS_EVENT_V1) { return DbusEventV1.serializeEvent(key, serializationBuffer, dbusEventInfo); } else if (version == DBUS_EVENT_V2) { return DbusEventV2.serializeEvent(key, serializationBuffer, dbusEventInfo); } throw new UnsupportedDbusEventVersionRuntimeException(version); } /* * * Creates a read-only but empty DbusEvent for "public" iterators (i.e., those used * outside DbusEventBuffer, including in the bootstrap and the client library). The * empty event can be initialized via its reset() method. * * @param version Version of the DbusEvent protocol to be created. * @return DbusEventInternalWritable object */ /* NOT YET USED (but intended for BaseEventIterator and subclasses thereof) [when uncommenting this, also re-javadoc-ize above comment] public static DbusEventInternalReadable createReadOnlyDbusEvent(byte version); */ /** * Creates a writable DbusEvent out of an already initialized (serialized) buffer. * (For DbusEventBuffer and testing ONLY! hmmm...also now used by SendEventsRequest.ExecHandler) * (TODO: enforce this? refactor?) * * @param buf buffer containing the serialized event * @param position byte-offset of the serialized event in the buffer * @return a writable DbusEvent */ public DbusEventInternalWritable createWritableDbusEventFromBuffer(ByteBuffer buf, int position) { if (buf.order() != getByteOrder()) { throw new DatabusRuntimeException("ByteBuffer byte-order mismatch [DbusEventFactory.createWritableDbusEventFromBuffer(): buf = " + buf.order() + ", this = " + getByteOrder() + "]"); } return DbusEventFactory.createWritableDbusEventFromBufferUnchecked(buf, position); } /** * Creates a writable DbusEvent out of an already initialized (serialized) buffer. * * @param buf buffer containing the serialized event * @param position byte-offset of the serialized event in the buffer * @return a writable DbusEvent */ private static DbusEventInternalWritable createWritableDbusEventFromBufferUnchecked(ByteBuffer buf, int position) { byte version = buf.get(position); if (version == DBUS_EVENT_V1) { return new DbusEventV1(buf, position); } else if (version == DBUS_EVENT_V2) { return new DbusEventV2(buf, position); } else if (version == DBUS_EVENT_CURLY_BRACE) { // Looks like a JSON-encoding exception (e.g., from the relay if we're the client lib), so try to print it. // TODO: Longer-term, handle exceptions at the client by checking the response header first (DDSDBUS-xxx). try { throw new DatabusRuntimeException("apparent remote exception: " + getStringFromBuffer(buf, position)); } catch (UnsupportedEncodingException ex) { // can't happen; UTF-8 always supported ... but will fall through anyway } } throw new UnsupportedDbusEventVersionRuntimeException(version); } /** * Quick and dirty method to pick out the ASCII characters from a ByteBuffer and stuff them into a string. * * @param buf buffer containing the (presumed) JSON-encoded exception in place of a serialized event * @param position byte-offset of the JSON string in the buffer * @return string containing the printable ASCII characters (up to the first non-ASCII one) */ static String getStringFromBuffer(ByteBuffer buf, int position) // package-private for unit-testing throws UnsupportedEncodingException { byte[] arr; int idxFirstPrintable, idxLastPrintable; if (buf.hasArray()) { arr = buf.array(); idxFirstPrintable = position + buf.arrayOffset(); } else // do a copy, sigh { ByteBuffer roBuf = buf.asReadOnlyBuffer(); // don't want to screw up original buffer's position with get() int length = roBuf.position(position).remaining(); arr = new byte[length]; roBuf.get(arr); idxFirstPrintable = 0; } assert arr[idxFirstPrintable] == DBUS_EVENT_CURLY_BRACE; for (idxLastPrintable = idxFirstPrintable; idxLastPrintable < arr.length; ++idxLastPrintable) { byte b = arr[idxLastPrintable]; // accept only space (32) and above, except DEL (127), plus tab (9), newline (10), and CR (13) if (((b < 32) && (b != 9) && (b !=10) && (b != 13)) || (b == 127)) { break; } } // idxLastPrintable points either just past the end of the array or at the first unprintable byte int length = Math.min(idxLastPrintable-idxFirstPrintable, 512); return new String(arr, idxFirstPrintable, length, "UTF-8"); // ASCII is pure subset of UTF-8 } /** * Creates a read-only DbusEvent out of an already initialized (serialized) buffer. * * @param buf buffer containing the serialized event * @param position byte-offset of the serialized event in the buffer * @return a read-only DbusEvent */ public DbusEventInternalReadable createReadOnlyDbusEventFromBuffer(ByteBuffer buf, int position) { return createWritableDbusEventFromBuffer(buf, position); } /** * Creates a read-only DbusEvent out of an already initialized (serialized) buffer. * Callers are STRONGLY encouraged to use the non-static version of this wherever * possible so we can compare the factory's byte order against the buffer's. A * mismatch would be Very Bad. * * @param buf buffer containing the serialized event * @param position byte-offset of the serialized event in the buffer * @return a read-only DbusEvent */ public static DbusEventInternalReadable createReadOnlyDbusEventFromBufferUnchecked(ByteBuffer buf, int position) { return DbusEventFactory.createWritableDbusEventFromBufferUnchecked(buf, position); } public static int computeEventLength(DbusEventKey key, DbusEventInfo eventInfo) throws KeyTypeNotImplementedException { if (eventInfo.getEventSerializationVersion() == DBUS_EVENT_V1) { return DbusEventV1.length(key, eventInfo.getValueLength()); } else if (eventInfo.getEventSerializationVersion() == DBUS_EVENT_V2) { return DbusEventV2.computeEventLength(key, eventInfo); } throw new UnsupportedDbusEventVersionRuntimeException(eventInfo.getEventSerializationVersion()); } public abstract int serializeLongKeyEndOfPeriodMarker(ByteBuffer buffer, DbusEventInfo eventInfo); /** * Creates an EOP event with a given sequence and partition number. * * @return the event object */ public abstract DbusEventInternalReadable createLongKeyEOPEvent(long seq, short partN); // This method works (and must be used) for all control events except EOP events. EOP events have // variants with/without external-replication-bit set, etc., that needs to be ironed out. (DDSDBUS-2296) // Some EOP events also need to have a timestamp specified by the caller. See serializeLongKeyEndOfPeriodMarker() protected abstract DbusEventInternalReadable createLongKeyControlEvent(long sequence, short srcId, String msg); /** * Create a checkpoint event in the version specified. The event will not have extReplicated bit or * any other special attributes set. */ public DbusEventInternalReadable createCheckpointEvent(Checkpoint checkpoint) { return createLongKeyControlEvent(0L, // sequence DbusEventInternalWritable.CHECKPOINT_SRCID, checkpoint.toString()); } /** * Create an error event in the version specified. The event will not have extReplicated bit or * any other special attributes set. */ public DbusEventInternalReadable createErrorEvent(DbusErrorEvent dbusErrorEvent) { return createLongKeyControlEvent(0L, // sequence dbusErrorEvent.getErrorId(), dbusErrorEvent.toString()); } /** * Create an SCN regression event in the version specified. The event will not have extReplicated bit or * any other special attributes set. */ public DbusEvent createSCNRegressEvent(SCNRegressMessage scnRegressMessage) { return createLongKeyControlEvent(scnRegressMessage.getCheckpoint().getWindowScn(), DbusEventInternalWritable.SCN_REGRESS, SCNRegressMessage.toJSONString(scnRegressMessage)); } }