Java tutorial
/* * The contents of this file are subject to the Mozilla Public * License Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Web Questionnaires 2 * * The Initial Owner of the Original Code is European Environment * Agency. Portions created by TripleDev are Copyright * (C) European Environment Agency. All Rights Reserved. * * Contributor(s): * Anton Dmitrijev */ package eionet.webq.web.controller; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableSet; import eionet.webq.converter.JsonXMLBidirectionalConverter; import eionet.webq.dao.MergeModules; import eionet.webq.dao.orm.MergeModule; import eionet.webq.dao.orm.ProjectFile; import eionet.webq.dao.orm.UploadedFile; import eionet.webq.dao.orm.UserFile; import eionet.webq.service.CDREnvelopeService; import eionet.webq.service.ConversionService; import eionet.webq.service.FileNotAvailableException; import eionet.webq.service.ProjectFileService; import eionet.webq.service.ProjectService; import eionet.webq.service.RemoteFileService; import eionet.webq.service.UserFileMergeService; import eionet.webq.service.UserFileService; import eionet.webq.web.controller.util.UserFileHelper; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mail.javamail.ConfigurableMimeFileTypeMap; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Spring controller for WebQ file download. */ @Controller @RequestMapping(value = { "download", "webform" }) public class FileDownloadController { /** * Logger for this class. */ public static final Logger LOGGER = Logger.getLogger(PublicPageController.class); /** * Json to XML converter. */ @Autowired JsonXMLBidirectionalConverter jsonXMLConverter; /** * Service for downloading remote files. */ @Autowired RemoteFileService remoteFileService; /** * Helper web layer service to match request parameters and UserFle object in database. */ @Autowired UserFileHelper userFileHelper; /** * Service for getting user file content from storage. */ @Autowired private UserFileService userFileService; /** * Service for getting user file content from storage. */ @Autowired private ProjectService projectService; /** * Service for getting project file content from storage. */ @Autowired private ProjectFileService projectFileService; /** * File conversion service. */ @Autowired private ConversionService conversionService; /** * Merge modules repository. */ @Autowired private MergeModules mergeModules; /** * User files merge service. */ @Autowired private UserFileMergeService mergeService; /** * Cdr envelope service. */ @Autowired private CDREnvelopeService envelopeService; /** * Convert and download the user XML file in JSON format if request header Accept content type is application/json. * * @param fileId user file id. * @param request http request to write file * @param response http response to write file * @throws FileNotAvailableException if user file is not available for given id */ @RequestMapping(value = { "/converted_user_file", "user_file" }, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @Transactional public void downloadUserFileJson(@RequestParam int fileId, HttpServletRequest request, HttpServletResponse response) throws FileNotAvailableException, URISyntaxException { UserFile file = userFileHelper.downloadUserFile(fileId, request); if (file.isFromCdr() && file.getContent() == null) { file.setContent( envelopeService.fetchFileFromCdr(file, file.getEnvelope() + "/" + file.getName()).getBody()); } byte[] json = jsonXMLConverter.convertXmlToJson(file.getContent()); setContentType(response, MediaType.APPLICATION_JSON); writeToResponse(response, json); } /** * Convert the user XML file into JSON format and then back to XML format to be able to evaluate the conversion. The method is * called when request header Accept content type is text/xml. * * @param fileId user file id. * @param request http request to write file * @param response http response to write file * @throws FileNotAvailableException user file is not available for given id */ @RequestMapping(value = "/converted_user_file", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET) @Transactional public void downloadUserFileJsonToXml(@RequestParam int fileId, HttpServletRequest request, HttpServletResponse response) throws FileNotAvailableException { UserFile file = userFileHelper.downloadUserFile(fileId, request); byte[] xml = jsonXMLConverter.convertJsonToXml(jsonXMLConverter.convertXmlToJson(file.getContent())); writeXmlFileToResponse("json.xml", xml, response); } /** * Download uploaded file action. * * @param fileId requested file id * @param request http request to write file * @param response http response to write file * @throws FileNotAvailableException if user file is not available for given id */ @RequestMapping(value = "/user_file") @Transactional public void downloadUserFile(@RequestParam int fileId, HttpServletRequest request, HttpServletResponse response) throws FileNotAvailableException { UserFile file = userFileHelper.downloadUserFile(fileId, request); writeXmlFileToResponse(file.getName(), file.getContent(), response); } /** * Download uploaded file action. * * @param projectId project id for file download * @param fileName requested file name * @param format if format=json, then convert XML file to json format * @param request http request * @param response http response to write file * @throws FileNotAvailableException if project file is not available for given id */ @RequestMapping(value = "/project/{projectId}/file/{fileName:.*}") @Transactional public void downloadProjectFile(@PathVariable String projectId, @PathVariable String fileName, @RequestParam(required = false) String format, HttpServletRequest request, HttpServletResponse response) throws FileNotAvailableException { ProjectFile projectFile = projectFileService.fileContentBy(fileName, projectService.getByProjectId(projectId)); if (projectFile == null) { throw new FileNotAvailableException("The requested project file is not available with path: /project/" + projectId + "/file/" + fileName); } String disposition = request.getServletPath().contains("/download/") ? "attachment" : "inline"; writeProjectFileToResponse(fileName, projectFile, response, disposition, format); } /** * Allows to download merge module file. * * @param moduleName module name. * @param response http response to write file */ @RequestMapping("/merge/file/{moduleName:.*}") @Transactional public void downloadMergeFile(@PathVariable String moduleName, HttpServletResponse response) { MergeModule module = mergeModules.findByFileName(moduleName); UploadedFile xslFile = module.getXslFile(); writeXmlFileToResponse(xslFile.getName(), xslFile.getContent().getFileContent(), response); } /** * Merge selected user files. * * @param selectedUserFile ids of user files * @param mergeModule module required to merge files. * @param request http request to write file * @param response http response * @throws TransformerException if transformation fails * @throws IOException if content operations fail. * @throws FileNotAvailableException if file is not available for given id */ @RequestMapping("/merge/files") @Transactional public void mergeFiles(@RequestParam(required = false) List<Integer> selectedUserFile, @RequestParam(required = false) Integer mergeModule, HttpServletRequest request, HttpServletResponse response) throws TransformerException, IOException, FileNotAvailableException { if (selectedUserFile == null || selectedUserFile.isEmpty()) { throw new IllegalArgumentException("No files selected"); } if (selectedUserFile.size() == 1) { downloadUserFile(selectedUserFile.get(0), request, response); return; } Collection<UserFile> userFiles = Collections2.transform(selectedUserFile, new Function<Integer, UserFile>() { @Override public UserFile apply(Integer id) { return userFileService.getById(id); } }); if (mergeModule != null) { MergeModule module = mergeModules.findById(mergeModule); mergeFiles(userFiles, module, response); return; } Set<String> xmlSchemas = ImmutableSet .copyOf(Collections2.transform(userFiles, new Function<UserFile, String>() { @Override public String apply(UserFile userFile) { return userFile.getXmlSchema(); } })); Collection<MergeModule> mergeModulesFound = mergeModules.findByXmlSchemas(xmlSchemas); if (mergeModulesFound.size() == 1) { mergeFiles(userFiles, mergeModulesFound.iterator().next(), response); return; } throw new MergeModuleChoiceRequiredException(userFiles, mergeModulesFound); } /** * Handler for case where automatic merge could not be performed. Such cases are: * <ul> * <li>Multiple modules found</li> * <li>No modules found</li> * </ul> * * @param e custom exception, holding modules and selected files * @return model and view */ @ExceptionHandler(MergeModuleChoiceRequiredException.class) public ModelAndView mergeSelect(MergeModuleChoiceRequiredException e) { Map<String, Object> model = new HashMap<String, Object>(); Collection<MergeModule> modules = e.getMergeModules(); if (modules.isEmpty()) { modules = mergeModules.findAll(); } model.put("mergeModules", modules); model.put("userFiles", e.getUserFiles()); return new ModelAndView("merge_options", model); } /** * Performs conversion of specified {@link eionet.webq.dao.orm.UserFile} to specific format. Format is defined by conversionId. * * @param fileId file id or xsl name, which will be used to convert file * @param conversionId id of conversion to be used * @param response object where conversion result will be written */ @RequestMapping("/convert") @Transactional public void convertXmlFile(@RequestParam int fileId, @RequestParam String conversionId, HttpServletResponse response) { UserFile fileContent = userFileService.getById(fileId); ResponseEntity<byte[]> convert = conversionService.convert(fileContent, conversionId); HttpHeaders headers = convert.getHeaders(); setContentType(response, headers.getContentType()); setContentDisposition(response, headers.getFirst("Content-Disposition")); writeToResponse(response, convert.getBody()); } /** * Merge selected files. * * @param userFiles list of selected files ids * @param mergeModule module used for merge * @param response http response * @throws TransformerException if transformation fails. * @throws IOException if content operations fail. */ private void mergeFiles(Collection<UserFile> userFiles, MergeModule mergeModule, HttpServletResponse response) throws TransformerException, IOException { byte[] mergeResult = mergeService.mergeFiles(userFiles, mergeModule); writeXmlFileToResponse("merged_files.xml", mergeResult, response); } /** * Writes project file to response. * * @param name file name * @param projectFile project file object * @param response http response * @param disposition inline or attachment */ private void writeProjectFileToResponse(String name, ProjectFile projectFile, HttpServletResponse response, String disposition, String format) { ConfigurableMimeFileTypeMap mimeTypesMap = new ConfigurableMimeFileTypeMap(); String contentType = mimeTypesMap.getContentType(name); // check if default if (mimeTypesMap.getContentType("").equals(contentType)) { if (name.endsWith(".xhtml")) { contentType = MediaType.APPLICATION_XHTML_XML_VALUE; } else if (name.endsWith(".js")) { contentType = "application/javascript"; } else if (name.endsWith(".json")) { contentType = MediaType.APPLICATION_JSON_VALUE; } else { contentType = MediaType.APPLICATION_XML_VALUE; } // TODO check if there are more missing mime types } byte[] fileContent = projectFile.getFileContent(); if ("json".equals(format)) { fileContent = jsonXMLConverter.convertXmlToJson(projectFile.getFileContent()); contentType = MediaType.APPLICATION_JSON_VALUE; disposition = "inline"; } if (contentType.startsWith("text") || contentType.startsWith("application")) { contentType += ";charset=UTF-8"; } response.setContentType(contentType); setContentDisposition(response, disposition + ";filename=" + name); if (projectFile.getUpdated() != null) { response.setDateHeader("Last-Modified", projectFile.getUpdated().getTime()); } else if (projectFile.getCreated() != null) { response.setDateHeader("Last-Modified", projectFile.getCreated().getTime()); } writeToResponse(response, fileContent); } /** * Writes xml files to response. * * @param name file name * @param content file content * @param response http response */ private void writeXmlFileToResponse(String name, byte[] content, HttpServletResponse response) { addXmlFileHeaders(response, encodeAsUrl(name)); writeToResponse(response, content); } /** * Sets headers required to xml file download. * * @param response http response * @param fileName file name */ private void addXmlFileHeaders(HttpServletResponse response, String fileName) { setContentType(response, MediaType.APPLICATION_XML); setContentDisposition(response, "attachment;filename=" + fileName); } /** * Sets content disposition to response. * * @param response {@link HttpServletResponse} * @param contentDisposition content disposition header value. */ private void setContentDisposition(HttpServletResponse response, String contentDisposition) { if (contentDisposition != null) { response.setHeader("Content-Disposition", contentDisposition); } } /** * Sets content type header to response. * * @param response http response * @param contentType content type */ private void setContentType(HttpServletResponse response, MediaType contentType) { if (contentType != null) { response.setContentType(contentType.toString()); response.setCharacterEncoding("utf-8"); } } /** * {@link URLEncoder#encode(String, String)} with default value. * * @param path string to encode, also default value * @return encoded string or default value. */ private String encodeAsUrl(String path) { try { return URLEncoder.encode(path, "UTF-8"); } catch (UnsupportedEncodingException e) { return path; } } /** * Writes specified content to http response. * * @param response http response * @param data content to be written to response */ private void writeToResponse(HttpServletResponse response, byte[] data) { ServletOutputStream output = null; try { response.setContentLength(data.length); boolean noCache = true; if (response.getContentType() != null && response.getContentType().startsWith("image")) { noCache = false; } if (noCache) { response.addHeader("Cache-control", "no-cache"); } output = response.getOutputStream(); IOUtils.write(data, output); output.flush(); } catch (IOException e) { throw new RuntimeException("Unable to write response", e); } finally { IOUtils.closeQuietly(output); } } /** * Exception indicating that merge module choice is required. */ public static class MergeModuleChoiceRequiredException extends RuntimeException { /** * Selected user files. */ private Collection<UserFile> userFiles; /** * Available merge modules. */ private Collection<MergeModule> mergeModules; /** * Initializes this object with user files and modules. * * @param userFiles user files * @param mergeModules merge modules */ public MergeModuleChoiceRequiredException(Collection<UserFile> userFiles, Collection<MergeModule> mergeModules) { this.userFiles = userFiles; this.mergeModules = mergeModules; } public Collection<UserFile> getUserFiles() { return userFiles; } public Collection<MergeModule> getMergeModules() { return mergeModules; } } }