All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.espertech.esperio.csv.CSVInputAdapter Maven / Gradle / Ivy

The newest version!
/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esperio.csv;

import com.espertech.esper.common.client.EPException;
import com.espertech.esper.common.client.EventType;
import com.espertech.esper.common.client.PropertyAccessException;
import com.espertech.esper.common.internal.event.eventtyperepo.EventTypeRepository;
import com.espertech.esper.common.internal.event.map.MapEventType;
import com.espertech.esper.common.internal.schedule.ScheduleBucket;
import com.espertech.esper.common.internal.settings.ClasspathImportService;
import com.espertech.esper.common.internal.util.ExecutionPathDebugLog;
import com.espertech.esper.common.internal.util.JavaClassHelper;
import com.espertech.esper.runtime.client.EPRuntime;
import com.espertech.esper.runtime.client.util.AdapterState;
import com.espertech.esper.runtime.client.util.InputAdapter;
import com.espertech.esper.runtime.internal.kernel.service.EPRuntimeSPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.EOFException;
import java.util.*;

/**
 * An event Adapter that uses a CSV file for a source.
 */
public class CSVInputAdapter extends AbstractCoordinatedAdapter implements InputAdapter {
    private static final Logger log = LoggerFactory.getLogger(CSVInputAdapter.class);

    private Integer eventsPerSec;
    private CSVReader reader;
    private AbstractTypeCoercer coercer = new BasicTypeCoercer();
    private String[] propertyOrder;
    private CSVInputAdapterSpec adapterSpec;
    private Map propertyTypes;
    private String eventTypeName;
    private long lastTimestamp = 0;
    private long totalDelay;
    boolean atEOF = false;
    private String[] firstRow;
    private Class beanClass;
    private int rowCount = 0;

    /**
     * Ctor.
     *
     * @param runtime - provides the runtimeruntime and services
     * @param spec    - the parameters for this adapter
     */
    public CSVInputAdapter(EPRuntime runtime, CSVInputAdapterSpec spec) {
        super(runtime, spec.isUsingRuntimeThread(), spec.isUsingExternalTimer(), spec.isUsingTimeSpanEvents());

        adapterSpec = spec;
        eventTypeName = adapterSpec.getEventTypeName();
        eventsPerSec = spec.getEventsPerSec();

        if (runtime != null) {
            finishInitialization(runtime, spec);
        }
    }

    /**
     * Ctor.
     *
     * @param runtime            - provides the runtimeruntime and services
     * @param adapterInputSource - the source of the CSV file
     * @param eventTypeName      - the type name of the Map event to create from the CSV data
     */
    public CSVInputAdapter(EPRuntime runtime, AdapterInputSource adapterInputSource, String eventTypeName) {
        this(runtime, new CSVInputAdapterSpec(adapterInputSource, eventTypeName));
    }

    /**
     * Ctor for adapters that will be passed to an AdapterCoordinator.
     *
     * @param adapterSpec contains parameters that specify the behavior of the input adapter
     */
    public CSVInputAdapter(CSVInputAdapterSpec adapterSpec) {
        this(null, adapterSpec);
    }

    /**
     * Ctor for adapters that will be passed to an AdapterCoordinator.
     *
     * @param adapterInputSource - the parameters for this adapter
     * @param eventTypeName      - the event type name that the input adapter generates events for
     */
    public CSVInputAdapter(AdapterInputSource adapterInputSource, String eventTypeName) {
        this(null, adapterInputSource, eventTypeName);
    }


    /* (non-Javadoc)
     * @see com.espertech.esperio.ReadableAdapter#read()
     */
    public SendableEvent read() throws EPException {
        if (stateManager.getState() == AdapterState.DESTROYED || atEOF) {
            return null;
        }

        try {
            if (eventsToSend.isEmpty()) {
                if (beanClass != null) {
                    return new SendableBeanEvent(newMapEvent(), beanClass, eventTypeName, totalDelay, scheduleSlot);
                } else {
                    return new SendableMapEvent(newMapEvent(), eventTypeName, totalDelay, scheduleSlot);
                }
            } else {
                SendableEvent theEvent = eventsToSend.first();
                eventsToSend.remove(theEvent);
                return theEvent;
            }
        } catch (EOFException e) {
            if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) {
                log.debug(".read reached end of CSV file");
            }
            atEOF = true;
            if (stateManager.getState() == AdapterState.STARTED) {
                stop();
            } else {
                destroy();
            }
            return null;
        }
    }


    /* (non-Javadoc)
     * @see com.espertech.esperio.csv.AbstractCoordinatedAdapter#setRuntime
     */
    @Override
    public void setRuntime(EPRuntime runtime) {
        super.setRuntime(runtime);
        finishInitialization(runtime, adapterSpec);
    }

    /**
     * Sets the coercing provider.
     *
     * @param coercer to use for coercing
     */
    public void setCoercer(AbstractTypeCoercer coercer) {
        this.coercer = coercer;
    }

    /**
     * Close the CSVReader.
     */
    protected void close() {
        reader.close();
    }

    /**
     * Remove the first member of eventsToSend. If there is
     * another record in the CSV file, insert the event created
     * from it into eventsToSend.
     */
    protected void replaceFirstEventToSend() {
        eventsToSend.remove(eventsToSend.first());
        SendableEvent theEvent = read();
        if (theEvent != null) {
            eventsToSend.add(theEvent);
        }
    }

    /**
     * Reset all the changeable state of this ReadableAdapter, as if it were just created.
     */
    protected void reset() {
        lastTimestamp = 0;
        totalDelay = 0;
        atEOF = false;
        if (reader.isResettable()) {
            reader.reset();
        }
    }

    private void finishInitialization(EPRuntime runtime, CSVInputAdapterSpec spec) {
        assertValidParameters(runtime, spec);

        EPRuntimeSPI spi = (EPRuntimeSPI) runtime;

        scheduleSlot = new ScheduleBucket(-1).allocateSlot();

        reader = new CSVReader(spec.getAdapterInputSource());
        reader.setLooping(spec.isLooping());

        String[] firstRow = getFirstRow();

        Map givenPropertyTypes = constructPropertyTypes(spec.getEventTypeName(), spec.getPropertyTypes(), spi.getServicesContext().getEventTypeRepositoryBus());

        propertyOrder = spec.getPropertyOrder() != null ?
            spec.getPropertyOrder() :
            CSVPropertyOrderHelper.resolvePropertyOrder(firstRow, givenPropertyTypes);

        reader.setIsUsingTitleRow(isUsingTitleRow(firstRow, propertyOrder));
        if (!isUsingTitleRow(firstRow, propertyOrder)) {
            this.firstRow = firstRow;
        }

        propertyTypes = resolvePropertyTypes(givenPropertyTypes, spi.getServicesContext().getClasspathImportServiceRuntime());
        if (givenPropertyTypes == null) {
            throw new EPException("CSV adapter requires a predefined event type name, the event type named '" + spec.getEventTypeName() + "' could not be found");
        }

        coercer.setPropertyTypes(propertyTypes);
    }

    private Map newMapEvent() throws EOFException {
        ++rowCount;
        String[] row = firstRow != null ? firstRow : reader.getNextRecord();
        firstRow = null;
        Map map = createMapFromRow(row);
        updateTotalDelay(map, reader.getAndClearIsReset());
        return map;
    }

    private Map createMapFromRow(String[] row) {
        Map map = new HashMap();

        int count = 0;

        try {
            for (String property : propertyOrder) {
                // Skip properties that are in the title row but not
                // part of the map to send
                if ((propertyTypes != null) &&
                    (!propertyTypes.containsKey(property)) &&
                    (!property.equals(adapterSpec.getTimestampColumn()))) {
                    count++;
                    continue;
                }
                Object value = coercer.coerce(property, row[count++]);
                map.put(property, value);
            }
        } catch (Exception e) {
            throw new EPException(e);
        }
        return map;
    }

    private Map constructPropertyTypes(String eventTypeName, Map propertyTypesGiven, EventTypeRepository eventAdapterService) {
        Map propertyTypes = new HashMap();
        EventType eventType = eventAdapterService.getNameToTypeMap().get(eventTypeName);
        if (eventType == null) {
            if (propertyTypesGiven != null) {
                throw new EPException("Failed to find event type named '" + eventTypeName + "'");
            }
            return propertyTypesGiven;
        }
        if (!eventType.getUnderlyingType().equals(Map.class)) {
            beanClass = eventType.getUnderlyingType();
        }
        if (propertyTypesGiven != null && eventType.getPropertyNames().length != propertyTypesGiven.size()) {
            // allow this scenario for beans as we may want to bring in a subset of properties
            if (beanClass != null) {
                return propertyTypesGiven;
            } else {
                throw new EPException("Event type " + eventTypeName + " has already been declared with a different number of parameters");
            }
        }
        for (String property : eventType.getPropertyNames()) {
            Class type;
            try {
                type = eventType.getPropertyType(property);
            } catch (PropertyAccessException e) {
                // thrown if trying to access an invalid property on an EventBean
                throw new EPException(e);
            }
            if (propertyTypesGiven != null && propertyTypesGiven.get(property) == null) {
                throw new EPException("Event type " + eventTypeName + "has already been declared with different parameters");
            }
            if (propertyTypesGiven != null && !propertyTypesGiven.get(property).equals(type)) {
                throw new EPException("Event type " + eventTypeName + "has already been declared with a different type for property " + property);
            }
            // we can't set read-only properties for bean
            if (!eventType.getUnderlyingType().equals(Map.class)) {
                PropertyDescriptor[] pds = new PropertyDescriptor[0];
                try {
                    pds = Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
                } catch (IntrospectionException e) {
                    throw new EPException("Failed to introspect class " + beanClass.getName() + ": " + e.getMessage(), e);
                }
                PropertyDescriptor pd = null;
                for (PropertyDescriptor p : pds) {
                    if (p.getName().equals(property))
                        pd = p;
                }
                if (pd == null) {
                    continue;
                }
                if (pd.getWriteMethod() == null) {
                    if (propertyTypesGiven == null) {
                        continue;
                    } else {
                        throw new EPException("Event type " + eventTypeName + "property " + property + " is read only");
                    }
                }
            }
            propertyTypes.put(property, type);
        }

        // flatten nested types
        Map flattenPropertyTypes = new HashMap();
        for (String p : propertyTypes.keySet()) {
            Object type = propertyTypes.get(p);
            if (type instanceof Class && ((Class) type).getName().equals("java.util.Map") && eventType instanceof MapEventType) {
                MapEventType mapEventType = (MapEventType) eventType;
                Map nested = (Map) mapEventType.getTypes().get(p);
                for (String nestedProperty : nested.keySet()) {
                    flattenPropertyTypes.put(p + "." + nestedProperty, nested.get(nestedProperty));
                }
            } else if (type instanceof Class) {
                Class c = (Class) type;
                if (!c.isPrimitive() && !c.getName().startsWith("java")) {
                    PropertyDescriptor[] pds = new PropertyDescriptor[0];
                    try {
                        pds = Introspector.getBeanInfo(c).getPropertyDescriptors();
                    } catch (IntrospectionException e) {
                        throw new EPException("Failed to introspect class " + c + ": " + e.getMessage(), e);
                    }
                    for (PropertyDescriptor pd : pds) {
                        if (pd.getWriteMethod() != null)
                            flattenPropertyTypes.put(p + "." + pd.getName(), pd.getPropertyType());
                    }
                } else {
                    flattenPropertyTypes.put(p, type);
                }
            } else {
                flattenPropertyTypes.put(p, type);
            }
        }
        return flattenPropertyTypes;
    }

    private void updateTotalDelay(Map map, boolean isFirstRow) {
        if (eventsPerSec != null) {
            int msecPerEvent = 1000 / eventsPerSec;
            totalDelay += msecPerEvent;
        } else if (adapterSpec.getTimestampColumn() != null) {
            Long timestamp = resolveTimestamp(map);
            if (timestamp == null) {
                throw new EPException("Couldn't resolve the timestamp for record " + map);
            } else if (timestamp < 0) {
                throw new EPException("Encountered negative timestamp for CSV record : " + map);
            } else {
                long timestampDifference = 0;
                if (timestamp < lastTimestamp) {
                    if (!isFirstRow) {
                        throw new EPException("Subsequent timestamp " + timestamp + " is smaller than previous timestamp " + lastTimestamp);
                    } else {
                        timestampDifference = timestamp;
                    }
                } else {
                    timestampDifference = timestamp - lastTimestamp;
                }
                lastTimestamp = timestamp;
                totalDelay += timestampDifference;
            }
        }
    }

    private Long resolveTimestamp(Map map) {
        if (adapterSpec.getTimestampColumn() != null) {
            Object value = map.get(adapterSpec.getTimestampColumn());
            return Long.parseLong(value.toString());
        } else {
            return null;
        }
    }

    private Map resolvePropertyTypes(Map propertyTypes, ClasspathImportService classpathImportService) {
        if (propertyTypes != null) {
            return propertyTypes;
        }

        Map result = new HashMap();
        for (int i = 0; i < propertyOrder.length; i++) {
            String name = propertyOrder[i];
            Class type = String.class;
            if (name.contains(" ")) {
                String[] typeAndName = name.split("\\s");
                try {
                    name = typeAndName[1];
                    type = JavaClassHelper.getClassForName(JavaClassHelper.getBoxedClassName(typeAndName[0]), classpathImportService.getClassForNameProvider());
                    propertyOrder[i] = name;
                } catch (Throwable e) {
                    log.warn("Unable to use given type for property, will default to String: " + propertyOrder[i], e);
                }
            }
            result.put(name, type);
        }
        return result;
    }

    private boolean isUsingTitleRow(String[] firstRow, String[] propertyOrder) {
        if (firstRow == null) {
            return false;
        }
        Set firstRowSet = new HashSet(Arrays.asList(firstRow));
        Set propertyOrderSet = new HashSet(Arrays.asList(propertyOrder));
        return firstRowSet.equals(propertyOrderSet);
    }

    private String[] getFirstRow() {
        String[] firstRow;
        try {
            firstRow = reader.getNextRecord();
        } catch (EOFException e) {
            atEOF = true;
            firstRow = null;
        }
        return firstRow;
    }

    private void assertValidEventsPerSec(Integer eventsPerSec) {
        if (eventsPerSec != null) {
            if (eventsPerSec < 1 || eventsPerSec > 1000) {
                throw new IllegalArgumentException("Illegal value of eventsPerSec:" + eventsPerSec);
            }
        }
    }

    private void assertValidParameters(EPRuntime runtime, CSVInputAdapterSpec adapterSpec) {
        if (!(runtime instanceof EPRuntimeSPI)) {
            throw new IllegalArgumentException("Invalid type of runtime");
        }

        if (adapterSpec.getEventTypeName() == null) {
            throw new NullPointerException("eventTypeName cannot be null");
        }

        if (adapterSpec.getAdapterInputSource() == null) {
            throw new NullPointerException("adapterInputSource cannot be null");
        }

        assertValidEventsPerSec(adapterSpec.getEventsPerSec());

        if (adapterSpec.isLooping() && !adapterSpec.getAdapterInputSource().isResettable()) {
            throw new EPException("Cannot loop on a non-resettable input source");
        }
    }

    /**
     * Returns row count.
     *
     * @return row count
     */
    public int getRowCount() {
        return rowCount;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy