package io.lumify.web.routes.resource;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.inject.Inject;
import io.lumify.core.config.Configuration;
import io.lumify.core.model.artifactThumbnails.ArtifactThumbnailRepository;
import io.lumify.core.model.ontology.Concept;
import io.lumify.core.model.ontology.OntologyRepository;
import io.lumify.core.model.user.UserRepository;
import io.lumify.core.model.workspace.WorkspaceRepository;
import io.lumify.core.user.User;
import io.lumify.core.util.LumifyLogger;
import io.lumify.core.util.LumifyLoggerFactory;
import io.lumify.miniweb.HandlerChain;
import io.lumify.web.BaseRequestHandler;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
public class MapMarkerImage extends BaseRequestHandler {
private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(MapMarkerImage.class);
private final OntologyRepository ontologyRepository;
private ArtifactThumbnailRepository artifactThumbnailRepository;
private final Cache<String, byte[]> imageCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
@Inject
public MapMarkerImage(
final OntologyRepository ontologyRepository,
final UserRepository userRepository,
final Configuration configuration,
final WorkspaceRepository workspaceRepository,
final ArtifactThumbnailRepository artifactThumbnailRepository) {
super(userRepository, workspaceRepository, configuration);
this.ontologyRepository = ontologyRepository;
this.artifactThumbnailRepository = artifactThumbnailRepository;
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, HandlerChain chain) throws Exception {
User user = getUser(request);
String typeStr = getAttributeString(request, "type");
long scale = getOptionalParameterLong(request, "scale", 1L);
int heading = roundHeadingAngle(getOptionalParameterDouble(request, "heading", 0.0));
boolean selected = getOptionalParameter(request, "selected") != null;
String cacheKey = typeStr + scale + heading + (selected ? "selected" : "unselected");
byte[] imageData = imageCache.getIfPresent(cacheKey);
if (imageData == null) {
LOGGER.info("map marker cache miss %s (scale: %d, heading: %d)", typeStr, scale, heading);
Concept concept = ontologyRepository.getConceptByIRI(typeStr);
if (concept == null) {
concept = ontologyRepository.getConceptByIRI(typeStr);
}
boolean isMapGlyphIcon = false;
byte[] glyphIcon = getMapGlyphIcon(concept, user);
if (glyphIcon != null) {
isMapGlyphIcon = true;
} else {
glyphIcon = getGlyphIcon(concept, user);
if (glyphIcon == null) {
respondWithNotFound(response);
return;
}
}
imageData = getMarkerImage(new ByteArrayInputStream(glyphIcon), scale, selected, heading, isMapGlyphIcon);
imageCache.put(cacheKey, imageData);
}
response.setHeader("Cache-Control", "max-age=" + (5 * 60));
ServletOutputStream out = response.getOutputStream();
out.write(imageData);
out.close();
}
private int roundHeadingAngle(double heading) {
while (heading < 0.0) {
heading += 360.0;
}
while (heading > 360.0) {
heading -= 360.0;
}
return (int) (Math.round(heading / 10.0) * 10.0);
}
private byte[] getMarkerImage(InputStream resource, long scale, boolean selected, int heading, boolean isMapGlyphIcon) throws IOException {
BufferedImage resourceImage = ImageIO.read(resource);
if (resourceImage == null) {
return null;
}
if (heading != 0) {
resourceImage = rotateImage(resourceImage, heading);
}
BufferedImage backgroundImage = getBackgroundImage(scale, selected);
if (backgroundImage == null) {
return null;
}
int[] resourceImageDim = new int[]{resourceImage.getWidth(), resourceImage.getHeight()};
BufferedImage image = new BufferedImage(backgroundImage.getWidth(), backgroundImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
if (isMapGlyphIcon) {
int[] boundary = new int[]{backgroundImage.getWidth(), backgroundImage.getHeight()};
int[] scaledDims = artifactThumbnailRepository.getScaledDimension(resourceImageDim, boundary);
g.drawImage(resourceImage, 0, 0, scaledDims[0], scaledDims[1], null);
} else {
g.drawImage(backgroundImage, 0, 0, backgroundImage.getWidth(), backgroundImage.getHeight(), null);
int size = image.getWidth() * 2 / 3;
int[] boundary = new int[]{size, size};
int[] scaledDims = artifactThumbnailRepository.getScaledDimension(resourceImageDim, boundary);
int x = (backgroundImage.getWidth() - scaledDims[0]) / 2;
int y = (backgroundImage.getWidth() - scaledDims[1]) / 2;
g.drawImage(resourceImage, x, y, scaledDims[0], scaledDims[1], null);
}
g.dispose();
return imageToBytes(image);
}
private BufferedImage rotateImage(BufferedImage image, int angleDeg) {
BufferedImage rotatedImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics2D g = rotatedImage.createGraphics();
g.rotate(Math.toRadians(angleDeg), rotatedImage.getWidth() / 2, rotatedImage.getHeight() / 2);
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
g.dispose();
return rotatedImage;
}
private BufferedImage getBackgroundImage(long scale, boolean selected) throws IOException {
String imageFileName;
if (scale == 1) {
imageFileName = selected ? "marker-background-selected.png" : "marker-background.png";
} else if (scale == 2) {
imageFileName = selected ? "marker-background-selected-2x.png" : "marker-background-2x.png";
} else {
return null;
}
try (InputStream resourceInputStream = MapMarkerImage.class.getResourceAsStream(imageFileName)) {
checkNotNull(resourceInputStream, "Could not find image resource: " + imageFileName);
return ImageIO.read(resourceInputStream);
}
}
private byte[] imageToBytes(BufferedImage image) throws IOException {
ByteArrayOutputStream imageData = new ByteArrayOutputStream();
ImageIO.write(image, "png", imageData);
imageData.close();
return imageData.toByteArray();
}
private byte[] getMapGlyphIcon(Concept concept, User user) {
byte[] mapGlyphIcon = null;
for (Concept con = concept; mapGlyphIcon == null && con != null; con = ontologyRepository.getParentConcept(con)) {
mapGlyphIcon = con.getMapGlyphIcon();
}
return mapGlyphIcon;
}
private byte[] getGlyphIcon(Concept concept, User user) {
byte[] glyphIcon = null;
for (Concept con = concept; glyphIcon == null && con != null; con = ontologyRepository.getParentConcept(con)) {
glyphIcon = con.hasGlyphIconResource() ? con.getGlyphIcon() : null;
}
return glyphIcon;
}
}