Java tutorial
/** * Copyright (C) 2010-2018 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.web.entity; import java.io.IOException; import java.net.URI; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.structr.common.ConstantBooleanTrue; import org.structr.common.Permission; import org.structr.common.PropertyView; import org.structr.common.SecurityContext; import org.structr.common.error.ErrorBuffer; import org.structr.common.error.FrameworkException; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.AbstractNode; import org.structr.core.entity.Relation; import org.structr.core.entity.Relation.Cardinality; import org.structr.core.graph.ModificationQueue; import org.structr.core.property.PropertyKey; import org.structr.core.property.PropertyMap; import org.structr.schema.SchemaService; import org.structr.schema.json.JsonMethod; import org.structr.schema.json.JsonObjectType; import org.structr.schema.json.JsonSchema; import org.structr.schema.json.JsonSchema.Cascade; import org.structr.web.common.FileHelper; import org.structr.web.common.ImageHelper; import org.structr.web.common.ImageHelper.Thumbnail; import org.structr.web.property.ImageDataProperty; import org.structr.web.property.ThumbnailProperty; /** * An image whose binary data will be stored on disk. */ public interface Image extends File { final static String STRUCTR_THUMBNAIL_FOLDER = "._structr_thumbnails/"; static class Impl { static { final JsonSchema schema = SchemaService.getDynamicSchema(); final JsonObjectType user = schema.addType("User"); final JsonObjectType image = schema.addType("Image"); image.setImplements(URI.create("https://structr.org/v1.1/definitions/Image")); image.setExtends(URI.create("#/definitions/File")); image.setCategory("core"); image.addIntegerProperty("width", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addIntegerProperty("height", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addIntegerProperty("orientation", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addStringProperty("exifIFD0Data", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addStringProperty("exifSubIFDData", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addStringProperty("gpsData", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addBooleanProperty("isImage", PropertyView.Public, PropertyView.Ui).setReadOnly(true) .addTransformer(ConstantBooleanTrue.class.getName()); image.addBooleanProperty("isThumbnail", PropertyView.Public, PropertyView.Ui).setIndexed(true); image.addBooleanProperty("isCreatingThumb").setIndexed(true); image.addCustomProperty("imageData", ImageDataProperty.class.getName()).setTypeHint("String"); image.addCustomProperty("tnSmall", ThumbnailProperty.class.getName(), PropertyView.Public, PropertyView.Ui).setTypeHint("Image").setFormat("100, 100, false"); image.addCustomProperty("tnMid", ThumbnailProperty.class.getName(), PropertyView.Public, PropertyView.Ui).setTypeHint("Image").setFormat("300, 300, false"); image.addPropertyGetter("isCreatingThumb", Boolean.TYPE); image.addPropertySetter("isCreatingThumb", Boolean.TYPE); image.addPropertyGetter("originalImage", Image.class); image.addPropertyGetter("thumbnails", List.class); image.addPropertyGetter("width", Integer.class); image.addPropertySetter("width", Integer.class); image.addPropertyGetter("height", Integer.class); image.addPropertySetter("height", Integer.class); // TODO: sysinternal and unvalidated properties are not possible right now image.overrideMethod("isImage", false, "return getProperty(isImageProperty);"); image.overrideMethod("isThumbnail", false, "return getProperty(isThumbnailProperty);"); image.overrideMethod("getOriginalImageName", false, "return " + Image.class.getName() + ".getOriginalImageName(this);"); image.overrideMethod("setProperty", true, "return " + Image.class.getName() + ".setProperty(this, arg0, arg1);"); image.overrideMethod("onModification", true, Image.class.getName() + ".onModification(this, arg0, arg1, arg2);"); image.overrideMethod("setProperties", true, Image.class.getName() + ".setProperties(this, arg0, arg1);"); image.overrideMethod("isGranted", false, "if (this.isThumbnail()) { final org.structr.web.entity.Image originalImage = getOriginalImage(); if (originalImage != null) { return originalImage.isGranted(arg0, arg1); } } return super.isGranted(arg0, arg1);"); image.overrideMethod("getThumbnailParentFolder", false, "final StringBuilder pathBuffer = new StringBuilder(" + Image.class.getName() + ".STRUCTR_THUMBNAIL_FOLDER); if (arg0 != null) { pathBuffer.append(arg0.getPath()); } return " + FileHelper.class.getName() + ".createFolderPath(SecurityContext.getSuperUserInstance(), pathBuffer.toString());"); final JsonMethod getScaledImage1 = image.addMethod("getScaledImage"); getScaledImage1.setReturnType(Image.class.getName()); getScaledImage1.setSource("return " + Image.class.getName() + ".getScaledImage(this, arg0, arg1);"); getScaledImage1.addParameter("arg0", "String"); getScaledImage1.addParameter("arg1", "String"); final JsonMethod getScaledImage2 = image.addMethod("getScaledImage"); getScaledImage2.setReturnType(Image.class.getName()); getScaledImage2 .setSource("return " + Image.class.getName() + ".getScaledImage(this, arg0, arg1, arg2);"); getScaledImage2.addParameter("arg0", "String"); getScaledImage2.addParameter("arg1", "String"); getScaledImage2.addParameter("arg2", "boolean"); final JsonMethod getScaledImage3 = image.addMethod("getScaledImage"); getScaledImage3.setReturnType(Image.class.getName()); getScaledImage3.setSource("return " + Image.class.getName() + ".getScaledImage(this, arg0, arg1);"); getScaledImage3.addParameter("arg0", "int"); getScaledImage3.addParameter("arg1", "int"); final JsonMethod getScaledImage4 = image.addMethod("getScaledImage"); getScaledImage4.setReturnType(Image.class.getName()); getScaledImage4 .setSource("return " + Image.class.getName() + ".getScaledImage(this, arg0, arg1, arg2);"); getScaledImage4.addParameter("arg0", "int"); getScaledImage4.addParameter("arg1", "int"); getScaledImage4.addParameter("arg2", "boolean"); image.relate(image, "THUMBNAIL", Cardinality.OneToMany, "originalImage", "thumbnails") .setCascadingDelete(Cascade.sourceToTarget); image.relate(user, "PICTURE_OF", Cardinality.OneToOne, "img", "user"); // view configuration image.addViewProperty(PropertyView.Public, "parent"); } } void setIsCreatingThumb(final boolean isCreatingThumb) throws FrameworkException; boolean isImage(); boolean isThumbnail(); boolean getIsCreatingThumb(); Integer getWidth(); Integer getHeight(); Image getOriginalImage(); String getOriginalImageName(); Image getScaledImage(final String maxWidthString, final String maxHeightString); Image getScaledImage(final String maxWidthString, final String maxHeightString, final boolean cropToFit); Image getScaledImage(final int maxWidth, final int maxHeight); Image getScaledImage(final int maxWidth, final int maxHeight, final boolean cropToFit); List<Image> getThumbnails(); Folder getThumbnailParentFolder(final Folder originalParentFolder, final SecurityContext securityContext) throws FrameworkException; //public Image getScaledImage(final int maxWidth, final int maxHeight, final boolean cropToFit) { /* TODO public static final Property<Image> tnSmall = new ThumbnailProperty("tnSmall").format("100, 100, false"); public static final Property<Image> tnMid = new ThumbnailProperty("tnMid").format("300, 300, false"); */ //public static final Property<Integer> height = new IntProperty("height").cmis().indexed(); //public static final Property<Integer> width = new IntProperty("width").cmis().indexed(); //public static final Property<Integer> orientation = new IntProperty("orientation").cmis().indexed(); //public static final Property<String> exifIFD0Data = new StringProperty("exifIFD0Data").cmis().indexed(); //public static final Property<String> exifSubIFDData = new StringProperty("exifSubIFDData").cmis().indexed(); //public static final Property<String> gpsData = new StringProperty("gpsData").cmis().indexed(); // public static final ImageDataProperty imageData = new ImageDataProperty("imageData"); //public static final Property<Boolean> isThumbnail = new BooleanProperty("isThumbnail").indexed().unvalidated().systemInternal(); //public static final Property<Boolean> isImage = new ConstantBooleanProperty("isImage", true); //public static final Property<Boolean> isCreatingThumb = new BooleanProperty("isCreatingThumb").systemInternal(); /* public static final org.structr.common.View uiView = new org.structr.common.View(Image.class, PropertyView.Ui, type, name, contentType, size, relativeFilePath, width, height, orientation, exifIFD0Data, exifSubIFDData, gpsData, tnSmall, tnMid, isThumbnail, owner, parent, path, isImage ); public static final org.structr.common.View publicView = new org.structr.common.View(Image.class, PropertyView.Public, type, name, width, height, orientation, exifIFD0Data, exifSubIFDData, gpsData, tnSmall, tnMid, isThumbnail, owner, parent, path, isImage ); */ public static boolean isGranted(final Image thisImage, final Permission permission, final SecurityContext context) { if (thisImage.isThumbnail()) { final Image originalImage = thisImage.getOriginalImage(); if (originalImage != null) { return originalImage.isGranted(permission, context); } } return thisImage.isGranted(permission, context); } public static Object setProperty(final Image thisImage, final PropertyKey key, final Object value) throws FrameworkException { // Copy visibility properties and owner to all thumbnails if (visibleToPublicUsers.equals(key) || visibleToAuthenticatedUsers.equals(key) || owner.equals(key)) { for (Image tn : thisImage.getThumbnails()) { if (!tn.getUuid().equals(thisImage.getUuid())) { tn.setProperty(key, value); } } } return null; } public static void setProperties(final Image thisImage, final SecurityContext securityContext, final PropertyMap properties) throws FrameworkException { if (!thisImage.isThumbnail()) { final PropertyMap propertiesCopiedToAllThumbnails = new PropertyMap(); for (final PropertyKey key : properties.keySet()) { if (visibleToPublicUsers.equals(key) || visibleToAuthenticatedUsers.equals(key) || owner.equals(key)) { propertiesCopiedToAllThumbnails.put(key, properties.get(key)); } } if (!propertiesCopiedToAllThumbnails.isEmpty()) { final List<Image> thumbnails = thisImage.getThumbnails(); for (Image tn : thumbnails) { if (!tn.getUuid().equals(thisImage.getUuid())) { tn.setProperties(tn.getSecurityContext(), propertiesCopiedToAllThumbnails); } } } } } public static void onModification(final Image thisImage, final SecurityContext securityContext, final ErrorBuffer errorBuffer, final ModificationQueue modificationQueue) throws FrameworkException { if (!thisImage.isThumbnail()) { if (modificationQueue.isPropertyModified(thisImage, name)) { final String newImageName = getName(); for (Image tn : thisImage.getThumbnails()) { final String expectedThumbnailName = ImageHelper.getThumbnailName(newImageName, tn.getWidth(), tn.getHeight()); final String currentThumbnailName = tn.getName(); if (!expectedThumbnailName.equals(currentThumbnailName)) { logger.debug("Auto-renaming Thumbnail({}) from '{}' to '{}'", tn.getUuid(), currentThumbnailName, expectedThumbnailName); tn.setProperty(AbstractNode.name, expectedThumbnailName); } } } } } /* public Integer getWidth() { return getProperty(Image.width); } public Integer getHeight() { return getProperty(Image.height); } public List<Image> getThumbnails() { final List<Image> thumbnails = new LinkedList<>(); for (final AbstractRelationship s : getThumbnailRelationships()) { thumbnails.add((Image) s.getTargetNode()); } return thumbnails; } /** * Get (down-)scaled image of this image * * If no scaled image of the requested size exists or the image is newer than the scaled image, create a new one * * @param maxWidthString * @param maxHeightString * * @return scaled image */ public static Image getScaledImage(final Image thisImage, final String maxWidthString, final String maxHeightString) { return getScaledImage(thisImage, Integer.parseInt(maxWidthString), Integer.parseInt(maxHeightString), false); } public static Image getScaledImage(final Image thisImage, final String maxWidthString, final String maxHeightString, final boolean cropToFit) { return getScaledImage(thisImage, Integer.parseInt(maxWidthString), Integer.parseInt(maxHeightString), cropToFit); } public static Image getScaledImage(final Image thisImage, final int maxWidth, final int maxHeight) { return getScaledImage(thisImage, maxWidth, maxHeight, false); } /** * Get (down-)scaled image of this image * * If no scaled image of the requested size exists or the image is newer than the scaled image, create a new one. * * Default behaviour is to make the scaled image complete fit inside a rectangle of maxWidth x maxHeight. * * @param maxWidth * @param maxHeight * @param cropToFit if true, scale down until the shorter edge fits inside the rectangle, and then crop * * @return scaled image * */ public static Image getScaledImage(final Image thisImage, final int maxWidth, final int maxHeight, final boolean cropToFit) { final Class<Relation> thumbnailRel = StructrApp.getConfiguration() .getRelationshipEntityClass("ImageTHUMBNAILImage"); final Iterable<Relation> thumbnailRelationships = thisImage.getOutgoingRelationships(thumbnailRel); final SecurityContext securityContext = thisImage.getSecurityContext(); final List<Image> oldThumbnails = new LinkedList<>(); Image thumbnail = null; final Image originalImage = thisImage; final Integer origWidth = originalImage.getWidth(); final Integer origHeight = originalImage.getHeight(); final Long currentChecksum = originalImage.getChecksum(); Long newChecksum = 0L; if (currentChecksum == null || currentChecksum == 0) { try { newChecksum = FileHelper.getChecksum(originalImage.getFileOnDisk()); if (newChecksum == null || newChecksum == 0) { logger.warn("Unable to calculate checksum of {}", originalImage.getName()); return null; } } catch (IOException ex) { logger.warn("Unable to calculate checksum of {}: {}", originalImage.getName(), ex.getMessage()); } } else { newChecksum = currentChecksum; } // Read Exif and GPS data from image and update properties ImageHelper.getExifData(originalImage); // Return self if SVG image final String _contentType = thisImage.getContentType(); if (_contentType != null && (_contentType.startsWith("image/svg") || (_contentType.startsWith("image/") && _contentType.endsWith("icon")))) { return thisImage; } if (origWidth != null && origHeight != null && thumbnailRelationships != null) { for (final Relation r : thumbnailRelationships) { final Integer w = r.getProperty(StructrApp.key(Image.class, "width")); final Integer h = r.getProperty(StructrApp.key(Image.class, "height")); if (w != null && h != null) { // orginal image is equal or smaller than requested size if (((w == maxWidth) && (h <= maxHeight)) || ((w <= maxWidth) && (h == maxHeight)) || ((origWidth <= w) && (origHeight <= h))) { thumbnail = (Image) r.getTargetNode(); // Use thumbnail only if checksum of original image matches with stored checksum final Long storedChecksum = r.getProperty(StructrApp.key(Image.class, "checksum")); if (storedChecksum != null && storedChecksum.equals(newChecksum)) { return thumbnail; } else { oldThumbnails.add(thumbnail); } } } } } // do not create thumbnails if this transaction is set to read-only if (securityContext.isReadOnlyTransaction()) { return null; } if (originalImage.getIsCreatingThumb()) { logger.debug("Another thumbnail is being created - waiting...."); } else { try { // No thumbnail exists, or thumbnail was too old, so let's create a new one logger.debug("Creating thumbnail for {} (w={} h={} crop={})", new Object[] { getName(), maxWidth, maxHeight, cropToFit }); originalImage.unlockSystemPropertiesOnce(); originalImage.setIsCreatingThumb(true); final App app = StructrApp.getInstance(); originalImage.unlockSystemPropertiesOnce(); originalImage.setProperty(StructrApp.key(File.class, "checksum"), newChecksum); final Thumbnail thumbnailData = ImageHelper.createThumbnail(originalImage, maxWidth, maxHeight, cropToFit); if (thumbnailData != null) { final Integer tnWidth = thumbnailData.getWidth(); final Integer tnHeight = thumbnailData.getHeight(); byte[] data = null; try { data = thumbnailData.getBytes(); final String thumbnailName = ImageHelper.getThumbnailName(originalImage.getName(), tnWidth, tnHeight); // create thumbnail node thumbnail = ImageHelper.createImageNode(securityContext, data, "image/" + Thumbnail.defaultFormat, Image.class, thumbnailName, true); } catch (IOException ex) { logger.warn("Could not create thumbnail image for " + getUuid(), ex); } if (thumbnail != null && data != null) { // Create a thumbnail relationship final PropertyMap relProperties = new PropertyMap(); relProperties.put(StructrApp.key(Image.class, "width"), tnWidth); relProperties.put(StructrApp.key(Image.class, "height"), tnHeight); relProperties.put(StructrApp.key(Image.class, "checksum"), newChecksum); app.create(originalImage, thumbnail, thumbnailRel, relProperties); final PropertyMap properties = new PropertyMap(); properties.put(StructrApp.key(Image.class, "width"), tnWidth); properties.put(StructrApp.key(Image.class, "height"), tnHeight); properties.put(StructrApp.key(AbstractNode.class, "hidden"), originalImage.getProperty(AbstractNode.hidden)); properties.put(StructrApp.key(AbstractNode.class, "visibleToAuthenticatedUsers"), originalImage.getProperty(AbstractNode.visibleToAuthenticatedUsers)); properties.put(StructrApp.key(AbstractNode.class, "visibleToPublicUsers"), originalImage.getProperty(AbstractNode.visibleToPublicUsers)); properties.put(StructrApp.key(File.class, "size"), Long.valueOf(data.length)); properties.put(StructrApp.key(AbstractNode.class, "owner"), originalImage.getProperty(AbstractNode.owner)); properties.put(StructrApp.key(File.class, "parent"), originalImage.getThumbnailParentFolder( originalImage.getProperty(StructrApp.key(File.class, "parent")), securityContext)); properties.put(StructrApp.key(File.class, "hasParent"), originalImage.getProperty(StructrApp.key(Image.class, "hasParent"))); thumbnail.unlockSystemPropertiesOnce(); thumbnail.setProperties(securityContext, properties); // Delete outdated thumbnails for (final Image tn : oldThumbnails) { app.delete(tn); } } } else { logger.debug("Could not create thumbnail for image {} ({})", getName(), getUuid()); } originalImage.unlockSystemPropertiesOnce(); originalImage.setIsCreatingThumb(false); } catch (FrameworkException fex) { logger.warn("Unable to create thumbnail for " + getUuid(), fex); } } return thumbnail; } /** * Return true if this image is a thumbnail image. * * This is determined by having at least one incoming THUMBNAIL relationship * * @return true if is thumbnail public boolean isThumbnail() { return getProperty(Image.isThumbnail) || getIncomingRelationship(Thumbnails.class) != null; } * */ /** * @return the name of the original image */ public static String getOriginalImageName(final Image thisImage) { final Integer tnWidth = thisImage.getWidth(); final Integer tnHeight = thisImage.getHeight(); return StringUtils.stripEnd(thisImage.getName(), "_thumb_" + tnWidth + "x" + tnHeight); } }