Java tutorial
/* * 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)); } } }