org.wso2.carbon.device.mgt.extensions.remote.session.RemoteSessionManagementServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.device.mgt.extensions.remote.session.RemoteSessionManagementServiceImpl.java

Source

/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.wso2.carbon.device.mgt.extensions.remote.session;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONObject;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.InvalidDeviceException;
import org.wso2.carbon.device.mgt.common.authorization.DeviceAccessAuthorizationException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.core.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.core.operation.mgt.ConfigOperation;
import org.wso2.carbon.device.mgt.extensions.remote.session.authentication.AuthenticationInfo;
import org.wso2.carbon.device.mgt.extensions.remote.session.authentication.OAuthAuthenticator;
import org.wso2.carbon.device.mgt.extensions.remote.session.constants.RemoteSessionConstants;
import org.wso2.carbon.device.mgt.extensions.remote.session.dto.RemoteSession;
import org.wso2.carbon.device.mgt.extensions.remote.session.exception.RemoteSessionManagementException;
import org.wso2.carbon.device.mgt.extensions.remote.session.internal.RemoteSessionManagementDataHolder;

import javax.websocket.CloseReason;
import javax.websocket.Session;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Class @{@link RemoteSessionManagementServiceImpl} is the implementation of @{@link RemoteSessionManagementService}
 * which is used manage initial connection, sending messages to peer session, manage and close the session.
 */
public class RemoteSessionManagementServiceImpl implements RemoteSessionManagementService {

    private static final Log log = LogFactory.getLog(RemoteSessionManagementServiceImpl.class);

    @Override
    public void initializeSession(Session session, String deviceType, String deviceId, String operationId)
            throws RemoteSessionManagementException {

        // Check whether required configurations are enabled
        if (!RemoteSessionManagementDataHolder.getInstance().isEnabled()) {
            throw new RemoteSessionManagementException("Remote session feature is disabled.");
        } else if (RemoteSessionManagementDataHolder.getInstance().getServerUrl() == null) {
            throw new RemoteSessionManagementException("Server url has not been configured.");
        }

        // Read Query Parameters for obtain the token
        Map<String, List<String>> sessionQueryParam = new HashMap();
        List<String> sessionQueryParamList = new LinkedList<>();
        sessionQueryParamList.add(session.getQueryString());
        sessionQueryParam.put(RemoteSessionConstants.QUERY_STRING, sessionQueryParamList);

        // Validate the token
        OAuthAuthenticator oAuthAuthenticator = RemoteSessionManagementDataHolder.getInstance()
                .getOauthAuthenticator();
        AuthenticationInfo authenticationInfo = oAuthAuthenticator.isAuthenticated(sessionQueryParam);

        if (authenticationInfo != null && authenticationInfo.isAuthenticated()) {
            try {
                PrivilegedCarbonContext.startTenantFlow();
                PrivilegedCarbonContext.getThreadLocalCarbonContext()
                        .setTenantDomain(authenticationInfo.getTenantDomain(), true);
                PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(authenticationInfo.getUsername());
                if (deviceId != null && !deviceId.isEmpty() && deviceType != null && !deviceType.isEmpty()) {
                    DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
                    deviceIdentifier.setId(deviceId);
                    deviceIdentifier.setType(deviceType);

                    // Check authorization of user for given device
                    boolean userAuthorized = RemoteSessionManagementDataHolder.getInstance()
                            .getDeviceAccessAuthorizationService()
                            .isUserAuthorized(deviceIdentifier, authenticationInfo.getUsername());
                    if (userAuthorized) {
                        // set common settings for session
                        session.setMaxBinaryMessageBufferSize(
                                RemoteSessionManagementDataHolder.getInstance().getMaxMessageBufferSize());
                        session.setMaxTextMessageBufferSize(
                                RemoteSessionManagementDataHolder.getInstance().getMaxMessageBufferSize());
                        session.setMaxIdleTimeout(
                                RemoteSessionManagementDataHolder.getInstance().getMaxIdleTimeout());

                        // if session initiated using operation id means request came from device
                        if (operationId != null) {
                            // create new device session
                            initializeDeviceSession(session, authenticationInfo.getTenantDomain(), deviceType,
                                    deviceId, operationId);
                        } else {
                            // create new client session
                            initializeClientSession(session, authenticationInfo.getTenantDomain(), deviceType,
                                    deviceId);
                        }
                        log.info("Current remote sessions count: "
                                + RemoteSessionManagementDataHolder.getInstance().getSessionMap().size());

                    } else {
                        throw new RemoteSessionManagementException("Missing device Id or type ");
                    }
                } else {
                    throw new RemoteSessionManagementException("Unauthorized Access for the device Type : "
                            + deviceType + " , deviceId : " + deviceId);
                }
            } catch (OperationManagementException | InvalidDeviceException e) {
                throw new RemoteSessionManagementException("Error occurred while adding initial operation for the "
                        + "device Type : " + deviceType + " , deviceId : " + deviceId);
            } catch (DeviceAccessAuthorizationException e) {
                throw new RemoteSessionManagementException(
                        "Error occurred while device access authorization for the " + "device Type : " + deviceType
                                + " , " + "deviceId : " + deviceId);
            } finally {
                PrivilegedCarbonContext.endTenantFlow();
            }

        } else {
            throw new RemoteSessionManagementException("Invalid token");
        }
    }

    @Override
    public void initializeSession(Session session, String deviceType, String deviceId)
            throws RemoteSessionManagementException {
        initializeSession(session, deviceType, deviceId, null);
    }

    /**
     * Implements the behaviour of sending message to peer connection
     *
     * @param session Web socket RemoteSession
     * @param message String message needs to send to peer connection
     * @throws RemoteSessionManagementException throws when session cannot be made due to invalid data
     * @throws RemoteSessionManagementException throws when session has error with accessing device resources
     */
    @Override
    public void sendMessageToPeer(Session session, String message) throws RemoteSessionManagementException {
        JSONObject jsonObject = new JSONObject(message);
        RemoteSession remoteSession = RemoteSessionManagementDataHolder.getInstance().getSessionMap()
                .get(session.getId());
        if (remoteSession != null) {
            remoteSession.sendMessageToPeer(jsonObject.toString());
        } else {
            throw new RemoteSessionManagementException("Remote Session cannot be found ");
        }
    }

    /**
     * Implements the behaviour of sending message to peer connection
     *
     * @param session Web socket RemoteSession
     * @param message Byte message needs to send to peer connection
     * @throws RemoteSessionManagementException throws when session cannot be made due to invalid data
     * @throws RemoteSessionManagementException throws when session has error with accessing device resources
     */
    @Override
    public void sendMessageToPeer(Session session, byte[] message) throws RemoteSessionManagementException {

        RemoteSession remoteSession = RemoteSessionManagementDataHolder.getInstance().getSessionMap()
                .get(session.getId());
        if (remoteSession != null) {
            remoteSession.sendMessageToPeer(message);
        } else {
            throw new RemoteSessionManagementException("Remote Session cannot be found ");
        }
    }

    /**
     * Closing the session and cleanup the resources
     *
     * @param session Web socket Remote Session
     */
    @Override
    public void endSession(Session session, String closeReason) {

        RemoteSession remoteSession = RemoteSessionManagementDataHolder.getInstance().getSessionMap()
                .remove(session.getId());
        if (remoteSession != null) {
            //String operationId = remoteSession.getOperationId();
            String deviceKey = remoteSession.getTenantDomain() + "/" + remoteSession.getDeviceType() + "/"
                    + remoteSession.getDeviceId();
            RemoteSession lastSession = RemoteSessionManagementDataHolder.getInstance()
                    .getActiveDeviceClientSessionMap().get(deviceKey);
            if (lastSession != null && lastSession.getMySession().getId().equals(session.getId())) {
                RemoteSessionManagementDataHolder.getInstance().getActiveDeviceClientSessionMap().remove(deviceKey);
            }
            if (remoteSession.getPeerSession() != null) {
                Session peerSession = remoteSession.getPeerSession().getMySession();
                if (peerSession != null) {
                    RemoteSessionManagementDataHolder.getInstance().getSessionMap().remove(peerSession.getId());
                    if (lastSession != null && lastSession.getMySession().getId().equals(peerSession.getId())) {
                        RemoteSessionManagementDataHolder.getInstance().getActiveDeviceClientSessionMap()
                                .remove(deviceKey);
                    }
                    if (peerSession.isOpen()) {
                        try {
                            peerSession.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, closeReason));
                        } catch (IOException ex) {
                            if (log.isDebugEnabled()) {
                                log.error("Failed to disconnect the client.", ex);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Starting new client session
     *
     * @param session      Web socket Session
     * @param tenantDomain Tenant domain
     * @param deviceType   Device Type
     * @param deviceId     Device Id
     * @throws RemoteSessionManagementException throws when session has errors with accessing device resources
     * @throws OperationManagementException     throws when error occured during new operation
     * @throws InvalidDeviceException           throws when incorrect device identifier
     */
    private void initializeClientSession(Session session, String tenantDomain, String deviceType, String deviceId)
            throws RemoteSessionManagementException, OperationManagementException, InvalidDeviceException {

        RemoteSession clientRemote = new RemoteSession(session, tenantDomain, deviceType, deviceId,
                RemoteSessionConstants.CONNECTION_TYPE.CLIENT);
        String deviceKey = tenantDomain + "/" + deviceType + "/" + deviceId;
        // Create new remote control operation to start the session
        RemoteSession activeSession = RemoteSessionManagementDataHolder.getInstance()
                .getActiveDeviceClientSessionMap().putIfAbsent(deviceKey, clientRemote);
        if (activeSession != null && activeSession.getMySession().isOpen()
                && activeSession.getPeerSession() == null) {
            throw new RemoteSessionManagementException("Another client session waiting on device to connect.");
        } else {
            // if there is pending session exists but already closed, then we need to remove it.
            if (activeSession != null) {
                RemoteSessionManagementDataHolder.getInstance().getActiveDeviceClientSessionMap().remove(deviceKey);
                try {
                    activeSession.getMySession().close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY,
                            "Remote " + "session closed due to new session request"));
                } catch (IOException ex) {
                    if (log.isDebugEnabled()) {
                        log.error("Failed to disconnect the client.", ex);
                    }
                }
                // Use put if absent for adding session to waiting list since we need to overcome
                // multithreaded session requests.
                activeSession = RemoteSessionManagementDataHolder.getInstance().getActiveDeviceClientSessionMap()
                        .putIfAbsent(deviceKey, clientRemote);
            }
            // If another client tried to start session same time then active session will be
            // exist. So we are adding session request only no parallel sessions added to map
            if (activeSession == null) {

                // Create operation if session initiated by client
                Operation operation = new ConfigOperation();
                operation.setCode(RemoteSessionConstants.REMOTE_CONNECT);
                operation.setEnabled(true);
                operation.setControl(Operation.Control.NO_REPEAT);
                JSONObject payload = new JSONObject();
                payload.put("serverUrl", RemoteSessionManagementDataHolder.getInstance().getServerUrl());
                operation.setPayLoad(payload.toString());
                String date = new SimpleDateFormat(RemoteSessionConstants.DATE_FORMAT_NOW).format(new Date());
                operation.setCreatedTimeStamp(date);
                List<DeviceIdentifier> deviceIdentifiers = new ArrayList<>();
                deviceIdentifiers.add(new DeviceIdentifier(deviceId, deviceType));
                Activity activity = RemoteSessionManagementDataHolder.getInstance()
                        .getDeviceManagementProviderService()
                        .addOperation(deviceType, operation, deviceIdentifiers);
                clientRemote.setOperationId(activity.getActivityId()
                        .replace(DeviceManagementConstants.OperationAttributes.ACTIVITY, ""));
                RemoteSessionManagementDataHolder.getInstance().getSessionMap().put(session.getId(), clientRemote);
                log.info("Client remote session opened for session id: " + session.getId() + " device Type : "
                        + deviceType + " , " + "deviceId : " + deviceId);
            } else {
                throw new RemoteSessionManagementException(
                        "Another client session waiting on " + "device to connect.");
            }
        }
    }

    /**
     * Starting new device session
     *
     * @param session      Web socket Session
     * @param tenantDomain Tenant domain
     * @param deviceType   Device Type
     * @param deviceId     Device Id
     * @param operationId  Operation id
     * @throws RemoteSessionManagementException throws when session has errors with accessing device resources
     */
    private void initializeDeviceSession(Session session, String tenantDomain, String deviceType, String deviceId,
            String operationId) throws RemoteSessionManagementException {
        String deviceKey = tenantDomain + "/" + deviceType + "/" + deviceId;
        RemoteSession activeSession = RemoteSessionManagementDataHolder.getInstance()
                .getActiveDeviceClientSessionMap().get(deviceKey);
        if (activeSession != null) {
            RemoteSession clientRemote = RemoteSessionManagementDataHolder.getInstance().getSessionMap()
                    .get(activeSession.getMySession().getId());
            if (clientRemote != null) {
                if (clientRemote.getOperationId().equals(operationId)) {
                    RemoteSession deviceRemote = new RemoteSession(session, tenantDomain, deviceType, deviceId,
                            RemoteSessionConstants.CONNECTION_TYPE.DEVICE);
                    deviceRemote.setOperationId(operationId);
                    deviceRemote.setPeerSession(clientRemote);
                    clientRemote.setPeerSession(deviceRemote);
                    RemoteSessionManagementDataHolder.getInstance().getSessionMap().put(session.getId(),
                            deviceRemote);
                    // Send Remote connect response
                    JSONObject message = new JSONObject();
                    message.put(RemoteSessionConstants.REMOTE_CONNECT_CODE, RemoteSessionConstants.REMOTE_CONNECT);
                    deviceRemote.sendMessageToPeer(message.toString());
                    log.info("Device session opened for session id: " + session.getId() + " device Type : "
                            + deviceType + " , " + "deviceId : " + deviceId);
                } else {
                    throw new RemoteSessionManagementException("Device and Operation information "
                            + "does not matched with client information for operation id: " + operationId
                            + " device " + "Type : " + deviceType + " , " + "deviceId : " + deviceId);
                }
            } else {
                throw new RemoteSessionManagementException("Device session is inactive for " + "operation id: "
                        + operationId + " device Type : " + deviceType + " , " + "deviceId : " + deviceId);
            }
        } else {
            throw new RemoteSessionManagementException("Device session is inactive for operation " + "id: "
                    + operationId + " device Type : " + deviceType + " , " + "deviceId : " + deviceId);
        }

    }
}