org.lwes.MapEvent.java Source code

Java tutorial

Introduction

Here is the source code for org.lwes.MapEvent.java

Source

/*======================================================================*
 * Licensed under the New BSD License (the "License"); you may not use  *
 * this file except in compliance with the License.  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. See accompanying LICENSE file.        *
 *======================================================================*/
package org.lwes;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lwes.db.EventTemplateDB;
import org.lwes.serializer.Deserializer;
import org.lwes.serializer.DeserializerState;
import org.lwes.serializer.Serializer;

public class MapEvent extends DefaultEvent {
    private static transient Log log = LogFactory.getLog(MapEvent.class);

    /**
     * Event data
     */
    private final ConcurrentHashMap<String, BaseType> attributes = new ConcurrentHashMap<String, BaseType>();
    private String name = null;
    private EventTemplateDB eventTemplateDB = null;
    private short encoding = DEFAULT_ENCODING;

    /**
     * If this is set to true, types and attributes are validated against the EventTemplateDB
     */
    private boolean validating = true;

    /**
     * Internal object for deserialization state
     */
    private DeserializerState state = null;

    /**
     * the size of the event in bytes
     */
    private int bytesStoreSize = 0;

    /**
     * Create an event with no name and no validation
     */
    public MapEvent() throws EventSystemException {
        this("");
    }

    /**
     * Create an event called <tt>eventName</tt> with no validation
     */
    public MapEvent(String eventName) throws EventSystemException {
        this(eventName, false, null);
    }

    /**
     * Create an event called <tt>eventName</tt>
     *
     * @param eventName       the name of the event
     * @param eventTemplateDB the EventTemplateDB to use for validation
     * @throws NoSuchEventException         if the Event does not exist in the EventTemplateDB
     * @throws NoSuchAttributeException     if an attribute does not exist in the EventTemplateDB
     * @throws NoSuchAttributeTypeException if an attribute type does not exist in the EventTemplateDB
     */
    public MapEvent(String eventName, EventTemplateDB eventTemplateDB) throws EventSystemException {
        this(eventName, true, eventTemplateDB);
    }

    /**
     * Create an event called <tt>eventName</tt>
     *
     * @param eventName       the name of the event
     * @param validate        true if the EventTemplateDB should be checked for types before all mutations
     * @param eventTemplateDB the EventTemplateDB to use for validation
     * @throws NoSuchEventException         if the Event does not exist in the EventTemplateDB
     * @throws NoSuchAttributeException     if an attribute does not exist in the EventTemplateDB
     * @throws NoSuchAttributeTypeException if an attribute type does not exist in the EventTemplateDB
     */
    public MapEvent(String eventName, boolean validate, EventTemplateDB eventTemplateDB)
            throws EventSystemException {
        this(eventName, validate, eventTemplateDB, DEFAULT_ENCODING);
    }

    /**
     * Create an event called <tt>eventName</tt>
     *
     * @param eventName the name of the event
     * @param validate  true if the EventTemplateDB should be checked for types before all mutations
     * @param encoding  the character encoding used by the event
     * @throws NoSuchEventException         if the Event does not exist in the EventTemplateDB
     * @throws NoSuchAttributeException     if an attribute does not exist in the EventTemplateDB
     * @throws NoSuchAttributeTypeException if an attribute type does not exist in the EventTemplateDB
     */
    public MapEvent(String eventName, boolean validate, EventTemplateDB eventTemplateDB, short encoding)
            throws EventSystemException {
        checkShortStringLength(eventName, encoding, MAX_EVENT_NAME_SIZE);
        setEventTemplateDB(eventTemplateDB);
        validating = validate;
        setEventName(eventName);
        setEncoding(encoding);
        setDefaultValues(eventTemplateDB);
    }

    /**
     * Creates an event by deserializing a raw byte array.
     *
     * @param bytes           the raw bytes to convert
     * @param eventTemplateDB the EventTemplateDB to use to validate the event
     * @throws NoSuchEventException
     * @throws NoSuchAttributeException
     * @throws NoSuchAttributeTypeException
     */
    public MapEvent(byte[] bytes, EventTemplateDB eventTemplateDB) throws EventSystemException {
        this(bytes, true, eventTemplateDB);
    }

    /**
     * Creates an event by deserializing a raw byte array.
     *
     * @param bytes           the raw bytes to convert
     * @param validate        whether or not to validate the event
     * @param eventTemplateDB the EventTemplateDB to use to validate the event
     * @throws NoSuchEventException
     * @throws NoSuchAttributeException
     * @throws NoSuchAttributeTypeException
     */
    public MapEvent(byte[] bytes, boolean validate, EventTemplateDB eventTemplateDB) throws EventSystemException {
        setEventTemplateDB(eventTemplateDB);
        validating = validate;
        deserialize(bytes);
        setDefaultValues(eventTemplateDB);
    }

    public MapEvent(Event event) throws NoSuchAttributeException, EventSystemException {
        this();
        copyFrom(event);
    }

    @Override
    public void reset() {
        name = "";
        validating = false;
        eventTemplateDB = null;
        attributes.clear();
        encoding = DEFAULT_ENCODING;
        if (state != null) {
            state.reset();
        }
        bytesStoreSize = 3;
    }

    protected void setDefaultValues(EventTemplateDB template) throws EventSystemException {
        if (template == null) {
            return;
        }
        for (Entry<String, BaseType> entry : template.getBaseTypesForEvent(getEventName()).entrySet()) {
            final String key = entry.getKey();
            final BaseType bt = entry.getValue();
            if (bt.getDefaultValue() != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting default value: " + key + "=" + bt.getDefaultValue());
                }
                set(key, bt.getType(), bt.getDefaultValue());
            }
        }
    }

    @Override
    public int getNumEventAttributes() {
        return attributes.size();
    }

    /**
     * Returns an enumeration of all the event attribute names
     *
     * @return an enumeration of attribute strings
     */
    @Override
    public Enumeration<String> getEventAttributeNames() {
        return attributes.keys();
    }

    @Override
    public SortedSet<String> getEventAttributes() {
        return new TreeSet<String>(attributes.keySet());
    }

    /**
     * Returns the number of attributes in the event
     *
     * @return number of attributes in the event
     */
    public int size() {
        return attributes.size();
    }

    /**
     * Returns true if the event validates against the EventTemplateDB before making changes
     *
     * @return the validating state
     */
    public boolean isValidating() {
        return this.validating;
    }

    /**
     * Set to true if the event should validate against the EventTemplateDB before making changes
     *
     * @param validate the validating value
     */
    public void setValidating(boolean validate) {
        this.validating = validate;
    }

    /**
     * Returns the EventTemplateDB for this event, used for validation of types and attributes.
     *
     * @return the EventTemplateDB
     */
    public EventTemplateDB getEventTemplateDB() {
        return this.eventTemplateDB;
    }

    /**
     * Sets the EventTemplateDB for this event, used for validation of types and attributes.
     *
     * @param eventTemplateDB the EventTemplateDB to be used for validation
     */
    public void setEventTemplateDB(EventTemplateDB eventTemplateDB) {
        this.eventTemplateDB = eventTemplateDB;
    }

    /**
     * Returns the name of the event
     *
     * @return the name of the event
     */
    @Override
    public synchronized String getEventName() {
        return this.name;
    }

    /**
     * Sets the name of the Event
     *
     * @param name the name of the event
     * @throws NoSuchEventException if the event is validating and does not exist in the EventTemplateDB
     */
    @Override
    public synchronized void setEventName(String name) {
        checkShortStringLength(name, encoding, MAX_EVENT_NAME_SIZE);

        /* determine if we already have the name and are just resetting it */
        if (this.name != null) {
            bytesStoreSize -= (this.name.length() + 1 + 2);
        }

        bytesStoreSize += (name.length() + 1 + 2);

        this.name = name;
    }

    /**
     * Get the character encoding for this event
     *
     * @return the encoding
     */
    @Override
    public short getEncoding() {
        return this.encoding;
    }

    /**
     * Set the character encoding for event strings
     *
     * @param encoding the character encoding
     * @throws NoSuchAttributeTypeException if the type for the encoding attribute does not exist
     * @throws NoSuchAttributeException     if the encoding attribute does not exist
     */
    @Override
    public void setEncoding(short encoding) throws EventSystemException {
        this.encoding = encoding;
        setInt16(ENCODING, this.encoding);
    }

    /**
     * Generic accessor, checks if an attribute exists and returns its value.  The user must do their
     * own type checking.
     *
     * @param attributeName name of the attribute to lookup
     * @return the object poitned to by attributeName
     * @throws NoSuchAttributeException if the attribute does not exist in this event
     */
    @Override
    public Object get(String attributeName) {
        if (attributes.containsKey(attributeName)) {
            return attributes.get(attributeName).getTypeObject();
        }

        return null;
    }

    @Override
    public void clear(String attributeName) {
        final BaseType bt = attributes.remove(attributeName);
        if (bt != null) {
            bytesStoreSize -= (attributeName.length() + 1) + bt.bytesStoreSize(encoding);
        }
    }

    /**
     * Set the object's attribute <tt>attributeName</tt> with the Object given
     *
     * @param attributeName  the name of the attribute to set
     * @param attributeValue the object to set the attribute with
     * @throws NoSuchAttributeException     if the attribute does not exist in this event
     * @throws NoSuchAttributeTypeException if there is an attribute with an undefined type
     */
    public void set(String attributeName, Object attributeValue) {
        if (isValidating() && getEventTemplateDB() != null) {
            if (getEventTemplateDB().checkForAttribute(getEventName(), attributeName)) {
                set(attributeName, getEventTemplateDB().getBaseTypeForObjectAttribute(getEventName(), attributeName,
                        attributeValue));
            }
        } else {
            throw new EventSystemException("Must be able to check the EventTemplateDB to use set(String,Object)");
        }
    }

    @Override
    public void set(String attribute, FieldType type, Object value) throws EventSystemException {
        set(attribute, new BaseType(type, value));
    }

    /**
     * @param attribute the name of the attribute to set
     * @param bt        the type of this attribute
     * @throws NoSuchAttributeException     if the attribute does not exist in this event
     * @throws NoSuchAttributeTypeException if there is an attribute with an undefined type
     */
    private void set(String attribute, BaseType bt) {
        checkShortStringLength(attribute, encoding, MAX_FIELD_NAME_SIZE);

        if (isValidating() && getEventTemplateDB() != null) {
            if (getEventTemplateDB().checkForAttribute(name, attribute)) {
                if (!getEventTemplateDB().checkTypeForAttribute(name, attribute, bt)) {
                    throw new EventSystemException(
                            "Wrong type '" + bt.getType() + "' for " + name + "." + attribute);
                }
            } else {
                throw new EventSystemException("Attribute " + attribute + " does not exist for event " + name);
            }
            getEventTemplateDB().checkForSize(name, attribute, bt);
        }

        // Remove the existing value, and record the reduction in the serialized size.
        final BaseType oldObject = attributes.remove(attribute);
        if (oldObject != null) {
            bytesStoreSize -= (attribute.length() + 1) + oldObject.bytesStoreSize(encoding);
        }

        if (bt.getTypeObject() != null) {
            int newSize = bytesStoreSize + ((attribute.length() + 1) + bt.bytesStoreSize(encoding));
            if (newSize > MAX_MESSAGE_SIZE) {
                throw new EventSystemException("Event size limit is " + MAX_MESSAGE_SIZE + " bytes.");
            }

            bytesStoreSize += (attribute.length() + 1) + bt.bytesStoreSize(encoding);
            attributes.put(attribute, bt);
        }
    }

    /**
     * Serializes the Event into a byte array
     *
     * @return the serialized byte array
     * @throws EventSystemException if there is a bug in predicting the serialized size
     */
    @Override
    public int serialize(byte[] bytes, int offset) {
        /*
           * Serialization uses the following protocol
           * EVENTWORD,<number of elements>,ATTRIBUTEWORD,TYPETOKEN,
           * (UINT16|INT16|UINT32|INT32|UINT64|INT64|BOOLEAN|STRING)
           * ...ATTRIBUTEWORD,TYPETOKEN(UINT16|INT16|UINT32|INT32|
           * UINT64|INT64|BOOLEAN|STRING)
           *
           * The first attribute will always be the encoding if present.
           */
        int pos = 0;
        int attributeCount = attributes.size();
        short encoding = DEFAULT_ENCODING;

        pos += Serializer.serializeEVENTWORD(name, bytes, pos);
        pos += Serializer.serializeUINT16((short) (attributeCount), bytes, pos);

        /*
           * Set the encoding attributes in the event
           */
        BaseType encodingBase = attributes.get(ENCODING);
        if (encodingBase != null) {
            Object encodingObj = encodingBase.getTypeObject();
            FieldType encodingType = encodingBase.getType();
            if (encodingObj != null) {
                if (encodingType == FieldType.INT16) {
                    encoding = (Short) encodingObj;
                    if (log.isTraceEnabled()) {
                        log.trace("Character encoding: " + encoding);
                    }
                    pos += Serializer.serializeATTRIBUTEWORD(ENCODING, bytes, pos);
                    pos += Serializer.serializeBYTE(encodingType.token, bytes, pos);
                    pos += Serializer.serializeUINT16(encoding, bytes, pos);
                }
            }
        } else {
            if (log.isWarnEnabled()) {
                log.warn("Character encoding null in event " + name);
            }
        }

        Enumeration<String> e = attributes.keys();
        while (e.hasMoreElements()) {
            String key = e.nextElement();
            if (key.equals(ENCODING)) {
                continue;
            }

            BaseType value = attributes.get(key);
            Object data = value.getTypeObject();
            FieldType type = value.getType();

            /* don't try to serialize nulls */
            if (data == null) {
                if (log.isWarnEnabled()) {
                    log.warn("Attribute " + key + " was null in event " + name);
                }
                continue;
            }

            pos += Serializer.serializeATTRIBUTEWORD(key, bytes, pos);
            pos += Serializer.serializeBYTE(type.token, bytes, pos);
            pos += Serializer.serializeValue(type, data, encoding, bytes, pos);

            if (log.isTraceEnabled()) {
                log.trace("Serialized attribute " + key);
            }
        } // while(e.hasMoreElements())

        final int bytesWritten = pos - offset;
        if (bytesStoreSize != bytesWritten) {
            String formatted = null;
            try {
                formatted = toString();
            } catch (Exception ex) {
                formatted = "<unprintable>";
            }
            throw new IllegalStateException("Expected to write " + bytesStoreSize + " bytes, but actually wrote "
                    + bytesWritten + " for " + formatted);
        }

        return bytesWritten;
    }

    @Override
    public int serialize(DataOutput output) throws IOException {
        final byte[] bytes = serialize();
        output.write(bytes);
        return bytes.length;
    }

    /**
     * Deserialize the Event from byte array
     *
     * @param bytes the byte array containing a serialized Event
     * @throws EventSystemException
     */
    @Override
    public void deserialize(byte[] bytes, int offset, int length) throws EventSystemException {
        if (bytes == null) {
            return;
        }
        if (state == null) {
            state = new DeserializerState();
        }

        state.reset();
        state.incr(offset);
        setEventName(Deserializer.deserializeEVENTWORD(state, bytes));
        long num = Deserializer.deserializeUINT16(state, bytes);
        if (log.isTraceEnabled()) {
            log.trace("Event name = " + getEventName());
            log.trace("Number of attribute: " + num);
        }
        attributes.clear();
        bytesStoreSize = state.currentIndex() - offset;
        for (int i = 0; i < num; ++i) {
            String attribute = Deserializer.deserializeATTRIBUTEWORD(state, bytes);

            final FieldType type = FieldType.byToken(Deserializer.deserializeBYTE(state, bytes));
            if (log.isTraceEnabled()) {
                log.trace("Attribute: " + attribute);
                log.trace("Type: " + type);
                log.trace("State: " + state);
            }
            if (attribute != null) {
                if (i == 0 && attribute.equals(ENCODING)) {
                    if (type == FieldType.INT16) {
                        setEncoding(Deserializer.deserializeINT16(state, bytes));
                        continue;
                    } else {
                        log.warn("Found encoding, but type was not int16 while deserializing");
                    }
                }

                set(attribute, type, Deserializer.deserializeValue(state, bytes, type, encoding));
            }
            if (bytesStoreSize != state.currentIndex() - offset) {
                throw new EventSystemException("Deserializing " + type + " field " + attribute
                        + " resulted in incorrect cache of serialized size");
            }
        }

        if (bytesStoreSize != length) {
            throw new EventSystemException(
                    "Expected to deserialize " + length + " bytes, but actually read " + bytesStoreSize);
        }
    }

    @Override
    public void deserialize(DataInput stream, int length) throws IOException, EventSystemException {
        final byte[] bytes = new byte[length];
        stream.readFully(bytes);
        deserialize(bytes);
    }

    /**
     * Returns a mutable copy of the event.  This is a SLOW operation.
     *
     * @return Event the Event object
     * @throws NoSuchEventException         if the Event does not exist in the EventTemplateDB
     * @throws NoSuchAttributeException     if the attribute does not exist in this event
     * @throws NoSuchAttributeTypeException if there is an attribute that does not match a type in the EventTemplateDB
     */
    @Override
    public Event copy() throws EventSystemException {
        /* match the type-checking of the original event */
        MapEvent evt = new MapEvent(name, isValidating(), getEventTemplateDB());
        for (Enumeration<String> e = attributes.keys(); e.hasMoreElements();) {
            String key = e.nextElement();
            BaseType value = attributes.get(key);
            evt.set(key, value.cloneBaseType());
        }
        return evt;
    }

    /**
     * This method can be used to validate an event after it has been created.
     *
     * @throws ValidationExceptions A list of validation errors
     *                              use {@link EventTemplateDB#validate(Event)}
     */
    @Deprecated
    public void validate() throws ValidationExceptions {
        EventTemplateDB templ = getEventTemplateDB();
        if (templ == null) {
            ValidationExceptions ve = new ValidationExceptions(name);
            ve.addException(new EventSystemException("No template defined."));
            throw ve;
        }

        templ.validate(this);
    }

    @Override
    public FieldType getType(String field) {
        final BaseType bt = attributes.get(field);
        return bt == null ? null : bt.getType();
    }

    /**
     * This returns the number of bytes necessary for serialization, not the number of attributes.
     */
    @Override
    public int getBytesSize() {
        return bytesStoreSize;
    }
}