com.baasbox.controllers.Root.java Source code

Java tutorial

Introduction

Here is the source code for com.baasbox.controllers.Root.java

Source

/*
 * Copyright (c) 2014.
 *
 * BaasBox - info-at-baasbox.com
 *
 * Licensed 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 com.baasbox.controllers;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;

import com.baasbox.service.logging.BaasBoxLogger;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Http.MultipartFormData;
import play.mvc.Http.MultipartFormData.FilePart;
import play.mvc.Result;
import play.mvc.With;

import com.baasbox.BBConfiguration;
import com.baasbox.controllers.actions.filters.ConnectToDBFilter;
import com.baasbox.controllers.actions.filters.RootCredentialWrapFilter;
import com.baasbox.dao.exception.FileNotFoundException;
import com.baasbox.dao.exception.SqlInjectionException;
import com.baasbox.exception.OpenTransactionException;
import com.baasbox.exception.UserNotFoundException;
import com.baasbox.metrics.BaasBoxMetric;
import com.baasbox.service.dbmanager.DbManagerService;
import com.baasbox.service.user.UserService;
import com.codahale.metrics.json.MetricsModule;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Root extends Controller {

    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result resetAdminPassword() {
        Http.RequestBody body = request().body();

        JsonNode bodyJson = body.asJson();
        if (BaasBoxLogger.isDebugEnabled())
            BaasBoxLogger.debug("resetAdminPassword bodyJson: " + bodyJson);
        //check and validate input
        if (bodyJson == null)
            return badRequest("The body payload cannot be empty.");
        if (!bodyJson.has("password"))
            return badRequest("The 'password' field is missing into the body");
        JsonNode passwordNode = bodyJson.findValue("password");

        if (passwordNode == null)
            return badRequest("The body payload doesn't contain password field");
        String password = passwordNode.asText();
        try {
            UserService.changePassword("admin", password);
        } catch (SqlInjectionException e) {
            return badRequest("The password is not valid");
        } catch (UserNotFoundException e) {
            BaasBoxLogger.error("User 'admin' not found!");
            return internalServerError("User 'admin' not found!");
        } catch (OpenTransactionException e) {
            BaasBoxLogger.error(ExceptionUtils.getFullStackTrace(e));
            throw new RuntimeException(e);
        }
        return ok("Admin password reset");
    }

    @With(RootCredentialWrapFilter.class)
    public static Result timers() throws JsonProcessingException {
        if (!BaasBoxMetric.isActivate())
            return status(SERVICE_UNAVAILABLE, "The metrics service are disabled");
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, false));
        return ok(mapper.writeValueAsString(BaasBoxMetric.registry.getTimers()));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result counters() throws JsonProcessingException {
        if (!BaasBoxMetric.isActivate())
            return status(SERVICE_UNAVAILABLE, "The metrics service are disabled");
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
        return ok(mapper.writeValueAsString(BaasBoxMetric.registry.getCounters()));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result meters() throws JsonProcessingException {
        if (!BaasBoxMetric.isActivate())
            return status(SERVICE_UNAVAILABLE, "The metrics service are disabled");
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
        return ok(mapper.writeValueAsString(BaasBoxMetric.registry.getMeters()));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result gauges() throws JsonProcessingException {
        if (!BaasBoxMetric.isActivate())
            return status(SERVICE_UNAVAILABLE, "The metrics service are disabled");
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
        return ok(mapper.writeValueAsString(BaasBoxMetric.registry.getGauges()));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result histograms() throws JsonProcessingException {
        if (!BaasBoxMetric.isActivate())
            return status(SERVICE_UNAVAILABLE, "The metrics service is disabled");
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
        return ok(mapper.writeValueAsString(BaasBoxMetric.registry.getHistograms()));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result uptime() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        HashMap<String, Object> ret = new HashMap<String, Object>();
        ret.put("start_time", BaasBoxMetric.Track.getStartTime());
        ret.put("time_zone", "UTC");
        ret.put("uptime", BaasBoxMetric.Track.getUpTimeinMillis());
        ret.put("time_unit", "ms");
        return ok(mapper.writeValueAsString(ret));
    }

    @With(RootCredentialWrapFilter.class)
    public static Result startMetrics() throws JsonProcessingException {
        BaasBoxMetric.start();
        return ok("Metrics service started");
    }

    @With(RootCredentialWrapFilter.class)
    public static Result stopMetrics() throws JsonProcessingException {
        BaasBoxMetric.stop();
        return ok("Metrics service stopped");
    }

    //backup & restore
    /**
     * /root/db/export (POST)
     * 
     * the method generate a full dump of the db in an asyncronus task.
     * the response returns a 202 code (ACCEPTED) and the filename of the
     * file that will be generated.
     * 
     * The async nature of the method DOES NOT ensure the creation of the file
     * so, querying for the file name with the /admin/db/:filename could return a 404
     * @return a 202 accepted code and a json representation containing the filename of the generated file
     */
    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result exportDb() {
        String appcode = (String) ctx().args.get("appcode");
        String fileName = "";
        try {
            fileName = DbManagerService.exportDb(appcode);
        } catch (FileNotFoundException e) {
            return internalServerError(ExceptionUtils.getMessage(e));
        }
        return status(202, Json.toJson(fileName));
    }

    /**
     * /root/db/export/:filename (GET)
     * 
     * the method returns the stream of the export file named after :filename parameter. 
     * 
     * if the file is not present a 404 code is returned to the client
     * 
     * @return a 200 ok code and the stream of the file
     */
    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result getExport(String filename) {
        java.io.File file = new java.io.File(
                DbManagerService.backupDir + DbManagerService.fileSeparator + filename);
        if (!file.exists()) {
            return notFound();
        } else {
            response().setContentType("application/zip"); //added in Play 2.2.1. it is very strange because the content type should be set by the framework
            return ok(file);
        }

    }

    /**
     * /root/db/export/:filename (DELETE)
     * 
     * Deletes an export file from the db backup folder, if it exists 
     * 
     * 
     * @param fileName the name of the file to be deleted
     * @return a 200 code if the file is deleted correctly or a 404.If the file could not
     * be deleted a 500 error code is returned
     */
    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result deleteExport(String filename) {
        try {
            DbManagerService.deleteExport(filename);
        } catch (FileNotFoundException e1) {
            return notFound();
        } catch (IOException e1) {
            return internalServerError("Unable to delete export.It will be deleted on the next reboot." + filename);
        }
        return ok();
    }

    /**
     * /root/db/export (GET)
     * 
     * the method returns the list as a json array of all the export files
     * stored into the db export folder ({@link BBConfiguration#getDBBackupDir()})
     * 
     * @return a 200 ok code and a json representation containing the list of files stored in the db backup folder
     */
    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result getExports() {
        List<String> fileNames = DbManagerService.getExports();
        return ok(Json.toJson(fileNames));
    }

    /**
     * /admin/db/import (POST)
     * 
     * the method allows to upload a json export file and apply it to the db.
     * WARNING: all data on the db will be wiped out before importing
     * 
     * @return a 200 Status code when the import is successfull,a 500 status code otherwise
     */
    @With({ RootCredentialWrapFilter.class, ConnectToDBFilter.class })
    public static Result importDb() {
        String appcode = (String) ctx().args.get("appcode");
        MultipartFormData body = request().body().asMultipartFormData();
        if (body == null)
            return badRequest("missing data: is the body multipart/form-data?");
        FilePart fp = body.getFile("file");

        if (fp != null) {
            ZipInputStream zis = null;
            try {
                java.io.File multipartFile = fp.getFile();
                java.util.UUID uuid = java.util.UUID.randomUUID();
                File zipFile = File.createTempFile(uuid.toString(), ".zip");
                FileUtils.copyFile(multipartFile, zipFile);
                zis = new ZipInputStream(new FileInputStream(zipFile));
                DbManagerService.importDb(appcode, zis);
                zipFile.delete();
                return ok();
            } catch (Exception e) {
                BaasBoxLogger.error(ExceptionUtils.getStackTrace(e));
                return internalServerError(ExceptionUtils.getStackTrace(e));
            } finally {
                try {
                    if (zis != null) {
                        zis.close();
                    }
                } catch (IOException e) {
                    // Nothing to do here
                }
            }
        } else {
            return badRequest("The form was submitted without a multipart file field.");
        }
    }

    //DB size and alert thresholds
    /**
     * /root/configuration (POST)
     * 
     * this method allows to set (or override) just two configuration parameter (at the moment)
     * the db size Threshold in bytes:
     *       baasbox.db.size 
     * A percentage needed by the console to show alerts on dashboard when DB size is near the defined Threshold
     *       baasbox.db.alert
     * 
     * @return a 200 OK with the new values
     */
    @With({ RootCredentialWrapFilter.class })
    public static Result overrideConfiguration() {
        Http.RequestBody body = request().body();
        JsonNode bodyJson = body.asJson();
        JsonNode newDBAlert = bodyJson.get(BBConfiguration.DB_ALERT_THRESHOLD);
        JsonNode newDBSize = bodyJson.get(BBConfiguration.DB_SIZE_THRESHOLD);
        try {
            if (newDBAlert != null && !newDBAlert.isInt() && newDBAlert.asInt() < 1)
                throw new IllegalArgumentException(
                        BBConfiguration.DB_ALERT_THRESHOLD + " must be a positive integer value");
            if (newDBSize != null && !newDBSize.isLong() && newDBSize.asInt() < 0)
                throw new IllegalArgumentException(BBConfiguration.DB_SIZE_THRESHOLD
                        + " must be a positive integer value, or 0 to disable it");
        } catch (Throwable e) {
            return badRequest(ExceptionUtils.getMessage(e));
        }
        if (newDBAlert != null)
            BBConfiguration.setDBAlertThreshold(newDBAlert.asInt());
        if (newDBSize != null)
            BBConfiguration.setDBSizeThreshold(BigInteger.valueOf(newDBSize.asLong()));
        HashMap returnMap = new HashMap();
        returnMap.put(BBConfiguration.DB_ALERT_THRESHOLD, BBConfiguration.getDBAlertThreshold());
        returnMap.put(BBConfiguration.DB_SIZE_THRESHOLD, BBConfiguration.getDBSizeThreshold());
        try {
            return ok(new ObjectMapper().writeValueAsString(returnMap));
        } catch (JsonProcessingException e) {
            return internalServerError(ExceptionUtils.getMessage(e));
        }
    }

    @With({ RootCredentialWrapFilter.class })
    public static Result getOverridableConfiguration() {
        HashMap returnMap = new HashMap();
        returnMap.put(BBConfiguration.DB_ALERT_THRESHOLD, BBConfiguration.getDBAlertThreshold());
        returnMap.put(BBConfiguration.DB_SIZE_THRESHOLD, BBConfiguration.getDBSizeThreshold());
        try {
            return ok(new ObjectMapper().writeValueAsString(returnMap));
        } catch (JsonProcessingException e) {
            return internalServerError(ExceptionUtils.getMessage(e));
        }
    }

}