org.orbisgis.orbisserver.baseserver.model.Session.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.orbisserver.baseserver.model.Session.java

Source

/*
 * OrbisServer is an OSGI web application to expose OGC services.
 *
 * OrbisServer is part of the OrbisGIS platform
 *
 * OrbisGIS is a java GIS application dedicated to research in GIScience.
 * OrbisGIS is developed by the GIS group of the DECIDE team of the
 * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
 *
 * The GIS group of the DECIDE team is located at :
 *
 * Laboratoire Lab-STICC  CNRS UMR 6285
 * Equipe DECIDE
 * UNIVERSIT DE BRETAGNE-SUD
 * Institut Universitaire de Technologie de Vannes
 * 8, Rue Montaigne - BP 561 56017 Vannes Cedex
 *
 * OrbisServer is distributed under LGPL 3 license.
 *
 * Copyright (C) 2017 CNRS (Lab-STICC UMR CNRS 6285)
 *
 *
 * OrbisServer is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * OrbisServer is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * OrbisServer. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, please consult: <http://www.orbisgis.org/>
 * or contact directly:
 * info_at_ orbisgis.org
 */
package org.orbisgis.orbisserver.baseserver.model;

import org.apache.commons.io.filefilter.NameFileFilter;
import org.h2gis.utilities.JDBCUtilities;
import org.h2gis.utilities.SFSUtilities;
import org.h2gis.utilities.TableLocation;
import org.orbisgis.orbisserver.api.model.*;
import org.orbisgis.orbisserver.api.service.Service;
import org.orbisgis.orbisserver.api.service.ServiceFactory;
import org.orbisgis.orbisserver.baseserver.BaseServerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.*;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Session of the server. A session contains a list of Services, a DataSource and a workspace.
 *
 * @author Sylvain PALOMINOS
 */
public class Session {

    /** Logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(Session.class);

    public static final String PROPERTY_EXPIRATION_TIME_MILLIS = "PROPERTY_EXPIRATION_TIME_MILLIS";
    public static final String JOB_POOL_SIZE = "JOB_POOL_SIZE";
    public static final String SERVICE_LIST = "SERVICE_LIST";

    private static final int BASE_POOL_SIZE = 5;

    /** Token associated to the session. It is used for the identification of the web client requests. */
    private UUID token;
    /** DataSource associated to the session. This data source is used for the differents services associated. */
    private DataSource ds;
    /** Executor services dedicated to the session. */
    private ExecutorService executorService;
    /** Workspace folder. */
    private File workspaceFolder;
    /** Username associated to the session. */
    private String username;
    /** List of services instance for the Session. */
    private List<Service> serviceList;
    /** List of StatusInfo. This list is used as a cache saving all the process executed and waiting for the data
     * retrieving.*/
    private List<StatusInfo> statusInfoList;
    /** Map linking the job id with the service executing it. */
    private Map<String, Service> jobIdServiceMap;
    /** Map with the finished job. */
    private Map<String, StatusInfo> finishedJobMap;
    /** Time before expiration of the session. If equals to -1, there is no expiration. */
    private long expirationTimeMillis;
    /** ScheduledThreadPoolExecutor for the jobs results expiration and the session inactivity. */
    private ScheduledThreadPoolExecutor resultExpirationExecutor;
    /** Instance of the BaseServerImpl. */
    private BaseServerImpl baseServerImpl;
    /** Indicates if the session is active or not. */
    private boolean isActive;

    /**
     * Main constructor.
     */
    public Session(String username, UUID token, BaseServerImpl baseServerImpl) {
        this.token = token;
        this.username = username;
        isActive = false;
        jobIdServiceMap = new HashMap<>();
        finishedJobMap = new HashMap<>();
        statusInfoList = new ArrayList<>();
        expirationTimeMillis = -1;
        this.serviceList = new ArrayList<>();
        this.baseServerImpl = baseServerImpl;
    }

    /**
     * Sets the properties of the Session.
     * @param propertyMap Map containing the properties of the session.
     */
    public void setProperties(Map<String, Object> propertyMap) {
        if (propertyMap.containsKey(PROPERTY_EXPIRATION_TIME_MILLIS)) {
            this.expirationTimeMillis = (long) propertyMap.get(PROPERTY_EXPIRATION_TIME_MILLIS);
        } else {
            this.expirationTimeMillis = -1;
        }

        if (propertyMap.containsKey(ServiceFactory.DATA_SOURCE_PROP)) {
            this.ds = (DataSource) propertyMap.get(ServiceFactory.DATA_SOURCE_PROP);
        }

        if (propertyMap.containsKey(ServiceFactory.EXECUTOR_SERVICE_PROP)) {
            this.executorService = (ExecutorService) propertyMap.get(ServiceFactory.EXECUTOR_SERVICE_PROP);
        }

        if (propertyMap.containsKey(ServiceFactory.WORKSPACE_FOLDER_PROP)) {
            this.workspaceFolder = (File) propertyMap.get(ServiceFactory.WORKSPACE_FOLDER_PROP);
        }

        if (propertyMap.containsKey(JOB_POOL_SIZE)) {
            this.resultExpirationExecutor = new ScheduledThreadPoolExecutor((int) propertyMap.get(JOB_POOL_SIZE));
        } else {
            this.resultExpirationExecutor = new ScheduledThreadPoolExecutor(BASE_POOL_SIZE);
        }

        if (propertyMap.containsKey(ServiceFactory.DATA_SOURCE_PROP)) {
            this.serviceList = (List<Service>) propertyMap.get(SERVICE_LIST);
        } else {
            this.serviceList = new ArrayList<>();
            LOGGER.info("No services available on starting the session.");
        }
    }

    /**
     * Returns the DataSource of the Session.
     * @return The session DataSource.
     */
    public DataSource getDataSource() {
        return ds;
    }

    /**
     * Returns the ExecutorService of the session.
     * @return The session ExecutorService.
     */
    public ExecutorService getExecutorService() {
        return executorService;
    }

    /**
     * Returns the workspace folder of the sessions.
     * @return The session workspace folder.
     */
    public File getWorkspaceFolder() {
        return workspaceFolder;
    }

    /**
     * Returns the username of the session.
     * @return The session username.
     */
    public String getUsername() {
        return username;
    }

    /**
     * Sets the state of the session : true the session is active( process running or results available), false inactive.
     * @param active State of the session : true active, false inactive.
     */
    public void setActive(boolean active) {
        this.isActive = active;
    }

    /**
     * Returns the state of the session : true the session is active( process running or results available), false inactive.
     * @return State of the session : true active, false inactive.
     */
    public boolean isActive() {
        return isActive;
    }

    /**
     * Returns the list of operations available in ths session.
     * @return The available operation list.
     */
    public List<Operation> getOperationList() {
        List<Operation> operationList = new ArrayList<>();
        for (Service service : serviceList) {
            operationList.addAll(service.getAllOperation());
        }
        return operationList;
    }

    /**
     * Returns the Operation with the given identifier.
     * @param id Identifier of the operation.
     * @return The operation with the given identifier.
     */
    public Operation getOperation(String id) {
        Service serv = null;
        for (Service service : serviceList) {
            if (service.hasOperation(id)) {
                serv = service;
            }
        }
        if (serv == null) {
            return null;
        }
        return serv.getOperation(id);
    }

    /**
     * Execute the operation corresponding to the given identifier, using the given input data Map.
     * @param id Identifier of the operation to execute.
     * @param inputData Input data Map to use on the execution.
     */
    public void executeOperation(String id, Map<String, String> inputData) {
        isActive = true;
        Operation operation = getOperation(id);
        Map<String, String> tmpMap = new HashMap<>();
        for (Input input : operation.getInputList()) {
            if (input.getName().equalsIgnoreCase("RawData")) {
                for (Map.Entry<String, String> entry : inputData.entrySet()) {
                    if (input.getId().equalsIgnoreCase(entry.getKey())) {
                        tmpMap.put(entry.getKey(), new File(workspaceFolder, entry.getValue()).getAbsolutePath());
                    }
                }
            }
        }
        inputData.putAll(tmpMap);
        ExecuteRequest executeRequest = new ExecuteRequest(id, inputData);
        Service serv = null;
        for (Service service : serviceList) {
            if (service.hasOperation(id)) {
                serv = service;
            }
        }
        if (serv != null) {
            StatusInfo statusInfo = serv.executeOperation(executeRequest);
            statusInfo.setProcessID(id);
            statusInfo.setProcessTitle(getTitle(id));
            statusInfoList.add(statusInfo);
            jobIdServiceMap.put(statusInfo.getJobId(), serv);
        }
    }

    /**
     * Returns the title of the operation with the given identifier. If no operation is found, returns an empty string.
     * @param id Identifier of the operation to find.
     * @return The title of the operation.
     */
    private String getTitle(String id) {
        for (Service service : serviceList) {
            if (service.hasOperation(id)) {
                return service.getOperation(id).getTitle();
            }
        }
        return "";
    }

    /**
     * Returns the list of the cached list of StatusInfo responses.
     * @return The cached StatusInfo list.
     */
    public List<StatusInfo> getAllStatusInfo() {
        List<StatusInfo> list = new ArrayList<>();
        list.addAll(statusInfoList);
        for (Map.Entry<String, StatusInfo> entry : finishedJobMap.entrySet()) {
            list.add(entry.getValue());
        }
        return list;
    }

    /**
     * Return the list of all the StatusInfo with the next poll date is reached and which needs to be refreshed.
     * @return The list of Status info to refresh.
     */
    public List<StatusInfo> getAllStatusInfoToRefresh() {
        List<StatusInfo> allStatusInfoToRefresh = new ArrayList<>();
        long timeMillisNow = System.currentTimeMillis();
        for (StatusInfo statusInfo : statusInfoList) {
            long comparison = -1;
            if (statusInfo.getNextPoll() != null) {
                long timeMillisPoll = statusInfo.getNextPoll().toGregorianCalendar().getTime().getTime();
                comparison = timeMillisPoll - timeMillisNow;
            }
            if (comparison < 0) {
                allStatusInfoToRefresh.add(statusInfo);
            }
        }
        return allStatusInfoToRefresh;
    }

    /**
     * Refresh the status of the job with the given identifier.
     */
    public long refreshStatus(StatusInfo statusInfo) {
        long timeMillisNow = System.currentTimeMillis();
        long minRefresh = Long.MAX_VALUE;
        String jobId = statusInfo.getJobId();
        Service service = jobIdServiceMap.get(jobId);
        StatusRequest statusRequest = new StatusRequest(jobId);

        StatusInfo statusInfoToRemove = null;
        for (StatusInfo info : statusInfoList) {
            if (info.getJobId().equalsIgnoreCase(jobId)) {
                statusInfoToRemove = info;
            }
        }
        statusInfoList.remove(statusInfoToRemove);

        StatusInfo info = service.getStatus(statusRequest);
        if (info != null) {
            statusRequest.setProcessId(statusInfo.getProcessID());
            info.setProcessID(statusInfo.getProcessID());
            info.setProcessTitle(statusInfo.getProcessTitle());
            info.setNextRefreshMillis(-1);
            if (info.getNextPoll() != null) {
                long timeMillisPoll = info.getNextPoll().toGregorianCalendar().getTime().getTime();
                info.setNextRefreshMillis(timeMillisPoll - timeMillisNow);
            }
            if (info.getStatus().equalsIgnoreCase("SUCCEEDED") || info.getStatus().equalsIgnoreCase("FAILED")) {
                info.setResult(service.getResult(statusRequest));
                //Schedule the expiration of the result
                resultExpirationExecutor.schedule(new ResultExpirationTask(jobId, this),
                        info.getResult().getExpirationDate().toGregorianCalendar().getTimeInMillis()
                                - timeMillisNow,
                        TimeUnit.MILLISECONDS);
                jobIdServiceMap.remove(jobId);
                finishedJobMap.put(jobId, info);
            } else {
                statusInfoList.add(info);
            }
        }
        return minRefresh;
    }

    /**
     * Returns the token of the session.
     * @return The token of the session.
     */
    public UUID getToken() {
        return token;
    }

    /**
     * Returns the DatabaseContent object which contains the representation of the Database.
     * @return The DatabaseContent object.
     */
    public DatabaseContent getDatabaseContent() {
        DatabaseContent dbContent = new DatabaseContent();
        try (Connection connection = ds.getConnection()) {
            for (String tableName : JDBCUtilities.getTableNames(connection.getMetaData(), null, null, null,
                    new String[] { "TABLE", "LINKED TABLE", "VIEW", "EXTERNAL", "UIodfsghjmodfhjgodujhfg" })) {
                DatabaseTable dbTable = new DatabaseTable(TableLocation.parse(tableName));
                //Get the list of the columns of a table
                ResultSet rs1 = connection.createStatement()
                        .executeQuery(String.format("select * from %s limit 1", dbTable.getName()));
                ResultSetMetaData metaData = rs1.getMetaData();
                //If the column isn't a geometry, add it to the map
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    if (!metaData.getColumnTypeName(i).equalsIgnoreCase("GEOMETRY")) {
                        dbTable.addField(metaData.getColumnLabel(i), metaData.getColumnTypeName(i));
                    }
                }
                //Once the non geometric columns are get, do the same with the geometric one.
                Statement statement = connection.createStatement();
                String query = String.format("SELECT * FROM GEOMETRY_COLUMNS WHERE F_TABLE_NAME LIKE '%s';",
                        TableLocation.parse(dbTable.getName()).getTable());
                ResultSet rs = statement.executeQuery(query);
                while (rs.next()) {
                    dbTable.addField(rs.getString(4), SFSUtilities.getGeometryTypeNameFromCode(rs.getInt(6)));
                }
                dbContent.addTable(dbTable);
            }
        } catch (SQLException e) {
            LOGGER.error("Unable to get the database information.\nCause : " + e.getMessage());
        }
        return dbContent;
    }

    /**
     * Generate an archive with the result of the job available on the server.
     * @param jobId Id of the job which has generated the results.
     * @return File object of the archive containing the results. If an error appends in the archive creation, returns null.
     */
    public File getResultAchive(String jobId) {
        File jobFolder = new File(workspaceFolder, jobId);
        for (StatusInfo statusInfo : getAllStatusInfo()) {
            if (statusInfo.getJobId().equalsIgnoreCase(jobId)) {
                //Once the good StatusInfo in found, for each output store its data in the archive
                for (Output out : statusInfo.getResult().getOutputList()) {
                    //In the case of plain data, write it into a file
                    if (out.getData() != null) {
                        try {
                            for (Object content : out.getData().getContent()) {
                                File outFile;
                                //If a file with the output name already exists, adds a number to it
                                if (jobFolder.list(new NameFileFilter(out.getTitle())) != null) {
                                    int diff = 1;
                                    while (jobFolder.list(new NameFileFilter(out.getTitle() + diff)) != null) {
                                        diff++;
                                    }
                                    outFile = new File(jobFolder,
                                            out.getTitle().replaceAll(File.separator, "") + diff);
                                } else {
                                    outFile = new File(jobFolder, out.getTitle().replaceAll(File.separator, ""));
                                }
                                //Create the file and write data inside
                                if (jobFolder.mkdirs() || outFile.createNewFile()) {
                                    try (FileWriter fileWriter = new FileWriter(outFile)) {
                                        try (PrintWriter out1 = new PrintWriter(fileWriter)) {
                                            out1.append(content.toString());
                                        }
                                    }
                                } else {
                                    LOGGER.error("Unable to create the output as a file.");
                                }
                            }
                        } catch (IOException e) {
                            LOGGER.error("Unable to write the output as a file.\n" + e.getMessage());
                        }
                    }
                    //If the result is a reference, copy if to the archive folder
                    else if (out.getReference() != null) {
                        try {
                            URL url = new URL(out.getReference());
                            ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
                            FileOutputStream fos = new FileOutputStream(out.getTitle());
                            fos.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
                        } catch (IOException e) {
                            LOGGER.error("Unable to download the result.\n" + e.getMessage());
                        }
                    }
                }
            }
        }
        try {
            //Create a zip file with the archive folder
            File zipFile = new File(workspaceFolder, "Result.zip");
            FileOutputStream fos = new FileOutputStream(zipFile);
            ZipOutputStream zos = new ZipOutputStream(fos);
            for (File f : jobFolder.listFiles()) {
                FileInputStream fis = new FileInputStream(f);
                ZipEntry zipEntry = new ZipEntry(f.getName());
                zos.putNextEntry(zipEntry);

                byte[] bytes = new byte[1024];
                int length;
                while ((length = fis.read(bytes)) >= 0) {
                    zos.write(bytes, 0, length);
                }

                zos.closeEntry();
                fis.close();
            }
            zos.close();
            fos.close();
            return zipFile;
        } catch (IOException e) {
            LOGGER.error("Unable to zip the result folder.\n" + e.getMessage());
        }
        return null;
    }

    /**
     * If there is no more running job and no result in the cache list, schedule the session inactivity
     */
    private void scheduleInactivity() {
        if (statusInfoList.isEmpty() && finishedJobMap.isEmpty()) {
            resultExpirationExecutor.schedule(new InactiveSessionTask(this), expirationTimeMillis,
                    TimeUnit.MILLISECONDS);
        }
    }

    /**
     * Shutdown the session of free resources.
     */
    public void shutdown() {
        try {
            ds.getConnection().close();
        } catch (SQLException ignored) {
        }
        executorService.shutdownNow();
        for (Service service : serviceList) {
            service.shutdown();
        }
        resultExpirationExecutor.purge();
        isActive = false;
    }

    /**
     * Shutdown the service with the given class.
     * @param serviceClass The class of the service to shutdown.
     */
    public void shutdownService(Class serviceClass) {
        Service toRemove = null;
        for (Service service : serviceList) {
            if (serviceClass.isInstance(service)) {
                service.shutdown();
                toRemove = service;
            }
        }
        serviceList.remove(toRemove);
    }

    /**
     * TimerTask implementation which is called once a result expiration date is reached.
     */
    private class ResultExpirationTask implements Runnable {

        /** Unique id of the job which results are tracked; */
        private String jobId;
        /** Session which run the job. */
        private Session session;

        public ResultExpirationTask(String jobId, Session session) {
            this.jobId = jobId;
            this.session = session;
        }

        @Override
        public void run() {
            //Removes the job from the finished map
            finishedJobMap.remove(jobId);
            session.scheduleInactivity();
        }
    }

    /**
     * Task making the session inactive.
     */
    private class InactiveSessionTask implements Runnable {

        private Session session;

        public InactiveSessionTask(Session session) {
            this.session = session;
        }

        @Override
        public void run() {
            session.setActive(false);
            baseServerImpl.inactiveSession(session);
        }
    }
}