package org.cbir.retrieval.web.rest; /* * Copyright (c) 2009-2015. Authors: see NOTICE file. * * 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. */ import com.codahale.metrics.annotation.Timed; import org.cbir.retrieval.security.AuthoritiesConstants; import org.cbir.retrieval.service.RetrievalService; import org.cbir.retrieval.service.StoreImageService; import org.cbir.retrieval.service.exception.*; import org.cbir.retrieval.web.rest.dto.ResultsJSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.json.JacksonJsonParser; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import retrieval.client.RetrievalClient; import retrieval.dist.ResultsSimilarities; import retrieval.server.RetrievalServer; import retrieval.storage.Storage; import retrieval.storage.exception.AlreadyIndexedException; import retrieval.storage.exception.NoValidPictureException; import javax.annotation.security.RolesAllowed; import javax.imageio.ImageIO; import javax.inject.Inject; import java.awt.image.BufferedImage; import java.io.*; import java.net.URL; import java.util.*; import java.util.stream.Collectors; /** * REST controller for managing images. */ @RestController @RequestMapping("/api") public class ImageResource { private final Logger log = LoggerFactory.getLogger(ImageResource.class); @Inject private RetrievalService retrievalService; @Inject private StoreImageService storeImageService; @RequestMapping(value = "/storages/{storage}/images/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<Map> getByStorage(@PathVariable Long id, @PathVariable String storage) throws ResourceNotFoundException { log.debug("REST request to get image : {}"); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); Storage storageImage = retrievalServer.getStorage(storage); if (storageImage == null) throw new ResourceNotFoundException("Storage " + storage + " cannot be found!"); Map<String, String> properties = storageImage.getProperties(id); if (properties == null) throw new ResourceNotFoundException("Image " + id + " cannot be found on storage " + storage + " !"); return new ResponseEntity<>(properties, HttpStatus.OK); } @RequestMapping(value = "/images/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<Map> get(@PathVariable Long id) throws ResourceNotFoundException { log.debug("REST request to get image : " + id); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); List<Map<String, String>> properties = retrievalServer.getStorageList() .parallelStream() .map(x -> x.getProperties(id)) .filter(x -> x != null && !x.isEmpty()) .collect(Collectors.toList()); log.info(properties.toString()); if (properties.isEmpty()) { throw new ResourceNotFoundException("Image " + id + " cannot be found !"); } return new ResponseEntity<>(properties.get(0), HttpStatus.OK); } @RequestMapping(value = "/storages/{storage}/images", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE ) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<List> getAllByStorage(@PathVariable String storage) throws CBIRException { log.debug("REST request to list images : {}"); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); Storage storageImage = retrievalServer.getStorage(storage); if (storageImage == null) { throw new ResourceNotFoundException("Storage " + storage + " cannot be found!"); } try { List<Map<String, String>> map = storageImage.getAllPicturesMap() .entrySet() .stream() .map(Map.Entry::getValue) .collect(Collectors.toList()); return new ResponseEntity<>(map, HttpStatus.OK); } catch (Exception e) { throw new CBIRException(e.toString(), HttpStatus.INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/images", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE ) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<List> getAll() throws CBIRException { log.debug("REST request to list images : {}"); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); try { List<Map<String, String>> map = retrievalServer.getInfos() .entrySet() .stream() .map(Map.Entry::getValue) .collect(Collectors.toList()); return new ResponseEntity<>(map, HttpStatus.OK); } catch (Exception e) { throw new CBIRException(e.toString(), HttpStatus.INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/images", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) public ResponseEntity<Map<String, String>> create( @RequestParam(value = "id", required = false) Long idImage, @RequestParam(value = "storage", required = false) String idStorage, @RequestParam(required = false) String keys, @RequestParam(required = false) String values, @RequestParam(defaultValue = "false") Boolean async,//http://stackoverflow.com/questions/17693435/how-to-give-default-date-values-in-requestparam-in-spring //MultipartHttpServletRequest request @RequestParam("file") MultipartFile file ) throws CBIRException, IOException { log.debug("REST request to create image : {}", idImage); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); BufferedImage image; try { byte[] data = file.getBytes(); image = ImageIO.read(new ByteArrayInputStream(data)); } catch (IOException ex) { throw new ResourceNotValidException("Image not valid:" + ex.toString()); } Map<String, String> result = indexPicture(idImage, idStorage, keys, values, async, image, retrievalServer,true); return new ResponseEntity<>(result, HttpStatus.OK); } @RequestMapping(value = "/storages/{storage}/images/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) public void delete( @PathVariable Long id, @PathVariable String storage ) throws Exception { log.debug("REST request to delete storage : {}", id); RetrievalServer retrievalServer = retrievalService.getRetrievalServer(); Storage storageImage = retrievalServer.getStorage(storage); if (storageImage == null) { throw new ResourceNotFoundException("Storage " + storage + " cannot be found!"); } if (!storageImage.isPictureInIndex(id)) { throw new ResourceNotFoundException("Image " + id + " cannot be found on storage " + storage + " !"); } try { storageImage.deletePicture(id); } catch (Exception e) { log.error(e.getMessage()); throw new CBIRException("Cannot delete image:" + e.toString(), HttpStatus.INTERNAL_SERVER_ERROR); } } @RequestMapping(value = "/search", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<ResultsJSON> search( @RequestParam(defaultValue = "30") Integer max, @RequestParam(defaultValue = "") String storages, @RequestParam("file") MultipartFile file, @RequestParam(value = "saveImage",defaultValue = "false") Boolean saveimage ) throws CBIRException, IOException { log.debug("REST request to get CBIR results : max=" + max + " storages=" + storages); BufferedImage image = null; try { System.out.println(file.getOriginalFilename()); byte[] data = file.getBytes(); image = ImageIO.read(new ByteArrayInputStream(data)); } catch (IOException ex) { throw new ResourceNotValidException("Image not valid:" + ex.toString()); } log.info(storeImageService + " " ); return doSearchSim(max, storages, image,saveimage); } @RequestMapping(value = "/searchUrl", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.USER) ResponseEntity<ResultsJSON> search( @RequestParam(defaultValue = "30") Integer max, @RequestParam(defaultValue = "") String storages, @RequestParam(required = false) String url, @RequestParam(required = false) Long id, @RequestParam(value = "saveImage",defaultValue = "false") Boolean saveimage ) throws CBIRException, IOException { log.debug("REST request to get CBIR results : max=" + max + " url=" + url + " id=" +id+" storages=" + storages); BufferedImage image = null; try { if(id!=null) { image = storeImageService.tryReadIndexImage(id); } //if no id or id has no valid images if(image==null) { image = ImageIO.read(new URL(url)); } } catch (IOException ex) { throw new ResourceNotValidException("Image not valid:" + ex.toString()); } log.info(storeImageService + " " ); return doSearchSim(max, storages, image,saveimage); } @RequestMapping(value = "/index/file", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.ADMIN) public void indexFile( @RequestParam(required = true) String file, @RequestParam(required = false,defaultValue = "false") Boolean async ) throws CBIRException, IOException { log.debug("REST request to INDEX FULL : file="+file + " async="+async); log.debug("REST request to INDEX FULL : file="+new File(file).getAbsolutePath()); StringBuffer jsonString = new StringBuffer(); FileReader fr = new FileReader(new File(file)); BufferedReader br = new BufferedReader(fr); String line; while((line = br.readLine()) != null){ jsonString.append(line); } br.close(); fr.close(); List<Object> list = new JacksonJsonParser().parseList(jsonString.toString()); for(Object entry : list) { try { Map map = (Map) entry;//new JacksonJsonParser().parseMap((String)entry); Long id = Long.parseLong(map.get("id").toString()); String storage = map.get("storage").toString(); String cropURL = map.get("url").toString(); //read images if on disk boolean saveImage = true; BufferedImage image = null; try { image = storeImageService.readIndexImage(id); saveImage = false; //don't save image, already on disk! } catch (Exception e) { image = ImageIO.read(new URL(cropURL)); } indexPicture(id, storage, "", "", async, image, retrievalService.getRetrievalServer(),saveImage); } catch(Exception e) { log.error(e.toString()); } } } @RequestMapping(value = "/index/full", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Timed @RolesAllowed(AuthoritiesConstants.ADMIN) public void indexFull( @RequestBody String json ) throws CBIRException, IOException { log.debug("REST request to INDEX FULL : json="+json); List<Object> list = new JacksonJsonParser().parseList(json); log.debug(list.toString()); for(Object entry : list) { try { Map map = (Map) entry;//new JacksonJsonParser().parseMap((String)entry); Long id = Long.parseLong(map.get("id").toString()); String storage = map.get("storage").toString(); String cropURL = map.get("url").toString(); //read images if on disk boolean saveImage = true; BufferedImage image = null; try { image = storeImageService.readIndexImage(id); saveImage = false; //don't save image, already on disk! } catch (Exception e) { image = ImageIO.read(new URL(cropURL)); } indexPicture(id, storage, "", "", false, image, retrievalService.getRetrievalServer(),saveImage); } catch(Exception e) { log.error(e.toString()); } } } private ResponseEntity<ResultsJSON> doSearchSim(Integer max, String storages, BufferedImage image, Boolean saveImage) throws ResourceNotValidException, IOException { String[] storagesArray = new String[0]; if (!storages.isEmpty()) { storagesArray = storages.split(";"); } Long searchId = new Date().getTime() + new Random().nextInt(100); RetrievalClient retrievalClient = retrievalService.getRetrievalClient(); ResultsSimilarities rs = null; try { rs = retrievalClient.search(image, max, storagesArray); log.info(storeImageService + " " + searchId); if(saveImage) { storeImageService.saveSearchImage(searchId,image); } } catch (retrieval.exception.CBIRException e) { throw new ResourceNotValidException("Cannot process CBIR request:" + e); } return new ResponseEntity<>(new ResultsJSON(searchId,rs), HttpStatus.OK); } private Map<String, String> indexPicture(Long idImage, String idStorage, String keys, String values, Boolean async, BufferedImage image, RetrievalServer retrievalServer, boolean saveImage) throws CBIRException, IOException { Storage storage; if (idStorage == null) { storage = retrievalServer.getNextStorage(); } else { storage = retrievalServer.getStorage(idStorage, true); } Map<String, String> properties = new TreeMap<>(); if (keys != null) { String[] keysArray = keys.split(";"); String[] valuesArray = values.split(";"); if (keysArray.length != valuesArray.length) { throw new ParamsNotValidException("keys.size()!=values.size()"); } for (int i = 0; i < keysArray.length; i++) { properties.put(keysArray[i], valuesArray[i]); } } //index picture Long id; try { if (async) { id = storage.addToIndexQueue(image, idImage, properties); } else { id = storage.indexPicture(image, idImage, properties); } } catch (AlreadyIndexedException e) { throw new ResourceAlreadyExistException("Image " + idImage + "already exist in storage " + idStorage); } catch (NoValidPictureException e) { throw new ResourceNotValidException("Cannot insert image:" + e.toString()); } catch (Exception e) { throw new CBIRException("Cannot insert image:" + e.toString(), HttpStatus.INTERNAL_SERVER_ERROR); } if(saveImage) { storeImageService.saveIndexImage(id, image); } return storage.getProperties(id); } }