/* * Copyright 2010 Robert Csakany <robson@semmi.se>. * * 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. * under the License. */ package org.liveSense.service.thumbnailGenerator; /** * * @author Robert Csakany (robson@semmi.se) * @created Feb 13, 2010 */ import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; 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.ConfigurationPolicy; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.event.EventUtil; import org.apache.sling.event.jobs.JobUtil; import org.apache.sling.jcr.api.SlingRepository; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.EventAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Observe the users content for changes, and generate * thumbnail generation job when images are added/changed/deleted. */ @Component(label="%thumbnailResourceChangeListener.name", description="%thumbnailResourceChangeListener.description", immediate=true, metatype=true, policy=ConfigurationPolicy.OPTIONAL) public class ThumbnailGeneratorResourceChangeListener { private static final Logger log = LoggerFactory.getLogger(ThumbnailGeneratorResourceChangeListener.class); public static final String PARAM_CONTENT_PATHES = "contentPathes"; public static final String CONTENT_PATH_SITES = "/sites"; public static final String CONTENT_PATH_USERS = "/users"; public static final String[] DEFAULT_CONTENT_PATHES = new String[] {CONTENT_PATH_SITES, CONTENT_PATH_USERS}; public static final String PARAM_SUPPORTED_MIME_TYPES = "supportedMimeTypes"; public static final String MIME_TYPE_IMAGE_JPEG = "image/jpeg"; public static final String MIME_TYPE_IMAGE_GIF = "image/gif"; public static final String MIME_TYPE_IMAGE_PNG = "image/png"; public static final String[] DEFAULT_SUPPORTED_MIME_TYPES = {MIME_TYPE_IMAGE_JPEG, MIME_TYPE_IMAGE_GIF, MIME_TYPE_IMAGE_PNG}; public static final String PARAM_SUPPORTED_NODE_TYPES = "supportedNodeTypes"; public static final String NODE_TYPE_NT_FILE = "nt:file"; public static final String[] DEFAULT_NODE_TYPES = new String[]{NODE_TYPE_NT_FILE}; public static final String THUMBNAIL_GENERATE_TOPIC = "org/liveSense/thumbnail/generate"; public static final String THUMBNAIL_REMOVE_TOPIC = "org/liveSense/thumbnail/remove"; @Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.STATIC) private SlingRepository repository; @Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC) private EventAdmin eventAdmin; @Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC) ResourceResolverFactory resourceResolverFactory; @Property(name=PARAM_CONTENT_PATHES, label="%contentPathes.name", description="%contentPathes.description", value={CONTENT_PATH_SITES, CONTENT_PATH_USERS}) private String[] contentPathes = DEFAULT_CONTENT_PATHES; @Property(name = PARAM_SUPPORTED_MIME_TYPES, label = "%supported.mimeTypes", description = "%supported.mimeTypes.description", value = { MIME_TYPE_IMAGE_JPEG, MIME_TYPE_IMAGE_GIF, MIME_TYPE_IMAGE_PNG }) private String[] supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES; @Property(name = PARAM_SUPPORTED_NODE_TYPES, label = "%supported.nodeTypes", description = "%supported.nodeTypes.description", value = { NODE_TYPE_NT_FILE }) private String[] supportedNodeTypes = DEFAULT_NODE_TYPES; private Session session; class PathEventListener implements EventListener { private void generateJobEvent(String eventType, String filePath, String fileName) { if (!fileName.startsWith(".")) { String mimeType = null; try { mimeType = session.getRootNode().getNode(filePath+"/"+fileName+"/jcr:content").getProperty("jcr:mimeType").getString(); boolean foundMimeType = false; for (String sup : supportedMimeTypes) { if (sup.equals(mimeType)) { foundMimeType = true; } } if (foundMimeType) { log.info(">Generate thumbnail event "+JobUtil.PROPERTY_JOB_TOPIC+" "+THUMBNAIL_GENERATE_TOPIC+" for " +eventType+" "+filePath+" "+fileName); final Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(JobUtil.PROPERTY_JOB_TOPIC, THUMBNAIL_GENERATE_TOPIC); props.put("resourcePath", "/"+filePath+"/"+fileName); org.osgi.service.event.Event generateThumbnailJob = new org.osgi.service.event.Event(JobUtil.TOPIC_JOB, props); eventAdmin.sendEvent(generateThumbnailJob); } } catch (Exception e) { log.error("Error resolving mimeType: "+filePath+"/"+fileName); } } } private void removeJobEvent(String eventType, String filePath, String fileName) { if (!fileName.startsWith(".")) { log.info(">Remove thumbnail event "+JobUtil.PROPERTY_JOB_TOPIC+" "+THUMBNAIL_REMOVE_TOPIC+" for " +eventType+" "+filePath+" "+fileName); final Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(JobUtil.PROPERTY_JOB_TOPIC, THUMBNAIL_REMOVE_TOPIC); props.put("resourcePath", "/"+filePath+"/"+fileName); org.osgi.service.event.Event generateThumbnailJob = new org.osgi.service.event.Event(EventUtil.TOPIC_JOB, props); eventAdmin.sendEvent(generateThumbnailJob); } } @Override public void onEvent(EventIterator it) { while (it.hasNext()) { Event event = it.nextEvent(); try { if (event.getType() == Event.NODE_REMOVED || event.getType() == Event.NODE_ADDED) { String[] pathElements = event.getPath().split("/"); StringBuffer sb = new StringBuffer(); for (int i = 1; i < pathElements.length-2; i++) { if (i!=0) sb.append("/"); sb.append(pathElements[i]); } String filePath = sb.toString().substring(1); String fileName = pathElements[pathElements.length-2]; String parentFolder = pathElements[pathElements.length-3]; String eventType = (event.getType()==Event.NODE_ADDED ? "NODE_ADDED" : (event.getType()==Event.NODE_REMOVED ? "NODE_REMOVED" : "UNHANDLED_EVENT")); if (event.getType() == Event.NODE_ADDED) { generateJobEvent(eventType, filePath, fileName); } else if (event.getType() == Event.NODE_REMOVED) { removeJobEvent(eventType, filePath, fileName); } } else { String[] pathElements = event.getPath().split("/"); StringBuffer sb = new StringBuffer(); for (int i = 1; i < pathElements.length-3; i++) { if (i!=0) sb.append("/"); sb.append(pathElements[i]); } String filePath = sb.toString().substring(1); String fileName = pathElements[pathElements.length-3]; String parentFolder = pathElements[pathElements.length-4]; String propertyName = pathElements[pathElements.length-1]; String eventType = (event.getType()==Event.PROPERTY_ADDED ? "PROPERTY_ADDED" : (event.getType()==Event.PROPERTY_CHANGED ? "PROPERTY_CHANGED" : (event.getType()==Event.PROPERTY_REMOVED ? "PROPERTY_REMOVED" : "UNHANDLED_EVENT"))); if ("jcr:data".equals(propertyName)) generateJobEvent(eventType, filePath, fileName); } } catch (Exception e) { log.error(e.getMessage(), e); } } } } private final ArrayList<PathEventListener> eventListeners = new ArrayList<PathEventListener>(); private ObservationManager observationManager; /** * Activates this component. * * @param componentContext The OSGi <code>ComponentContext</code> of this * component. */ protected void activate(ComponentContext componentContext) throws RepositoryException { // Setting up contentPathes contentPathes = PropertiesUtil.toStringArray(componentContext.getProperties().get(PARAM_CONTENT_PATHES), DEFAULT_CONTENT_PATHES); // Setting up supportedMimeTypes supportedMimeTypes = PropertiesUtil.toStringArray(componentContext.getProperties().get(PARAM_SUPPORTED_MIME_TYPES), DEFAULT_SUPPORTED_MIME_TYPES); // Setting up supported node types supportedNodeTypes = PropertiesUtil.toStringArray(componentContext.getProperties().get(PARAM_SUPPORTED_NODE_TYPES), DEFAULT_NODE_TYPES); session = repository.loginAdministrative(null); if (repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED).equals("true")) { observationManager = session.getWorkspace().getObservationManager(); //String[] types = {"nt:resource"}; for (int i = 0; i < contentPathes.length; i++) { String[] propType = {"nt:resource"}; PathEventListener listener = new PathEventListener(); observationManager.addEventListener(listener, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED, contentPathes[i], true, null, propType, true); eventListeners.add(listener); //String[] fileType = {"nt:file"}; listener = new PathEventListener(); observationManager.addEventListener(listener, Event.NODE_REMOVED, contentPathes[i], true, null, supportedNodeTypes, true); eventListeners.add(listener); } } } public void deactivate(ComponentContext componentContext) throws RepositoryException { if (session != null && session.isLive()) { session.logout(); } if (observationManager != null) { for (PathEventListener listener : eventListeners) { observationManager.removeEventListener(listener); } } } }