Java tutorial
/* * #%L * wcm.io * %% * Copyright (C) 2014 wcm.io * %% * 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. * #L% */ package io.wcm.handler.mediasource.dam.impl; import io.wcm.wcm.commons.contenttype.FileExtension; import io.wcm.wcm.commons.util.RunMode; import java.util.EnumSet; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.commons.lang3.StringUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.jackrabbit.util.Text; import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.settings.SlingSettingsService; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.dam.api.Asset; import com.day.cq.dam.api.DamEvent; import com.day.image.Layer; /** * Background service that extracts additional metadata like width and height for DAM renditions. */ @Component(immediate = true, metatype = true, label = "wcm.io DAM Rendition Metadata Service", description = "Extracts additional metadata like width and height for DAM renditions.") @Property(name = EventConstants.EVENT_TOPIC, value = DamEvent.EVENT_TOPIC, propertyPrivate = true) @Service(EventHandler.class) public final class DamRenditionMetadataService implements EventHandler { /** * Name for Renditions Metadata node */ public static final String NN_RENDITIONS_METADATA = "renditionsMetadata"; /** * Property for image with in pixels */ public static final String PN_IMAGE_WIDTH = "imageWidth"; /** * Property for image height in pixels */ public static final String PN_IMAGE_HEIGHT = "imageHeight"; private static final EnumSet<DamEvent.Type> SUPPORTED_EVENT_TYPES = EnumSet.of(DamEvent.Type.RENDITION_UPDATED, DamEvent.Type.RENDITION_REMOVED); private static final boolean DEFAULT_ENABLED = true; @Property(boolValue = DEFAULT_ENABLED, label = "Enabled", description = "Switch to enable or disable this service.") static final String PROPERTY_ENABLED = "enabled"; private final Logger log = LoggerFactory.getLogger(this.getClass()); private boolean enabled; @Reference private ResourceResolverFactory resourceResolverFactory; @Reference private SlingSettingsService slingSettings; @Activate private void activate(ComponentContext componentContext) { // Activate only in author mode, and check enabled status in service configuration as well enabled = !RunMode.disableIfNotAuthor(slingSettings.getRunModes(), componentContext, log) && PropertiesUtil .toBoolean(componentContext.getProperties().get(PROPERTY_ENABLED), DEFAULT_ENABLED); } @Override public void handleEvent(Event event) { if (!enabled || !StringUtils.equals(event.getTopic(), DamEvent.EVENT_TOPIC)) { return; } DamEvent damEvent = DamEvent.fromEvent(event); if (SUPPORTED_EVENT_TYPES.contains(damEvent.getType())) { handleDamEvent(damEvent); } } /** * Handle dam event if certain conditions are fulfilled. * @param event DAM event */ private void handleDamEvent(DamEvent event) { // make sure rendition file extension is an image extensions String renditionPath = event.getAdditionalInfo(); String renditionNodeName = Text.getName(renditionPath); String fileExtension = StringUtils.substringAfterLast(renditionNodeName, "."); if (!FileExtension.isImage(fileExtension)) { return; } // open admin session for reading/writing rendition metadata ResourceResolver adminResourceResolver = null; try { adminResourceResolver = resourceResolverFactory.getServiceResourceResolver(null); // make sure asset exists Asset asset = getAsset(event.getAssetPath(), adminResourceResolver); if (asset == null) { return; } if (event.getType() == DamEvent.Type.RENDITION_UPDATED) { renditionAddedOrUpdated(asset, renditionPath, event.getUserId(), adminResourceResolver); } else if (event.getType() == DamEvent.Type.RENDITION_REMOVED) { renditionRemoved(asset, renditionPath, event.getUserId(), adminResourceResolver); } } catch (LoginException ex) { log.warn("Getting service resource resolver failed. " + "Please make sure a service user is defined for bundle 'io.wcm.handler.media'.", ex); } finally { if (adminResourceResolver != null) { adminResourceResolver.close(); } } } /** * Create or update rendition metadata if rendition is created or updated. * @param asset Asset * @param renditionPath Rendition path */ private void renditionAddedOrUpdated(Asset asset, String renditionPath, String userId, ResourceResolver resolver) { String renditionNodeName = Text.getName(renditionPath); // check for resource existence and try to get layer from image Resource renditionResource = resolver.getResource(renditionPath); if (renditionResource == null) { return; } Layer renditionLayer = renditionResource.adaptTo(Layer.class); if (renditionLayer == null) { return; } // update metadata Node renditionsMetadata = getRenditionsMetadataNode(asset, true); if (renditionsMetadata != null) { try { Node metadataNode; if (renditionsMetadata.hasNode(renditionNodeName)) { metadataNode = renditionsMetadata.getNode(renditionNodeName); } else { metadataNode = renditionsMetadata.addNode(renditionNodeName, JcrConstants.NT_UNSTRUCTURED); } metadataNode.setProperty(PN_IMAGE_WIDTH, renditionLayer.getWidth()); metadataNode.setProperty(PN_IMAGE_HEIGHT, renditionLayer.getHeight()); updateLastModifiedAndSave(asset, userId, resolver); log.debug("Updated rendition metadata at " + metadataNode.getPath() + " " + "(width=" + renditionLayer.getWidth() + ", height=" + renditionLayer.getHeight() + ")."); } catch (RepositoryException ex) { log.error("Unable to create or update rendition metadata node for " + renditionPath, ex); } } } /** * Remove rendition metadata node if rendition is removed. * @param asset Asset * @param renditionPath Rendition path */ private void renditionRemoved(Asset asset, String renditionPath, String userId, ResourceResolver resolver) { Node renditionsMetadata = getRenditionsMetadataNode(asset, false); if (renditionsMetadata == null) { return; } try { String renditionNodeName = Text.getName(renditionPath); if (renditionsMetadata.hasNode(renditionNodeName)) { Node metadataNode = renditionsMetadata.getNode(renditionNodeName); String pathToRemove = metadataNode.getPath(); metadataNode.remove(); updateLastModifiedAndSave(asset, userId, resolver); log.debug("Removed rendition metadata at " + pathToRemove + "."); } } catch (RepositoryException ex) { log.error("Unable to delete rendition metadata node for " + renditionPath, ex); } } /** * Updates last modified information and saves the session. * @param asset Asset * @param userId User id * @throws RepositoryException */ private void updateLastModifiedAndSave(Asset asset, String userId, ResourceResolver resolver) throws RepositoryException { // -- this is currently DISABLED due to WCMIO-28, concurrency issues with DAM workflows /* Node node = asset.adaptTo(Node.class); Node contentNode = node.getNode(JcrConstants.JCR_CONTENT); // this is a workaround to make sure asset is marked as modified contentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance()); contentNode.setProperty(JcrConstants.JCR_LAST_MODIFIED_BY, userId); */ resolver.adaptTo(Session.class).save(); } /** * Get asset instance for given asset path. * @param assetPath Asset path * @return Asset or null if path is invalid */ private Asset getAsset(String assetPath, ResourceResolver resolver) { Resource assetResource = resolver.getResource(assetPath); if (assetResource != null) { return assetResource.adaptTo(Asset.class); } else { return null; } } /** * Get node for storing the renditions additional metadata. * @param asset Asset * @param createIfNotExists if true the node is (tried to be) created automatically if it does not exist * @return Node or null if it does not exist or could not be created */ private Node getRenditionsMetadataNode(Asset asset, boolean createIfNotExists) { try { Node assetNode = asset.adaptTo(Node.class); if (assetNode != null) { Node assetContentNode = assetNode.getNode(JcrConstants.JCR_CONTENT); if (assetContentNode.hasNode(NN_RENDITIONS_METADATA)) { return assetContentNode.getNode(NN_RENDITIONS_METADATA); } else if (createIfNotExists) { return assetContentNode.addNode(NN_RENDITIONS_METADATA, JcrConstants.NT_UNSTRUCTURED); } } } catch (RepositoryException ex) { log.error("Unable to get/create renditions metadata node at " + asset.getPath(), ex); } return null; } }