com.npower.dm.server.session.ManagementSessionHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.npower.dm.server.session.ManagementSessionHandler.java

Source

/**
 * $Header: /home/master/nWave-DM-Common/src/com/npower/dm/server/session/ManagementSessionHandler.java,v 1.16 2008/08/04 09:48:40 zhao Exp $
 * $Revision: 1.16 $
 * $Date: 2008/08/04 09:48:40 $
 *
 * ===============================================================================================
 * License, Version 1.1
 *
 * Copyright (c) 1994-2006 NPower Network Software Ltd.  All rights reserved.
 *
 * This SOURCE CODE FILE, which has been provided by NPower as part
 * of a NPower product for use ONLY by licensed users of the product,
 * includes CONFIDENTIAL and PROPRIETARY information of NPower.
 *
 * USE OF THIS SOFTWARE IS GOVERNED BY THE TERMS AND CONDITIONS
 * OF THE LICENSE STATEMENT AND LIMITED WARRANTY FURNISHED WITH
 * THE PRODUCT.
 *
 * IN PARTICULAR, YOU WILL INDEMNIFY AND HOLD NPower, ITS RELATED
 * COMPANIES AND ITS SUPPLIERS, HARMLESS FROM AND AGAINST ANY CLAIMS
 * OR LIABILITIES ARISING OUT OF THE USE, REPRODUCTION, OR DISTRIBUTION
 * OF YOUR PROGRAMS, INCLUDING ANY CLAIMS OR LIABILITIES ARISING OUT OF
 * OR RESULTING FROM THE USE, MODIFICATION, OR DISTRIBUTION OF PROGRAMS
 * OR FILES CREATED FROM, BASED ON, AND/OR DERIVED FROM THIS SOURCE
 * CODE FILE.
 * ===============================================================================================
 */
package com.npower.dm.server.session;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;

import sync4j.framework.config.Configuration;
import sync4j.framework.config.ConfigurationConstants;
import sync4j.framework.core.AbstractCommand;
import sync4j.framework.core.Add;
import sync4j.framework.core.Alert;
import sync4j.framework.core.AlertCode;
import sync4j.framework.core.Atomic;
import sync4j.framework.core.Authentication;
import sync4j.framework.core.Chal;
import sync4j.framework.core.ComplexData;
import sync4j.framework.core.Constants;
import sync4j.framework.core.Copy;
import sync4j.framework.core.Cred;
import sync4j.framework.core.Data;
import sync4j.framework.core.Delete;
import sync4j.framework.core.Exec;
import sync4j.framework.core.Get;
import sync4j.framework.core.Item;
import sync4j.framework.core.ItemizedCommand;
import sync4j.framework.core.Meta;
import sync4j.framework.core.NextNonce;
import sync4j.framework.core.Replace;
import sync4j.framework.core.Results;
import sync4j.framework.core.Sequence;
import sync4j.framework.core.Source;
import sync4j.framework.core.SourceRef;
import sync4j.framework.core.Status;
import sync4j.framework.core.StatusCode;
import sync4j.framework.core.Sync4jException;
import sync4j.framework.core.SyncBody;
import sync4j.framework.core.SyncHdr;
import sync4j.framework.core.SyncML;
import sync4j.framework.core.Target;
import sync4j.framework.core.TargetRef;
import sync4j.framework.engine.dm.DeviceDMState;
import sync4j.framework.engine.dm.ManagementCommandDescriptor;
import sync4j.framework.engine.dm.ManagementException;
import sync4j.framework.engine.dm.ManagementProcessor;
import sync4j.framework.engine.dm.SessionContext;
import sync4j.framework.engine.dm.Util;
import sync4j.framework.logging.Sync4jLogger;
import sync4j.framework.logging.Sync4jLoggerName;
import sync4j.framework.protocol.Flags;
import sync4j.framework.protocol.ManagementActions;
import sync4j.framework.protocol.ManagementInitialization;
import sync4j.framework.protocol.ProtocolException;
import sync4j.framework.protocol.ProtocolUtil;
import sync4j.framework.security.Officer;
import sync4j.framework.security.SecurityConstants;
import sync4j.framework.security.Sync4jPrincipal;
import sync4j.framework.server.Sync4jDevice;
import sync4j.framework.server.SyncTimestamp;
import sync4j.framework.server.dm.ProcessorSelector;
import sync4j.framework.server.error.InvalidCredentialsException;
import sync4j.framework.server.error.ServerException;
import sync4j.framework.server.error.ServerFailureException;
import sync4j.framework.server.session.ManagementState;
import sync4j.framework.server.session.SessionHandler;
import sync4j.framework.server.store.NotFoundException;
import sync4j.framework.server.store.PersistentStoreException;
import sync4j.framework.tools.Base64;
import sync4j.framework.tools.SimpleIdGenerator;
import sync4j.framework.tools.SizeCalculator;

import com.npower.dm.server.engine.DMManagementEngine;

/**
 * This class represents the handler for a SyncML DM session. It coordinates and
 * handles the packages and messages as dictated by the protocol.
 * <p>
 * The entry point is <i>processMessage()</i>, which determines which message is
 * expected and what processing has to be done (depending on the value of
 * <i>currentState</i>). If an error accours, the session goes to the state
 * <i>STATE_ERROR</i>; in this state no other messages but initialization can be
 * performed.
 * <p>
 *
 * LOG NAME: funambol.handler
 *
 * @see sync4j.framework.server.session.SessionHandler
 *
 * @version $Id: ManagementSessionHandler.java,v 1.16 2008/08/04 09:48:40 zhao Exp $
 *
 */
public class ManagementSessionHandler
        implements SessionHandler, java.io.Serializable, SecurityConstants, ConfigurationConstants {

    // --------------------------------------------------------------- Constants

    //
    // NOTE: the following states are in addition to the ones defined in
    //       SessionHandler
    //
    public static final int STATE_INITIALIZATION_PROCESSING = 0x0010;
    public static final int STATE_INITIALIZATION_PROCESSED = 0x0011;
    public static final int STATE_MANAGEMENT_PROCESSING = 0x0012;
    public static final int STATE_MANAGEMENT_PROCESSED = 0x0013;
    public static final int STATE_MANAGEMENT_COMPLETION = 0x0014;
    public static final int STATE_CLIENT_UNAUTHORIZED = 0x0016;

    //
    // The server has sent a complete package. It is awainting status from the
    // client on the commands sent in the package. Because the status and results
    //  may be large, such as the result of Get commands, the client may send
    // multiple message back to the server before completing its response
    //
    public static final int STATE_WAITING_MORE_MSG = 0x0015;

    // ------------------------------------------------------- Private Constants
    private static final String[] MANAGEMENT_COMMANDS = { "Add", "Alert", "Copy", "Delete", "Exec", "Get",
            "Replace", "Atomic", "Sequence" };

    /**
     * Given a maxMsgSize, only the 85% is used, so in the pipeline manager,
     * the server has the 15% available
     */
    private static final double TOLLERANCE_MAX_MSG_SIZE = 0.85d;

    // ------------------------------------------------------------ Private data

    private int currentState = STATE_START;

    /**
     * Gets the current state
     *
     * @return the current state
     */
    public int getCurrentState() {
        return currentState;
    }

    private long creationTimestamp = -1;

    /**
     * Gets the creation timestamp of the session
     *
     * @return the creation timestamp
     */
    public long getCreationTimestamp() {
        return creationTimestamp;
    }

    private transient Logger log = Sync4jLogger.getLogger(Sync4jLoggerName.HANDLER);

    private transient ManagementInitialization init = null;
    private transient ManagementActions actions = null;

    /**
     * Keeps the state information of this management session
     */
    private ManagementState sessionState = null;

    /**
     * The management processor to be used for this management session
     */
    private ManagementProcessor processor = null;

    /**
     * The server authentication type
     */
    private String serverAuthType = null;

    /**
     * The supported server authentication type
     */
    private String supportedAuthType = null;

    /**
     * The client authentication type
     */
    private String clientAuthType = null;

    /**
     * Sync4j engine object
     */
    private DMManagementEngine engine = null;

    /**
     * The message mime type
     */
    private String mimeType = null;

    /**
     * Set message mime type
     */
    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

    /**
     * Indicates if the session is a new session
     */
    //
    // @TODO is this still used
    //
    private boolean newSession = true;

    /**
     * List of command in order to create an answer with the maximum size
     * available (case of MultiMessage)
     */
    private ArrayList addStatus = null;
    private ArrayList addAbsCmd = null;

    /**
     * The max msg size
     */
    private long maxSizeAvailable = 0;

    // -------------------------------------------------------------- Properties

    /**
     * Returns the management session id
     *
     * @return the management session id
     */
    public String getSessionId() {
        return sessionState.getSessionId();
    }

    /**
     * Returns the management device
     *
     * @return the management device
     */
    public Sync4jDevice getDevice() {
        return sessionState.device;
    }

    /**
     * The cmdIdGenerator must be reset each time the process
     * of a message is starting
     */
    private void resetIdGenerator() {
        engine.getCommandIdGenerator().reset();
    }

    /**
     * The message id generator (it defaults ro a <i>SimpleIdGenerator</i> instance)
     */
    private SimpleIdGenerator msgIdGenerator = new SimpleIdGenerator();

    // ------------------------------------------------------------ Constructors

    /**
     * Creates a new instance of SimpleSessionHandler
     */
    public ManagementSessionHandler() {
        creationTimestamp = System.currentTimeMillis();
        init();
    }

    /**
     * Creates a new instance of SimpleSessionHandler with a given session id
     *
     * @param sessionId session id
     */
    public ManagementSessionHandler(String sessionId) {
        this();
        sessionState.sessionId = sessionId;
    }

    // ---------------------------------------------------------- Public methods

    /**
     * Sets newSession
     *
     * @param newSession the new value
     */
    public void setNew(boolean newSession) {
        this.newSession = newSession;
    }

    /**
     * Is newSession true?
     *
     * @return the newSession value
     */
    public boolean isNew() {
        return this.newSession;
    }

    /**
     * Returns true if the sessione has been authenticated.
     *
     * @return true if the sessione has been authenticated, false otherwise
     */
    public boolean isAuthenticated() {
        return sessionState.authenticationState == AUTH_AUTHENTICATED;
    }

    /**
     * Processes the given message. See the class description for more
     * information.
     *
     * @param message the message to be processed
     *
     * @return the response message
     *
     * @throws ProtocolException in case of a protocol error
     * @throws InvalidCredentialsException in case of invalid credentials
     * @throws ServerFailureException in case of an internal server error
     */
    public SyncML processMessage(SyncML message, HttpServletRequest httpRequest)
            throws ProtocolException, InvalidCredentialsException, ServerFailureException {
        String deviceId = null;
        SyncML response = null;

        //
        // Reset the cmdIdGenerator has specified in the spec
        //
        resetIdGenerator();

        //
        // Each time a message is received for a particular session adjust the message ID
        //
        msgIdGenerator.next();

        //
        //  We maintain the message Id from client
        //
        sessionState.lastMsgIdFromClient = message.getSyncHdr().getMsgID();

        //
        // Initialize the device ID from the client request.
        //
        deviceId = message.getSyncHdr().getSource().getLocURI();

        if (log.isLoggable(Level.FINEST)) {
            log.finest("device id: " + deviceId);
            log.finest("current state: " + getStateName(currentState));
        }
        //
        // Set maximum message size
        //
        Meta meta = message.getSyncHdr().getMeta();
        if (meta != null) {
            Long maxMsgSize = meta.getMaxMsgSize();
            if (maxMsgSize != null) {
                sessionState.setMaxMsgSize(maxMsgSize.longValue());
            }

            Long maxObjSize = meta.getMaxObjSize();
            if (maxObjSize != null) {
                sessionState.setMaxObjSize(maxObjSize.longValue());
            }
        }

        try {
            switch (currentState) {
            case STATE_ERROR: // in case of error you can start a new initialization
            case STATE_END:
            case STATE_START:
            case STATE_CLIENT_UNAUTHORIZED:
                sessionState.reset();

                sessionState.nextTimestamp = new SyncTimestamp(System.currentTimeMillis());

                //
                // Read device information. Use this information with care
                // until the client has been authenticated.
                //
                sessionState.device = new Sync4jDevice(deviceId);
                engine.readDevice(sessionState.device);

                sessionState.syncMLVerProto = message.getSyncHdr().getVerProto();
                this.engine.setSyncMLVerProto(sessionState.syncMLVerProto.getVersion());

                if (currentState != STATE_CLIENT_UNAUTHORIZED) {
                    moveTo(STATE_INITIALIZATION_PROCESSING);
                }
            case STATE_INITIALIZATION_PROCESSING:
                //
                // Skip authentication if already authenticated
                //
                if (!isAuthenticated()) {

                    //
                    // Check if the client has sent the credentials with a
                    // authentication type supported by the server
                    //
                    Cred cred = message.getSyncHdr().getCred();
                    if (!checkAuthType(cred)) {
                        //
                        // the client uses an authentication type different
                        // from the server authentication type
                        //
                        sessionState.loggedCredential = null;
                    } else {
                        login(cred, deviceId);

                        if (isAuthenticated()) {
                            try {
                                engine.readPrincipal(sessionState.loggedPrincipal);
                            } catch (NotFoundException e) {
                                if (log.isLoggable(Level.INFO)) {
                                    log.info("Authenticated principal not found: " + sessionState.loggedPrincipal);
                                }
                                sessionState.authenticationState = AUTH_INVALID_CREDENTIALS;
                                sessionState.loggedCredential = null;
                            }
                        }
                    }
                }

                boolean clientAuthenticated = isAuthenticated();
                boolean chalNotRequired = false;

                if (!clientAuthenticated && (currentState == STATE_CLIENT_UNAUTHORIZED)) {
                    //
                    // If the client isn't authenticated for the second time,
                    // no chal must be sent from the server
                    //
                    chalNotRequired = true;
                }

                response = processInitMessage(message, chalNotRequired);

                if (!clientAuthenticated) {
                    if (currentState == STATE_CLIENT_UNAUTHORIZED) {
                        response.setLastMessage(true);
                        moveTo(STATE_ERROR);
                    } else {
                        moveTo(STATE_CLIENT_UNAUTHORIZED);
                    }
                    break;
                }

                if (message.getSyncBody().isFinalMsg()) {
                    moveTo(STATE_INITIALIZATION_PROCESSED);
                } else {
                    break;
                }

            case STATE_INITIALIZATION_PROCESSED:
                //
                // If the client did not authenticate the server, we have to
                // set the credentials once more if the authentication type
                // is MD5 or stop the processing otherwise
                //
                checkServerAuthentication(message);

                if ((sessionState.started == false)
                        || ((sessionState.serverAuthenticationState != AUTH_AUTHENTICATED)
                                && (sessionState.serverAuthenticationState != AUTH_ACCEPTED))) {
                    init.setFlag(Flags.FLAG_FINAL_MESSAGE);

                    // if addAbsCmd != null means that
                    // the server have already sent to the client the command,
                    // in previous message
                    // but the client not have authenticate the server. So,
                    // re-add the addAbsCmd to the cache. Before re-add the commands,
                    // we checks if there is one command splitted. If  happens,
                    // we merge data command with data splitted (splittedData in
                    // sessionState)
                    //
                    if (addAbsCmd != null) {
                        if (sessionState.getSplittedCommand() != null) {
                            if (addAbsCmd.contains(sessionState.getSplittedCommand())) {
                                mergeData();
                                sessionState.setSplittedCommand(null);
                                sessionState.setNextDataToSend(null);
                            }
                        }
                        sessionState.addCmdOut(0, addAbsCmd);
                    }

                    // re-set the ManagementInitialization with the clientCommand
                    AbstractCommand[] allClientCommands = (AbstractCommand[]) (message.getSyncBody()).getCommands()
                            .toArray(new AbstractCommand[0]);

                    init.addClientCommand(allClientCommands);

                    response = startManagementSession(message, httpRequest);

                    if (init.isSessionAbortRequired()) {
                        // session abort

                        if (log.isLoggable(Level.INFO)) {
                            log.info("Session aborted by client");
                        }

                        response.setLastMessage(true);
                        sessionState.nextTimestamp.end = System.currentTimeMillis();
                        sessionState.dmstate.state = DeviceDMState.STATE_ABORTED;
                        endSession();
                        moveTo(STATE_SESSION_ABORTED);
                        break;
                    }

                    sessionState.started = true;

                    if (ProtocolUtil.noMoreResponse(response)) {
                        moveTo(STATE_MANAGEMENT_COMPLETION);
                        sessionState.nextTimestamp.end = System.currentTimeMillis();
                        endSession();
                        moveTo(STATE_END);
                    }
                    break;
                } else {
                    moveTo(STATE_MANAGEMENT_PROCESSING);
                }

            case STATE_MANAGEMENT_PROCESSING:

                response = processManagementMessage(message);

                if (actions.isSessionAbortRequired()) {
                    // session abort

                    if (log.isLoggable(Level.INFO)) {
                        log.info("Session aborted by client");
                    }

                    response.setLastMessage(true);
                    sessionState.nextTimestamp.end = System.currentTimeMillis();
                    sessionState.dmstate.state = DeviceDMState.STATE_ABORTED;
                    endSession();
                    moveTo(STATE_SESSION_ABORTED);

                    break;
                }

                if (ProtocolUtil.noMoreResponse(response)) {
                    moveTo(STATE_MANAGEMENT_PROCESSED);
                    moveTo(STATE_MANAGEMENT_COMPLETION);
                } else {
                    break;
                }

            case STATE_MANAGEMENT_COMPLETION:
                sessionState.nextTimestamp.end = System.currentTimeMillis();

                response.setLastMessage(true);

                endSession();

                moveTo(STATE_END);
                break;

            default:
                endSession();
                throw new ProtocolException(
                        "Illegal state: " + getStateName(currentState) + '(' + currentState + ')');
            }
        } catch (ProtocolException e) {
            if (sessionState.dmstate != null) {
                sessionState.dmstate.state = DeviceDMState.STATE_ERROR;
            }
            endSession();
            log.throwing(getClass().getName(), "processMessage", e);
            moveTo(STATE_ERROR);
            throw e;
        } catch (NotFoundException e) {
            if (sessionState.dmstate != null) {
                sessionState.dmstate.state = DeviceDMState.STATE_ERROR;
            }
            endSession();
            log.throwing(getClass().getName(), "processMessage", e);
            moveTo(STATE_ERROR);
            throw new InvalidCredentialsException("Invalid credential error", e);
        } catch (PersistentStoreException e) {
            if (sessionState.dmstate != null) {
                sessionState.dmstate.state = DeviceDMState.STATE_ERROR;
            }
            endSession();
            log.throwing(getClass().getName(), "processMessage", e);
            moveTo(STATE_ERROR);
            throw new ServerFailureException("Persistent store error", e);
        } catch (ManagementException e) {
            if (sessionState.dmstate != null) {
                sessionState.dmstate.state = DeviceDMState.STATE_ERROR;
            }
            endSession();
            log.throwing(getClass().getName(), "processMessage", e);
            moveTo(STATE_ERROR);
            throw new ServerFailureException("Management error", e);
        } catch (Throwable t) {
            if (sessionState.dmstate != null) {
                sessionState.dmstate.state = DeviceDMState.STATE_ERROR;
            }
            endSession();
            log.throwing(getClass().getName(), "processMessage", t);
            moveTo(STATE_ERROR);
        }

        List<AbstractCommand> commands = response.getSyncBody().getCommands();
        ProtocolUtil.updateCmdId(commands);

        //
        // In order to handle the Status sent by the client as result of the
        // commands, we have to store the ManagementCommandDescriptors for the
        // sent management commands
        //
        storeManagementCommandDescriptorForManagementCommands(msgIdGenerator.current(), commands);

        return response;
    }

    /**
     * Processes an error condition. This method is called when the error is
     * is not fatal and is manageable at a protocol/session level. This results
     * in a well formed SyncML message with an appropriete error code.
     * <p>
     * Note that the offending message <i>msg</i> cannot be null, meaning that
     * at least the incoming message was a SyncML message. In this context,
     * <i>RepresentationException</i>s are excluded.
     *
     * @param the offending message - NOT NULL
     * @param the exception representing the error condition - NOT NULL
     *
     * @throws sync4j.framework.core.Sync4jException in case of unexpected errors
     *
     * @return the response message
     */
    public SyncML processError(SyncML msg, HttpServletRequest httpRequest, Throwable error) throws Sync4jException {
        SyncHdr msgHeader = msg.getSyncHdr();

        int status = StatusCode.SERVER_FAILURE;

        if (error instanceof ServerException) {
            status = ((ServerException) error).getStatusCode();
        }

        Item item = new Item(new Target(msgHeader.getSource().getLocURI()),
                new Source(msgHeader.getTarget().getLocURI()), null, new ComplexData(error.getMessage()),
                false /*  MoreData  */
        );

        Status statusCommand = new Status(engine.getCommandIdGenerator().next(), msgHeader.getMsgID(),
                "0" /* command ref */ , "SyncHdr" /* see SyncML specs */ , new TargetRef(msgHeader.getTarget()),
                new SourceRef(msgHeader.getSource()), null /* credential */ , null /* challenge */ ,
                new Data(status), new Item[] { item });

        String serverURI = Configuration.getConfiguration().getStringValue(CFG_SERVER_URI);
        SyncHdr syncHeader = new SyncHdr(msgHeader.getVerDTD(), msgHeader.getVerProto(), msgHeader.getSessionID(),
                msgIdGenerator.current(), new Target(msgHeader.getSource().getLocURI()), new Source(serverURI),
                null /* response URI */ , false, null /* credentials */ , null /* metadata */
        );

        SyncBody syncBody = new SyncBody(new AbstractCommand[] { statusCommand }, true /* final */
        );

        moveTo(STATE_ERROR);

        return new SyncML(syncHeader, syncBody);
    }

    /**
     * Called by the <i>SessionManager</i> when the session is expired.
     * It logs out the credential and release aquired resources.
     */

    public void expire() {
        logout();
    }

    /**
     * Called to interrupt the processing in case of errors depending on
     * extenal causes (i.e. the transport) or in case of session abort.
     * The current implementation just move the session state to the error state.
     * <p>
     *
     * @param statusCode the error code
     *
     * @see sync4j.framework.core.StatusCode for valid status codes
     *
     */
    public void abort(int statusCode) {
        if (statusCode != StatusCode.SESSION_ABORTED) {
            moveTo(STATE_ERROR);
        }
    }

    /**
     * Called by the <i>SyncBean</i> when the container release the session.
     * It commit the change to the DB, logs out the credential and
     * release aquired resources.
     */
    public void endSession() {
        logout();

        if (sessionState.dmstate != null) {

            switch (sessionState.dmstate.state) {

            case DeviceDMState.STATE_COMPLETED:

                engine.deleteDeviceDMState(sessionState.dmstate);
                try {
                    processor.endSession(DeviceDMState.STATE_COMPLETED);
                } catch (ManagementException e) {
                    if (log.isLoggable(Level.SEVERE)) {
                        log.severe("Error calling endSession: " + e.getMessage());
                    }
                    log.throwing("ManagementSessionHandler", "endSession", e);
                }
                break;

            case DeviceDMState.STATE_ABORTED:

                try {
                    processor.endSession(DeviceDMState.STATE_ABORTED);
                } catch (ManagementException e) {
                    if (log.isLoggable(Level.SEVERE)) {
                        log.severe("Error calling endSession: " + e.getMessage());
                    }
                    log.throwing("ManagementSessionHandler", "endSession", e);
                }

            default:
                sessionState.dmstate.end = new Date(System.currentTimeMillis());
                engine.storeDeviceDMState(sessionState.dmstate);
            }

        }
    }

    /**
     * Called to permanently commit the synchronization. It does the following:
     * <ul>
     *  <li>persists the <i>last</i> timestamp to the database for the sources
     *      successfully synchronized
     * </ul>
     */
    public void commit() {
        sessionState.dmstate.end = new Date(System.currentTimeMillis());

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Committing state: " + sessionState.dmstate);
        }

        engine.storeDeviceDMState(sessionState.dmstate);
    }

    // --------------------------------------------------------- Private methods

    /**
     * Initializes internal handler state
     */
    private void init() {
        sessionState = new ManagementState();

        engine = new DMManagementEngine(Configuration.getConfiguration());
    }

    /**
     * Processes the given initialization message.
     *
     *
     * @param message the message to be processed
     * @param chalNotRequired if a chal with a new next noce is not required
     * @return the response message
     *
     * @throws ProtocolException, ManagementException
     */
    private SyncML processInitMessage(SyncML request, boolean chalNotRequired)
            throws ProtocolException, ManagementException {
        //
        // In some circumstances, the device doesn't send the replace command (devInf)
        // if the server has answered 401 to the first message. The device sends
        // only the new credential.
        // If there is the replace command, the SessionHandler caches it.
        // If there isn't the replace command in the message, but there is a replace command in
        // cache, the SessionHandler adds it to the message because the ManagementInitialization
        // wants the replace command.
        //

        SyncBody syncBody = request.getSyncBody();
        ArrayList<AbstractCommand> allClientCommands = syncBody.getCommands();
        List replaceCommands = ProtocolUtil.filterCommands(allClientCommands, new String[] { "Replace" });

        if (replaceCommands.size() == 0) {
            if (sessionState.devInfReplaceCommand != null) {
                allClientCommands.add(sessionState.devInfReplaceCommand);
                syncBody.setCommands((AbstractCommand[]) allClientCommands.toArray(new AbstractCommand[0]));
            }
        } else {
            sessionState.devInfReplaceCommand = (Replace) replaceCommands.get(0);
        }

        init = new ManagementInitialization(request.getSyncHdr(), request.getSyncBody());

        init.setIdGenerator(engine.getCommandIdGenerator());

        //
        // Sets the server authentication type so that the server will be able
        // to accordingly challenge the client
        //
        init.setServerAuthType(serverAuthType);

        init.setSupportedAuthType(supportedAuthType);

        init.setClientAuthType(clientAuthType);

        if (request.getSyncBody().isFinalMsg()) {
            init.setFlag(Flags.FLAG_FINAL_MESSAGE);
        } else {
            init.unsetFlag(Flags.FLAG_FINAL_MESSAGE);
        }

        //
        // If authentication type is MD5 or HMAC then generate a new NextNonce and store
        // it into the database
        //
        if (clientAuthType != null && !chalNotRequired) {
            if (clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_MD5)
                    || clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_HMAC)) {

                NextNonce nonce = ProtocolUtil.generateNextNonce();
                sessionState.device.setClientNonce(nonce.getValue());
                init.setNextNonce(nonce);
                engine.storeDevice(sessionState.device);

            }

        }

        if (!isAuthenticated()) {

            switch (sessionState.authenticationState) {
            case AUTH_MISSING_CREDENTIALS:
                init.setAuthorizedStatusCode(StatusCode.MISSING_CREDENTIALS);

                // if missing credential then server returns a chal with the
                // server auth type. If the server auth type is md5 or hmac then set next nonce

                if (serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_MD5)
                        || serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_HMAC)) {
                    if (!chalNotRequired) {
                        NextNonce nonce = ProtocolUtil.generateNextNonce();
                        sessionState.device.setClientNonce(nonce.getValue());
                        init.setNextNonce(nonce);
                        engine.storeDevice(sessionState.device);
                    }
                }

                break;
            case AUTH_INVALID_CREDENTIALS:
                init.setAuthorizedStatusCode(StatusCode.INVALID_CREDENTIALS);

                // if missing credential then server returns a chal with the
                // server auth type. If the server auth type is md5 or hmac then set next nonce

                if (serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_MD5)
                        || serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_HMAC)) {
                    if (!chalNotRequired) {
                        NextNonce nonce = ProtocolUtil.generateNextNonce();
                        sessionState.device.setClientNonce(nonce.getValue());
                        init.setNextNonce(nonce);
                        engine.storeDevice(sessionState.device);
                    }
                }

                break;
            default:
                init.setAuthorizedStatusCode(StatusCode.FORBIDDEN);
            }
        } else {
            if (Constants.AUTH_TYPE_HMAC.equalsIgnoreCase(clientAuthType)) {
                //
                // Using HMAC the server must return 200
                //
                init.setAuthorizedStatusCode(StatusCode.OK);
            } else {
                init.setAuthorizedStatusCode(StatusCode.AUTHENTICATION_ACCEPTED);
            }
            //
            // The alert could be issued in any of the initialization messages
            //
            Alert alert = init.getClientAlert();

            if (alert != null) {
                sessionState.type = alert.getData();
            }
        }

        //
        // Set server credentials if required
        //
        if (sessionState.serverAuthenticationState != AUTH_ACCEPTED) {
            init.setServerCredentials(engine.getServerCredentials(getChal(request), sessionState.device));
        }
        if (chalNotRequired) {
            init.setFlag(Flags.FLAG_CHAL_NOT_REQUIRED);
        } else {
            init.unsetFlag(Flags.FLAG_CHAL_NOT_REQUIRED);
        }
        SyncML response = init.getResponse(msgIdGenerator.current());

        return response;
    }

    /**
     * Processes the given management message.
     *
     * @param syncRequest the message to be processed
     *
     * @return the response message
     *
     * @throws ProtocolException
     */
    private SyncML processManagementMessage(SyncML request) throws ProtocolException, ManagementException {

        //
        // If the client sent a MD5/HMAC Chal, store the server next nonce
        //
        storeServerNonce(ProtocolUtil.getStatusChal(request));

        actions = new ManagementActions(request.getSyncHdr(), request.getSyncBody());

        actions.setClientAuthType(clientAuthType);
        actions.setIdGenerator(engine.getCommandIdGenerator());

        //
        // If the server uses the HMAC and the client not sends the chal with the next nonce or
        //  if the client uses the HMAC but the value received is not correct, the server abort the session
        //
        boolean serverAbortRequired = false;

        if (Constants.AUTH_TYPE_HMAC.equalsIgnoreCase(serverAuthType)) {
            //
            // Checks if the client has sent the next nonce
            //
            Chal chal = getMessageChal(request);
            if (chal == null) {
                //
                // The server must abort the session (see OMA-SyncML-DMSecurity)
                //
                if (log.isLoggable(Level.FINEST)) {
                    log.finest("Server abort the session because the client has not sent the next nonce");
                }

                //serverAbortRequired = true;
            }
        }

        //
        // If the client uses the HMAC the server must check again the credential
        // and generate new nonce
        //
        Cred clientCred = null;
        if (Constants.AUTH_TYPE_HMAC.equalsIgnoreCase(clientAuthType)) {
            //
            // Generate new nonce
            //
            NextNonce nonce = ProtocolUtil.generateNextNonce();
            sessionState.device.setClientNonce(nonce.getValue());
            actions.setNextNonce(nonce);
            engine.storeDevice(sessionState.device);

            //
            // Checks if the credential is valid
            //
            clientCred = request.getSyncHdr().getCred();
            Officer officer = engine.getOfficer();

            if (!officer.authenticate(clientCred)) {

                if (log.isLoggable(Level.FINEST)) {
                    log.finest("Server abort the session because the credential sent by the client is not valid");
                }

                serverAbortRequired = true;
            }
        }

        if (serverAbortRequired) {
            //
            // We must abort the session
            //
            //
            // Set server credentials if required
            //
            if (sessionState.serverAuthenticationState != AUTH_ACCEPTED) {
                actions.setServerCredentials(engine.getServerCredentials(getChal(request), sessionState.device));
            }
            actions.setFlag(Flags.FLAG_SERVER_ABORT_REQUIRED);
            actions.setFlag(Flags.FLAG_FINAL_MESSAGE);

            sessionState.nextTimestamp.end = System.currentTimeMillis();
            sessionState.dmstate.state = DeviceDMState.STATE_ABORTED;
            endSession();
            moveTo(STATE_SESSION_ABORTED);
            SyncML response = actions.getResponse(msgIdGenerator.current());
            response.setLastMessage(true);
            return response;

        }

        //
        // Add Generic alerts to Cache
        //
        Alert[] alertsToCache = ProtocolUtil.searchGenericAlertCommands(request.getSyncBody());
        sessionState.addGenericAlert(alertsToCache);

        boolean alertCode1222sentFromTheClient = false;
        boolean alertCode1222requiredFromTheClient = false;

        //
        // Caches the status/results of the client
        //
        List commandToCache = ProtocolUtil.filterCommands(request.getSyncBody().getCommands(),
                new String[] { Status.COMMAND_NAME, Results.COMMAND_NAME, Alert.COMMAND_NAME });

        Alert[] removedAlerts = removeAlerts(commandToCache);

        checkForReceivedLargeObject(commandToCache);

        sessionState.addClientCommands((AbstractCommand[]) commandToCache.toArray(new AbstractCommand[0]));

        //
        // If the request is not final and there isn't an alert with code 1222, the server must answer
        // with a 1222 alert code without new commands and caches, if there is, large object
        //
        if (!request.getSyncBody().isFinalMsg()) {

            //
            // Check if in the request there is a 1222 alert code. If there is it
            // then we check if there is a large object to send
            //
            //
            Alert alert = ProtocolUtil.searchAlertCommand(request.getSyncBody(), AlertCode.MORE_DATA);
            if (alert != null) {
                //
                // The client wants more data
                //
                alertCode1222sentFromTheClient = true;
            } else {
                //
                // The client has not sent all status/results
                //
                actions.setFlag(Flags.FLAG_MORE_MSG_REQUIRED);
                alertCode1222requiredFromTheClient = true;

                Item itemWithLargeObject = ProtocolUtil.getLargeObject(commandToCache);

                if (itemWithLargeObject != null) {
                    sessionState.setReceivedLargeObject(itemWithLargeObject.getData().getData());
                    Long size = sync4j.framework.core.Util.getItemSize(itemWithLargeObject);
                    if (size != null) {
                        sessionState.setSizeOfReceivedLargeObject(size);
                    } else {
                        if (sessionState.getSizeOfReceivedLargeObject() == null) {
                            // Sets size to -1 so the ManagementActions knows when
                            // a large object is sent from the client without the meta size
                            sessionState.setSizeOfReceivedLargeObject(new Long(-1));
                        }
                    }
                } else {
                    //
                    // If the request is final, the large object in cache not must
                    // be more used
                    //
                    sessionState.setReceivedLargeObject(null);
                    sessionState.setSizeOfReceivedLargeObject(null);
                }
            }

        } else {
            actions.setFlag(Flags.FLAG_FINAL_MESSAGE);
            //
            // Set the clientCommands with the client commands in cache
            //
            if (removedAlerts == null || removedAlerts.length == 0) {
                actions.setClientCommands(sessionState.getClientCommands());
            } else {
                //
                // We should add the removed alerts because otherwise the server
                // doesn't return the Status for that
                //
                AbstractCommand[] commands = new AbstractCommand[sessionState.getClientCommands().length
                        + removedAlerts.length];

                System.arraycopy(sessionState.getClientCommands(), 0, commands, 0,
                        sessionState.getClientCommands().length);

                System.arraycopy(removedAlerts, 0, commands, sessionState.getClientCommands().length,
                        removedAlerts.length);

                actions.setClientCommands(commands);
            }

            //
            // If the cache is empty we call the setOperationResults with all results
            //
            if (sessionState.getCmdOut() == null || sessionState.getCmdOut().size() == 0) {
                //
                // Gets the client Status and Results commands and merge them in
                // in a corresponding array of ManagementOperationResult objects.
                // The so returned array can then be passed to the management
                // processor.
                //
                AbstractCommand[] clientCommands = actions.getClientCommands();

                //
                // The status without a command descriptor must be removed
                //
                clientCommands = removeStatusWithoutCommandDescriptor(clientCommands,
                        sessionState.managementCommandDescriptors);

                processor.setOperationResults(
                        Util.operationResults(clientCommands, String.valueOf(StatusCode.CHUNKED_ITEM_ACCEPTED)));
                sessionState.clearClientCommands();

                //
                // Set Generic alerts and clear cache
                //
                Alert[] genericAlerts = sessionState.getGenericAlert();
                if (genericAlerts != null || genericAlerts.length > 0) {
                    if (log.isLoggable(Level.FINER)) {
                        log.finer("Call setGenericAlert with " + genericAlerts.length + " generic alerts");
                    }
                    processor.setGenericAlert(genericAlerts);
                    sessionState.clearGenericAlert();
                }

            }

            // If the client sent a final message, the previous data is removed from the
            // sessionState because it is already merge with new data
            sessionState.setReceivedLargeObject(null);
        }

        actions.setFlag(Flags.FLAG_ALL_RESPONSES_REQUIRED);

        //
        // If the session is aborted or the server is awaiting for more msg or the
        // client is awaiting for more data, no commands must be added to the response
        //
        if (!actions.isSessionAbortRequired() && !alertCode1222requiredFromTheClient
                && !alertCode1222sentFromTheClient) {
            //
            // Checks if there are command in cache
            //
            List commandInCache = sessionState.getCmdOut();
            if (log.isLoggable(Level.FINER)) {
                log.finer("command in cache: " + commandInCache.size());
            }
            if (commandInCache.size() != 0) {
                AbstractCommand[] newCommands = (AbstractCommand[]) commandInCache
                        .toArray(new AbstractCommand[] {});
                actions.setManagementCommands(newCommands);
                //
                // remove the commands from the cache
                //
                sessionState.removeCmdOut(commandInCache);
                if (log.isLoggable(Level.FINER)) {
                    log.finer("Num. of managementCommands in actions: " + actions.getManagementCommands().length);
                }
            } else {
                //
                // Gets the next available operations from the processor
                //
                if (log.isLoggable(Level.FINER)) {
                    log.finer("Call getNextOperations for new operation");
                }

                actions.setManagementCommands(Util.managementOperations2commands(processor.getNextOperations(),
                        engine.getCommandIdGenerator(), mimeType));
            }
        } else if (alertCode1222sentFromTheClient) {
            //
            // We must check if there is previous command splitted on more
            // message (large object)
            //
            AbstractCommand previousCommand = sessionState.getSplittedCommand();

            if (previousCommand == null) {
                throw new ProtocolException("No more data to send");
            }
            //
            // If previousCmd is not null then it is a ItemizedCommand
            // (only ItemizedCommand are splittabled)
            // with only one item
            //
            Item item = (Item) ((ItemizedCommand) previousCommand).getItems().get(0);
            item.getData().setData(sessionState.getNextDataToSend());

            //
            // The dimension of the data is already sent to the client in the previous message.
            // Then remove meta size information from the item
            //
            Meta meta = item.getMeta();
            if (meta != null) {
                meta.setSize(null);
            }

            // Sets more data to false
            item.setMoreData(Boolean.FALSE);

            List commandInCache = sessionState.getCmdOut();

            //
            // Put the command in cache so tha actions manage all command
            //
            commandInCache.add(0, previousCommand);

            AbstractCommand[] newCommands = (AbstractCommand[]) commandInCache.toArray(new AbstractCommand[] {});
            actions.setManagementCommands(newCommands);
        }

        //
        // Set server credentials if required
        //
        if (sessionState.serverAuthenticationState != AUTH_ACCEPTED) {
            actions.setServerCredentials(engine.getServerCredentials(getChal(request), sessionState.device));
        }

        actions.setMimeType(mimeType);
        SyncML response = actions.getResponse(msgIdGenerator.current());

        if (alertCode1222requiredFromTheClient) {
            //
            // Returns the response without checks the dimension because the
            // response must contain only status for header and alert code 1222
            //
            return response;
        }

        //
        // Cache the commands to send in the next message
        //
        clearCache();
        cacheCommands(response);

        //
        // Calculate size of response message
        //
        SyncHdr syncHdr = response.getSyncHdr();
        SyncBody syncBody = response.getSyncBody();
        long sizeSyncHdr = 0, sizeSyncBody = 0;

        /**
         * The test case suite currently send only ds mime type, then we must check
         * also MIMETYPE_SYNCMLDS_WBXML and MIMETYPE_SYNCMLDS_XML
         */
        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
            sizeSyncHdr = SizeCalculator.getWBXMLSize(syncHdr);
            sizeSyncBody = SizeCalculator.getWBXMLSize(syncBody);
        } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
            sizeSyncHdr = SizeCalculator.getXMLSize(syncHdr);
            sizeSyncBody = SizeCalculator.getXMLSize(syncBody);
        }

        if (log.isLoggable(Level.FINER)) {
            log.finer("maxMsgSize: " + sessionState.getMaxMsgSize());
            log.finer("sizeSyncHdr: " + sizeSyncHdr);
            log.finer("sizeSyncBody: " + sizeSyncBody);
        }

        sessionState.setOverheadHdr(sizeSyncHdr);
        maxSizeAvailable = (int) (sessionState.getMaxMsgSize() * TOLLERANCE_MAX_MSG_SIZE);

        //
        // If the dimension of the response is < maxSizeAvailable or maxSizeAvailable is = 0
        // and there aren't the status/alerts/cmds cached then returns the response.
        // Else caches the commands of the response and re-create it.
        //
        if ((maxSizeAvailable >= sizeSyncHdr + sizeSyncBody || maxSizeAvailable == 0)
                && sessionState.getStatusCmdOut().size() == 0 && sessionState.getAlertCmdOut().size() == 0
                && sessionState.getCmdOut().size() == 0) {
            return response;
        }

        //
        // The size of the answer is greater then the allowed MaxMsgSize
        // Calculate size of the single commands of the response.
        // Create one answer of the allowed dimension.
        //
        try {
            response = createNextMsg(response);
        } catch (Sync4jException e) {
            throw new ProtocolException(e);
        }

        return response;
    }

    /**
     * Makes a state transition. Very simple implementation at the moment: it
     * changes the value of <i>currentState</i> to the given value.
     *
     * @param state the new state
     */
    private void moveTo(int state) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("moving to state " + getStateName(state));
        }
        currentState = state;
    }

    /**
     * Checks that the credentials of the given message are allowed to start a
     * session.
     *
     * @param credential the message
     * @param deviceId the deviceId
     */
    private boolean login(Cred credential, String deviceId) {
        //
        // May be the credential is already logged in...
        //
        logout();

        // Modified By Zhao DongLu, link the credential with it's deviceID
        // Reuqired by SecurityOfficer
        Authentication authentication = credential.getAuthentication();
        authentication.setDeviceId(deviceId);

        //
        // If the credential is not specified, create a new "guest" credential
        // but only if the property isGuestEnabled is true
        //
        if (credential == null) {
            sessionState.authenticationState = AUTH_MISSING_CREDENTIALS;

            return false;
        }

        clientAuthType = credential.getType();
        Sync4jPrincipal p = Sync4jPrincipal.fromCredential(credential.getData(), credential.getType(), deviceId);
        //
        // If the client authentication type is MD5 or HMAC then set the server nonce
        // value into credential
        //
        if (clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_MD5)
                || clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_HMAC)) {

            NextNonce nonce = new NextNonce(sessionState.device.getClientNonce());

            Authentication auth = credential.getAuthentication();
            auth.setNextNonce(nonce);
            auth.setDeviceId(deviceId);
        }

        try {
            Officer officer = engine.getOfficer();
            if (officer.authenticate(credential)) {
                //
                // if authType == MD5, the officer sets in the Authentication the
                // correct username, then sets it in the principal
                //
                if (clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_MD5)
                        || clientAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_HMAC)) {
                    p.setUsername(credential.getAuthentication().getUsername());
                }

                if (officer.authorize(p, RESOURCE_MANAGEMENT)) {

                    sessionState.loggedCredential = credential;
                    sessionState.loggedPrincipal = p;

                    sessionState.authenticationState = AUTH_AUTHENTICATED;

                    return true;
                }
            }
        } catch (Exception e) {
            if (log.isLoggable(Level.SEVERE)) {
                log.severe("Unable to login due to the error: " + e.getMessage());
            }
            log.throwing(getClass().getName(), "login", e);
        }

        return false;
    }

    /**
     * Logs out the logged in credential
     */
    private void logout() {
        sessionState.authenticationState = AUTH_INVALID_CREDENTIALS;
        sessionState.loggedCredential = null;
        sessionState.loggedPrincipal = null;
    }

    /**
     * Starts a new management session, choosing the right managment processor.
     *
     * @param requestMessage the request
     *
     * @return the SyncML message to send back to the client containing the
     *         management commands (if there is any) as obtained by the
     *         management processor
     *
     * @throws ProtocolException in the case the message could not meet SyncML specs
     * @throws ManagementException if any other error occurs
     */
    private SyncML startManagementSession(SyncML requestMessage, HttpServletRequest httpRequest)
            throws ProtocolException, ManagementException {

        //
        // If the client sent a MD5/HMAC Chal, store the server next nonce
        //
        storeServerNonce(ProtocolUtil.getStatusChal(requestMessage));

        // Prameter name "sid" defined by Sync4jServlet
        String httpSessionId = httpRequest.getParameter("sid");
        if (StringUtils.isEmpty(httpSessionId)) {
            httpSessionId = (String) httpRequest.getAttribute("sid");
        }
        String dmSessionId = requestMessage.getSyncHdr().getSessionID().getSessionID();

        //
        // Gets the processor selector and retrieve the processor to be used
        // for this management session
        //
        Configuration c = Configuration.getConfiguration();

        //
        // Try to associate this session to an existing state (by session id or
        // device id). See the design document for details.
        //
        sessionState.dmstate = new DeviceDMState(sessionState.device.getDeviceId());
        sessionState.dmstate.mssid = init.getSessionId();
        if (!engine.resolveDMState(sessionState.dmstate, sessionState.device)) {
            //
            // This is new DM session for which the server has no information
            //
            sessionState.dmstate.id = null;
            // Fix bug#79
            //sessionState.dmstate.mssid = init.getSessionId();
            sessionState.dmstate.mssid = "0";
        }

        //
        // If addAbsCmd is != null means that the processor already has been called.
        // Then cache contains the AbstractCommand to send
        //
        if (addAbsCmd == null) {
            ProcessorSelector selector = (ProcessorSelector) c.getBeanInstance(CFG_SERVER_DM_SELECTOR);

            processor = selector.getProcessor(sessionState.dmstate, init.getDevInfo(), init);

            if (processor == null) {
                throw new ManagementException("No management processor could be selected!");
            }

            if (sessionState.dmstate.state == DeviceDMState.STATE_NOTIFIED) {
                //
                // Change the state to IN_PROGRESS
                //
                sessionState.dmstate.state = DeviceDMState.STATE_IN_PROGRESS;
            }
        }

        //
        // The alert could be issued in any of the initialization messages
        //
        Alert alert = init.getClientAlert();

        if (alert != null) {
            sessionState.type = alert.getData();
        }

        //
        // If addAbsCmd is != null means that the processor already has been called.
        // Then cache contains the AbstractCommand to send
        //
        if (addAbsCmd == null) {
            sessionState.dmstate.start = new Date(System.currentTimeMillis());
            //
            // Perform session initialization
            //

            SessionContext context = new SessionContext(httpSessionId, dmSessionId, sessionState.loggedPrincipal,
                    sessionState.type, init.getDevInfo(), sessionState.dmstate, sessionState.syncMLVerProto, init);

            processor.beginSession(context);

            //
            // Set Generic alerts
            //
            Alert[] genericAlerts = ProtocolUtil.searchGenericAlertCommands(requestMessage.getSyncBody());
            if (genericAlerts != null || genericAlerts.length > 0) {
                if (log.isLoggable(Level.FINER)) {
                    log.finer(
                            "Start session calls setGenericAlert with " + genericAlerts.length + " generic alerts");
                }
                processor.setGenericAlert(genericAlerts);
            }

            if (!init.isSessionAbortRequired()) {
                //
                // Get the next available operations
                //
                init.setManagementCommands(Util.managementOperations2commands(processor.getNextOperations(),
                        engine.getCommandIdGenerator(), mimeType));
            }
        }

        // re-set the request because the init put in the response a reference to the request (MsgRef)
        init.setRequest(requestMessage);
        if (requestMessage.getSyncBody().isFinalMsg()) {
            init.setFlag(Flags.FLAG_FINAL_MESSAGE);
        } else {
            init.unsetFlag(Flags.FLAG_FINAL_MESSAGE);
        }

        SyncML response = init.getResponse(msgIdGenerator.current());

        //
        // Calculate size of response message
        //
        SyncHdr syncHdr = response.getSyncHdr();
        SyncBody syncBody = response.getSyncBody();
        long sizeSyncHdr = 0, sizeSyncBody = 0;

        /**
         * The test case suite currently send only ds mime type, then we must check
         * also MIMETYPE_SYNCMLDS_WBXML and MIMETYPE_SYNCMLDS_XML
         */
        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
            sizeSyncHdr = SizeCalculator.getWBXMLSize(syncHdr);
            sizeSyncBody = SizeCalculator.getWBXMLSize(syncBody);
        } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
            sizeSyncHdr = SizeCalculator.getXMLSize(syncHdr);
            sizeSyncBody = SizeCalculator.getXMLSize(syncBody);
        }

        if (log.isLoggable(Level.FINER)) {
            log.finer("maxMsgSize: " + sessionState.getMaxMsgSize());
            log.finer("sizeSyncHdr: " + sizeSyncHdr);
            log.finer("sizeSyncBody: " + sizeSyncBody);
        }

        sessionState.setOverheadHdr(sizeSyncHdr);
        maxSizeAvailable = sessionState.getMaxMsgSize();

        //
        // Check if the client MaxMsgSize is greater then the server
        // minmsgsize
        //
        checkMaxMsgSize(response);

        //
        // If the dimension of the response is < maxSizeAvailable or maxSizeAvailable is = 0
        // and there aren't the status/alerts/cmds cached then returns the response.
        // Else caches the commands of the response and re-create it.
        //
        if ((maxSizeAvailable >= sizeSyncHdr + sizeSyncBody || maxSizeAvailable == 0)
                && sessionState.getStatusCmdOut().size() == 0 && sessionState.getAlertCmdOut().size() == 0
                && sessionState.getCmdOut().size() == 0) {
            return response;
        }

        clearCache();
        //
        // Cache the alert/status/commands to send in the next package
        //
        cacheCommands(response);

        //
        // The size of the answer is greater then the allowed MaxMsgSize
        // Calculate size of the single commands of the response.
        // Create one answer of the allowed dimension.
        //
        try {
            response = createNextMsg(response);
        } catch (Sync4jException e) {
            throw new ProtocolException(e);
        }

        return response;

    }

    /**
     * Check if the client authentication type is supported from the officer
     *
     * @param cred the client credential
     *
     * @return true if the client authentication type is supported, false otherwise
     */
    private boolean checkAuthType(Cred cred) {
        Officer officer = engine.getOfficer();

        serverAuthType = officer.getAuthType();
        supportedAuthType = officer.getSupportedAuthType();

        if (cred == null) {
            if (officer.isGuestEnabled()) {
                sessionState.authenticationState = ManagementState.AUTH_AUTHENTICATED;
            } else {
                sessionState.authenticationState = ManagementState.AUTH_MISSING_CREDENTIALS;
            }
            return officer.isGuestEnabled();
        }

        String clientAuthType = cred.getType();

        if (supportedAuthType.indexOf(clientAuthType) != -1) {
            return true;
        }

        sessionState.authenticationState = ManagementState.AUTH_INVALID_CREDENTIALS;

        return false;
    }

    /**
     * Checks if the client expects the server to send its credentials. If so,
     * checkServerAuth returns the client Chal command.
     *
     * @param message the SyncML object
     *
     * @return the client Chal command or null if the client did not
     *         challenge the server
     */
    private Chal getMessageChal(SyncML message) {
        Status[] statusCmds = (Status[]) ProtocolUtil
                .filterCommands(message.getSyncBody().getCommands(), new String[] { Status.COMMAND_NAME })
                .toArray(new Status[0]);

        for (int i = 0; statusCmds != null && i < statusCmds.length; i++) {
            if ("0".equals(statusCmds[i].getCmdRef()) && "SyncHdr".equals(statusCmds[i].getCmd())) {

                return statusCmds[i].getChal();
            }
        }

        return null;
    }

    /**
     * Extracts or create a Chal object that represents how server credentials
     * should be created.<br>
     * If the client challenged the server, we must use the requested auth
     * mechanism, otherwise, if the server is not logged, we use the default server authentication scheme.
     *
     * @param msg the request message
     *
     * @return a Chal object representing how server credentials should be
     *         formatted. <code>null</code> if the client not requires the
     *         authentication and if serverAuthType is set to Constants.AUTH_TYPE_NONE
     */
    private Chal getChal(final SyncML msg) {
        Chal chal = getMessageChal(msg);

        if (chal == null) {
            //
            // client not challenged the server
            //
            if (sessionState.serverAuthenticationState != AUTH_ACCEPTED) {
                //
                // the server is not logged
                //
                if (serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_NONE)) {
                    return null;
                }

                Meta meta = new Meta();
                meta.setType(serverAuthType);
                meta.setFormat(Constants.FORMAT_CLEAR);
                meta.setNextNonce(new NextNonce(sessionState.device.getServerNonce()));
                chal = new Chal(meta);

            }
        } else {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Challenged server authentication with scheme " + chal.getType());
            }
        }

        return chal;
    }

    /**
     * Checks if the server authentication succedeed. It throws a
     * ProtocolException if not.
     *
     * @param msg the SyncML message to check
     *
     * @return true if the server should retry authentication, false otherwise
     *
     * @throw ProcotolException if server authentication did not succeed
     */
    private void checkServerAuthentication(SyncML msg) throws ProtocolException {
        int headerStatusCode = ProtocolUtil.getHeaderStatusCode(msg);
        if (headerStatusCode == -1) {
            return;
        }

        if ((headerStatusCode == StatusCode.INVALID_CREDENTIALS
                || headerStatusCode == StatusCode.MISSING_CREDENTIALS)) {

            /**
             * The server isn't authorized.
             * If server has used basic authentication and the client has required
             * basic authentication means that the server is unable to authenticate
             * to the client.
             * If the client has required a authentication type different from that used
             * from the server, then set the credential for the server to the new authentication
             * type.
             * If the authentication type used from the server is equal to that required
             * from the client (different from basic), the server retries with the same
             * authentication type (the client could have sent a new nonce)
             */

            String authRequiredFromClient = null;
            Chal chal = getChal(msg);

            if (chal != null) {
                authRequiredFromClient = chal.getType();
            }

            if (serverAuthType.equalsIgnoreCase(Constants.AUTH_TYPE_BASIC)
                    && authRequiredFromClient.equalsIgnoreCase(Constants.AUTH_TYPE_BASIC)) {

                throw new ProtocolException("Unable to authenticate to the client");

            } else if (!authRequiredFromClient.equalsIgnoreCase(serverAuthType)) {
                serverAuthType = authRequiredFromClient;

                init.setServerCredentials(engine.getServerCredentials(chal, sessionState.device));
                sessionState.serverAuthenticationState = AUTH_RETRY_1;

            } else if (authRequiredFromClient.equalsIgnoreCase(serverAuthType)
                    && sessionState.serverAuthenticationState == AUTH_UNAUTHENTICATED) {
                init.setServerCredentials(engine.getServerCredentials(chal, sessionState.device));
                sessionState.serverAuthenticationState = AUTH_RETRY_1;
            } else {
                throw new ProtocolException("Unable to authenticate to the client");
            }

            return;
        } else if (headerStatusCode == StatusCode.AUTHENTICATION_ACCEPTED) {
            //
            // Authenticated with code 212
            //
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Server logged (code 212)");
            }
            sessionState.serverAuthenticationState = AUTH_ACCEPTED;
        } else {
            //
            // Authenticated with code 200
            //
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Server auhenticated (code 200)");
            }
            sessionState.serverAuthenticationState = AUTH_AUTHENTICATED;
        }
    }

    /**
     * Stores the server next nonce in both the database and the managementState
     * device member if the chal type is MD5/HMAC. If chal is null, nothing is saved.
     *
     * @parameter chal the Chal object; if null, nothing is saved
     */
    private void storeServerNonce(Chal chal) {
        if (chal == null) {
            return;
        }

        if (Constants.AUTH_TYPE_MD5.equals(chal.getType()) || Constants.AUTH_TYPE_HMAC.equals(chal.getType())) {

            NextNonce nonce = chal.getNextNonce();
            if (nonce != null) {
                sessionState.device.setServerNonce(Base64.decode(nonce.getValue()));
                engine.storeDevice(sessionState.device);
            }
        }
    }

    /**
     * Returns a String representation of the given state
     *
     * @param state the state code
     *
     * @return a String representation of the given state
     */
    private String getStateName(int state) {
        String stateName = "STATE_UNKNOWN";

        switch (state) {
        case STATE_START:
            stateName = "STATE_START";
            break;
        case STATE_END:
            stateName = "STATE_END";
            break;
        case STATE_ERROR:
            stateName = "STATE_ERROR";
            break;
        case STATE_INITIALIZATION_PROCESSING:
            stateName = "STATE_INITIALIZATION_PROCESSING";
            break;
        case STATE_INITIALIZATION_PROCESSED:
            stateName = "STATE_INITIALIZATION_PROCESSED";
            break;
        case STATE_MANAGEMENT_PROCESSING:
            stateName = "STATE_MANAGEMENT_PROCESSING";
            break;
        case STATE_MANAGEMENT_PROCESSED:
            stateName = "STATE_MANAGEMENT_PROCESSED";
            break;
        case STATE_MANAGEMENT_COMPLETION:
            stateName = "STATE_MANAGEMENT_COMPLETION";
            break;
        case STATE_SESSION_ABORTED:
            stateName = "STATE_SESSION_ABORTED";
            break;

        default:
            stateName = "STATE_UNKNOWN";
            break;
        }

        return stateName;
    }

    /**
     * Clear the cache
     */
    private void clearCache() {
        sessionState.getAlertCmdOut().clear();
        sessionState.getStatusCmdOut().clear();
        sessionState.getCmdOut().clear();
    }

    /**
     * Cache the commands to send in the next message
     */
    private void cacheCommands(SyncML response) {
        //
        // Filter Status commands
        //
        List statusCmdOut = ProtocolUtil.filterCommands(response.getSyncBody().getCommands(),
                new String[] { Status.COMMAND_NAME });
        //
        // Sort Status for CmdRef
        //
        List listStatus = Arrays.asList(ProtocolUtil.sortStatusCommand(statusCmdOut.toArray(new Status[0])));
        //
        // Cache Status commands
        //
        sessionState.addStatusCmdOut(listStatus);

        Status statusSyncHdr = ProtocolUtil.filterStatus((Status[]) listStatus.toArray(new Status[0]), Status.class,
                "SyncHdr");

        /**
         * The test case suite currently send only ds mime type, then we must check
         * also MIMETYPE_SYNCMLDS_WBXML and MIMETYPE_SYNCMLDS_XML
         */
        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
            sessionState.setSizeStatusSyncHdr(SizeCalculator.getWBXMLSize(statusSyncHdr));
        } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
            sessionState.setSizeStatusSyncHdr(SizeCalculator.getXMLSize(statusSyncHdr));
        }

        //
        // Cache Status for SyncHdr to use (in order to create the asnwer) when
        // the server isn't in the state "STATE_SYNCHRONIZATION_PROCESSING"
        //
        sessionState.setStatusSyncHdr(statusSyncHdr);

        //
        // Remove status of the SyncHdr
        //
        ArrayList removeStatus = new ArrayList();
        removeStatus.add(statusSyncHdr);
        sessionState.removeStatusCmdOut(removeStatus);

        //
        // Filter AbstractCommand commands
        // (in particular the Sync, Results and Get commands)
        //
        List cmdOut = ProtocolUtil.filterCommands(response.getSyncBody().getCommands(), MANAGEMENT_COMMANDS);

        //
        // Sort AbstractCommand for CmdID
        //
        List listCmd = Arrays.asList(ProtocolUtil.sortAbstractCommand(cmdOut.toArray(new AbstractCommand[0])));
        //
        // Cache commands
        //
        sessionState.addCmdOut(new LinkedList(listCmd));
    }

    /**
     * Create the next Initialization message with commands presents into queues
     *
     * @param syncML the hypothetical answer
     *
     * @return the server response
     */
    private SyncML createNextMsg(SyncML syncML) throws Sync4jException {

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Create Next Message");
        }

        long sizeSyncHdr = sessionState.getOverheadHdr();
        long sizeStatusSyncHdr = sessionState.getSizeStatusSyncHdr();

        maxSizeAvailable = sessionState.getMaxMsgSize() - sizeSyncHdr - sizeStatusSyncHdr;

        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
            maxSizeAvailable -= SizeCalculator.getWBXMLOverheadSyncML();
            maxSizeAvailable -= SizeCalculator.getWBXMLOverheadSyncBody();
        } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
            maxSizeAvailable -= SizeCalculator.getXMLOverheadSyncML();
            maxSizeAvailable -= SizeCalculator.getXMLOverheadSyncBody();
        }

        //
        // Checks status
        //
        if (log.isLoggable(Level.FINEST)) {
            log.finest("How many Status can I include into the response?");
        }
        howManyStatus(sessionState.getStatusCmdOut());

        sessionState.removeStatusCmdOut(addStatus);

        //
        // Checks AbstractCommand
        //
        if (log.isLoggable(Level.FINEST)) {
            log.finest("How many AbstractCommand can I include into the response?");
        }
        howManyAbstractCommand();

        sessionState.removeCmdOut(addAbsCmd);

        int size = addStatus.size() + addAbsCmd.size();
        ArrayList commandList = new ArrayList(size);
        commandList.addAll(addStatus);
        commandList.addAll(addAbsCmd);

        AbstractCommand[] absCommands = (AbstractCommand[]) commandList.toArray(new AbstractCommand[size]);

        // if there is a data splitted in session, the message is not final
        boolean isFinal = (sessionState.getNextDataToSend() == null);
        SyncBody responseBody = new SyncBody(absCommands, isFinal);

        if (log.isLoggable(Level.FINEST)) {
            log.finest("status in cache after creation: " + sessionState.getStatusCmdOut().size());
            log.finest("alert in cache after creation: " + sessionState.getAlertCmdOut().size());
            log.finest("command in cache after creation: " + sessionState.getCmdOut().size());
        }

        SyncML newResponse = new SyncML(syncML.getSyncHdr(), responseBody);

        if (log.isLoggable(Level.FINEST)) {
            if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
                size = (int) SizeCalculator.getWBXMLSize(newResponse);
            } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
                size = (int) SizeCalculator.getXMLSize(newResponse);
            }
            log.finest("Response size: " + size);
        }

        return newResponse;
    }

    /**
     * Calculates how many Status can be sent
     */
    private void howManyStatus(List allStatus) {
        addStatus = new ArrayList();
        addStatus.add(sessionState.getStatusSyncHdr());

        //
        // Number of Status to insert into response
        //
        int x = 0;
        Status status = null;
        long size = 0;
        for (int i = 0; allStatus != null && i < allStatus.size(); i++) {
            status = (Status) allStatus.get(i);
            //
            // Calculate size Status
            //
            size = 0;
            if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
                size = SizeCalculator.getWBXMLSize(status);
            } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
                size = SizeCalculator.getXMLSize(status);
            }

            // If there is space, we add the state
            if (maxSizeAvailable - size >= 0) {
                addStatus.add((Status) allStatus.get(i));
                maxSizeAvailable -= size;
                x++;
            } else {
                break;
            }
        }

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Number of Status inserted: " + x);
        }
    }

    /**
     * Calculates how many AbstractCommand can be sent.
     */
    private void howManyAbstractCommand() {
        addAbsCmd = new ArrayList();
        boolean isCmdWithLargeObject = false;

        //
        // Number of AbstractCommand to insert into response
        //
        int x = 0;
        List allCmd = sessionState.getCmdOut();

        if (maxSizeAvailable > 0 && sessionState.getStatusCmdOut().size() == 0
                && sessionState.getAlertCmdOut().size() == 0) {

            long size = 0;
            AbstractCommand cmd = null;
            for (int i = 0; allCmd != null && i < allCmd.size(); i++) {
                size = 0;
                cmd = (AbstractCommand) allCmd.get(i);

                if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                        || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
                    size = SizeCalculator.getCommandWBXMLSize(cmd);
                } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                        || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
                    size = SizeCalculator.getCommandXMLSize(cmd);
                }

                // If there is space, we add the abstract command
                if (maxSizeAvailable - size >= 0) {
                    addAbsCmd.add(cmd);
                    maxSizeAvailable -= size;
                    if (cmd == sessionState.getSplittedCommand()) {
                        //
                        // I have added a previous splitted cmd.
                        //
                        sessionState.setSplittedCommand(null);
                        sessionState.setNextDataToSend(null);
                    }
                    x++;
                } else {

                    isCmdWithLargeObject = checkForSplitData(cmd, maxSizeAvailable);

                    //
                    // If cmd is the first command that we try to add and there isn't
                    // sufficiently space then we try to split the data in more message (large object).
                    // If large object isn't permitted (because the dimension is greater of the maxObjectSize)
                    // we log the info and remove the command from the list
                    // because this command not will be never in the list
                    //
                    if (i == 0) {

                        if (!isCmdWithLargeObject) {
                            if (log.isLoggable(Level.INFO)) {
                                log.info("The command " + cmd + " is too large (" + size + " bytes)");
                            }
                            sessionState.removeCmdOut(cmd);
                        } else {
                            addAbsCmd.add(cmd);
                            x++;
                            if (sessionState.getNextDataToSend() == null) {
                                //
                                // All data is sent. Try to add more commands
                                //
                                continue;
                            } else {
                                break;
                            }
                        }
                    } else {
                        if (!isCmdWithLargeObject) {
                            //
                            // This isn't first command and isn't a command with
                            // large object, then break the process
                            //
                            break;
                        } else {
                            //
                            // This isn't first command but is a command with
                            // large object, then add the command and break the process
                            //
                            addAbsCmd.add(cmd);
                            x++;
                            if (sessionState.getNextDataToSend() == null) {
                                //
                                // All data is sent. Try to add more commands
                                //
                                continue;
                            } else {
                                break;
                            }
                        }
                    }
                    break;
                }
            }
        }
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Number of AbstractCommand inserted: " + x);
        }

    }

    /**
     * Checks if the given command is splittable with the given sizeAvailable.
     *
     * @param cmd AbstractCommand
     * @param sizeAvailable long
     * @return boolean
     */
    private boolean checkForSplitData(AbstractCommand cmd, long sizeAvailable) {
        Item itemToSplit = null;
        //
        // If cmd contains no items or more than 1 items, no large object are permitted
        //
        if (cmd instanceof ItemizedCommand) {
            if (((ItemizedCommand) cmd).getItems().size() != 1) {
                if (log.isLoggable(Level.FINER)) {
                    log.finer("Command with more items isn't splittable");
                }
                return false;
            } else {
                itemToSplit = (Item) ((ItemizedCommand) cmd).getItems().get(0);
            }
        } else {
            if (log.isLoggable(Level.FINER)) {
                log.finer("Command isn't a ItemizedCommand then it isn't splittable");
            }
            return false;
        }

        Data data = itemToSplit.getData();
        if (data == null) {
            return false;
        }
        String dataValue = data.getData();
        if (dataValue == null) {
            return false;
        }

        Object dataToSplit = dataValue;
        boolean isBinaryData = false;
        int lengthDataToSplit = 0;

        Meta meta = itemToSplit.getMeta();
        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {

            if (meta != null) {
                String format = meta.getFormat();
                if (format != null && format.equalsIgnoreCase("b64")) {
                    isBinaryData = true;
                    dataToSplit = Base64.decode(((String) dataToSplit).getBytes());
                    lengthDataToSplit = ((byte[]) dataToSplit).length;
                }

            }
        }

        if (!isBinaryData) {
            lengthDataToSplit = ((String) dataToSplit).length();
        }

        long maxObjSize = sessionState.getMaxObjSize();
        if (maxObjSize != 0 && lengthDataToSplit > maxObjSize) {
            if (log.isLoggable(Level.FINER)) {
                log.finer("The dimension of the data is greater of the maxObjSize");
            }
            // data too large
            return false;
        }
        //
        // try to split the cmd
        //
        if (sessionState.getNextDataToSend() == dataValue) {
            //
            // the item already has been splitted,
            // then no meta size is set
            //
        } else {
            //
            // We add data lenght information on the item
            //
            if (meta == null) {
                meta = new Meta();
                itemToSplit.setMeta(meta);
            }

            meta.setSize(new Long(lengthDataToSplit));
        }

        int sizeAvailableForData = calculateDataSizeAvailable(sizeAvailable, cmd, itemToSplit);

        if (sizeAvailableForData <= 0) {
            //
            // No space for data
            //
            return false;
        }

        sessionState.setSplittedCommand(cmd);

        Object newData = null;
        @SuppressWarnings("unused")
        int lengthNewData = 0;

        if (isBinaryData) {
            if (sizeAvailableForData > lengthDataToSplit) {
                // send all
                sizeAvailableForData = lengthDataToSplit;
                newData = dataToSplit;
            } else {
                newData = new byte[sizeAvailableForData];
                System.arraycopy(dataToSplit, 0, newData, 0, sizeAvailableForData);
            }
            lengthNewData = sizeAvailableForData;
        } else {
            newData = ((String) dataToSplit).substring(0, sizeAvailableForData);
            lengthNewData = ((String) newData).length();
        }

        if (isBinaryData) {
            newData = new String(Base64.encode((byte[]) newData));
        }
        itemToSplit.getData().setData((String) newData);

        if (sizeAvailableForData >= lengthDataToSplit) {
            // all data is sent
            sessionState.setNextDataToSend(null);
            itemToSplit.setMoreData(Boolean.FALSE);
        } else {
            //
            // We must save the data not sent
            //
            String dataNotSent = null;
            if (isBinaryData) {
                byte[] byteNotSent = new byte[lengthDataToSplit - sizeAvailableForData];
                System.arraycopy(dataToSplit, sizeAvailableForData, byteNotSent, 0,
                        lengthDataToSplit - sizeAvailableForData);
                dataNotSent = new String(Base64.encode(byteNotSent));
            } else {
                dataNotSent = ((String) dataToSplit).substring(sizeAvailableForData);

            }
            itemToSplit.setMoreData(Boolean.TRUE);
            sessionState.setNextDataToSend(dataNotSent);
        }

        return true;
    }

    /**
     * Returns the space available for data.
     *
     * @param commandSizeAvailable long
     * @param cmd AbstractCommand
     * @param item Item
     * @return int
     */
    private int calculateDataSizeAvailable(long commandSizeAvailable, AbstractCommand cmd, Item item) {
        //
        // Caches the original data
        //
        ComplexData itemData = item.getData();
        item.setData(new ComplexData(""));

        long commandSize = -1;

        if (Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)) {
            commandSize = SizeCalculator.getCommandWBXMLSize(cmd);
        } else if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
            commandSize = SizeCalculator.getCommandXMLSize(cmd);
        }

        int sizeAvailableForMoreData = (int) (commandSizeAvailable - commandSize);
        //
        // Re-set the original data
        //
        item.setData(itemData);
        return sizeAvailableForMoreData;
    }

    /**
     * Checks if in the given list of commands there is data to add with
     * the previous data. The command with the data to add is the first in the list.
     * If there is a command, merges the previous data with the data contained in
     * the command.
     * @param clientCommands the command list to check
     * @throws ProtocolException if the list not contains a valid Results
     */
    private void checkForReceivedLargeObject(List clientCommands) throws ProtocolException {
        String previousReceivedLargeObject = sessionState.getReceivedLargeObject();

        if (previousReceivedLargeObject == null) {
            return;
        }

        //
        // There is a large object in cache. The first Results command
        // contains data to add to previousReceivedLargeObject
        //
        ArrayList results = ProtocolUtil.filterCommands(
                (AbstractCommand[]) (clientCommands.toArray(new AbstractCommand[0])), Results.class);
        if (results.size() == 0) {
            throw new ProtocolException("Error awaiting more data from the client - No Results in the request");
        }

        //
        // If there is previousReceivedLargeObject, the first command in the next
        // message contains the data to add with the previous data
        //
        Results result = (Results) results.get(0);
        Item item = (Item) result.getItems().get(0);

        boolean isReceivedItemWithBinaryData = sync4j.framework.core.Util.isItemWithBinaryData(item);

        String receivedData = null;

        if (item.getData() != null) {
            receivedData = item.getData().getData();
        } else {
            throw new ProtocolException("Error awaiting more data from the client - No Data in the first item");
        }
        if (isReceivedItemWithBinaryData) {
            //
            // If mime type is xml, binary data are managed as string
            //
            if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
                isReceivedItemWithBinaryData = false;
            }
        }

        if (isReceivedItemWithBinaryData) {
            byte[] previousData = Base64.decode(previousReceivedLargeObject.getBytes());
            byte[] bReceivedData = Base64.decode(receivedData.getBytes());
            byte[] newData = new byte[previousData.length + bReceivedData.length];
            System.arraycopy(previousData, 0, newData, 0, previousData.length);
            System.arraycopy(bReceivedData, 0, newData, previousData.length, bReceivedData.length);
            item.getData().setData(new String(Base64.encode(newData)));
        } else {
            String newData = previousReceivedLargeObject + receivedData;
            item.getData().setData(newData);
        }
        //
        // re-add the size of the data in the item because the client sends the size
        // only in the first message. So, the ManagementAction can checks the correct
        // dimension
        //
        Meta meta = item.getMeta();
        if (meta == null) {
            meta = new Meta();
            item.setMeta(meta);
        }
        meta.setSize(sessionState.getSizeOfReceivedLargeObject());
    }

    /**
     * Merge the data contained in the command splitted with the data still
     * to send. It is use for rebuild the data in case in which the client
     * sends a 401 or 407 code
     */
    private void mergeData() {
        AbstractCommand cmd = sessionState.getSplittedCommand();
        //
        // Only itemizedCommand with only one item are splittable
        //
        Item itemSplitted = (Item) (((ItemizedCommand) cmd).getItems()).get(0);
        boolean isBinary = sync4j.framework.core.Util.isItemWithBinaryData(itemSplitted);

        if (isBinary) {
            //
            // If mime type is xml, binary data are managed as string
            //
            if (Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)) {
                isBinary = false;
            }
        }

        if (isBinary) {
            byte[] previousData = Base64.decode(itemSplitted.getData().getData().getBytes());
            byte[] nextData = Base64.decode(sessionState.getNextDataToSend().getBytes());
            byte[] newData = new byte[previousData.length + nextData.length];
            System.arraycopy(previousData, 0, newData, 0, previousData.length);
            System.arraycopy(nextData, 0, newData, previousData.length, nextData.length);
            itemSplitted.getData().setData(new String(Base64.encode(newData)));
        } else {
            String previousData = itemSplitted.getData().getData();
            String nextData = sessionState.getNextDataToSend();
            String newData = previousData + nextData;
            itemSplitted.getData().setData(newData);
        }
    }

    /*
     * Check if the client MaxMsgSize is greater than server minmsgsize.
     * If the client MaxMsgSize is smaller than minmsgsize then change
     * the status code of SyncHdr into StatusCode.SYNCHRONIZATION_FAILED.
     *
     * @param response the SyncML response message
     */
    private void checkMaxMsgSize(SyncML response) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Check if the MaxMsgSize is larger of the minimal " + "size of the messages of the server");
        }
        long minMsgSize = 0;
        if (sessionState.getMaxMsgSize() != 0) {
            if (Constants.MIMETYPE_SYNCMLDS_WBXML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDM_WBXML.equals(mimeType)) {
                minMsgSize = Long.parseLong(
                        engine.getConfiguration().getStringValue(DMManagementEngine.CFG_MIN_MSGSIZE_WBXML));
            } else if (Constants.MIMETYPE_SYNCMLDS_XML.equals(mimeType)
                    || Constants.MIMETYPE_SYNCMLDM_XML.equals(mimeType)) {
                minMsgSize = Long.parseLong(
                        engine.getConfiguration().getStringValue(DMManagementEngine.CFG_MIN_MSGSIZE_XML));
            }
            if (sessionState.getMaxMsgSize() < minMsgSize) {
                Status statusHdr = (Status) response.getSyncBody().getCommands().get(0);
                statusHdr.setData(new Data(StatusCode.COMMAND_FAILED));

                if (log.isLoggable(Level.INFO)) {
                    log.info("The MaxMsgSize is smaller than minimum size "
                            + "that the server response could have!");
                    log.info("The server will not answer to some message " + "of the client.");
                }
            }
        }
    }

    /**
     * Removes Alert commands from the list of the given commands
     * @param commands List
     *
     * @return an array with the removed alerts
     */
    private Alert[] removeAlerts(List commands) {
        if (commands == null || commands.size() == 0) {
            return new Alert[0];
        }
        List commandsToRemove = new ArrayList();

        Iterator itCommands = commands.iterator();
        AbstractCommand command = null;
        while (itCommands.hasNext()) {
            command = (AbstractCommand) itCommands.next();
            if (command instanceof Alert) {
                commandsToRemove.add(command);
            }
        }
        commands.removeAll(commandsToRemove);
        return (Alert[]) commandsToRemove.toArray(new Alert[0]);
    }

    /**
     * Stores for each commands in the given list, a new entry in the
     * commandsDescriptors map with:
     * key: msgId|cmdId
     * value: commandDescriptor
     * @param msgId String
     * @param commands List
     */
    private void storeManagementCommandDescriptorForManagementCommands(String msgId, List commands) {
        if (commands == null) {
            return;
        }

        Iterator itCommands = commands.iterator();
        String cmdId = null;
        AbstractCommand command = null;

        ManagementCommandDescriptor commandDescriptor = null;

        while (itCommands.hasNext()) {

            command = (AbstractCommand) (itCommands.next());
            if (command instanceof Atomic) {
                storeManagementCommandDescriptorForManagementCommands(msgId, ((Atomic) command).getCommands());
                continue;
            }
            if (command instanceof Sequence) {
                storeManagementCommandDescriptorForManagementCommands(msgId, ((Sequence) command).getCommands());
                continue;
            }
            cmdId = command.getCmdID().getCmdID();
            String key = getCommandDescriptorKey(msgId, cmdId);
            commandDescriptor = createManagementCommandDescriptor(msgId, command);

            sessionState.managementCommandDescriptors.put(key.toString(), commandDescriptor);
        }
    }

    /**
     * Creates a ManagementCommandDescriptor with the given data.
     * @param msgId String
     * @param command AbstractCommand
     * @return ManagementCommandDescriptor
     */
    private ManagementCommandDescriptor createManagementCommandDescriptor(String msgId, AbstractCommand command) {

        if (command instanceof Alert) {
            if (!isAManagementAlert((Alert) command)) {
                //
                // The alert command is not a management alert
                // (it can be a 1222 or a 1223)
                //
                return null;
            }
        }
        ManagementCommandDescriptor commandDescriptor = null;

        if (command instanceof Add || command instanceof Alert || command instanceof Copy
                || command instanceof Delete || command instanceof Exec || command instanceof Get
                || command instanceof Replace) {

            commandDescriptor = new ManagementCommandDescriptor(msgId, command.getCmdID().getCmdID(),
                    command.getName());
        }

        return commandDescriptor;
    }

    /**
     * Is the given alert a management alert ? A management alert must have code:
     * <br/>
     * <ui>
     *    <li>DISPLAY: 1100</li>
     *    <li>CONFIRM_OR_REJECT: 1101</li>
     *    <li>INPUT: 1102</li>
     *    <li>SINGLE_CHOICE: 1103</li>
     *    <li>MULTIPLE_CHOICE: 1104</li>
     * </ui>
     * @param alert the alert to check
     * @return true if the given alert is a management alert, false otherwise
     */
    private boolean isAManagementAlert(Alert alert) {
        if (alert == null) {
            return false;
        }
        int code = alert.getData();
        switch (code) {
        case AlertCode.DISPLAY:
        case AlertCode.CONFIRM_OR_REJECT:
        case AlertCode.INPUT:
        case AlertCode.SINGLE_CHOICE:
        case AlertCode.MULTIPLE_CHOICE:
            return true;
        default:
            return false;
        }
    }

    /**
     * Removed the status from the given abstract commands that don't have a command descriptor
     * @param commands the commands
     * @param commandDescriptors the command descriptors
     * @return the given commands without status without command descriptor
     */
    private AbstractCommand[] removeStatusWithoutCommandDescriptor(AbstractCommand[] commands,
            java.util.Map commandDescriptors) {

        if (commands == null || commands.length == 0) {
            return commands;
        }

        List commandsToReturn = new ArrayList(commands.length);
        String msgRef = null;
        String cmdRef = null;
        String cmd = null;
        String key = null;
        ManagementCommandDescriptor cmdDescriptor = null;
        for (int i = 0; i < commands.length; i++) {

            if (!(commands[i] instanceof Status)) {
                commandsToReturn.add(commands[i]);
                continue;
            }
            msgRef = ((Status) commands[i]).getMsgRef();
            cmdRef = ((Status) commands[i]).getCmdRef();
            cmd = ((Status) commands[i]).getCmd();
            key = getCommandDescriptorKey(msgRef, cmdRef);
            cmdDescriptor = (ManagementCommandDescriptor) sessionState.managementCommandDescriptors.get(key);
            if (cmdDescriptor == null) {
                //
                // The status doesn't have a command descriptor
                //
                continue;
            }
            if (!cmdDescriptor.getCommandName().equalsIgnoreCase(cmd)) {
                //
                // The command is different !
                //
                continue;
            }
            commandsToReturn.add(commands[i]);
        }
        return (AbstractCommand[]) commandsToReturn.toArray(new AbstractCommand[commandsToReturn.size()]);
    }

    /**
     * Returns the key to use handling command descriptors from the given
     * msgId and cmdId
     * @param msgId the message id
     * @param cmdId the command id
     * @return msgId + "|" + cmdId
     */
    private String getCommandDescriptorKey(String msgId, String cmdId) {
        StringBuffer sb = new StringBuffer(msgId);
        sb.append('|').append(cmdId);
        return sb.toString();
    }
}