ControlService.java [src/csip] Revision: d5970d7c0e30454d24a80dc6a55dd59e3bcbc398  Date: Sat Apr 15 07:53:13 MDT 2017
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2017, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip;

import csip.Config.SessionStore;
import csip.utils.*;
import java.io.*;
import java.text.DateFormat;
import java.util.*;
import java.util.logging.*;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;
import org.apache.commons.io.*;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 * ControlService. Use services in this class to control the backend.
 *
 * @author od
 */
@Path("c")
public class ControlService {

    static final Logger LOG = Logger.getLogger(ControlService.class.getName());


    /**
     * Clean up the whole session store.
     *
     * @param uriInfo
     * @param req
     * @return
     * @throws JSONException
     * @throws Exception
     */
    @GET
    @Path("clean")
    @Produces(MediaType.APPLICATION_JSON)
    public String getClean(@Context UriInfo uriInfo, @Context HttpServletRequest req) throws JSONException, Exception {
        Config.checkRemoteAccessACL(req);

        SessionStore s = Config.getSessionStore();
        Set<String> keys = s.keys(0, Integer.MAX_VALUE, null, true);
        int isnull = 0;
        int removed = 0;
        int skipped = 0;
        for (String id : keys) {
            String result = remove(uriInfo, req, id);
            JSONObject r = new JSONObject(result);
            if (r.has("isnull")) {
                isnull++;
            } else if (r.has("removed")) {
                removed++;
            } else if (r.has("skipped")) {
                skipped++;
            }
        }
        JSONObject o = new JSONObject();
        o.put("sessions.total", keys.size());
        o.put("sessions.removed", removed);
        o.put("sessions.isnull", isnull);
        o.put("sessions.skipped", skipped);
        return o.toString(4);
    }


    /**
     * Remove the session if expired. cleanup also the workspace and results.
     *
     * @param uriInfo
     * @param req
     * @param suid
     * @return
     * @throws Exception
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("clean/{suid}")
    public String remove(@Context UriInfo uriInfo, @Context HttpServletRequest req, @PathParam("suid") String suid) throws Exception {
        Config.checkRemoteAccessACL(req);

        ModelSession session = Config.getSessionStore().getSession(suid);
        JSONObject o = new JSONObject();
        if (session == null) {
            o.put("isnull", 1);
            return o.toString();
        }
        if (session.getNodeIP().equals(Services.LOCAL_IP_ADDR)) {
            DateFormat df = Dates.newISOFormat();
            String now = df.format(new Date());
            String exp = session.getExpDate();
            if (exp.compareTo(now) < 0) {  // check is zombie that is passt expiration
                File res = Services.getResultsDir(suid);
                if (res.exists()) {
                    FileUtils.deleteQuietly(res);
                }
                File work = Services.getWorkDir(suid);
                if (work.exists()) {
                    FileUtils.deleteQuietly(work);
                }
                Config.getSessionStore().removeSession(suid);
                o.put("removed", 1);
                return o.toString();
            }
            o.put("skipped", 1);
            return o.toString();
        } else {
            String redirect = Services.replaceHostinURI(uriInfo.getBaseUri(), session.getNodeIP());
            LOG.info("Redirect query to: " + redirect + "c/clean/" + suid);
            javax.ws.rs.client.Client client = ClientBuilder.newClient();
            WebTarget service = client.target(redirect + "c/clean/" + suid);
            Response response = service.request(MediaType.APPLICATION_JSON).get();
            return response.readEntity(String.class);
        }
    }


    /**
     * Get the Configuration as JSON.
     *
     * @return
     * @throws JSONException
     */
    @GET
    @Path("conf")
    @Produces(MediaType.APPLICATION_JSON)
    public String getJSON(@Context UriInfo uriInfo, @Context HttpServletRequest req) throws JSONException {
        Config.checkRemoteAccessACL(req);

        Properties p = Config.getMergedProperties();
        Map<Object, Object> m = new TreeMap<>();
        for (Object key : p.keySet()) {
            String key_ = key.toString();
            String val = p.get(key).toString();
            val = val.replaceAll("password=.+[;]?", "password=****;");
            if (key_.toLowerCase().contains("password")) {
                val = "****";
            }
            m.put(key_, val);
        }

        JSONObject theconfig = new JSONObject(m);
        Collection<Config.PostgresChunk> pcs = Config.getPostgresChunks();
        // Add PGChunks to output if they exist...
        if ((pcs != null) && (pcs.size() > 0)) {
            int i = 1;
            for (Config.PostgresChunk ch : pcs) {
                theconfig.put("pgdb" + i, ch.getJSON());
                i++;
            }
        }
        return theconfig.toString(2);
    }


    // update from properties, this is for silent property update.
    static void updateConfig(Properties p1) {
        if (p1.isEmpty()) {
            return;
        }
        Properties p = Config.getProperties();
        synchronized (p) {
            for (String key : p1.stringPropertyNames()) {
                String val = p1.getProperty(key);
                LOG.info("Set Config: " + key + " = " + val);
                p.setProperty(key, val);
            }
            Config.update();
        }
    }


    // update from YAML, this is for silent property update.
    static void updateConfig(Map<String, String> p1) {
        if (p1.isEmpty()) {
            return;
        }
        Properties p = Config.getProperties();
        synchronized (p) {
            for (String key : p1.keySet()) {
                String val = p1.get(key);
                if (val != null) {
                    LOG.info("Config => " + key + " = " + val);
                    p.setProperty(key, val);
                }
            }
            Config.update();
        }
    }


    static String updateConfig(String inputObj) throws JSONException {
        Properties p = Config.getProperties();
        Collection<Config.PostgresChunk> pcs = Config.getPostgresChunks();
        synchronized (p) {
            try {
                JSONObject o = new JSONObject(inputObj);
                Iterator i = o.keys();
                while (i.hasNext()) {
                    String key = i.next().toString();
                    if (key.trim().equals("#") || key.trim().equals("//") || key.toLowerCase().startsWith("note")) {
                        continue;
                    }
                    LOG.info("  Set Config: " + key + " = " + o.getString(key));

                    // pg dbs go into the list
                    if ((key.length() > 4) && (key.startsWith("pgdb")) && (Integer.parseInt(key.substring(4))) > 0) {
                        JSONObject pgsvr = o.getJSONObject(key);
                        String name = pgsvr.getString("name");
                        double minLong = pgsvr.getDouble("minLong");
                        double maxLong = pgsvr.getDouble("maxLong");
                        Config.PostgresChunk newchunk = new Config.PostgresChunk(name, minLong, maxLong);
                        if (!pcs.contains(newchunk)) {
                            pcs.add(newchunk);
                        } else {
                            pcs.remove(newchunk);
                            pcs.add(newchunk);

                        }
                        LOG.info("adding " + name + " for " + minLong + " to " + maxLong);
                    } else {
                        // all other items go into properties collection
                        p.setProperty(key, o.getString(key));
                    }
                }
            } catch (JSONException | NumberFormatException ex) {
                LOG.log(Level.WARNING, null, ex);
                return "Error: " + ex.getMessage();
            }
            Config.update();
        }
        LOG.log(Level.INFO, "config complete");
        JSONObject theconfig = new JSONObject(new TreeMap<>(p));
        // Add PGChunks to output if they exist...
        if ((pcs != null) && (pcs.size() > 0)) {
            int i = 1;
            for (Config.PostgresChunk ch : pcs) {
                theconfig.put("pgdb" + i, ch.getJSON());
                i++;
            }
        }
        return theconfig.toString(2);
    }


    /**
     * Change the configuration.
     *
     * @param inputObj
     * @return
     * @throws JSONException
     */
    @POST
    @Path("conf")
    @Consumes(MediaType.APPLICATION_JSON)
    public String putJSON(@Context UriInfo uriInfo, @Context HttpServletRequest req, String inputObj) throws JSONException {
        LOG.log(Level.INFO, "HTTP/POST {0}", uriInfo.getRequestUri().toString() + " " + inputObj);
        Config.checkRemoteAccessACL(req);
        return updateConfig(inputObj);
    }


    /**
     * Cancel the request.
     *
     * @param uriInfo
     * @param suid
     * @return
     * @throws JSONException
     */
    @GET
    @Path("cancel/{suid}")
    @Produces(MediaType.APPLICATION_JSON)
    public String cancel(@Context UriInfo uriInfo, @PathParam("suid") String suid) throws Exception {
        LOG.log(Level.INFO, "HTTP/GET {0}", uriInfo.getRequestUri().toString());
        ModelSession session = Config.getSessionStore().getSession(suid);
        if (session == null) {
            return JSONUtils.error("suid unknown: " + suid).toString();
        }

        if (session.getNodeIP().equals(Services.LOCAL_IP_ADDR)) {
            // check the local box first.
            for (ModelDataService.Task modelTask : Config.getModelTasks()) {
                if (modelTask.getService().getSUID().equals(suid)) {
                    modelTask.cancel();
                    LOG.log(Level.INFO, "canceled " + suid);
                    return JSONUtils.ok(suid + " canceled").toString();
                }
            }
            LOG.log(Level.WARNING, " not found to cancel: " + suid);
            return JSONUtils.error("suid unknown: " + suid).toString();
        } else {
            // redrection here
            // where is that session?
            String redirect = Services.replaceHostinURI(uriInfo.getBaseUri(), session.getNodeIP());
            javax.ws.rs.client.Client client = ClientBuilder.newClient();
            WebTarget service = client.target(UriBuilder.fromUri(redirect + "c/cancel/" + suid).build());
            return service.request().get().readEntity(String.class);
        }
    }


    @GET
    @Path("cleanresults")
    @Produces(MediaType.APPLICATION_JSON)
    public String dropResults(@Context UriInfo uriInfo) {
        LOG.log(Level.INFO, "HTTP/GET {0}", uriInfo.getRequestUri().toString());
        Config.getResultStore().purge();
        return JSONUtils.ok("purged").toString();
    }

}