class Session implements Closeable {

    public void next(Message message) throws FieldNotFound, RejectLogon, IncorrectDataFormat,
            IncorrectTagValue, UnsupportedMessageType, IOException, InvalidMessage {

        final Header header = message.getHeader();
        final String msgType = header.getString(MsgType.FIELD);

        // QFJ-650
        if (!header.isSetField(MsgSeqNum.FIELD)) {
            generateLogout("Received message without MsgSeqNum");
            disconnect("Received message without MsgSeqNum: " + message, true);
            return;
        }

        try {

            final String beginString = header.getString(BeginString.FIELD);

            if (!beginString.equals(sessionID.getBeginString())) {
                throw new UnsupportedVersion();
            }

            if (msgType.equals(MsgType.LOGON)) {
                if (sessionID.isFIXT()) {
                    targetDefaultApplVerID.set(new ApplVerID(message
                            .getString(DefaultApplVerID.FIELD)));

                } else {
                    targetDefaultApplVerID.set(MessageUtils.toApplVerID(beginString));
                }

                // QFJ-648
                if (message.isSetField(HeartBtInt.FIELD)) {
                    if (message.getInt(HeartBtInt.FIELD) < 0) {
                        throw new RejectLogon("HeartBtInt must not be negative");
                    }
                }
            }

            if (validateIncomingMessage && dataDictionaryProvider != null) {
                final DataDictionary sessionDataDictionary = dataDictionaryProvider
                        .getSessionDataDictionary(beginString);

                final ApplVerID applVerID = header.isSetField(ApplVerID.FIELD) ? new ApplVerID(
                        header.getString(ApplVerID.FIELD)) : targetDefaultApplVerID.get();

                final DataDictionary applicationDataDictionary = MessageUtils
                        .isAdminMessage(msgType) ? dataDictionaryProvider
                        .getSessionDataDictionary(beginString) : dataDictionaryProvider
                        .getApplicationDataDictionary(applVerID);

                // related to QFJ-367 : just warn invalid incoming field/tags
                try {
                    DataDictionary.validate(message, sessionDataDictionary,
                            applicationDataDictionary);
                } catch (final IncorrectTagValue e) {
                    if (rejectInvalidMessage) {
                        throw e;
                    } else {
                        getLog().onErrorEvent("Warn: incoming message with " + e + ": " + message);
                    }
                } catch (final FieldException e) {
                    if (message.isSetField(e.getField())) {
                        if (rejectInvalidMessage) {
                            throw e;
                        } else {
                            getLog().onErrorEvent(
                                    "Warn: incoming message with incorrect field: "
                                            + message.getField(e.getField()) + ": " + message);
                        }
                    } else {
                        if (rejectInvalidMessage) {
                            throw e;
                        } else {
                            getLog().onErrorEvent(
                                    "Warn: incoming message with missing field: " + e.getField()
                                            + ": " + e.getMessage() + ": " + message);
                        }
                    }
                } catch (final FieldNotFound e) {
                    if (rejectInvalidMessage) {
                        throw e;
                    } else {
                        getLog().onErrorEvent("Warn: incoming " + e + ": " + message);
                    }
                }

            }

            if (msgType.equals(MsgType.LOGON)) {
                nextLogon(message);
            } else if (msgType.equals(MsgType.HEARTBEAT)) {
                nextHeartBeat(message);
            } else if (msgType.equals(MsgType.TEST_REQUEST)) {
                nextTestRequest(message);
            } else if (msgType.equals(MsgType.SEQUENCE_RESET)) {
                nextSequenceReset(message);
            } else if (msgType.equals(MsgType.LOGOUT)) {
                nextLogout(message);
            } else if (msgType.equals(MsgType.RESEND_REQUEST)) {
                nextResendRequest(message);
            } else if (msgType.equals(MsgType.REJECT)) {
                nextReject(message);
            } else {
                if (!verify(message)) {
                    return;
                }
                state.incrNextTargetMsgSeqNum();
            }
        } catch (final FieldException e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
            generateReject(message, e.getSessionRejectReason(), e.getField());
        } catch (final FieldNotFound e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
            if (sessionID.getBeginString().compareTo(FixVersions.BEGINSTRING_FIX42) >= 0
                    && message.isApp()) {
                generateBusinessReject(message,
                        BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, e.field);
            } else {
                if (msgType.equals(MsgType.LOGON)) {
                    getLog().onErrorEvent("Required field missing from logon");
                    disconnect("Required field missing from logon", true);
                } else {
                    generateReject(message, SessionRejectReason.REQUIRED_TAG_MISSING, e.field);
                }
            }
        } catch (final IncorrectDataFormat e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
            generateReject(message, SessionRejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE, e.field);
        } catch (final IncorrectTagValue e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            generateReject(message, SessionRejectReason.VALUE_IS_INCORRECT, e.field);
        } catch (final InvalidMessage e) {
            getLog().onErrorEvent("Skipping invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
        } catch (final RejectLogon e) {
            final String rejectMessage = e.getMessage() != null ? (": " + e) : "";
            getLog().onErrorEvent("Logon rejected" + rejectMessage);
            if (e.isLogoutBeforeDisconnect()) {
                generateLogout(e.getMessage());
            }
            state.incrNextTargetMsgSeqNum();
            disconnect("Logon rejected: " + e, true);
        } catch (final UnsupportedMessageType e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
            if (sessionID.getBeginString().compareTo(FixVersions.BEGINSTRING_FIX42) >= 0) {
                generateBusinessReject(message, BusinessRejectReason.UNSUPPORTED_MESSAGE_TYPE, 0);
            } else {
                generateReject(message, "Unsupported message type");
            }
        } catch (final UnsupportedVersion e) {
            getLog().onErrorEvent("Rejecting invalid message: " + e + ": " + message);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
            if (msgType.equals(MsgType.LOGOUT)) {
                nextLogout(message);
            } else {
                generateLogout("Incorrect BeginString");
                state.incrNextTargetMsgSeqNum();
                // 1d_InvalidLogonWrongBeginString.def appears to require
                // a disconnect although the C++ didn't appear to be doing it.
                // ???
                disconnect("Incorrect BeginString: " + e, true);
            }
        } catch (final IOException e) {
            LogUtil.logThrowable(sessionID, "Error processing message: " + message, e);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
        }

        nextQueued();
        if (isLoggedOn()) {
            next();
        }
    }

      Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID,
            DataDictionaryProvider dataDictionaryProvider, SessionSchedule sessionSchedule,
            LogFactory logFactory, MessageFactory messageFactory, int heartbeatInterval);
      Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID,
            DataDictionaryProvider dataDictionaryProvider, SessionSchedule sessionSchedule,
            LogFactory logFactory, MessageFactory messageFactory, int heartbeatInterval,
            boolean checkLatency, int maxLatency, boolean millisecondsInTimeStamp,
            boolean resetOnLogon, boolean resetOnLogout, boolean resetOnDisconnect,
            boolean refreshMessageStoreAtLogon, boolean checkCompID,
            boolean redundantResentRequestsAllowed, boolean persistMessages,
            boolean useClosedRangeForResend, double testRequestDelayMultiplier,
            DefaultApplVerID senderDefaultApplVerID, boolean validateSequenceNumbers,
            int[] logonIntervals, boolean resetOnError, boolean disconnectOnError,
            boolean ignoreHeartBeatFailure, boolean rejectInvalidMessage,
            boolean forceResendWhenCorruptedStore, Set<InetAddress> allowedRemoteAddresses,
            boolean validateIncomingMessage, int resendRequestChunkSize,
            boolean enableNextExpectedMsgSeqNum, boolean enableLastMsgSeqNumProcessed);

}