| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.apache.sling.samples.espblog.internal; |
| |
| import java.awt.Graphics2D; |
| import java.awt.geom.AffineTransform; |
| import java.awt.image.BufferedImage; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Calendar; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.imageio.ImageIO; |
| import javax.jcr.Node; |
| import javax.jcr.Repository; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.observation.Event; |
| import javax.jcr.observation.EventIterator; |
| import javax.jcr.observation.EventListener; |
| import javax.jcr.observation.ObservationManager; |
| |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Property; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.sling.jcr.api.SlingRepository; |
| import org.osgi.service.component.ComponentContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Observe the espblog content for changes, and generate |
| * thumbnails when images are added. |
| * |
| */ |
| @Component(immediate=true) |
| @Property(name="service.description", value="Sling ESP blog sample thumbnails generator") |
| public class ThumbnailGenerator implements EventListener { |
| |
| private Session session; |
| private ObservationManager observationManager; |
| |
| @Reference |
| private SlingRepository repository; |
| |
| @Property(value="/content/espblog") |
| private static final String CONTENT_PATH_PROPERTY = "content.path"; |
| |
| private static final Logger log = LoggerFactory |
| .getLogger(ThumbnailGenerator.class); |
| |
| private Map<String, String> supportedMimeTypes = new HashMap<String, String>(); |
| |
| protected void activate(ComponentContext context) throws Exception { |
| supportedMimeTypes.put("image/jpeg", ".jpg"); |
| supportedMimeTypes.put("image/png", ".png"); |
| |
| String contentPath = (String)context.getProperties().get(CONTENT_PATH_PROPERTY); |
| |
| session = repository.loginAdministrative(null); |
| if (repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED).equals("true")) { |
| observationManager = session.getWorkspace().getObservationManager(); |
| String[] types = { "nt:file" }; |
| observationManager.addEventListener(this, Event.NODE_ADDED, contentPath, true, null, types, false); |
| } |
| } |
| |
| protected void deactivate(ComponentContext componentContext) throws RepositoryException { |
| if(observationManager != null) { |
| observationManager.removeEventListener(this); |
| } |
| if (session != null) { |
| session.logout(); |
| session = null; |
| } |
| } |
| |
| public void onEvent(EventIterator it) { |
| while (it.hasNext()) { |
| Event event = it.nextEvent(); |
| try { |
| if (event.getType() == Event.NODE_ADDED && !(event.getPath().contains("thumbnails"))) { |
| log.info("new upload: {}", event.getPath()); |
| Node addedNode = session.getRootNode().getNode(event.getPath().substring(1)); |
| processNewNode(addedNode); |
| log.info("finished processing of {}", event.getPath()); |
| } |
| } catch (Exception e) { |
| log.error(e.getMessage(), e); |
| } |
| } |
| } |
| |
| private String getMimeType(Node n) throws RepositoryException { |
| String result = null; |
| final String mimeType = n.getProperty("jcr:mimeType").getString(); |
| |
| for(String key : supportedMimeTypes.keySet()) { |
| if(mimeType!=null && mimeType.startsWith(key)) { |
| result = key; |
| break; |
| } |
| } |
| |
| if(result == null) { |
| log.info("Node {} rejected, unsupported mime-type {}", n.getPath(), mimeType); |
| } |
| |
| if(n.getName().startsWith(".")) { |
| log.info("Node {} rejected, name starts with '.'", n.getPath(), mimeType); |
| result = null; |
| } |
| |
| return result; |
| } |
| |
| private void processNewNode(Node addedNode) throws Exception { |
| final String mimeType = getMimeType(addedNode); |
| if (mimeType == null) { |
| return; |
| } |
| final String suffix = supportedMimeTypes.get(mimeType); |
| |
| // Scale to a temp file for simplicity |
| log.info("Creating thumbnails for node {}", addedNode.getPath()); |
| final int [] widths = { 50, 100, 250 }; |
| for(int width : widths) { |
| createThumbnail(addedNode, width, mimeType, suffix); |
| } |
| } |
| |
| private void createThumbnail(Node image, int scalePercent, String mimeType, String suffix) throws Exception { |
| final File tmp = File.createTempFile(getClass().getSimpleName(), suffix); |
| try { |
| scale(image.getProperty("jcr:data").getStream(), scalePercent, new FileOutputStream(tmp), suffix); |
| |
| // Create thumbnail node and set the mandatory properties |
| Node thumbnailFolder = getThumbnailFolder(image); |
| Node thumbnail = thumbnailFolder.addNode(image.getParent().getName() + "_" + scalePercent + suffix, "nt:file"); |
| Node contentNode = thumbnail.addNode("jcr:content", "nt:resource"); |
| contentNode.setProperty("jcr:data", new FileInputStream(tmp)); |
| contentNode.setProperty("jcr:lastModified", Calendar.getInstance()); |
| contentNode.setProperty("jcr:mimeType", mimeType); |
| |
| session.save(); |
| |
| log.info("Created thumbnail " + contentNode.getPath()); |
| } finally { |
| if(tmp != null) { |
| tmp.delete(); |
| } |
| } |
| |
| } |
| |
| private Node getThumbnailFolder(Node addedNode) throws Exception { |
| Node post = addedNode.getParent().getParent().getParent(); |
| if (post.hasNode("thumbnails")) { |
| log.info("thumbnails node exists already at " + post.getPath()); |
| return post.getNode("thumbnails"); |
| } else { |
| Node t = post.addNode("thumbnails", "nt:folder"); |
| session.save(); |
| return t; |
| } |
| } |
| |
| public void scale(InputStream inputStream, int width, OutputStream outputStream, String suffix) throws IOException { |
| if(inputStream == null) { |
| throw new IOException("InputStream is null"); |
| } |
| |
| final BufferedImage src = ImageIO.read(inputStream); |
| if(src == null) { |
| final StringBuffer sb = new StringBuffer(); |
| for(String fmt : ImageIO.getReaderFormatNames()) { |
| sb.append(fmt); |
| sb.append(' '); |
| } |
| throw new IOException("Unable to read image, registered formats: " + sb); |
| } |
| |
| final double scale = (double)width / src.getWidth(); |
| |
| int destWidth = width; |
| int destHeight = new Double(src.getHeight() * scale).intValue(); |
| log.debug("Generating thumbnail, w={}, h={}", destWidth, destHeight); |
| BufferedImage dest = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); |
| Graphics2D g = dest.createGraphics(); |
| AffineTransform at = AffineTransform.getScaleInstance((double) destWidth / src.getWidth(), (double) destHeight / src.getHeight()); |
| g.drawRenderedImage(src, at); |
| ImageIO.write(dest, suffix.substring(1), outputStream); |
| } |
| } |