Java tutorial
/*! * Copyright 2002 - 2015 Webdetails, a Pentaho company. All rights reserved. * * This software was developed by Webdetails and is provided under the terms * of the Mozilla Public License, Version 2.0, or any later version. You may not use * this file except in compliance with the license. If you need a copy of the license, * please go to http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails. * * Software distributed under the Mozilla Public License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to * the license for the specific language governing your rights and limitations. */ package pt.webdetails.cfr; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLDecoder; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.TreeSet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import pt.webdetails.cfr.auth.FilePermissionEnum; import pt.webdetails.cfr.auth.FilePermissionMetadata; import pt.webdetails.cfr.file.CfrFile; import pt.webdetails.cfr.file.FileStorer; import pt.webdetails.cfr.file.IFile; import pt.webdetails.cfr.file.MetadataReader; import pt.webdetails.cfr.repository.IFileRepository; import pt.webdetails.cpf.persistence.PersistenceEngine; import pt.webdetails.cpf.utils.CharsetHelper; import pt.webdetails.cpf.VersionChecker; import pt.webdetails.cpf.utils.MimeTypes; import com.sun.jersey.multipart.FormDataParam; import com.sun.jersey.core.header.FormDataContentDisposition; @Path("cfr/api") public class CfrApi { private static final Log logger = LogFactory.getLog(CfrApi.class); private CfrService service = getCfrService(); protected MetadataReader mr = new MetadataReader(service); private static final String UI_PATH = "cfr/presentation/"; static String checkRelativePathSanity(String path) { String result = path; if (path != null) { if (result.startsWith("./")) { result = result.replaceFirst("./", ""); } if (result.startsWith(".")) { result = result.replaceFirst(".", ""); } if (result.startsWith("/")) { result = result.replaceFirst("/", ""); } if (result.endsWith("/")) { result = result.substring(0, result.length() - 1); } } return result; } static String relativeFilePath(String baseDir, String file) { String _baseDir = checkRelativePathSanity(baseDir); String _file = checkRelativePathSanity(file); String result = null; if (_baseDir == null || _baseDir.length() == 0) { return _file; } else { if (_baseDir.endsWith("/")) { result = new StringBuilder(_baseDir).append(_file).toString(); } else { result = new StringBuilder(_baseDir).append('/').append(_file).toString(); } } return result; } @GET @Path("/home") public void home(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception { Map<String, Object> params = new HashMap<String, Object>(); params.put("solution", "system"); params.put("path", "cfr/presentation/"); params.put("file", "cfr.wcdf"); params.put("absolute", "false"); params.put("inferScheme", "false"); if (Boolean.parseBoolean(request.getParameter("debug"))) { params.put("debug", "true"); } renderInCde(response, params); } @GET @Path("/createFolder") @Produces(MimeTypes.JSON) public String createFolder(@QueryParam(MethodParams.PATH) String path) throws Exception { path = checkRelativePathSanity(path); if (path == null || StringUtils.isBlank(path)) { throw new Exception("path is null or empty"); } boolean createResult = getRepository().createFolder(path); return new JSONObject().put("result", createResult).toString(); } @POST @Path("/remove") @Produces(MimeTypes.JSON) public String remove(@QueryParam(MethodParams.FILENAME) String filename) throws Exception { String fullFileName = checkRelativePathSanity(filename); if (fullFileName == null || StringUtils.isBlank(fullFileName)) { throw new Exception("fileName is null or empty"); } boolean removeResult = getRepository().deleteFile(fullFileName); boolean result = false; if (removeResult) { deletePermissions(fullFileName, null); result = FileStorer.removeFile(fullFileName, null); } return new JSONObject().put("result", result).toString(); } @POST @Path("/store") @Consumes("multipart/form-data") @Produces(MimeTypes.JSON) public String store(@FormDataParam("file") InputStream uploadedInputStream, @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("path") String path) throws IOException, JSONException, Exception { String fileName = fileDetail.getFileName(), savePath = path; ByteArrayOutputStream oStream = new ByteArrayOutputStream(); IOUtils.copy(uploadedInputStream, oStream); oStream.flush(); byte[] contents = oStream.toByteArray(); oStream.close(); if (fileName == null) { logger.error("parameter fileName must not be null"); throw new Exception("parameter fileName must not be null"); } if (savePath == null) { logger.error("parameter path must not be null"); throw new Exception("parameter path must not be null"); } if (contents == null) { logger.error("File content must not be null"); throw new Exception("File content must not be null"); } FileStorer fileStorer = new FileStorer(getRepository()); boolean stored = fileStorer.storeFile(checkRelativePathSanity(fileName), checkRelativePathSanity(savePath), contents, service.getCurrentUserName()); JSONObject result = new JSONObject().put("result", stored); return result.toString(); } @POST @Path("/listFiles") @Produces(MimeTypes.HTML) public String listFiles(@QueryParam(MethodParams.FILEEXTENSIONS) @DefaultValue("") String extensions, @FormParam("dir") @DefaultValue("") String baseDir) throws IOException { baseDir = URLDecoder.decode(baseDir, CharsetHelper.getEncoding()); IFile[] files = getRepository().listFiles(baseDir); List<IFile> allowedFiles = new ArrayList<IFile>(files.length); // checks permissions /* * remarks: ideally the repository must list only the files that the current user is allowed to access? */ for (IFile file : files) { String relativePath = relativeFilePath(baseDir, file.getName()); if (mr.isCurrentUserAllowed(FilePermissionEnum.READ, relativePath)) { allowedFiles.add(file); } } String[] exts = null; if (!StringUtils.isBlank(extensions)) { exts = extensions.split(" "); } IFile[] allowedFilesArray = new IFile[allowedFiles.size()]; return toJQueryFileTree(baseDir, allowedFiles.toArray(allowedFilesArray), exts); } @GET @Path("/getFile") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getFile(@QueryParam(MethodParams.FILENAME) String fileName) throws Exception { String fullFileName = checkRelativePathSanity(fileName); if (fullFileName == null) { logger.error("request query parameter fileName must not be null"); throw new Exception("request query parameter fileName must not be null"); } if (mr.isCurrentUserAllowed(FilePermissionEnum.READ, fullFileName)) { CfrFile file = getRepository().getFile(fullFileName); ResponseBuilder rsp = Response.ok(file.getContent()); rsp.type(getMimeType(file.getFileName())); return buildResponseOk(file, -1, true); } else { return buildResponseError(Status.UNAUTHORIZED, "User \"" + getCfrService().getCurrentUserName() + "\" doesn't have permissions to access the file \"" + fileName + "\""); } } @GET @Path("/viewFile") @Produces("*/*") public Response viewFile(@QueryParam(MethodParams.FILENAME) String fileName) throws Exception { String fullFileName = checkRelativePathSanity(fileName); if (fullFileName == null) { logger.error("request query parameter fileName must not be null"); throw new Exception("request query parameter fileName must not be null"); } if (mr.isCurrentUserAllowed(FilePermissionEnum.READ, fullFileName)) { CfrFile file = getRepository().getFile(fullFileName); return buildResponseOk(file, -1, false); } else { return buildResponseError(Status.UNAUTHORIZED, "User \"" + getCfrService().getCurrentUserName() + "\" doesn't have permissions to access the file \"" + fileName + "\""); } } @GET @Path("/listFilesJson") @Produces(MimeTypes.JSON) public String listFilesJson(@QueryParam(MethodParams.DIR) @DefaultValue("") String dir) throws JSONException { String baseDir = checkRelativePathSanity(dir); JSONArray array = getFileListJson(baseDir); return array.toString(2); } @GET @Path("/listFilesJSON") @Produces(MimeTypes.JSON) public String listFilesJSON(@QueryParam(MethodParams.DIR) @DefaultValue("") String dir) throws JSONException { return listFilesJson(dir); } @GET @Path("/listUploads") @Produces(MimeTypes.JSON) public String listUploads(@QueryParam(MethodParams.FILENAME) @DefaultValue("") String filename, @QueryParam(MethodParams.USER) @DefaultValue("") String user, @QueryParam(MethodParams.STARTDATE) @DefaultValue("") String startDate, @QueryParam(MethodParams.ENDDATE) @DefaultValue("") String endDate) throws JSONException { String path = checkRelativePathSanity(filename); return mr.listFiles(path, user, startDate, endDate).toString(); } @GET @Path("/listUploadsFlat") @Produces(MimeTypes.JSON) public String listUploadsFlat(@QueryParam(MethodParams.FILENAME) @DefaultValue("") String filename, @QueryParam(MethodParams.USER) @DefaultValue("") String user, @QueryParam(MethodParams.STARTDATE) @DefaultValue("") String startDate, @QueryParam(MethodParams.ENDDATE) @DefaultValue("") String endDate) throws JSONException { String path = checkRelativePathSanity(filename); return mr.listFilesFlat(path, user, startDate, endDate).toString(); } private static String toJQueryFileTree(String baseDir, IFile[] files, String[] extensions) { StringBuilder out = new StringBuilder(); out.append("<ul class=\"jqueryFileTree\" style=\"display: none;\">"); for (IFile file : files) { if (file.isDirectory()) { out.append("<li class=\"directory collapsed\"><a href=\"#\" rel=\"" + baseDir + file.getName() + "/\">" + file.getName() + "</a></li>"); } } for (IFile file : files) { if (!file.isDirectory()) { int dotIndex = file.getName().lastIndexOf('.'); String ext = dotIndex > 0 ? file.getName().substring(dotIndex + 1) : ""; boolean accepted = ext.equals(""); if (!ext.equals("")) { if (extensions == null || extensions.length == 0) { accepted = true; } else { for (String acceptedExtension : extensions) { if (ext.equals(acceptedExtension)) { accepted = true; break; } } } } if (accepted) { out.append("<li class=\"file ext_" + ext + "\"><a href=\"#\" rel=\"" + baseDir + file.getName() + "\">" + file.getName() + "</a></li>"); } } } out.append("</ul>"); return out.toString(); } @GET @Path("/setPermissions") @Produces(MimeTypes.JSON) public String setPermissions(@QueryParam(MethodParams.PATH) String path, @QueryParam(MethodParams.ID) @DefaultValue("") List<String> ids, @QueryParam(MethodParams.PERMISSION) @DefaultValue("") List<String> permissions, @QueryParam(MethodParams.RECURSIVE) @DefaultValue("false") Boolean recursive) throws JSONException { path = checkRelativePathSanity(path); String[] userOrGroupId = ids.toArray(new String[ids.size()]); String[] _permissions = permissions.toArray(new String[permissions.size()]); boolean isDir; boolean admin = isUserAdmin(); boolean errorSetting = false; JSONObject result = new JSONObject(); if (path != null && userOrGroupId.length > 0 && _permissions.length > 0) { List<String> files = new ArrayList<String>(); if (recursive) { files = getFileNameTree(path); } else { files.add(path); } isDir = getRepository().getFile(path).isDirectory(); // build valid permissions set Set<FilePermissionEnum> validPermissions = new TreeSet<FilePermissionEnum>(); for (String permission : _permissions) { FilePermissionEnum perm = FilePermissionEnum.resolve(permission); if (perm != null) { validPermissions.add(perm); } } JSONArray permissionAddResultArray = new JSONArray(); for (String file : files) { CfrFile f = getRepository().getFile(file); if (isDir && f.isFile()) { continue; } for (String id : userOrGroupId) { boolean storeResult = storeFile(file, id, validPermissions); if (storeResult) { permissionAddResultArray.put(new JSONObject().put("status", String.format("Added permission for path %s and user/role %s", file, id))); } else { if (admin) { permissionAddResultArray.put(new JSONObject().put("status", String .format("Failed to add permission for path %s and user/role %s", file, id))); } else { errorSetting = true; } } } } result.put("status", "Operation finished. Check statusArray for details."); if (errorSetting) { permissionAddResultArray.put(new JSONObject().put("status", "Some permissions could not be set")); } result.put("statusArray", permissionAddResultArray); } else { result.put("status", "Path or user group parameters not found"); } return result.toString(2); } @GET @Path("/deletePermissions") @Produces(MimeTypes.JSON) public String deletePermissions(@QueryParam(MethodParams.PATH) String path, @QueryParam(MethodParams.ID) @DefaultValue("") List<String> ids, @QueryParam(MethodParams.RECURSIVE) @DefaultValue("false") Boolean recursive) throws JSONException, IOException { path = checkRelativePathSanity(path); String[] userOrGroupId = ids.toArray(new String[ids.size()]); JSONObject result = new JSONObject(); boolean admin = isUserAdmin(); boolean errorDeleting = false; if (path != null || (userOrGroupId != null && userOrGroupId.length > 0)) { List<String> files = new ArrayList<String>(); if (recursive) { files = getFileNameTree(path); } else { files.add(path); } JSONArray permissionDeleteResultArray = new JSONArray(); if (userOrGroupId == null || userOrGroupId.length == 0) { for (String f : files) { if (deletePermissions(f, null)) { permissionDeleteResultArray .put(new JSONObject().put("status", "Permissions for " + f + " deleted")); } else { if (admin) { permissionDeleteResultArray .put(new JSONObject().put("status", "Error deleting permissions for " + f)); } else { errorDeleting = true; } } } result.put("status", "Multiple permission deletion. Check Status array"); if (errorDeleting) { permissionDeleteResultArray .put(new JSONObject().put("status", "Some permissions could not be removed")); } result.put("statusArray", permissionDeleteResultArray); } else { for (String id : userOrGroupId) { for (String f : files) { JSONObject individualResult = new JSONObject(); boolean deleteResult = deletePermissions(f, id); if (deleteResult) { individualResult.put("status", String.format("Permission for %s and path %s deleted.", id, f)); } else { individualResult.put("status", String.format("Failed to delete permission for %s and path %s.", id, f)); } permissionDeleteResultArray.put(individualResult); } } result.put("status", "Multiple permission deletion. Check Status array"); result.put("statusArray", permissionDeleteResultArray); } } else { result.put("status", "Required arguments user/role and path not found"); } return result.toString(2); } @GET @Path("/getPermissions") @Produces(MimeTypes.JSON) public String getPermissions(@QueryParam(MethodParams.PATH) String path, @QueryParam(MethodParams.ID) String id) throws JSONException { path = checkRelativePathSanity(path); if (path != null || id != null) { JSONArray permissions = mr.getPermissions(path, id, FilePermissionMetadata.DEFAULT_PERMISSIONS); return permissions.toString(0); } return "{\n \"status\": \"error\",\n \"result\": \"false\",\n \"message\": \"Must supply a path and/or an " + "id\"\n}"; } @GET @Path("/resetRepository") public String resetRepository() { if (!isUserAdmin()) { logger.warn("Reset repository called by a non admin user. Aborting"); return "User has no access to this endpoint"; } PersistenceEngine.getInstance().dropClass(FileStorer.FILE_METADATA_STORE_CLASS); PersistenceEngine.getInstance().initializeClass(FileStorer.FILE_METADATA_STORE_CLASS); PersistenceEngine.getInstance().dropClass(FileStorer.FILE_PERMISSIONS_METADATA_STORE_CLASS); PersistenceEngine.getInstance().initializeClass(FileStorer.FILE_PERMISSIONS_METADATA_STORE_CLASS); IFileRepository repo = new CfrService().getRepository(); for (IFile file : repo.listFiles("")) { repo.deleteFile(file.getFullPath()); } return "Repository Reset complete"; } @GET @Path("/checkVersion") @Produces(MimeTypes.JSON) public String checkVersion() throws JSONException { return getVersionChecker().checkVersion().toJSON().toString(); } @GET @Path("/getVersion") public void getVersion(@Context HttpServletResponse response) throws IOException, JSONException { writeMessage(getVersionChecker().getVersion(), response.getOutputStream()); } @GET @Path("/about") public void about(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception { renderInCde(response, getRenderRequestParameters("cfrAbout.wcdf", request)); } @GET @Path("/browser") public void browser(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception { renderInCde(response, getRenderRequestParameters("cfrBrowser.wcdf", request)); } public VersionChecker getVersionChecker() { return new VersionChecker(new CfrPluginSettings()) { @Override protected String getVersionCheckUrl(VersionChecker.Branch branch) { switch (branch) { case TRUNK: return "http://ci.pentaho.com/job/pentaho-cfr/lastSuccessfulBuild/artifact/cfr-pentaho5/dist/marketplace" + ".xml"; // case STABLE: // return "http://ci.analytical-labs.com/job/Webdetails-CFR-Release/" // + "lastSuccessfulBuild/artifact/dist/marketplace.xml"; default: return null; } } }; } private void renderInCde(HttpServletResponse response, Map<String, Object> params) throws Exception { response.setContentType(MimeTypes.HTML); InterPluginBroker.run(params, response.getOutputStream()); } private Map<String, Object> getRenderRequestParameters(String dashboardName, HttpServletRequest request) { Map<String, Object> params = new HashMap<String, Object>(); params.put("solution", "system"); params.put("path", UI_PATH); params.put("file", dashboardName); params.put("bypassCache", "true"); params.put("absolute", "false"); params.put("inferScheme", "false"); // add request parameters Enumeration<String> originalParams = request.getParameterNames(); // Iterate and put the values there while (originalParams.hasMoreElements()) { String originalParam = originalParams.nextElement(); params.put(originalParam, request.getParameter(originalParam)); } return params; } private Response buildResponseOk(CfrFile file, int cacheDuration, boolean download) { ResponseBuilder responseBuilder = Response.ok(file.getContent()); responseBuilder.header("Content-Type", getMimeType(file.getName())); if (download) { responseBuilder.header("Content-Disposition", "attachment; filename=" + file.getName()); } // Cache? if (cacheDuration > 0) { responseBuilder.header("Cache-Control", "max-age=" + cacheDuration); } else { responseBuilder.header("Cache-Control", "max-age=0, no-store"); } return responseBuilder.build(); } private Response buildResponseError(Status status, String message) { ResponseBuilder responseBuilder = Response.status(status); responseBuilder.entity(message); responseBuilder.type("text/plain"); return responseBuilder.build(); } private String getMimeType(String fileName) { return MimeTypes.getMimeType(fileName); } private void writeMessage(String message, OutputStream out) throws IOException { IOUtils.write(message, out); out.flush(); } protected List<String> getFileNameTree(String path) { List<String> files = new ArrayList<String>(); if (!StringUtils.isEmpty(path)) { files.add(path); } files.addAll(buildFileNameTree(path, getFileNames(getRepository().listFiles(path)))); List<String> treatedFileNames = new ArrayList<String>(); for (String file : files) { if (file.startsWith("/")) { treatedFileNames.add(file.replaceFirst("/", "")); } else { treatedFileNames.add(file); } } return treatedFileNames; } private List<String> buildFileNameTree(String basePath, List<String> children) { List<String> result = new ArrayList<String>(); for (String child : children) { String newEntry = basePath + "/" + child; result.add(newEntry); result.addAll(buildFileNameTree(newEntry, getFileNames(getRepository().listFiles(newEntry)))); } return result; } private List<String> getFileNames(IFile[] files) { List<String> names = new ArrayList<String>(); for (IFile file : files) { names.add(file.getName()); } return names; } private JSONArray getFileListJson(String baseDir) throws JSONException { IFile[] files = getRepository().listFiles(baseDir); JSONArray arr = new JSONArray(); if (files != null) { for (IFile file : files) { if (mr.isCurrentUserAllowed(FilePermissionEnum.READ, relativeFilePath(baseDir, file.getName()))) { JSONObject obj = new JSONObject(); obj.put("fileName", file.getName()); obj.put("isDirectory", file.isDirectory()); obj.put("path", baseDir); arr.put(obj); } } } return arr; } protected CfrService getCfrService() { return new CfrService(); } protected boolean storeFile(String file, String id, Set<FilePermissionEnum> validPermissions) { return FileStorer.storeFilePermissions(new FilePermissionMetadata(file, id, validPermissions)); } protected IFileRepository getRepository() { return service.getRepository(); } private boolean deletePermissions(String path, String id) { return FileStorer.deletePermissions(path, id); } protected boolean isUserAdmin() { return this.service.isCurrentUserAdmin(); } private class MethodParams { public static final String PATH = "path"; public static final String FILENAME = "fileName"; public static final String FILEEXTENSIONS = "fileExtensions"; public static final String DIR = "dir"; public static final String USER = "user"; public static final String STARTDATE = "startDate"; public static final String ENDDATE = "endDate"; public static final String ID = "id"; public static final String PERMISSION = "permission"; public static final String RECURSIVE = "recursive"; } }