org.rifidi.edge.readerplugin.llrp.LLRPReaderSession.java Source code

Java tutorial

Introduction

Here is the source code for org.rifidi.edge.readerplugin.llrp.LLRPReaderSession.java

Source

/*
 *  LLRPReaderSession.java
 *
 * Created:     July 8th, 2009
 * Project:       Rifidi Edge Server - A middleware platform for RFID applications
 *                   http://www.rifidi.org
 *                   http://rifidi.sourceforge.net
 * Copyright:   Pramari LLC and the Rifidi Project
 * License:      The software in this package is published under the terms of the GPL License
 *                   A copy of the license is included in this distribution under RifidiEdge-License.txt 
 */
package org.rifidi.edge.readerplugin.llrp;

import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.common.RuntimeIOException;
import org.llrp.ltk.exceptions.InvalidLLRPMessageException;
import org.llrp.ltk.generated.LLRPMessageFactory;
import org.llrp.ltk.generated.enumerations.AccessReportTriggerType;
import org.llrp.ltk.generated.enumerations.GetReaderCapabilitiesRequestedData;
import org.llrp.ltk.generated.enumerations.NotificationEventType;
import org.llrp.ltk.generated.enumerations.ROReportTriggerType;
import org.llrp.ltk.generated.enumerations.StatusCode;
import org.llrp.ltk.generated.messages.GET_READER_CAPABILITIES;
import org.llrp.ltk.generated.messages.SET_READER_CONFIG;
import org.llrp.ltk.generated.messages.SET_READER_CONFIG_RESPONSE;
import org.llrp.ltk.generated.parameters.AccessReportSpec;
import org.llrp.ltk.generated.parameters.C1G2EPCMemorySelector;
import org.llrp.ltk.generated.parameters.Custom;
import org.llrp.ltk.generated.parameters.EventNotificationState;
import org.llrp.ltk.generated.parameters.ROReportSpec;
import org.llrp.ltk.generated.parameters.ReaderEventNotificationSpec;
import org.llrp.ltk.generated.parameters.TagReportContentSelector;
import org.llrp.ltk.net.LLRPConnection;
import org.llrp.ltk.net.LLRPConnectionAttemptFailedException;
import org.llrp.ltk.net.LLRPConnector;
import org.llrp.ltk.net.LLRPEndpoint;
import org.llrp.ltk.types.Bit;
import org.llrp.ltk.types.LLRPMessage;
import org.llrp.ltk.types.UnsignedInteger;
import org.llrp.ltk.types.UnsignedShort;
import org.llrp.ltk.util.Util;
import org.rifidi.edge.api.SessionStatus;
import org.rifidi.edge.core.sensors.base.AbstractSensor;
import org.rifidi.edge.core.sensors.commands.AbstractCommandConfiguration;
import org.rifidi.edge.core.sensors.commands.Command;
import org.rifidi.edge.core.sensors.commands.TimeoutCommand;
import org.rifidi.edge.core.sensors.sessions.AbstractSensorSession;
import org.rifidi.edge.core.services.notification.NotifierService;
import org.rifidi.edge.core.services.notification.data.ReadCycle;
import org.rifidi.edge.core.services.notification.data.TagReadEvent;
import org.rifidi.edge.readerplugin.llrp.commands.internal.LLRPReset;

/**
 * This class represents a session with an LLRP reader. It handles connecting
 * and disconnecting, as well as receiving tag data.
 * 
 * @author Matthew Dean
 */
public class LLRPReaderSession extends AbstractSensorSession implements LLRPEndpoint {

    /** Logger for this class. */
    private static final Log logger = LogFactory.getLog(LLRPReaderSession.class);

    private volatile LLRPConnection connection = null;
    /** Service used to send out notifications */
    private volatile NotifierService notifierService;
    /** The ID of the reader this session belongs to */
    private final String readerID;
    private final String readerConfigPath;

    /** Ok, because only accessed from synchronized block */
    int messageID = 1;
    int maxConAttempts = -1;
    int reconnectionInterval = -1;
    private AtomicBoolean timingOut = new AtomicBoolean(false);

    /** atomic boolean that is true if we are inside the connection attempt loop */
    private AtomicBoolean connectingLoop = new AtomicBoolean(false);
    /** LLRP host */
    private String host;
    /** LLRP port */
    private int port;

    /**
     * 
     * @param sensor
     * @param id
     * @param host
     * @param port
     * @param reconnectionInterval
     * @param maxConAttempts
     * @param readerConfigPath
     * @param timeout
     * @param notifierService
     * @param readerID
     * @param commands
     */
    public LLRPReaderSession(AbstractSensor<?> sensor, String id, String host, int port, int reconnectionInterval,
            int maxConAttempts, String readerConfigPath, NotifierService notifierService, String readerID,
            Set<AbstractCommandConfiguration<?>> commands) {
        super(sensor, id, commands);
        this.host = host;
        this.port = port;
        this.connection = new LLRPConnector(this, host, port);
        this.maxConAttempts = maxConAttempts;
        this.reconnectionInterval = reconnectionInterval;
        this.readerConfigPath = readerConfigPath;
        this.notifierService = notifierService;
        this.readerID = readerID;
        this.setStatus(SessionStatus.CLOSED);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.rifidi.edge.core.readers.ReaderSession#connect()
     */
    @Override
    protected synchronized void _connect() throws IOException {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>LLRP Session " + this.getID() + " on sensor "
                + this.getSensor().getID() + " attempting to connect to " + host + ":" + port);
        this.setStatus(SessionStatus.CONNECTING);
        // Connected flag
        boolean connected = false;
        // try to connect up to MaxConAttempts number of times, unless
        // maxConAttempts is -1, in which case, try forever
        connectingLoop.set(true);
        try {
            for (int connCount = 0; connCount < maxConAttempts || maxConAttempts == -1; connCount++) {

                // attempt to make the connection
                try {
                    ((LLRPConnector) connection).connect();
                    connected = true;
                    break;
                } catch (LLRPConnectionAttemptFailedException e) {
                    logger.debug("Attempt to connect to LLRP reader failed: " + connCount);
                } catch (RuntimeIOException e) {
                    logger.debug("Attempt to connect to LLRP reader failed: " + connCount);
                }

                // wait for a specified number of ms or until someone calls
                // notify on the connetingLoop object
                try {
                    synchronized (connectingLoop) {
                        connectingLoop.wait(reconnectionInterval);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }

                // if someone else wants us to stop, break from the for loop
                if (!connectingLoop.get())
                    break;
            }
        } finally {
            // make sure connecting loop is false!
            connectingLoop.set(false);
        }

        // if not connected, exit
        if (!connected) {
            setStatus(SessionStatus.CLOSED);
            throw new IOException("Cannot connect");
        }

        // physical connection established, set up session
        timingOut.set(false);
        onConnect();

        logger.info(">>>>>>>>>>>>>>>>>>>LLRP Session " + this.getID() + " on sensor " + this.getSensor().getID()
                + " connected to " + host + ":" + port);
    }

    /**
     * This logic executes as soon as a socket is established to initialize the
     * connection. It occurs before any commands are scheduled
     */
    private void onConnect() {
        logger.info(">>>>>>>>>>>>>>>>>>>LLRP Session " + this.getID() + " on sensor " + this.getSensor().getID()
                + " attempting to log in to " + host + ":" + port);
        setStatus(SessionStatus.LOGGINGIN);
        executor = new ScheduledThreadPoolExecutor(1);

        try {
            SET_READER_CONFIG config = createSetReaderConfig();
            config.setMessageID(new UnsignedInteger(messageID++));

            SET_READER_CONFIG_RESPONSE config_response = (SET_READER_CONFIG_RESPONSE) connection.transact(config);

            // modified by limg00n

            //---------------
            StatusCode sc = config_response.getLLRPStatus().getStatusCode();
            if (sc.intValue() != StatusCode.M_Success) {
                if (config_response.getLLRPStatus().getStatusCode().toInteger() != 0) {
                    try {
                        logger.error("Problem with SET_READER_CONFIG: \n" + config_response.toXMLString());
                    } catch (InvalidLLRPMessageException e) {
                        logger.warn("Cannot print XML for " + "SET_READER_CONFIG_RESPONSE");
                    }
                }
            }

            if (!processing.compareAndSet(false, true)) {
                logger.warn("Executor was already active! ");
            }
            submit(getTimeoutCommand(), 10, TimeUnit.SECONDS);
            setStatus(SessionStatus.PROCESSING);

        } catch (TimeoutException e) {
            logger.error(e.getMessage());
            disconnect();
        } catch (ClassCastException ex) {
            logger.error(ex.getMessage());
            disconnect();
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.rifidi.edge.core.sensors.SensorSession#getResetCommand()
     */
    @Override
    protected Command getResetCommand() {
        return new LLRPReset("LLRPResetCommand");
    }

    /**
     * This method returns a command used to ping the reader. It is used to
     * determine if the connection is still alive
     * 
     * @return
     */
    private Command getTimeoutCommand() {
        return new TimeoutCommand("LLRP Timeout") {

            @Override
            protected void execute() throws TimeoutException {
                GET_READER_CAPABILITIES grc = new GET_READER_CAPABILITIES();
                GetReaderCapabilitiesRequestedData data = new GetReaderCapabilitiesRequestedData();
                data.set(GetReaderCapabilitiesRequestedData.LLRP_Capabilities);
                grc.setRequestedData(data);
                transact(grc);

            }
        };
    }

    /**
     * 
     */
    @Override
    public void disconnect() {
        resetCommands();
        this.submitAndBlock(getResetCommand(), getTimeout(), TimeUnit.MILLISECONDS);
        try {
            // if in the connecting loop, set atomic boolean to false and call
            // notify on the connectingLoop monitor
            if (connectingLoop.getAndSet(false)) {
                synchronized (connectingLoop) {
                    connectingLoop.notify();
                }
            }
            // if already connected, disconnect
            if (processing.get()) {
                if (processing.compareAndSet(true, false)) {
                    logger.debug("Disconnecting");
                    ((LLRPConnector) connection).disconnect();
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        } finally {

            // make sure executor is shutdown!
            if (executor != null) {
                executor.shutdownNow();
                executor = null;
            }
            // notify anyone who cares that session is now closed
            setStatus(SessionStatus.CLOSED);
        }

    }

    /**
     * This method sends a message and waits for an LLRP response message.
     * 
     * @param message
     *            The LLRP message to send to the reader
     * @return The response message
     * @throws TimeoutException
     *             If there was a timeout when processing this command.
     */
    public LLRPMessage transact(LLRPMessage message) throws TimeoutException {
        synchronized (connection) {
            try {
                if (timingOut.get()) {
                    throw new IllegalStateException("Cannot execute while timing out: " + message.getName());
                }
                LLRPMessage response = this.connection.transact(message, 20000);
                if (response != null) {
                    return response;
                } else {
                    logger.error("Response for message: " + message.getName() + " was null");
                    throw new TimeoutException();
                }
            } catch (TimeoutException e) {
                timingOut.set(true);
                logger.error("Timeout when sending an LLRP Message: " + message.getName());
                throw e;
            }
        }
    }

    /**
     * This command sends a message and does not wait for a response. Transact
     * should normally be preferred to this method.
     * 
     * @param message
     */
    public void send(LLRPMessage message) {
        synchronized (connection) {
            connection.send(message);
        }
    }

    /**
     * 
     */
    @Override
    public void errorOccured(String arg0) {
        // temporarily skip by limg00n
        //logger.error("LLRP Error Occurred: " + arg0);
        // TODO: should we disconnect?
    }

    /**
     * This method creates a SET_READER_CONFIG method.
     * 
     * @return The SET_READER_CONFIG object.
     */
    public SET_READER_CONFIG createSetReaderConfig() {
        try {
            String directory = System.getProperty("org.rifidi.home");
            String file = directory + File.separator + readerConfigPath;
            LLRPMessage message = Util.loadXMLLLRPMessage(new File(file));
            return (SET_READER_CONFIG) message;
        } catch (Exception e) {
            logger.warn("No SET_READER_CONFIG message found at " + readerConfigPath
                    + " Using default SET_READER_CONFIG");
        }

        return createDefaultConfig();

    }

    /**
     * A default SET_READER_CONFIG message to use in case the one from the file
     * cannot be loaded.
     * 
     * @return
     */
    private SET_READER_CONFIG createDefaultConfig() {
        SET_READER_CONFIG setReaderConfig = new SET_READER_CONFIG();

        // Create a default RoReportSpec so that reports are sent at the end of
        // ROSpecs
        ROReportSpec roReportSpec = new ROReportSpec();

        roReportSpec.setN(new UnsignedShort(0));
        roReportSpec.setROReportTrigger(new ROReportTriggerType(ROReportTriggerType.None));
        TagReportContentSelector tagReportContentSelector = new TagReportContentSelector();
        tagReportContentSelector.setEnableAccessSpecID(new Bit(0));
        tagReportContentSelector.setEnableAntennaID(new Bit(1));
        tagReportContentSelector.setEnableChannelIndex(new Bit(0));
        tagReportContentSelector.setEnableFirstSeenTimestamp(new Bit(0));
        tagReportContentSelector.setEnableInventoryParameterSpecID(new Bit(0));
        tagReportContentSelector.setEnableLastSeenTimestamp(new Bit(0));
        tagReportContentSelector.setEnablePeakRSSI(new Bit(0));
        tagReportContentSelector.setEnableROSpecID(new Bit(1));
        tagReportContentSelector.setEnableSpecIndex(new Bit(0));
        tagReportContentSelector.setEnableTagSeenCount(new Bit(0));
        C1G2EPCMemorySelector epcMemSel = new C1G2EPCMemorySelector();
        epcMemSel.setEnableCRC(new Bit(0));
        epcMemSel.setEnablePCBits(new Bit(0));
        tagReportContentSelector.addToAirProtocolEPCMemorySelectorList(epcMemSel);

        roReportSpec.setTagReportContentSelector(tagReportContentSelector);
        setReaderConfig.setROReportSpec(roReportSpec);

        // Set default AccessReportSpec

        AccessReportSpec accessReportSpec = new AccessReportSpec();
        accessReportSpec.setAccessReportTrigger(
                new AccessReportTriggerType(AccessReportTriggerType.Whenever_ROReport_Is_Generated));
        setReaderConfig.setAccessReportSpec(accessReportSpec);

        // Set up reporting for AISpec events, ROSpec events, and GPI Events

        ReaderEventNotificationSpec eventNoteSpec = new ReaderEventNotificationSpec();
        EventNotificationState noteState = new EventNotificationState();

        noteState.setEventType(new NotificationEventType(NotificationEventType.AISpec_Event));
        noteState.setNotificationState(new Bit(0));
        eventNoteSpec.addToEventNotificationStateList(noteState);
        noteState = new EventNotificationState();
        noteState.setEventType(new NotificationEventType(NotificationEventType.ROSpec_Event));
        noteState.setNotificationState(new Bit(0));
        eventNoteSpec.addToEventNotificationStateList(noteState);
        noteState = new EventNotificationState();
        noteState.setEventType(new NotificationEventType(NotificationEventType.GPI_Event));
        noteState.setNotificationState(new Bit(0));
        eventNoteSpec.addToEventNotificationStateList(noteState);
        setReaderConfig.setReaderEventNotificationSpec(eventNoteSpec);

        setReaderConfig.setResetToFactoryDefault(new Bit(0));

        // modified by limg00n

        return setReaderConfig;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.rifidi.edge.core.readers.impl.AbstractReaderSession#setStatus(org
     * .rifidi.edge.core.api.SessionStatus)
     */
    @Override
    protected synchronized void setStatus(SessionStatus status) {
        super.setStatus(status);
        // TODO: Remove this once we have aspectJ
        NotifierService service = notifierService;
        if (service != null) {
            service.sessionStatusChanged(this.readerID, this.getID(), status);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.rifidi.edge.core.sensors.base.AbstractSensorSession#submit(java.lang
     * .String, long, java.util.concurrent.TimeUnit)
     */
    @Override
    public Integer submit(String commandID, long interval, TimeUnit unit) {
        Integer retVal = super.submit(commandID, interval, unit);
        // TODO: Remove this once we have aspectJ
        try {
            NotifierService service = notifierService;
            if (service != null) {
                service.jobSubmitted(this.readerID, this.getID(), retVal, commandID, (interval > 0));
            }
        } catch (Exception e) {
            // make sure the notification doesn't cause this method to exit
            // under any circumstances
            logger.error(e);
        }
        return retVal;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.rifidi.edge.core.readers.impl.AbstractReaderSession#killComand(java
     * .lang.Integer)
     */
    @Override
    public void killComand(Integer id) {
        super.killComand(id);
        // TODO: Remove this once we have aspectJ
        NotifierService service = notifierService;
        if (service != null) {
            service.jobDeleted(this.readerID, this.getID(), id);
        }
    }

    /**
     * This method receives asynchronous messages back from the LLRP reader. We
     * add relevant events to the esper runtime
     */
    @Override
    public void messageReceived(LLRPMessage arg0) {
        if (!processing.get()) {
            return;
        }
        try {
            //System.out.println(arg0.toXMLString());
            Object event = LLRPEventFactory.createEvent(arg0, readerID);
            if (event != null) {
                if (event instanceof ReadCycle) {
                    ReadCycle cycle = (ReadCycle) event;
                    sensor.send(cycle);

                    // inserted by limg00n :
                    // if this is not present, TagReadEvent seems not to be pushed to Esper.
                    // but is it right?
                    for (TagReadEvent e : cycle.getTags())
                        sensor.sendEvent(e);

                    // TODO: get rid of this for performance reasons. Need to
                    // have a better way to figure out if we need to send tag
                    // read to JMS
                    // this.getTemplate().send(this.getDestination(),
                    // new ObjectMessageCreator(cycle));
                } else {
                    sensor.sendEvent(event);
                }
            }

        } catch (Exception e) {
            logger.error("Exception while parsing message: " + e.getMessage());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "LLRPSession: " + host + ":" + port + " (" + getStatus() + ")";
    }

}