Java tutorial
/************************************************************************** * Copyright (C) 2010 Atlas of Living Australia * All Rights Reserved. * * 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. ***************************************************************************/ package au.org.ala.layers.web; import au.org.ala.layers.dao.ObjectDAO; import au.org.ala.layers.intersect.IntersectConfig; import au.org.ala.layers.util.JSONRequestBodyParser; import au.org.ala.layers.util.SpatialConversionUtils; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; import org.geotools.geojson.geom.GeometryJSON; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URL; import java.text.MessageFormat; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @author Adam */ @Controller public class ShapesService { /** * Log4j instance */ protected Logger logger = Logger.getLogger(this.getClass()); @Resource(name = "objectDao") private ObjectDAO objectDao; /* * return a shape as kml */ @RequestMapping(value = "/shape/{type}/{id}", method = RequestMethod.GET) public void findShape(@PathVariable("type") String type, @PathVariable("id") String id, HttpServletRequest req, HttpServletResponse resp) { OutputStream os = null; try { os = resp.getOutputStream(); // validate object id id = cleanObjectId(id); if (type.equalsIgnoreCase("wkt")) { resp.setContentType("application/wkt"); objectDao.streamObjectsGeometryById(os, id, type); } else if (type.equalsIgnoreCase("kml")) { resp.setContentType("application/vnd.google-earth.kml+xml"); objectDao.streamObjectsGeometryById(os, id, type); } else if (type.equalsIgnoreCase("geojson")) { resp.setContentType("application/json; subtype=geojson"); objectDao.streamObjectsGeometryById(os, id, type); } else if (type.equalsIgnoreCase("shp")) { resp.setContentType("application/zip"); resp.setHeader("Content-Disposition", "attachment;filename=" + id + ".zip"); objectDao.streamObjectsGeometryById(os, id, type); } else { os.write(("'" + type + "' type not supported yet.").getBytes()); } } catch (Exception e) { logger.error("An error has occurred retrieving '" + type + "' for object id " + id); logger.error(ExceptionUtils.getFullStackTrace(e)); } finally { if (os != null) { try { os.close(); } catch (Exception e) { logger.error("Error closing http request stream", e); } } } } private String cleanObjectId(String id) { return id.replaceAll("[^a-zA-Z0-9]:", ""); } private Map<String, Object> processGeoJSONRequest(String json, Integer pid) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("geojson", Map.class, false); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("description", String.class, false); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String wkt = null; Map<String, Object> geojsonAsMap = (Map<String, Object>) reqBodyParser.getParsedValue("geojson"); try { String geojsonString = new ObjectMapper().writeValueAsString(geojsonAsMap); GeometryJSON gJson = new GeometryJSON(); Geometry geometry = gJson.read(new StringReader(geojsonString)); if (!geometry.isValid()) { retMap.put("error", "Invalid geometry"); } wkt = geometry.toText(); } catch (Exception ex) { logger.error( "Malformed GeoJSON geometry. Note that only GeoJSON geometries can be supplied here. Features and FeatureCollections cannot.", ex); retMap.put("error", "Malformed GeoJSON geometry. Note that only GeoJSON geometries can be supplied here. Features and FeatureCollections cannot."); return retMap; } String name = (String) reqBodyParser.getParsedValue("name"); String description = (String) reqBodyParser.getParsedValue("description"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { if (pid != null) { objectDao.updateUserUploadedObject(pid, wkt, name, description, user_id); objectDao.updateObjectNames(); retMap.put("updated", true); } else { String generatedPid = objectDao.createUserUploadedObject(wkt, name, description, user_id); objectDao.updateObjectNames(); retMap.put("id", Integer.parseInt(generatedPid)); } } catch (Exception ex) { logger.error("Error uploading geojson", ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } // Create from geoJSON @RequestMapping(value = "/shape/upload/geojson", method = RequestMethod.POST) @ResponseBody public Map<String, Object> uploadGeoJSON(@RequestBody String json) throws Exception { return processGeoJSONRequest(json, null); } // Create from geoJSON @RequestMapping(value = "/shape/upload/geojson/{pid}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> updateWithGeoJSON(@RequestBody String json, @PathVariable("pid") int pid) throws Exception { return processGeoJSONRequest(json, pid); } private Map<String, Object> processWKTRequest(String json, Integer pid, boolean namesearch) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("wkt", String.class, false); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("description", String.class, false); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String wkt = (String) reqBodyParser.getParsedValue("wkt"); String name = (String) reqBodyParser.getParsedValue("name"); String description = (String) reqBodyParser.getParsedValue("description"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } if (!isWKTValid(wkt)) { retMap.put("error", "Invalid WKT"); } try { if (pid != null) { objectDao.updateUserUploadedObject(pid, wkt, name, description, user_id); objectDao.updateObjectNames(); retMap.put("updated", true); } else { String generatedPid = objectDao.createUserUploadedObject(wkt, name, description, user_id, namesearch); objectDao.updateObjectNames(); retMap.put("id", Integer.parseInt(generatedPid)); } } catch (DataAccessException ex) { logger.error("Malformed WKT.", ex); retMap.put("error", "Malformed WKT."); } catch (Exception ex) { logger.error("Error uploading WKT", ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } // Create from WKT @RequestMapping(value = "/shape/upload/wkt", method = { RequestMethod.POST, RequestMethod.OPTIONS }) @ResponseBody public Map<String, Object> uploadWKT(@RequestBody String json, @RequestParam(value = "namesearch", required = false, defaultValue = "true") Boolean namesearch) throws Exception { return processWKTRequest(json, null, namesearch); } // Create from WKT @RequestMapping(value = "/shape/upload/wkt/{pid}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> updateWithWKT(@RequestBody String json, @PathVariable("pid") int pid, @RequestParam(value = "namesearch", required = false, defaultValue = "true") Boolean namesearch) throws Exception { return processWKTRequest(json, pid, namesearch); } // UploadShapeFile @RequestMapping(value = "/shape/upload/shp", method = RequestMethod.POST) @ResponseBody public Map<Object, Object> uploadShapeFile(HttpServletRequest req, HttpServletResponse resp, @RequestParam(value = "user_id", required = false) String userId, @RequestParam(value = "api_key", required = false) String apiKey) throws Exception { // Use linked hash map to maintain key ordering Map<Object, Object> retMap = new LinkedHashMap<Object, Object>(); File tmpZipFile = File.createTempFile("shpUpload", ".zip"); if (!ServletFileUpload.isMultipartContent(req)) { String jsonRequestBody = IOUtils.toString(req.getReader()); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("shp_file_url", String.class, false); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(jsonRequestBody)) { String shpFileUrl = (String) reqBodyParser.getParsedValue("shp_file_url"); userId = (String) reqBodyParser.getParsedValue("user_id"); apiKey = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(apiKey, userId)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } // Use shape file url from json body InputStream is = null; OutputStream os = null; try { is = new URL(shpFileUrl).openStream(); os = new FileOutputStream(tmpZipFile); IOUtils.copy(is, os); retMap.putAll(handleZippedShapeFile(tmpZipFile)); os.flush(); } catch (Exception e) { logger.error(e.getMessage(), e); } finally { if (is != null) { try { is.close(); } catch (Exception e) { logger.error(e.getMessage(), e); } } if (os != null) { try { os.close(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } } else { if (false && !checkAPIKey(apiKey, userId)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } // Create a factory for disk-based file items. File size limit is // 50MB // Configure a repository (to ensure a secure temp location is used) File repository = new File(System.getProperty("java.io.tmpdir")); DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 1024 * 50, repository); factory.setRepository(repository); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); // Parse the request List<FileItem> items = upload.parseRequest(req); if (items.size() == 1) { FileItem fileItem = items.get(0); IOUtils.copy(fileItem.getInputStream(), new FileOutputStream(tmpZipFile)); retMap.putAll(handleZippedShapeFile(tmpZipFile)); } else { retMap.put("error", "Multiple files sent in request. A single zipped shape file should be supplied."); } } return retMap; } private Map<Object, Object> handleZippedShapeFile(File zippedShp) throws IOException { // Use linked hash map to maintain key ordering Map<Object, Object> retMap = new LinkedHashMap<Object, Object>(); Pair<String, File> idFilePair = SpatialConversionUtils.extractZippedShapeFile(zippedShp); String uploadedShpId = idFilePair.getLeft(); File shpFile = idFilePair.getRight(); retMap.put("shp_id", uploadedShpId); List<List<Pair<String, Object>>> manifestData = SpatialConversionUtils.getShapeFileManifest(shpFile); int featureIndex = 0; for (List<Pair<String, Object>> featureData : manifestData) { // Use linked hash map to maintain key ordering Map<String, Object> featureDataMap = new LinkedHashMap<String, Object>(); for (Pair<String, Object> fieldData : featureData) { featureDataMap.put(fieldData.getLeft(), fieldData.getRight()); } retMap.put(featureIndex, featureDataMap); featureIndex++; } return retMap; } private Map<String, Object> processShapeFileFeatureRequest(String json, Integer pid, String shapeFileId, int featureIndex) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("description", String.class, false); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String name = (String) reqBodyParser.getParsedValue("name"); String description = (String) reqBodyParser.getParsedValue("description"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (false && !checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { File shpFileDir = new File(System.getProperty("java.io.tmpdir"), shapeFileId); String wkt = SpatialConversionUtils.getShapeFileFeatureAsWKT(shpFileDir, featureIndex); if (!isWKTValid(wkt)) { retMap.put("error", "Invalid geometry"); } if (pid != null) { objectDao.updateUserUploadedObject(pid, wkt, name, description, user_id); retMap.put("updated", true); } else { String generatedPid = objectDao.createUserUploadedObject(wkt, name, description, user_id); retMap.put("id", Integer.parseInt(generatedPid)); } } catch (Exception ex) { logger.error("Error processsing shapefile feature request", ex); retMap.put("error", ex.getMessage()); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } // UploadShapeFile @RequestMapping(value = "/shape/upload/shp/{shapeId}/{featureIndex}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> saveFeatureFromShapeFile(@RequestBody String json, @PathVariable("shapeId") String shapeId, @PathVariable("featureIndex") int featureIndex) throws Exception { return processShapeFileFeatureRequest(json, null, shapeId, featureIndex); } // UploadShapeFile @RequestMapping(value = "/shape/upload/shp/{objectPid}/{shapeId}/{featureIndex}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> updateFromShapeFileFeature(@RequestBody String json, @PathVariable("objectPid") int objectPid, @PathVariable("shapeId") String shapeId, @PathVariable("featureIndex") int featureIndex) throws Exception { return processShapeFileFeatureRequest(json, objectPid, shapeId, featureIndex); } @RequestMapping(value = "/shape/upload/pointradius/{latitude}/{longitude}/{radius}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> createPointRadius(@RequestBody String json, @PathVariable("latitude") double latitude, @PathVariable("longitude") double longitude, @PathVariable("radius") double radius) throws Exception { return processPointRadiusRequest(json, null, latitude, longitude, radius); } @RequestMapping(value = "/shape/upload/pointradius/{objectPid}/{latitude}/{longitude}/{radius}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> updateWithPointRadius(@RequestBody String json, @PathVariable("latitude") double latitude, @PathVariable("longitude") double longitude, @PathVariable("radius") double radius, @PathVariable("objectPid") int objectPid) throws Exception { return processPointRadiusRequest(json, objectPid, latitude, longitude, radius); } private Map<String, Object> processPointRadiusRequest(String json, Integer pid, double latitude, double longitude, double radiusKm) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("description", String.class, false); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String name = (String) reqBodyParser.getParsedValue("name"); String description = (String) reqBodyParser.getParsedValue("description"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { String wkt = SpatialConversionUtils.createCircleJs(longitude, latitude, radiusKm * 1000); if (pid == null) { String generatedPid = objectDao.createUserUploadedObject(wkt, name, description, user_id); retMap.put("id", Integer.parseInt(generatedPid)); } else { objectDao.updateUserUploadedObject(pid, wkt, name, description, user_id); retMap.put("updated", true); } } catch (Exception ex) { ex.printStackTrace(); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } private boolean checkAPIKey(String apiKey, String userId) { if (IntersectConfig.getApiKeyCheckUrlTemplate() == null || IntersectConfig.getApiKeyCheckUrlTemplate().isEmpty()) { return true; } GetMethod get = null; try { HttpClient httpClient = new HttpClient(); get = new GetMethod(MessageFormat.format(IntersectConfig.getApiKeyCheckUrlTemplate(), apiKey)); int returnCode = httpClient.executeMethod(get); if (returnCode != 200) { throw new RuntimeException("Error occurred checking api key"); } String responseText = get.getResponseBodyAsString(); ObjectMapper mapper = new ObjectMapper(); Map parsedJSON = mapper.readValue(responseText, Map.class); return (Boolean) parsedJSON.get("valid"); } catch (Exception ex) { throw new RuntimeException("Error checking API key"); } finally { if (get != null) { try { get.releaseConnection(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } } @RequestMapping(value = "/shape/upload/{pid}", method = RequestMethod.DELETE) @ResponseBody public Map<String, Object> deleteShape(@PathVariable("pid") int pid) { Map<String, Object> retMap = new HashMap<String, Object>(); try { boolean success = objectDao.deleteUserUploadedObject(pid); retMap.put("success", success); } catch (Exception ex) { logger.error("Error deleting shape " + pid, ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } return retMap; } @RequestMapping(value = "/poi", method = RequestMethod.POST) @ResponseBody public Map<String, Object> createPointOfInterest(@RequestBody String json) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("object_id", String.class, true); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("type", String.class, false); reqBodyParser.addParameter("latitude", Double.class, false); reqBodyParser.addParameter("longitude", Double.class, false); reqBodyParser.addParameter("bearing", Double.class, true); reqBodyParser.addParameter("user_id", String.class, false); reqBodyParser.addParameter("description", String.class, true); reqBodyParser.addParameter("focal_length", Double.class, true); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String object_id = (String) reqBodyParser.getParsedValue("object_id"); String name = (String) reqBodyParser.getParsedValue("name"); String type = (String) reqBodyParser.getParsedValue("type"); Double latitude = (Double) reqBodyParser.getParsedValue("latitude"); Double longitude = (Double) reqBodyParser.getParsedValue("longitude"); Double bearing = (Double) reqBodyParser.getParsedValue("bearing"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String description = (String) reqBodyParser.getParsedValue("description"); Double focal_length = (Double) reqBodyParser.getParsedValue("focal_length"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { int id = objectDao.createPointOfInterest(object_id, name, type, latitude, longitude, bearing, user_id, description, focal_length); retMap.put("id", id); } catch (Exception ex) { logger.error("Error creating point of interest", ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } @RequestMapping(value = "/poi/{id}", method = RequestMethod.DELETE) @ResponseBody public Map<String, Object> deletePointOfInterest(@PathVariable("id") int id, @RequestParam(value = "user_id", required = true, defaultValue = "") String userId, @RequestParam(value = "api_key", required = true, defaultValue = "") String apiKey) { Map<String, Object> retMap = new HashMap<String, Object>(); if (!checkAPIKey(apiKey, userId)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { boolean success = objectDao.deletePointOfInterest(id); retMap.put("deleted", success); } catch (Exception ex) { logger.error("Error uploading point of interest " + id, ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } return retMap; } @RequestMapping(value = "/poi/{id}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> updatePointOfInterest(@RequestBody String json, @PathVariable("id") int id) { Map<String, Object> retMap = new HashMap<String, Object>(); JSONRequestBodyParser reqBodyParser = new JSONRequestBodyParser(); reqBodyParser.addParameter("object_id", String.class, true); reqBodyParser.addParameter("name", String.class, false); reqBodyParser.addParameter("type", String.class, false); reqBodyParser.addParameter("latitude", Double.class, false); reqBodyParser.addParameter("longitude", Double.class, false); reqBodyParser.addParameter("bearing", Double.class, true); reqBodyParser.addParameter("user_id", String.class, true); reqBodyParser.addParameter("description", String.class, true); reqBodyParser.addParameter("focal_length", Double.class, true); reqBodyParser.addParameter("api_key", String.class, false); if (reqBodyParser.parseJSON(json)) { String object_id = (String) reqBodyParser.getParsedValue("object_id"); String name = (String) reqBodyParser.getParsedValue("name"); String type = (String) reqBodyParser.getParsedValue("type"); Double latitude = (Double) reqBodyParser.getParsedValue("latitude"); Double longitude = (Double) reqBodyParser.getParsedValue("longitude"); Double bearing = (Double) reqBodyParser.getParsedValue("bearing"); String user_id = (String) reqBodyParser.getParsedValue("user_id"); String description = (String) reqBodyParser.getParsedValue("description"); Double focal_length = (Double) reqBodyParser.getParsedValue("focal_length"); String api_key = (String) reqBodyParser.getParsedValue("api_key"); if (!checkAPIKey(api_key, user_id)) { retMap.put("error", "Invalid user ID or API key"); return retMap; } try { boolean updateSuccessful = objectDao.updatePointOfInterest(id, object_id, name, type, latitude, longitude, bearing, user_id, description, focal_length); retMap.put("updated", updateSuccessful); } catch (Exception ex) { logger.error("Error updating point of interest " + id, ex); retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } } else { retMap.put("error", StringUtils.join(reqBodyParser.getErrorMessages(), ",")); } return retMap; } @RequestMapping(value = "/poi/{id}", method = RequestMethod.GET) @ResponseBody public Map<String, Object> getPointOfInterestDetails(@PathVariable("id") int id) { Map<String, Object> retMap = new HashMap<String, Object>(); try { return objectDao.getPointOfInterestDetails(id); } catch (IllegalArgumentException ex) { retMap.put("error", "Invalid point of interest id " + id); } catch (Exception ex) { retMap.put("error", "Unexpected error. Please notify support@ala.org.au."); } return retMap; } private boolean isWKTValid(String wkt) { WKTReader wktReader = new WKTReader(); try { Geometry geom = wktReader.read(wkt); return geom.isValid(); } catch (ParseException ex) { return false; } } }