From 976ed5eaa6ee127f9c17fe683227188f72c0a8a4 Mon Sep 17 00:00:00 2001 From: Andreas Schaefer Date: Mon, 20 Jul 2020 13:14:58 -0700 Subject: [PATCH 01/19] #460 - Added the Image Info annotation to the Peregrine Sling Module --- .../peregrine/commons/util/PerConstants.java | 1 + .../com/peregrine/commons/util/PerUtil.java | 4 +- platform/per-model/Readme.md | 43 +++ platform/per-model/pom.xml | 245 ++++++++++++++++++ .../peregrine/cms/model/api/ImageInfo.java | 36 +++ .../cms/model/impl/ImageInfoInjector.java | 223 ++++++++++++++++ .../cms/model/impl/ImageInfoInjectorTest.java | 222 ++++++++++++++++ .../cms/testmodels/DefaultImageInfoModel.java | 64 +++++ .../models/impl/ModelAdapterFactorySetup.java | 43 +++ .../models/impl/ServicePropertiesMap.java | 32 +++ .../src/test/resources/test.image.jpg | Bin 0 -> 45139 bytes platform/pom.xml | 1 + 12 files changed, 913 insertions(+), 1 deletion(-) create mode 100644 platform/per-model/Readme.md create mode 100644 platform/per-model/pom.xml create mode 100644 platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java create mode 100644 platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java create mode 100644 platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java create mode 100644 platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java create mode 100644 platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java create mode 100644 platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java create mode 100644 platform/per-model/src/test/resources/test.image.jpg diff --git a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java index 199b44d239..9800099d85 100644 --- a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java +++ b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java @@ -77,6 +77,7 @@ public class PerConstants { public static final String PNG_MIME_TYPE = "image/png"; public static final String JPEG_MIME_TYPE = "image/jpeg"; public static final String WEBP_MIME_TYPE = "image/webp"; + public static final String SVG_MIME_TYPE = "image/svg+xml"; public static final String JPG_MIME_TYPE = "image/jpg"; public static final String PACKAGES = "packages"; diff --git a/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java b/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java index 022c858821..7f78e70c2a 100644 --- a/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java +++ b/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java @@ -49,7 +49,9 @@ public class PerUtil { public static final String RENDITIONS = "renditions"; public static final String METADATA = "metadata"; -// public static final String TEMPLATE = "template"; + public static final String PER_DATA = "per-data"; + public static final String IMAGE_WIDTH = "width"; + public static final String IMAGE_HEIGHT = "height"; public static final String PER_VENDOR = "headwire.com, Inc"; public static final String PER_PREFIX = "Peregrine: "; diff --git a/platform/per-model/Readme.md b/platform/per-model/Readme.md new file mode 100644 index 0000000000..0d6b67fd97 --- /dev/null +++ b/platform/per-model/Readme.md @@ -0,0 +1,43 @@ +# Peregrine Sling Model Annotations + +## Image Info + +The @ImageInfo annotation marks a given string property as the container for image data like +**width and height**. + +### Setup + +1. Add a **String property** to your Sling Model that will contain the info +2. Add these annotations + 1. @Inject + 2. @ImageInfo +3. Add the field name of the image source to the **name** property of the @ImageInfo annotation + +### Effect + +If an image has these nodes: **jcr:content -> metadata -> per-data** the data will be stored in the +String property as: "{'width': 123, 'height': 234}". +If no image data was found then the Image Info property is null. + +## Externalize Path + +The path of an Image can be externalized so that it will reflect the way the users sees the nodes and +not how they are stored in the JCR tree. + +### Setup + +1. Add **ExternalizePathSerializer** as Json Serializer with: +```java +@JsonSerialize(using = ExternalizePathSerializer.class) +``` +2. Add **@ExternalizePath** to the image path field or getter method +```java +@ExternalizePath +public String getImagePath() { + return imagePath; +} +``` + +### Effect + +The image source property is now changed so that it matches the user's view. \ No newline at end of file diff --git a/platform/per-model/pom.xml b/platform/per-model/pom.xml new file mode 100644 index 0000000000..68b99dcdbc --- /dev/null +++ b/platform/per-model/pom.xml @@ -0,0 +1,245 @@ + + + + + 4.0.0 + + + com.peregrine-cms + platform.parent + 1.0-SNAPSHOT + ../pom.xml + + + per-model + bundle + + Peregrine CMS - Platform - Sling Models + + Provides Peregrine Sling Model Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.sling + maven-sling-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + com.peregrine-cms.per-model + *;scope=compile;inline=true + + com.peregrine.cms.model.api, + com.peregrine.cms.model.impl + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + + ${project.groupId} + commons + ${project.version} + + + ${project.groupId} + base.core + ${project.version} + provided + + + + org.osgi + org.osgi.core + provided + + + org.osgi + org.osgi.compendium + provided + + + org.osgi + org.osgi.service.component.annotations + + + javax.inject + javax.inject + + + javax.servlet + javax.servlet-api + + + org.jetbrains + annotations + + + org.apache.commons + commons-lang3 + + + org.apache.sling + org.apache.sling.api + + + org.apache.sling + org.apache.sling.commons.osgi + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + + + org.apache.sling + org.apache.sling.models.api + + + + ${project.groupId} + commons-test + ${project.version} + test + + + org.apache.sling + org.apache.sling.models.impl + 1.4.4 + test + + + org.apache.sling + org.apache.sling.scripting.api + 2.2.0 + test + + + org.apache.felix + org.apache.felix.converter + 1.0.14 + test + + + org.osgi + org.osgi.util.function + 1.0.0 + test + + + junit + junit + test + + + org.mockito + mockito-all + 1.9.5 + test + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + org.slf4j + slf4j-simple + test + + + + + + autoInstallPackage + + + + org.apache.sling + maven-sling-plugin + + + install-bundle + + install + + + + + + + + + diff --git a/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java b/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java new file mode 100644 index 0000000000..5603e30982 --- /dev/null +++ b/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java @@ -0,0 +1,36 @@ +/* + * 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 com.peregrine.cms.model.api; + +import javax.inject.Qualifier; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an Image Property as source for the generation of Image Data. + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface ImageInfo { + /** + * @return Name of the Property with the Image Path + */ + public String name() default ""; +} diff --git a/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java b/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java new file mode 100644 index 0000000000..2d2da5ff5f --- /dev/null +++ b/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java @@ -0,0 +1,223 @@ +/* + * 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 com.peregrine.cms.model.impl; + +import com.peregrine.cms.model.api.ImageInfo; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.adapter.Adaptable; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.models.spi.DisposalCallbackRegistry; +import org.apache.sling.models.spi.Injector; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.peregrine.commons.util.PerUtil.IMAGE_HEIGHT; +import static com.peregrine.commons.util.PerUtil.IMAGE_WIDTH; +import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; +import static com.peregrine.commons.util.PerConstants.JCR_DATA; +import static com.peregrine.commons.util.PerConstants.JCR_MIME_TYPE; +import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; +import static com.peregrine.commons.util.PerUtil.PER_DATA; +import static com.peregrine.commons.util.PerConstants.SLING_FOLDER; +import static com.peregrine.commons.util.PerUtil.METADATA; +import static com.peregrine.commons.util.PerConstants.SVG_MIME_TYPE; + +@Component( + service={ + Injector.class + }, + property=Constants.SERVICE_RANKING + "=" + "2000" +) +public class ImageInfoInjector implements Injector { + + private static final Logger log = LoggerFactory.getLogger(ImageInfoInjector.class); + + public static final String IMAGE_INFO_FORMAT = "{'width': %1$d, 'height': %2$d}"; + + private static final List UNSUPPORTED_IMAGE_MIMES = Arrays.asList(SVG_MIME_TYPE); + + @Override + public @NotNull String getName() { + return "imageinfo"; + } + + @Override + public Object getValue(@NotNull Object adaptable, String name, @NotNull Type type, @NotNull AnnotatedElement element, + @NotNull DisposalCallbackRegistry callbackRegistry + ) { + String answer = null; + if (adaptable != ObjectUtils.NULL) { + Annotation annotation = element.getDeclaredAnnotation(ImageInfo.class); + if (annotation != null) { + ValueMap map = getValueMap(adaptable); + if (map == null) { + return null; + } else if (type instanceof Class) { + Class clazz = (Class) type; + if (clazz == String.class) { + try { + String sourceName = ((ImageInfo) annotation).name(); + ImageData imageData = getImageData(sourceName, adaptable); + if(imageData != null) { + answer = String.format(IMAGE_INFO_FORMAT, imageData.width, imageData.height); + } + } catch (ClassCastException e) { + log.warn("Could not obtain Value from ValueMap with name: '{}'", name); + } + } + } + } + } + return answer; + } + + private ImageData getImageData(String imagePropertyName, Object adaptable) { + ImageData answer = null; + ValueMap map = getValueMap(adaptable); + String imagePath = map.get(imagePropertyName, String.class); + // Only handle local images + if(imagePath.indexOf(":/") < 0) { + Resource imageResource = imagePath == null ? null : getResourceResolver(adaptable).getResource(imagePath); + // Obtain Image + Resource imageContentResource = imageResource == null ? null : imageResource.getChild(JCR_CONTENT); + // Try to get the 'metadata' and the dimension based on the Image Type + if (imageContentResource != null) { + Resource metaData = imageContentResource.getChild(METADATA); + if (metaData != null) { + answer = getDataFromMeta(metaData); + } +// Right now we do not obtain the info when there is no metadata there +// if (answer == null) { +// answer = getDataFromImage(imageContentResource); +// } + } + } + + return answer; + } + + private ImageData getDataFromMeta(Resource metaData) { + ImageData answer = null; + Resource perData = metaData.getChild(PER_DATA); + ValueMap pngProperties = perData == null ? null : perData.getValueMap(); + Integer width = pngProperties.get(IMAGE_WIDTH, Integer.class); + Integer height = pngProperties.get(IMAGE_HEIGHT, Integer.class); + if(pngProperties != null && (width != null || height != null)) { + answer = new ImageData(); + if(width != null) { + answer.setWidth(width); + } + if(height != null) { + answer.setHeight(height); + } + } + return answer; + } + + private ImageData getDataFromImage(Resource imageContentResource) { + ImageData answer = null; + ValueMap properties = imageContentResource.getValueMap(); + String mimeType = properties.get(JCR_MIME_TYPE, String.class); + // SVG images are not handled by ImageIO so we ignore it here + if (mimeType != null && !UNSUPPORTED_IMAGE_MIMES.contains(mimeType)) { + InputStream imageStream = properties.get(JCR_DATA, InputStream.class); + try { + BufferedImage image = imageStream == null ? null : ImageIO.read(imageStream); + if (image != null) { + // Obtain height and width + int height = image.getHeight(); + int width = image.getWidth(); + // Store info into property + answer = new ImageData().setWidth(width).setHeight(height); + // Store data into metadata/per-data node + Resource metaData = imageContentResource.getChild(METADATA); + if (metaData == null) { + Map metadataProperties = new HashMap<>(); + metadataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); + metaData = imageContentResource.getResourceResolver().create(imageContentResource, METADATA, metadataProperties); + Resource perData = metaData.getChild(PER_DATA); + if (perData == null) { + Map perDataProperties = new HashMap<>(); + perDataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); + perData = imageContentResource.getResourceResolver().create(metaData, PER_DATA, perDataProperties); + } + ValueMap perDataProperties = perData.getValueMap(); + perDataProperties.put(IMAGE_WIDTH, width); + perDataProperties.put(IMAGE_HEIGHT, height); + } + } + } catch (IOException e) { + // Ignore + } + } + return answer; + } + + private class ImageData { + private int width; + private int height; + + public ImageData setWidth(int width) { + this.width = width; + return this; + } + + public ImageData setHeight(int height) { + this.height = height; + return this; + } + } + + protected ValueMap getValueMap(Object adaptable) { + if (adaptable instanceof ValueMap) { + return (ValueMap) adaptable; + } else if (adaptable instanceof Adaptable) { + ValueMap map = ((Adaptable) adaptable).adaptTo(ValueMap.class); + return map; + } else { + return null; + } + } + + protected ResourceResolver getResourceResolver(Object adaptable) { + ResourceResolver resolver = null; + if (adaptable instanceof Resource) { + resolver = ((Resource) adaptable).getResourceResolver(); + } else if (adaptable instanceof SlingHttpServletRequest) { + resolver = ((SlingHttpServletRequest) adaptable).getResourceResolver(); + } + return resolver; + } +} diff --git a/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java b/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java new file mode 100644 index 0000000000..827a3d9fb1 --- /dev/null +++ b/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java @@ -0,0 +1,222 @@ +package com.peregrine.cms.model.impl; + +import com.peregrine.cms.testmodels.DefaultImageInfoModel; +import com.peregrine.commons.test.AbstractTest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.models.impl.ModelAdapterFactory; +import org.apache.sling.models.impl.ModelAdapterFactorySetup; +import org.apache.sling.models.impl.injectors.ValueMapInjector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ImageInfoInjectorTest extends AbstractTest { + private Logger logger = LoggerFactory.getLogger(getClass()); + private ModelAdapterFactory factory; + private ResourceResolver resourceResolver; + + @Override + public Logger getLogger() { + return logger; + } + + @Before + public void setup() { + super.setup(); + try { + factory = ModelAdapterFactorySetup.createModelAdapterFactory(null, DefaultImageInfoModel.class); + // Add the Resource in Constructor Injection + ModelAdapterFactorySetup.addInjector(factory, new ImageInfoInjector(), 1); + // Add the Value Map properties Injection + ModelAdapterFactorySetup.addInjector(factory, new ValueMapInjector(), 2); + resourceResolver = mock(ResourceResolver.class, "Test Resource Resolver"); + } catch (Exception e) { + throw new RuntimeException("Setup Failed", e); + } + } + + @Test + public void testImageDataFromMeta() { + String title = "testImageInfo"; + String caption = "Test Image Info"; + String imageName = "013.jpg"; + String imagePath = "/content/unitTest/assets/images/" + imageName; + int width = 111; + int height = 999; + + Resource testPageResource = createMockResource( + "test page", null, "per:Page", + "title", title, "caption", caption, "imagePath", imagePath + ); + // Asset Test Resource + Resource assetTestResource = createMockResource( + imageName, null, "per:Asset" + ); + // Resource Resolver needs to return Asset + when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); + when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); + + // Create JCR Content, Metadata and Per Data node + Resource jcrContentTestResource = createMockResource( + "jcr:content", assetTestResource, "per:AssetContent", + "jcr:mimeType", "image/jpeg" + ); + Resource metaDataResource = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); + Resource perDataResource = createMockResource( + "per-data", metaDataResource, "sling:Folder", + "width", width, "height", height + ); + + DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); + assertNotNull(model); + assertEquals(title, model.getTitle()); + assertEquals(caption, model.getCaption()); + assertEquals(imagePath, model.getImagePath()); + assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); + } + + @Test + public void testImageDataWitNoData() { + String title = "noDataTestImage"; + String caption = "Test Image Info"; + String imageName = "013.jpg"; + String imagePath = "/content/unitTest/assets/images/" + imageName; + + Resource testPageResource = createMockResource( + "test page", null, "per:Page", + "title", title, "caption", caption, "imagePath", imagePath + ); + // Asset Test Resource + Resource assetTestResource = createMockResource( + imageName, null, "per:Asset" + ); + // Resource Resolver needs to return Asset + when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); + when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); + + // Create JCR Content, Metadata and Per Data node + Resource jcrContentTestResource = createMockResource( + "jcr:content", assetTestResource, "per:AssetContent", + "jcr:mimeType", "image/jpeg" + ); + + DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); + assertNotNull(model); + assertEquals(title, model.getTitle()); + assertEquals(caption, model.getCaption()); + assertEquals(imagePath, model.getImagePath()); + assertNull(model.getImageInfo()); + } + +// @Test +// public void testImageDataFromImage() throws Exception { +// String title = "testImageInfo"; +// String caption = "Test Image Info"; +// String imageName = "013.jpg"; +// String imagePath = "/content/unitTest/assets/images/" + imageName; +// int width = 1280; +// int height = 1024; +// +// Resource testPageResource = createMockResource( +// "test page", null, "per:Page", +// "title", title, "caption", caption, "imagePath", imagePath +// ); +// // Asset Test Resource +// Resource assetTestResource = createMockResource( +// imageName, null, "per:Asset" +// ); +// // Resource Resolver needs to return Asset +// when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); +// when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); +// +// InputStream is = getClass().getResourceAsStream("/test.image.jpg"); +// // Create JCR Content, Metadata and Per Data node +// Resource jcrContentTestResource = createMockResource( +// "jcr:content", assetTestResource, "per:AssetContent", +// "jcr:mimeType", "image/jpeg", +// "jcr:data", is +// ); +// +// // Creation of the Metadata resource must be delated until it is created by the ImageInfoInjector and with it the per-data resource +// Map createdResource = new HashMap<>(); +// when(resourceResolver.create(eq(jcrContentTestResource), eq("metadata"), any(Map.class))) +// .thenAnswer( +// invocation -> { +// Resource answer = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); +// createdResource.put("metadata", answer); +// return answer; +// } +// ); +// when(resourceResolver.create(any(Resource.class), eq("per-data"), any(Map.class))) +// .thenAnswer( +// invocation -> { +// Resource metaDataResource = createdResource.get("metadata"); +// Resource answer = createMockResource("per-data", metaDataResource, "sling:Folder"); +// createdResource.put("per-data", answer); +// return answer; +// } +// ); +// +// DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); +// assertNotNull("Failed to create Model", model); +// assertEquals(title, model.getTitle()); +// assertEquals(caption, model.getCaption()); +// assertEquals(imagePath, model.getImagePath()); +// assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); +// Resource perDataResource = createdResource.get("per-data"); +// assertNotNull("per-data resource not found", perDataResource); +// ValueMap properties = perDataResource.getValueMap(); +// assertEquals(properties.get("width"), width); +// assertEquals(properties.get("height"), height); +// } + + private Resource createMockResource(String name, Resource parent, String primaryType, Object...properties) { + Resource answer = name == null ? mock(Resource.class) : mock(Resource.class, name); + when(answer.getResourceResolver()).thenReturn(resourceResolver); + if(parent != null) { + when(parent.getParent()).thenReturn(parent); + when(parent.getChild(name)).thenReturn(answer); + } + HashMap values = new HashMap<>(); + if(primaryType != null) { + values.put(JCR_PRIMARY_TYPE, primaryType); + } + ValueMap vm; + if(properties != null) { + if(properties.length % 2 != 0) { + throw new IllegalArgumentException("Properties list must be on even number of entries but was: " + properties.length); + } + int count = properties.length / 2; + for(int i = 0; i < count; i++) { + values.put(properties[2 * i] + "", properties[(2*i)+1]); + } + vm = new ValueMapDecorator(values); + } else { + vm = new ValueMapDecorator(Collections.EMPTY_MAP); + } + when(answer.getValueMap()).thenReturn(vm); + when(answer.adaptTo(ValueMap.class)).thenReturn(vm); + return answer; + } +} diff --git a/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java b/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java new file mode 100644 index 0000000000..1f48a6b1e1 --- /dev/null +++ b/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java @@ -0,0 +1,64 @@ +/* + * 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 com.peregrine.cms.testmodels; + +import com.peregrine.cms.model.api.ImageInfo; +import com.peregrine.nodetypes.models.AbstractComponent; +import com.peregrine.nodetypes.models.IComponent; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; +import org.apache.sling.models.annotations.DefaultInjectionStrategy; +import org.apache.sling.models.annotations.Model; + +import javax.inject.Inject; + +@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, adapters = IComponent.class) +public class DefaultImageInfoModel extends AbstractComponent { + + @Inject + private String title; + + @Inject + private String caption; + + @Inject @Default(values = "") + private String imagePath; + + @Inject + @ImageInfo(name = "imagePath") + private String imageInfo; + + public DefaultImageInfoModel(Resource resource) { + super(resource); + } + + public String getTitle() { + return title; + } + + public String getCaption() { + return caption; + } + + public String getImagePath() { + return imagePath; + } + + public String getImageInfo() { + return imageInfo; + } +} diff --git a/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java b/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java new file mode 100644 index 0000000000..8ba2d205f5 --- /dev/null +++ b/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java @@ -0,0 +1,43 @@ +package org.apache.sling.models.impl; + +import org.apache.sling.models.spi.Injector; +import org.mockito.Mockito; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.Converters; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static org.mockito.Mockito.when; + +public class ModelAdapterFactorySetup { + public static ModelAdapterFactory createModelAdapterFactory(Injector injector, Class...models) throws Exception { + BundleContext bundleContext = Mockito.mock(BundleContext.class); + ModelAdapterFactory factory = createModelAdapterFactory(bundleContext); + if(injector != null) { + factory.bindInjector(injector, new ServicePropertiesMap(0, 0)); + } + factory.adapterImplementations.addClassesAsAdapterAndImplementation(models); + return factory; + } + + public static ModelAdapterFactory createModelAdapterFactory(BundleContext bundleContext) throws Exception { + ComponentContext componentCtx = Mockito.mock(ComponentContext.class); + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + when(componentCtx.getProperties()).thenReturn(new Hashtable()); + + ModelAdapterFactory factory = new ModelAdapterFactory(); + Converter c = Converters.standardConverter(); + Map map = new HashMap<>(); + factory.activate(componentCtx); + return factory; + } + + public static void addInjector(ModelAdapterFactory factory, Injector injector, int ranking) { + factory.bindInjector(injector, new ServicePropertiesMap(ranking, ranking)); + } +} diff --git a/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java b/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java new file mode 100644 index 0000000000..2f5294a90b --- /dev/null +++ b/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java @@ -0,0 +1,32 @@ +/* + * 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.models.impl; + +import org.osgi.framework.Constants; + +import java.util.HashMap; + +@SuppressWarnings("serial") +public class ServicePropertiesMap extends HashMap { + + public ServicePropertiesMap(long serviceId, int serviceRanking) { + super(); + put(Constants.SERVICE_ID, serviceId); + put(Constants.SERVICE_RANKING, serviceRanking); + } + +} diff --git a/platform/per-model/src/test/resources/test.image.jpg b/platform/per-model/src/test/resources/test.image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..41cb267c86bf1fc24b95fd0be00285a020fdfd4b GIT binary patch literal 45139 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 literal 0 HcmV?d00001 diff --git a/platform/pom.xml b/platform/pom.xml index 570a9106b2..7133193a72 100644 --- a/platform/pom.xml +++ b/platform/pom.xml @@ -17,6 +17,7 @@ commons commons-test + per-model base external distribution From 0829c50b1c96ca9e3c9c4ed1e1b320422a65b36d Mon Sep 17 00:00:00 2001 From: Andreas Schaefer Date: Tue, 4 Aug 2020 14:23:44 -0700 Subject: [PATCH 02/19] Removed per-model and moved the code into base.core --- platform/base/core/pom.xml | 46 +++- .../com/peregrine/model/api/ImageInfo.java | 36 +++ .../model/impl/ImageInfoInjector.java | 223 ++++++++++++++++++ .../model/impl/ImageInfoInjectorTest.java | 222 +++++++++++++++++ .../testmodels/DefaultImageInfoModel.java | 64 +++++ .../models/impl/ModelAdapterFactorySetup.java | 42 ++++ .../models/impl/ServicePropertiesMap.java | 32 +++ platform/pom.xml | 1 - 8 files changed, 656 insertions(+), 10 deletions(-) create mode 100644 platform/base/core/src/main/java/com/peregrine/model/api/ImageInfo.java create mode 100644 platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java create mode 100644 platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java create mode 100644 platform/base/core/src/test/java/com/peregrine/testmodels/DefaultImageInfoModel.java create mode 100644 platform/base/core/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java create mode 100644 platform/base/core/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java diff --git a/platform/base/core/pom.xml b/platform/base/core/pom.xml index 710e34f339..226dbb2d6e 100644 --- a/platform/base/core/pom.xml +++ b/platform/base/core/pom.xml @@ -58,7 +58,9 @@ com.peregrine.replication, com.peregrine.transform, com.peregrine.transform.*, - com.peregrine.commons.util + com.peregrine.commons.util, + com.peregrine.model.api, + com.peregrine.model.impl *;scope=compile;inline=true com.peregrine.nodetypes.models @@ -223,14 +225,6 @@ com.google.guava - - - - - - - - com.fasterxml.jackson.core jackson-annotations @@ -248,6 +242,10 @@ javax.inject javax.inject + + org.jetbrains + annotations + org.apache.sling @@ -276,6 +274,36 @@ test-jar test + + ${project.groupId} + commons-test + ${project.version} + test + + + org.apache.sling + org.apache.sling.models.impl + 1.4.2 + test + + + org.apache.sling + org.apache.sling.scripting.api + 2.2.0 + test + + + org.apache.felix + org.apache.felix.converter + 1.0.14 + test + + + org.osgi + org.osgi.util.function + 1.0.0 + test + diff --git a/platform/base/core/src/main/java/com/peregrine/model/api/ImageInfo.java b/platform/base/core/src/main/java/com/peregrine/model/api/ImageInfo.java new file mode 100644 index 0000000000..7d862f283a --- /dev/null +++ b/platform/base/core/src/main/java/com/peregrine/model/api/ImageInfo.java @@ -0,0 +1,36 @@ +/* + * 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 com.peregrine.model.api; + +import javax.inject.Qualifier; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an Image Property as source for the generation of Image Data. + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface ImageInfo { + /** + * @return Name of the Property with the Image Path + */ + public String name() default ""; +} diff --git a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java new file mode 100644 index 0000000000..572477f850 --- /dev/null +++ b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java @@ -0,0 +1,223 @@ +/* + * 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 com.peregrine.model.impl; + +import com.peregrine.model.api.ImageInfo; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.adapter.Adaptable; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.models.spi.DisposalCallbackRegistry; +import org.apache.sling.models.spi.Injector; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; +import static com.peregrine.commons.util.PerConstants.JCR_DATA; +import static com.peregrine.commons.util.PerConstants.JCR_MIME_TYPE; +import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; +import static com.peregrine.commons.util.PerConstants.SLING_FOLDER; +import static com.peregrine.commons.util.PerConstants.SVG_MIME_TYPE; +import static com.peregrine.commons.util.PerUtil.IMAGE_HEIGHT; +import static com.peregrine.commons.util.PerUtil.IMAGE_WIDTH; +import static com.peregrine.commons.util.PerUtil.METADATA; +import static com.peregrine.commons.util.PerUtil.PER_DATA; + +@Component( + service={ + Injector.class + }, + property=Constants.SERVICE_RANKING + "=" + "2000" +) +public class ImageInfoInjector implements Injector { + + private static final Logger log = LoggerFactory.getLogger(ImageInfoInjector.class); + + public static final String IMAGE_INFO_FORMAT = "{'width': %1$d, 'height': %2$d}"; + + private static final List UNSUPPORTED_IMAGE_MIMES = Arrays.asList(SVG_MIME_TYPE); + + @Override + public @NotNull String getName() { + return "imageinfo"; + } + + @Override + public Object getValue(@NotNull Object adaptable, String name, @NotNull Type type, @NotNull AnnotatedElement element, + @NotNull DisposalCallbackRegistry callbackRegistry + ) { + String answer = null; + try { + if (adaptable != ObjectUtils.NULL) { + Annotation annotation = element.getDeclaredAnnotation(ImageInfo.class); + if (annotation != null) { + ValueMap map = getValueMap(adaptable); + if (map == null) { + return null; + } else if (type instanceof Class) { + Class clazz = (Class) type; + if (clazz == String.class) { + try { + String sourceName = ((ImageInfo) annotation).name(); + ImageData imageData = getImageData(sourceName, adaptable); + if (imageData != null) { + answer = String.format(IMAGE_INFO_FORMAT, imageData.width, imageData.height); + } + } catch (ClassCastException e) { + log.warn("Could not obtain Value from ValueMap with name: '{}'", name); + } + } + } + } + } + } catch(RuntimeException e) { + // A Runtime Exception is causing the entire page fail to load (due to be children being null) + } + return answer; + } + + private ImageData getImageData(String imagePropertyName, Object adaptable) { + ImageData answer = null; + ValueMap map = getValueMap(adaptable); + String imagePath = map.get(imagePropertyName, String.class); + // Only handle local images + if(imagePath.indexOf(":/") < 0) { + Resource imageResource = imagePath == null ? null : getResourceResolver(adaptable).getResource(imagePath); + // Obtain Image + Resource imageContentResource = imageResource == null ? null : imageResource.getChild(JCR_CONTENT); + // Try to get the 'metadata' and the dimension based on the Image Type + if (imageContentResource != null) { + Resource metaData = imageContentResource.getChild(METADATA); + if (metaData != null) { + answer = getDataFromMeta(metaData); + } +// Right now we do not obtain the info when there is no metadata there +// if (answer == null) { +// answer = getDataFromImage(imageContentResource); +// } + } + } + + return answer; + } + + private ImageData getDataFromMeta(Resource metaData) { + ImageData answer = null; + Resource perData = metaData.getChild(PER_DATA); + ValueMap pngProperties = perData == null ? null : perData.getValueMap(); + if(pngProperties != null) { + Integer width = pngProperties.get(IMAGE_WIDTH, Integer.class); + Integer height = pngProperties.get(IMAGE_HEIGHT, Integer.class); + if (width != null || height != null) { + answer = new ImageData().setWidth(width).setHeight(height); + } + } + return answer; + } + + private ImageData getDataFromImage(Resource imageContentResource) { + ImageData answer = null; + ValueMap properties = imageContentResource.getValueMap(); + String mimeType = properties.get(JCR_MIME_TYPE, String.class); + // SVG images are not handled by ImageIO so we ignore it here + if (mimeType != null && !UNSUPPORTED_IMAGE_MIMES.contains(mimeType)) { + InputStream imageStream = properties.get(JCR_DATA, InputStream.class); + try { + BufferedImage image = imageStream == null ? null : ImageIO.read(imageStream); + if (image != null) { + // Obtain height and width + int height = image.getHeight(); + int width = image.getWidth(); + // Store info into property + answer = new ImageData().setWidth(width).setHeight(height); + // Store data into metadata/per-data node + Resource metaData = imageContentResource.getChild(METADATA); + if (metaData == null) { + Map metadataProperties = new HashMap<>(); + metadataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); + metaData = imageContentResource.getResourceResolver().create(imageContentResource, METADATA, metadataProperties); + Resource perData = metaData.getChild(PER_DATA); + if (perData == null) { + Map perDataProperties = new HashMap<>(); + perDataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); + perData = imageContentResource.getResourceResolver().create(metaData, PER_DATA, perDataProperties); + } + ValueMap perDataProperties = perData.getValueMap(); + perDataProperties.put(IMAGE_WIDTH, width); + perDataProperties.put(IMAGE_HEIGHT, height); + } + } + } catch (IOException e) { + // Ignore + } + } + return answer; + } + + private class ImageData { + private int width; + private int height; + + public ImageData setWidth(Integer width) { + this.width = width == null ? 0 : width; + return this; + } + + public ImageData setHeight(Integer height) { + this.height = height == null ? 0 : height; + return this; + } + } + + protected ValueMap getValueMap(Object adaptable) { + if (adaptable instanceof ValueMap) { + return (ValueMap) adaptable; + } else if (adaptable instanceof Adaptable) { + ValueMap map = ((Adaptable) adaptable).adaptTo(ValueMap.class); + return map; + } else { + return null; + } + } + + protected ResourceResolver getResourceResolver(Object adaptable) { + ResourceResolver resolver = null; + if (adaptable instanceof Resource) { + resolver = ((Resource) adaptable).getResourceResolver(); + } else if (adaptable instanceof SlingHttpServletRequest) { + resolver = ((SlingHttpServletRequest) adaptable).getResourceResolver(); + } + return resolver; + } +} diff --git a/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java b/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java new file mode 100644 index 0000000000..780a836560 --- /dev/null +++ b/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java @@ -0,0 +1,222 @@ +package com.peregrine.model.impl; + +import com.peregrine.testmodels.DefaultImageInfoModel; +import com.peregrine.commons.test.AbstractTest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.models.impl.ModelAdapterFactory; +import org.apache.sling.models.impl.ModelAdapterFactorySetup; +import org.apache.sling.models.impl.injectors.ValueMapInjector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ImageInfoInjectorTest extends AbstractTest { + private Logger logger = LoggerFactory.getLogger(getClass()); + private ModelAdapterFactory factory; + private ResourceResolver resourceResolver; + + @Override + public Logger getLogger() { + return logger; + } + + @Before + public void setup() { + super.setup(); + try { + factory = ModelAdapterFactorySetup.createModelAdapterFactory(null, DefaultImageInfoModel.class); + // Add the Resource in Constructor Injection + ModelAdapterFactorySetup.addInjector(factory, new ImageInfoInjector(), 1); + // Add the Value Map properties Injection + ModelAdapterFactorySetup.addInjector(factory, new ValueMapInjector(), 2); + resourceResolver = mock(ResourceResolver.class, "Test Resource Resolver"); + } catch (Exception e) { + throw new RuntimeException("Setup Failed", e); + } + } + + @Test + public void testImageDataFromMeta() { + String title = "testImageInfo"; + String caption = "Test Image Info"; + String imageName = "013.jpg"; + String imagePath = "/content/unitTest/assets/images/" + imageName; + int width = 111; + int height = 999; + + Resource testPageResource = createMockResource( + "test page", null, "per:Page", + "title", title, "caption", caption, "imagePath", imagePath + ); + // Asset Test Resource + Resource assetTestResource = createMockResource( + imageName, null, "per:Asset" + ); + // Resource Resolver needs to return Asset + when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); + when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); + + // Create JCR Content, Metadata and Per Data node + Resource jcrContentTestResource = createMockResource( + "jcr:content", assetTestResource, "per:AssetContent", + "jcr:mimeType", "image/jpeg" + ); + Resource metaDataResource = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); + Resource perDataResource = createMockResource( + "per-data", metaDataResource, "sling:Folder", + "width", width, "height", height + ); + + DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); + assertNotNull(model); + assertEquals(title, model.getTitle()); + assertEquals(caption, model.getCaption()); + assertEquals(imagePath, model.getImagePath()); + assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); + } + + @Test + public void testImageDataWitNoData() { + String title = "noDataTestImage"; + String caption = "Test Image Info"; + String imageName = "013.jpg"; + String imagePath = "/content/unitTest/assets/images/" + imageName; + + Resource testPageResource = createMockResource( + "test page", null, "per:Page", + "title", title, "caption", caption, "imagePath", imagePath + ); + // Asset Test Resource + Resource assetTestResource = createMockResource( + imageName, null, "per:Asset" + ); + // Resource Resolver needs to return Asset + when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); + when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); + + // Create JCR Content, Metadata and Per Data node + Resource jcrContentTestResource = createMockResource( + "jcr:content", assetTestResource, "per:AssetContent", + "jcr:mimeType", "image/jpeg" + ); + + DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); + assertNotNull(model); + assertEquals(title, model.getTitle()); + assertEquals(caption, model.getCaption()); + assertEquals(imagePath, model.getImagePath()); + assertNull(model.getImageInfo()); + } + +// @Test +// public void testImageDataFromImage() throws Exception { +// String title = "testImageInfo"; +// String caption = "Test Image Info"; +// String imageName = "013.jpg"; +// String imagePath = "/content/unitTest/assets/images/" + imageName; +// int width = 1280; +// int height = 1024; +// +// Resource testPageResource = createMockResource( +// "test page", null, "per:Page", +// "title", title, "caption", caption, "imagePath", imagePath +// ); +// // Asset Test Resource +// Resource assetTestResource = createMockResource( +// imageName, null, "per:Asset" +// ); +// // Resource Resolver needs to return Asset +// when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); +// when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); +// +// InputStream is = getClass().getResourceAsStream("/test.image.jpg"); +// // Create JCR Content, Metadata and Per Data node +// Resource jcrContentTestResource = createMockResource( +// "jcr:content", assetTestResource, "per:AssetContent", +// "jcr:mimeType", "image/jpeg", +// "jcr:data", is +// ); +// +// // Creation of the Metadata resource must be delated until it is created by the ImageInfoInjector and with it the per-data resource +// Map createdResource = new HashMap<>(); +// when(resourceResolver.create(eq(jcrContentTestResource), eq("metadata"), any(Map.class))) +// .thenAnswer( +// invocation -> { +// Resource answer = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); +// createdResource.put("metadata", answer); +// return answer; +// } +// ); +// when(resourceResolver.create(any(Resource.class), eq("per-data"), any(Map.class))) +// .thenAnswer( +// invocation -> { +// Resource metaDataResource = createdResource.get("metadata"); +// Resource answer = createMockResource("per-data", metaDataResource, "sling:Folder"); +// createdResource.put("per-data", answer); +// return answer; +// } +// ); +// +// DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); +// assertNotNull("Failed to create Model", model); +// assertEquals(title, model.getTitle()); +// assertEquals(caption, model.getCaption()); +// assertEquals(imagePath, model.getImagePath()); +// assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); +// Resource perDataResource = createdResource.get("per-data"); +// assertNotNull("per-data resource not found", perDataResource); +// ValueMap properties = perDataResource.getValueMap(); +// assertEquals(properties.get("width"), width); +// assertEquals(properties.get("height"), height); +// } + + private Resource createMockResource(String name, Resource parent, String primaryType, Object...properties) { + Resource answer = name == null ? mock(Resource.class) : mock(Resource.class, name); + when(answer.getResourceResolver()).thenReturn(resourceResolver); + if(parent != null) { + when(parent.getParent()).thenReturn(parent); + when(parent.getChild(name)).thenReturn(answer); + } + HashMap values = new HashMap<>(); + if(primaryType != null) { + values.put(JCR_PRIMARY_TYPE, primaryType); + } + ValueMap vm; + if(properties != null) { + if(properties.length % 2 != 0) { + throw new IllegalArgumentException("Properties list must be on even number of entries but was: " + properties.length); + } + int count = properties.length / 2; + for(int i = 0; i < count; i++) { + values.put(properties[2 * i] + "", properties[(2*i)+1]); + } + vm = new ValueMapDecorator(values); + } else { + vm = new ValueMapDecorator(Collections.EMPTY_MAP); + } + when(answer.getValueMap()).thenReturn(vm); + when(answer.adaptTo(ValueMap.class)).thenReturn(vm); + return answer; + } +} diff --git a/platform/base/core/src/test/java/com/peregrine/testmodels/DefaultImageInfoModel.java b/platform/base/core/src/test/java/com/peregrine/testmodels/DefaultImageInfoModel.java new file mode 100644 index 0000000000..803e20959e --- /dev/null +++ b/platform/base/core/src/test/java/com/peregrine/testmodels/DefaultImageInfoModel.java @@ -0,0 +1,64 @@ +/* + * 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 com.peregrine.testmodels; + +import com.peregrine.model.api.ImageInfo; +import com.peregrine.nodetypes.models.AbstractComponent; +import com.peregrine.nodetypes.models.IComponent; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; +import org.apache.sling.models.annotations.DefaultInjectionStrategy; +import org.apache.sling.models.annotations.Model; + +import javax.inject.Inject; + +@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, adapters = IComponent.class) +public class DefaultImageInfoModel extends AbstractComponent { + + @Inject + private String title; + + @Inject + private String caption; + + @Inject @Default(values = "") + private String imagePath; + + @Inject + @ImageInfo(name = "imagePath") + private String imageInfo; + + public DefaultImageInfoModel(Resource resource) { + super(resource); + } + + public String getTitle() { + return title; + } + + public String getCaption() { + return caption; + } + + public String getImagePath() { + return imagePath; + } + + public String getImageInfo() { + return imageInfo; + } +} diff --git a/platform/base/core/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java b/platform/base/core/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java new file mode 100644 index 0000000000..b89dccec40 --- /dev/null +++ b/platform/base/core/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java @@ -0,0 +1,42 @@ +package org.apache.sling.models.impl; + +import org.apache.sling.models.spi.Injector; +import org.mockito.Mockito; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.Converters; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import static org.mockito.Mockito.when; + +public class ModelAdapterFactorySetup { + public static ModelAdapterFactory createModelAdapterFactory(Injector injector, Class...models) throws Exception { + BundleContext bundleContext = Mockito.mock(BundleContext.class); + ModelAdapterFactory factory = createModelAdapterFactory(bundleContext); + if(injector != null) { + factory.bindInjector(injector, new ServicePropertiesMap(0, 0)); + } + factory.adapterImplementations.addClassesAsAdapterAndImplementation(models); + return factory; + } + + public static ModelAdapterFactory createModelAdapterFactory(BundleContext bundleContext) throws Exception { + ComponentContext componentCtx = Mockito.mock(ComponentContext.class); + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + when(componentCtx.getProperties()).thenReturn(new Hashtable()); + + ModelAdapterFactory factory = new ModelAdapterFactory(); + Converter c = Converters.standardConverter(); + Map map = new HashMap<>(); + factory.activate(componentCtx); + return factory; + } + + public static void addInjector(ModelAdapterFactory factory, Injector injector, int ranking) { + factory.bindInjector(injector, new ServicePropertiesMap(ranking, ranking)); + } +} diff --git a/platform/base/core/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java b/platform/base/core/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java new file mode 100644 index 0000000000..2f5294a90b --- /dev/null +++ b/platform/base/core/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java @@ -0,0 +1,32 @@ +/* + * 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.models.impl; + +import org.osgi.framework.Constants; + +import java.util.HashMap; + +@SuppressWarnings("serial") +public class ServicePropertiesMap extends HashMap { + + public ServicePropertiesMap(long serviceId, int serviceRanking) { + super(); + put(Constants.SERVICE_ID, serviceId); + put(Constants.SERVICE_RANKING, serviceRanking); + } + +} diff --git a/platform/pom.xml b/platform/pom.xml index 62409d471a..0a5378f730 100644 --- a/platform/pom.xml +++ b/platform/pom.xml @@ -17,7 +17,6 @@ commons commons-test - per-model base external distribution From 3271dcf0965d29a90e2c1efeae3f8eb9ad5b39aa Mon Sep 17 00:00:00 2001 From: Andreas Schaefer Date: Tue, 4 Aug 2020 14:39:59 -0700 Subject: [PATCH 03/19] Removed the per-model module --- platform/per-model/Readme.md | 43 --- platform/per-model/pom.xml | 245 ------------------ .../peregrine/cms/model/api/ImageInfo.java | 36 --- .../cms/model/impl/ImageInfoInjector.java | 223 ---------------- .../cms/model/impl/ImageInfoInjectorTest.java | 222 ---------------- .../cms/testmodels/DefaultImageInfoModel.java | 64 ----- .../models/impl/ModelAdapterFactorySetup.java | 43 --- .../models/impl/ServicePropertiesMap.java | 32 --- .../src/test/resources/test.image.jpg | Bin 45139 -> 0 bytes 9 files changed, 908 deletions(-) delete mode 100644 platform/per-model/Readme.md delete mode 100644 platform/per-model/pom.xml delete mode 100644 platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java delete mode 100644 platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java delete mode 100644 platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java delete mode 100644 platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java delete mode 100644 platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java delete mode 100644 platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java delete mode 100644 platform/per-model/src/test/resources/test.image.jpg diff --git a/platform/per-model/Readme.md b/platform/per-model/Readme.md deleted file mode 100644 index 0d6b67fd97..0000000000 --- a/platform/per-model/Readme.md +++ /dev/null @@ -1,43 +0,0 @@ -# Peregrine Sling Model Annotations - -## Image Info - -The @ImageInfo annotation marks a given string property as the container for image data like -**width and height**. - -### Setup - -1. Add a **String property** to your Sling Model that will contain the info -2. Add these annotations - 1. @Inject - 2. @ImageInfo -3. Add the field name of the image source to the **name** property of the @ImageInfo annotation - -### Effect - -If an image has these nodes: **jcr:content -> metadata -> per-data** the data will be stored in the -String property as: "{'width': 123, 'height': 234}". -If no image data was found then the Image Info property is null. - -## Externalize Path - -The path of an Image can be externalized so that it will reflect the way the users sees the nodes and -not how they are stored in the JCR tree. - -### Setup - -1. Add **ExternalizePathSerializer** as Json Serializer with: -```java -@JsonSerialize(using = ExternalizePathSerializer.class) -``` -2. Add **@ExternalizePath** to the image path field or getter method -```java -@ExternalizePath -public String getImagePath() { - return imagePath; -} -``` - -### Effect - -The image source property is now changed so that it matches the user's view. \ No newline at end of file diff --git a/platform/per-model/pom.xml b/platform/per-model/pom.xml deleted file mode 100644 index 68b99dcdbc..0000000000 --- a/platform/per-model/pom.xml +++ /dev/null @@ -1,245 +0,0 @@ - - - - - 4.0.0 - - - com.peregrine-cms - platform.parent - 1.0-SNAPSHOT - ../pom.xml - - - per-model - bundle - - Peregrine CMS - Platform - Sling Models - - Provides Peregrine Sling Model Components - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.apache.sling - maven-sling-plugin - - - org.apache.felix - maven-bundle-plugin - true - - - com.peregrine-cms.per-model - *;scope=compile;inline=true - - com.peregrine.cms.model.api, - com.peregrine.cms.model.impl - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - - - - - ${project.groupId} - commons - ${project.version} - - - ${project.groupId} - base.core - ${project.version} - provided - - - - org.osgi - org.osgi.core - provided - - - org.osgi - org.osgi.compendium - provided - - - org.osgi - org.osgi.service.component.annotations - - - javax.inject - javax.inject - - - javax.servlet - javax.servlet-api - - - org.jetbrains - annotations - - - org.apache.commons - commons-lang3 - - - org.apache.sling - org.apache.sling.api - - - org.apache.sling - org.apache.sling.commons.osgi - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - org.slf4j - slf4j-api - - - org.apache.sling - org.apache.sling.models.api - - - - ${project.groupId} - commons-test - ${project.version} - test - - - org.apache.sling - org.apache.sling.models.impl - 1.4.4 - test - - - org.apache.sling - org.apache.sling.scripting.api - 2.2.0 - test - - - org.apache.felix - org.apache.felix.converter - 1.0.14 - test - - - org.osgi - org.osgi.util.function - 1.0.0 - test - - - junit - junit - test - - - org.mockito - mockito-all - 1.9.5 - test - - - org.hamcrest - hamcrest-junit - 2.0.0.0 - test - - - org.slf4j - slf4j-simple - test - - - - - - autoInstallPackage - - - - org.apache.sling - maven-sling-plugin - - - install-bundle - - install - - - - - - - - - diff --git a/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java b/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java deleted file mode 100644 index 5603e30982..0000000000 --- a/platform/per-model/src/main/java/com/peregrine/cms/model/api/ImageInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 com.peregrine.cms.model.api; - -import javax.inject.Qualifier; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marks an Image Property as source for the generation of Image Data. - */ -@Target({ ElementType.FIELD }) -@Retention(RetentionPolicy.RUNTIME) -@Qualifier -public @interface ImageInfo { - /** - * @return Name of the Property with the Image Path - */ - public String name() default ""; -} diff --git a/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java b/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java deleted file mode 100644 index 2d2da5ff5f..0000000000 --- a/platform/per-model/src/main/java/com/peregrine/cms/model/impl/ImageInfoInjector.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 com.peregrine.cms.model.impl; - -import com.peregrine.cms.model.api.ImageInfo; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.adapter.Adaptable; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.models.spi.DisposalCallbackRegistry; -import org.apache.sling.models.spi.Injector; -import org.jetbrains.annotations.NotNull; -import org.osgi.framework.Constants; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.peregrine.commons.util.PerUtil.IMAGE_HEIGHT; -import static com.peregrine.commons.util.PerUtil.IMAGE_WIDTH; -import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; -import static com.peregrine.commons.util.PerConstants.JCR_DATA; -import static com.peregrine.commons.util.PerConstants.JCR_MIME_TYPE; -import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; -import static com.peregrine.commons.util.PerUtil.PER_DATA; -import static com.peregrine.commons.util.PerConstants.SLING_FOLDER; -import static com.peregrine.commons.util.PerUtil.METADATA; -import static com.peregrine.commons.util.PerConstants.SVG_MIME_TYPE; - -@Component( - service={ - Injector.class - }, - property=Constants.SERVICE_RANKING + "=" + "2000" -) -public class ImageInfoInjector implements Injector { - - private static final Logger log = LoggerFactory.getLogger(ImageInfoInjector.class); - - public static final String IMAGE_INFO_FORMAT = "{'width': %1$d, 'height': %2$d}"; - - private static final List UNSUPPORTED_IMAGE_MIMES = Arrays.asList(SVG_MIME_TYPE); - - @Override - public @NotNull String getName() { - return "imageinfo"; - } - - @Override - public Object getValue(@NotNull Object adaptable, String name, @NotNull Type type, @NotNull AnnotatedElement element, - @NotNull DisposalCallbackRegistry callbackRegistry - ) { - String answer = null; - if (adaptable != ObjectUtils.NULL) { - Annotation annotation = element.getDeclaredAnnotation(ImageInfo.class); - if (annotation != null) { - ValueMap map = getValueMap(adaptable); - if (map == null) { - return null; - } else if (type instanceof Class) { - Class clazz = (Class) type; - if (clazz == String.class) { - try { - String sourceName = ((ImageInfo) annotation).name(); - ImageData imageData = getImageData(sourceName, adaptable); - if(imageData != null) { - answer = String.format(IMAGE_INFO_FORMAT, imageData.width, imageData.height); - } - } catch (ClassCastException e) { - log.warn("Could not obtain Value from ValueMap with name: '{}'", name); - } - } - } - } - } - return answer; - } - - private ImageData getImageData(String imagePropertyName, Object adaptable) { - ImageData answer = null; - ValueMap map = getValueMap(adaptable); - String imagePath = map.get(imagePropertyName, String.class); - // Only handle local images - if(imagePath.indexOf(":/") < 0) { - Resource imageResource = imagePath == null ? null : getResourceResolver(adaptable).getResource(imagePath); - // Obtain Image - Resource imageContentResource = imageResource == null ? null : imageResource.getChild(JCR_CONTENT); - // Try to get the 'metadata' and the dimension based on the Image Type - if (imageContentResource != null) { - Resource metaData = imageContentResource.getChild(METADATA); - if (metaData != null) { - answer = getDataFromMeta(metaData); - } -// Right now we do not obtain the info when there is no metadata there -// if (answer == null) { -// answer = getDataFromImage(imageContentResource); -// } - } - } - - return answer; - } - - private ImageData getDataFromMeta(Resource metaData) { - ImageData answer = null; - Resource perData = metaData.getChild(PER_DATA); - ValueMap pngProperties = perData == null ? null : perData.getValueMap(); - Integer width = pngProperties.get(IMAGE_WIDTH, Integer.class); - Integer height = pngProperties.get(IMAGE_HEIGHT, Integer.class); - if(pngProperties != null && (width != null || height != null)) { - answer = new ImageData(); - if(width != null) { - answer.setWidth(width); - } - if(height != null) { - answer.setHeight(height); - } - } - return answer; - } - - private ImageData getDataFromImage(Resource imageContentResource) { - ImageData answer = null; - ValueMap properties = imageContentResource.getValueMap(); - String mimeType = properties.get(JCR_MIME_TYPE, String.class); - // SVG images are not handled by ImageIO so we ignore it here - if (mimeType != null && !UNSUPPORTED_IMAGE_MIMES.contains(mimeType)) { - InputStream imageStream = properties.get(JCR_DATA, InputStream.class); - try { - BufferedImage image = imageStream == null ? null : ImageIO.read(imageStream); - if (image != null) { - // Obtain height and width - int height = image.getHeight(); - int width = image.getWidth(); - // Store info into property - answer = new ImageData().setWidth(width).setHeight(height); - // Store data into metadata/per-data node - Resource metaData = imageContentResource.getChild(METADATA); - if (metaData == null) { - Map metadataProperties = new HashMap<>(); - metadataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); - metaData = imageContentResource.getResourceResolver().create(imageContentResource, METADATA, metadataProperties); - Resource perData = metaData.getChild(PER_DATA); - if (perData == null) { - Map perDataProperties = new HashMap<>(); - perDataProperties.put(JCR_PRIMARY_TYPE, SLING_FOLDER); - perData = imageContentResource.getResourceResolver().create(metaData, PER_DATA, perDataProperties); - } - ValueMap perDataProperties = perData.getValueMap(); - perDataProperties.put(IMAGE_WIDTH, width); - perDataProperties.put(IMAGE_HEIGHT, height); - } - } - } catch (IOException e) { - // Ignore - } - } - return answer; - } - - private class ImageData { - private int width; - private int height; - - public ImageData setWidth(int width) { - this.width = width; - return this; - } - - public ImageData setHeight(int height) { - this.height = height; - return this; - } - } - - protected ValueMap getValueMap(Object adaptable) { - if (adaptable instanceof ValueMap) { - return (ValueMap) adaptable; - } else if (adaptable instanceof Adaptable) { - ValueMap map = ((Adaptable) adaptable).adaptTo(ValueMap.class); - return map; - } else { - return null; - } - } - - protected ResourceResolver getResourceResolver(Object adaptable) { - ResourceResolver resolver = null; - if (adaptable instanceof Resource) { - resolver = ((Resource) adaptable).getResourceResolver(); - } else if (adaptable instanceof SlingHttpServletRequest) { - resolver = ((SlingHttpServletRequest) adaptable).getResourceResolver(); - } - return resolver; - } -} diff --git a/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java b/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java deleted file mode 100644 index 827a3d9fb1..0000000000 --- a/platform/per-model/src/test/java/com/peregrine/cms/model/impl/ImageInfoInjectorTest.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.peregrine.cms.model.impl; - -import com.peregrine.cms.testmodels.DefaultImageInfoModel; -import com.peregrine.commons.test.AbstractTest; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.api.wrappers.ValueMapDecorator; -import org.apache.sling.models.impl.ModelAdapterFactory; -import org.apache.sling.models.impl.ModelAdapterFactorySetup; -import org.apache.sling.models.impl.injectors.ValueMapInjector; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class ImageInfoInjectorTest extends AbstractTest { - private Logger logger = LoggerFactory.getLogger(getClass()); - private ModelAdapterFactory factory; - private ResourceResolver resourceResolver; - - @Override - public Logger getLogger() { - return logger; - } - - @Before - public void setup() { - super.setup(); - try { - factory = ModelAdapterFactorySetup.createModelAdapterFactory(null, DefaultImageInfoModel.class); - // Add the Resource in Constructor Injection - ModelAdapterFactorySetup.addInjector(factory, new ImageInfoInjector(), 1); - // Add the Value Map properties Injection - ModelAdapterFactorySetup.addInjector(factory, new ValueMapInjector(), 2); - resourceResolver = mock(ResourceResolver.class, "Test Resource Resolver"); - } catch (Exception e) { - throw new RuntimeException("Setup Failed", e); - } - } - - @Test - public void testImageDataFromMeta() { - String title = "testImageInfo"; - String caption = "Test Image Info"; - String imageName = "013.jpg"; - String imagePath = "/content/unitTest/assets/images/" + imageName; - int width = 111; - int height = 999; - - Resource testPageResource = createMockResource( - "test page", null, "per:Page", - "title", title, "caption", caption, "imagePath", imagePath - ); - // Asset Test Resource - Resource assetTestResource = createMockResource( - imageName, null, "per:Asset" - ); - // Resource Resolver needs to return Asset - when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); - when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); - - // Create JCR Content, Metadata and Per Data node - Resource jcrContentTestResource = createMockResource( - "jcr:content", assetTestResource, "per:AssetContent", - "jcr:mimeType", "image/jpeg" - ); - Resource metaDataResource = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); - Resource perDataResource = createMockResource( - "per-data", metaDataResource, "sling:Folder", - "width", width, "height", height - ); - - DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); - assertNotNull(model); - assertEquals(title, model.getTitle()); - assertEquals(caption, model.getCaption()); - assertEquals(imagePath, model.getImagePath()); - assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); - } - - @Test - public void testImageDataWitNoData() { - String title = "noDataTestImage"; - String caption = "Test Image Info"; - String imageName = "013.jpg"; - String imagePath = "/content/unitTest/assets/images/" + imageName; - - Resource testPageResource = createMockResource( - "test page", null, "per:Page", - "title", title, "caption", caption, "imagePath", imagePath - ); - // Asset Test Resource - Resource assetTestResource = createMockResource( - imageName, null, "per:Asset" - ); - // Resource Resolver needs to return Asset - when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); - when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); - - // Create JCR Content, Metadata and Per Data node - Resource jcrContentTestResource = createMockResource( - "jcr:content", assetTestResource, "per:AssetContent", - "jcr:mimeType", "image/jpeg" - ); - - DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); - assertNotNull(model); - assertEquals(title, model.getTitle()); - assertEquals(caption, model.getCaption()); - assertEquals(imagePath, model.getImagePath()); - assertNull(model.getImageInfo()); - } - -// @Test -// public void testImageDataFromImage() throws Exception { -// String title = "testImageInfo"; -// String caption = "Test Image Info"; -// String imageName = "013.jpg"; -// String imagePath = "/content/unitTest/assets/images/" + imageName; -// int width = 1280; -// int height = 1024; -// -// Resource testPageResource = createMockResource( -// "test page", null, "per:Page", -// "title", title, "caption", caption, "imagePath", imagePath -// ); -// // Asset Test Resource -// Resource assetTestResource = createMockResource( -// imageName, null, "per:Asset" -// ); -// // Resource Resolver needs to return Asset -// when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); -// when(resourceResolver.getResource(imagePath)).thenReturn(assetTestResource); -// -// InputStream is = getClass().getResourceAsStream("/test.image.jpg"); -// // Create JCR Content, Metadata and Per Data node -// Resource jcrContentTestResource = createMockResource( -// "jcr:content", assetTestResource, "per:AssetContent", -// "jcr:mimeType", "image/jpeg", -// "jcr:data", is -// ); -// -// // Creation of the Metadata resource must be delated until it is created by the ImageInfoInjector and with it the per-data resource -// Map createdResource = new HashMap<>(); -// when(resourceResolver.create(eq(jcrContentTestResource), eq("metadata"), any(Map.class))) -// .thenAnswer( -// invocation -> { -// Resource answer = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); -// createdResource.put("metadata", answer); -// return answer; -// } -// ); -// when(resourceResolver.create(any(Resource.class), eq("per-data"), any(Map.class))) -// .thenAnswer( -// invocation -> { -// Resource metaDataResource = createdResource.get("metadata"); -// Resource answer = createMockResource("per-data", metaDataResource, "sling:Folder"); -// createdResource.put("per-data", answer); -// return answer; -// } -// ); -// -// DefaultImageInfoModel model = factory.getAdapter(testPageResource, DefaultImageInfoModel.class); -// assertNotNull("Failed to create Model", model); -// assertEquals(title, model.getTitle()); -// assertEquals(caption, model.getCaption()); -// assertEquals(imagePath, model.getImagePath()); -// assertEquals("{'width': " + width + ", 'height': " + height + "}", model.getImageInfo()); -// Resource perDataResource = createdResource.get("per-data"); -// assertNotNull("per-data resource not found", perDataResource); -// ValueMap properties = perDataResource.getValueMap(); -// assertEquals(properties.get("width"), width); -// assertEquals(properties.get("height"), height); -// } - - private Resource createMockResource(String name, Resource parent, String primaryType, Object...properties) { - Resource answer = name == null ? mock(Resource.class) : mock(Resource.class, name); - when(answer.getResourceResolver()).thenReturn(resourceResolver); - if(parent != null) { - when(parent.getParent()).thenReturn(parent); - when(parent.getChild(name)).thenReturn(answer); - } - HashMap values = new HashMap<>(); - if(primaryType != null) { - values.put(JCR_PRIMARY_TYPE, primaryType); - } - ValueMap vm; - if(properties != null) { - if(properties.length % 2 != 0) { - throw new IllegalArgumentException("Properties list must be on even number of entries but was: " + properties.length); - } - int count = properties.length / 2; - for(int i = 0; i < count; i++) { - values.put(properties[2 * i] + "", properties[(2*i)+1]); - } - vm = new ValueMapDecorator(values); - } else { - vm = new ValueMapDecorator(Collections.EMPTY_MAP); - } - when(answer.getValueMap()).thenReturn(vm); - when(answer.adaptTo(ValueMap.class)).thenReturn(vm); - return answer; - } -} diff --git a/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java b/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java deleted file mode 100644 index 1f48a6b1e1..0000000000 --- a/platform/per-model/src/test/java/com/peregrine/cms/testmodels/DefaultImageInfoModel.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 com.peregrine.cms.testmodels; - -import com.peregrine.cms.model.api.ImageInfo; -import com.peregrine.nodetypes.models.AbstractComponent; -import com.peregrine.nodetypes.models.IComponent; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.models.annotations.Default; -import org.apache.sling.models.annotations.DefaultInjectionStrategy; -import org.apache.sling.models.annotations.Model; - -import javax.inject.Inject; - -@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, adapters = IComponent.class) -public class DefaultImageInfoModel extends AbstractComponent { - - @Inject - private String title; - - @Inject - private String caption; - - @Inject @Default(values = "") - private String imagePath; - - @Inject - @ImageInfo(name = "imagePath") - private String imageInfo; - - public DefaultImageInfoModel(Resource resource) { - super(resource); - } - - public String getTitle() { - return title; - } - - public String getCaption() { - return caption; - } - - public String getImagePath() { - return imagePath; - } - - public String getImageInfo() { - return imageInfo; - } -} diff --git a/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java b/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java deleted file mode 100644 index 8ba2d205f5..0000000000 --- a/platform/per-model/src/test/java/org/apache/sling/models/impl/ModelAdapterFactorySetup.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.apache.sling.models.impl; - -import org.apache.sling.models.spi.Injector; -import org.mockito.Mockito; -import org.osgi.framework.BundleContext; -import org.osgi.service.component.ComponentContext; -import org.osgi.util.converter.Converter; -import org.osgi.util.converter.Converters; - -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; - -import static java.util.Collections.emptyList; -import static org.mockito.Mockito.when; - -public class ModelAdapterFactorySetup { - public static ModelAdapterFactory createModelAdapterFactory(Injector injector, Class...models) throws Exception { - BundleContext bundleContext = Mockito.mock(BundleContext.class); - ModelAdapterFactory factory = createModelAdapterFactory(bundleContext); - if(injector != null) { - factory.bindInjector(injector, new ServicePropertiesMap(0, 0)); - } - factory.adapterImplementations.addClassesAsAdapterAndImplementation(models); - return factory; - } - - public static ModelAdapterFactory createModelAdapterFactory(BundleContext bundleContext) throws Exception { - ComponentContext componentCtx = Mockito.mock(ComponentContext.class); - when(componentCtx.getBundleContext()).thenReturn(bundleContext); - when(componentCtx.getProperties()).thenReturn(new Hashtable()); - - ModelAdapterFactory factory = new ModelAdapterFactory(); - Converter c = Converters.standardConverter(); - Map map = new HashMap<>(); - factory.activate(componentCtx); - return factory; - } - - public static void addInjector(ModelAdapterFactory factory, Injector injector, int ranking) { - factory.bindInjector(injector, new ServicePropertiesMap(ranking, ranking)); - } -} diff --git a/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java b/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java deleted file mode 100644 index 2f5294a90b..0000000000 --- a/platform/per-model/src/test/java/org/apache/sling/models/impl/ServicePropertiesMap.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.models.impl; - -import org.osgi.framework.Constants; - -import java.util.HashMap; - -@SuppressWarnings("serial") -public class ServicePropertiesMap extends HashMap { - - public ServicePropertiesMap(long serviceId, int serviceRanking) { - super(); - put(Constants.SERVICE_ID, serviceId); - put(Constants.SERVICE_RANKING, serviceRanking); - } - -} diff --git a/platform/per-model/src/test/resources/test.image.jpg b/platform/per-model/src/test/resources/test.image.jpg deleted file mode 100644 index 41cb267c86bf1fc24b95fd0be00285a020fdfd4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45139 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 From 7d211f6cd3fa1e9b4c5b65c64c060dd1c17b9e82 Mon Sep 17 00:00:00 2001 From: Andreas Schaefer Date: Tue, 4 Aug 2020 14:49:52 -0700 Subject: [PATCH 04/19] =?UTF-8?q?Added=20the=20Image=20Info=20to=20the=20T?= =?UTF-8?q?hemeclean=E2=80=99s=20Media=20Block=20Model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/themeclean/models/MediablockModel.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java b/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java index 71124eb21a..63438ecf94 100644 --- a/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java +++ b/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java @@ -1,5 +1,6 @@ package com.themeclean.models; +import com.peregrine.model.api.ImageInfo; import com.peregrine.nodetypes.models.AbstractComponent; import com.peregrine.nodetypes.models.IComponent; import javax.inject.Inject; @@ -393,6 +394,9 @@ public class MediablockModel extends AbstractComponent { @Inject private String bottompadding; + @Inject + @ImageInfo(name="imagesrc") + private String imageinfo; //GEN] @@ -512,6 +516,9 @@ public String getBottompadding() { return bottompadding; } + public String getImageinfo() { + return imageinfo; + } //GEN] From 38ff28fffbfe98084c2d4c20b2cec7bd0e73e599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Thu, 3 Dec 2020 22:49:03 +0100 Subject: [PATCH 05/19] Remove unused stuff --- platform/base/core/pom.xml | 31 ------------------- .../model/impl/ImageInfoInjector.java | 6 ++-- .../peregrine/commons/util/PerConstants.java | 1 - 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/platform/base/core/pom.xml b/platform/base/core/pom.xml index 1265886546..3ef8961e1b 100644 --- a/platform/base/core/pom.xml +++ b/platform/base/core/pom.xml @@ -61,7 +61,6 @@ com.peregrine.transform.*, com.peregrine.commons.util, com.peregrine.model.api, - com.peregrine.model.impl, com.peregrine.versions *;scope=compile;inline=true @@ -263,36 +262,6 @@ test-jar test - - ${project.groupId} - commons-test - ${project.version} - test - - - org.apache.sling - org.apache.sling.models.impl - 1.4.2 - test - - - org.apache.sling - org.apache.sling.scripting.api - 2.2.0 - test - - - org.apache.felix - org.apache.felix.converter - 1.0.14 - test - - - org.osgi - org.osgi.util.function - 1.0.0 - test - diff --git a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java index 58ebc330d7..833f284413 100644 --- a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java +++ b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java @@ -39,10 +39,8 @@ import static java.util.Objects.isNull; @Component( - service={ - Injector.class - }, - property=Constants.SERVICE_RANKING + "=" + "2000" + service = Injector.class, + property = Constants.SERVICE_RANKING + "=" + "2000" ) public final class ImageInfoInjector implements Injector { diff --git a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java index 012fa3f1e4..27e00f375a 100644 --- a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java +++ b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java @@ -85,7 +85,6 @@ public class PerConstants { public static final String PNG_MIME_TYPE = "image/png"; public static final String JPEG_MIME_TYPE = "image/jpeg"; public static final String WEBP_MIME_TYPE = "image/webp"; - public static final String SVG_MIME_TYPE = "image/svg+xml"; public static final String JPG_MIME_TYPE = "image/jpg"; public static final String PACKAGES = "packages"; From e9b396f2c93c9ee73e4be3de6839074a84c4fc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Fri, 4 Dec 2020 00:25:41 +0100 Subject: [PATCH 06/19] Grab dimensions from the source --- .../model/impl/ImageInfoInjector.java | 57 +++++++---- .../model/impl/ImageInfoInjectorTest.java | 96 +++++++----------- platform/base/core/src/test/resources/mj.jpg | Bin 0 -> 25845 bytes .../peregrine/commons/util/PerConstants.java | 1 + .../com/peregrine/commons/util/PerUtil.java | 3 - .../themeclean/models/MediablockModel.java | 6 +- 6 files changed, 78 insertions(+), 85 deletions(-) create mode 100644 platform/base/core/src/test/resources/mj.jpg diff --git a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java index 833f284413..4f2e015b6e 100644 --- a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java +++ b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java @@ -26,17 +26,19 @@ import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Component; +import javax.imageio.ImageIO; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; import java.util.Optional; -import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; -import static com.peregrine.commons.util.PerUtil.IMAGE_HEIGHT; -import static com.peregrine.commons.util.PerUtil.IMAGE_WIDTH; -import static com.peregrine.commons.util.PerUtil.METADATA; -import static com.peregrine.commons.util.PerUtil.PER_DATA; +import static com.peregrine.commons.util.PerConstants.*; import static java.util.Objects.isNull; +import static org.apache.commons.lang3.StringUtils.isBlank; @Component( service = Injector.class, @@ -44,8 +46,6 @@ ) public final class ImageInfoInjector implements Injector { - private static final String IMAGE_INFO_FORMAT = "{'width': %1$d, 'height': %2$d}"; - @Override public String getName() { return "imageinfo"; @@ -53,9 +53,9 @@ public String getName() { @Override public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, - DisposalCallbackRegistry callbackRegistry + DisposalCallbackRegistry callbackRegistry ) { - if (isNull(adaptable) || type != String.class) { + if (isNull(adaptable) || type != Dimension.class) { return null; } @@ -73,17 +73,15 @@ public Object getValue(Object adaptable, String name, Type type, AnnotatedElemen return null; } - final ValueMap map = getImagePerData(resource, ((ImageInfo) annotation).name()); - int width = map.get(IMAGE_WIDTH, 0); - int height = map.get(IMAGE_HEIGHT, 0); - if (width > 0 || height > 0) { - return String.format(IMAGE_INFO_FORMAT, width, height); + final BufferedImage image = getImage(resource, ((ImageInfo) annotation).name()); + if (isNull(image)) { + return null; } - return null; + return new Dimension(image.getWidth(), image.getHeight()); } - private ValueMap getImagePerData(Resource resource, String imagePropertyName) { + private BufferedImage getImage(Resource resource, String imagePropertyName) { return Optional.of(resource) .map(Resource::getValueMap) .map(map -> map.get(imagePropertyName, String.class)) @@ -91,10 +89,29 @@ private ValueMap getImagePerData(Resource resource, String imagePropertyName) { .filter(path -> !StringUtils.contains(path, ":/")) .map(resource.getResourceResolver()::getResource) .map(r -> r.getChild(JCR_CONTENT)) - .map(r -> r.getChild(METADATA)) - .map(r -> r.getChild(PER_DATA)) - .map(Resource::getValueMap) - .orElse(ValueMap.EMPTY); + .map(this::getImage) + .orElse(null); + } + + private BufferedImage getImage(final Resource content) { + final ValueMap properties = content.getValueMap(); + final String mimeType = properties.get(JCR_MIME_TYPE, String.class); + // SVG images are not handled by ImageIO so we ignore it here + if (isBlank(mimeType) || SVG_MIME_TYPE.equals(mimeType)) { + return null; + } + + final InputStream stream = properties.get(JCR_DATA, InputStream.class); + if (isNull(stream)) { + return null; + } + + try { + return ImageIO.read(stream); + } catch (IOException e) { + return null; + } } + } diff --git a/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java b/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java index 0b1d54abd9..b46e4cbfbc 100644 --- a/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java +++ b/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java @@ -11,10 +11,13 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; +import java.awt.Dimension; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; +import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; +import static com.peregrine.commons.util.PerConstants.JCR_DATA; import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -24,83 +27,35 @@ @RunWith(MockitoJUnitRunner.class) public final class ImageInfoInjectorTest { - private static final String CAPTION = "Test Image Info"; private static final String IMAGE_NAME = "013.jpg"; private static final String IMAGE_PATH = "/content/unitTest/assets/images/" + IMAGE_NAME; private final Injector model = new ImageInfoInjector(); private final ResourceResolver resourceResolver = mock(ResourceResolver.class, "Test Resource Resolver"); + private final Resource testPageResource = createMockResource( + "test page", null, "per:Page", + "imagePath", IMAGE_PATH + ); + private final Resource assetTestResource = createMockResource( + IMAGE_NAME, null, "per:Asset" + ); + private final Resource assetContent = createMockResource( + JCR_CONTENT, assetTestResource, "per:AssetContent", + "jcr:mimeType", "image/jpeg" + ); @ImageInfo(name = "imagePath") - private String imageInfo; + private Dimension imageInfo; private Field annotatedField; @Before public void setup() throws NoSuchFieldException { annotatedField = getClass().getDeclaredField("imageInfo"); - } - - private String getValue(final Resource resource) { - return (String) model.getValue(resource, null, String.class, annotatedField, null); - } - - @Test - public void testImageDataFromMeta() { - String title = "testImageInfo"; - int width = 111; - int height = 999; - - Resource testPageResource = createMockResource( - "test page", null, "per:Page", - "title", title, "caption", CAPTION, "imagePath", IMAGE_PATH - ); - // Asset Test Resource - Resource assetTestResource = createMockResource( - IMAGE_NAME, null, "per:Asset" - ); - // Resource Resolver needs to return Asset - when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); - when(resourceResolver.getResource(IMAGE_PATH)).thenReturn(assetTestResource); - - // Create JCR Content, Metadata and Per Data node - Resource jcrContentTestResource = createMockResource( - "jcr:content", assetTestResource, "per:AssetContent", - "jcr:mimeType", "image/jpeg" - ); - Resource metaDataResource = createMockResource("metadata", jcrContentTestResource, "sling:Folder"); - createMockResource( - "per-data", metaDataResource, "sling:Folder", - "width", width, "height", height - ); - - assertEquals("{'width': " + width + ", 'height': " + height + "}", getValue(testPageResource)); - } - - @Test - public void testImageDataWitNoData() { - String title = "noDataTestImage"; - - Resource testPageResource = createMockResource( - "test page", null, "per:Page", - "title", title, "caption", CAPTION, "imagePath", IMAGE_PATH - ); - // Asset Test Resource - Resource assetTestResource = createMockResource( - IMAGE_NAME, null, "per:Asset" - ); // Resource Resolver needs to return Asset when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); when(resourceResolver.getResource(IMAGE_PATH)).thenReturn(assetTestResource); - - // Create JCR Content, Metadata and Per Data node - Resource jcrContentTestResource = createMockResource( - "jcr:content", assetTestResource, "per:AssetContent", - "jcr:mimeType", "image/jpeg" - ); - - assertNull(getValue(testPageResource)); } private Resource createMockResource(String name, Resource parent, String primaryType, Object...properties) { @@ -131,4 +86,25 @@ private Resource createMockResource(String name, Resource parent, String primary when(answer.adaptTo(ValueMap.class)).thenReturn(vm); return answer; } + + private Dimension getValue(final Resource resource) { + return (Dimension) model.getValue(resource, null, Dimension.class, annotatedField, null); + } + + @Test + public void testImageDataFromMeta() { + final var map = new HashMap(); + map.put("jcr:mimeType", "image/jpeg"); + map.put(JCR_DATA, getClass().getResourceAsStream("/mj.jpg")); + when(assetContent.getValueMap()).thenReturn(new ValueMapDecorator(map)); + final var dimension = getValue(testPageResource); + assertEquals(614, dimension.getWidth(), 0.1); + assertEquals(410, dimension.getHeight(), 0.1); + } + + @Test + public void testImageWithNoData() { + assertNull(getValue(testPageResource)); + } + } diff --git a/platform/base/core/src/test/resources/mj.jpg b/platform/base/core/src/test/resources/mj.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e48c9a32b5ffbcee0a4b3d743e758745da458eb GIT binary patch literal 25845 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

xe9r6`Kp)_ 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 Q0|2(SN7Rk-uK&04e^+;v>i_@% literal 0 HcmV?d00001 diff --git a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java index 27e00f375a..012fa3f1e4 100644 --- a/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java +++ b/platform/commons/src/main/java/com/peregrine/commons/util/PerConstants.java @@ -85,6 +85,7 @@ public class PerConstants { public static final String PNG_MIME_TYPE = "image/png"; public static final String JPEG_MIME_TYPE = "image/jpeg"; public static final String WEBP_MIME_TYPE = "image/webp"; + public static final String SVG_MIME_TYPE = "image/svg+xml"; public static final String JPG_MIME_TYPE = "image/jpg"; public static final String PACKAGES = "packages"; diff --git a/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java b/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java index b24743d91d..ccd4c68542 100644 --- a/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java +++ b/platform/commons/src/main/java/com/peregrine/commons/util/PerUtil.java @@ -52,9 +52,6 @@ public class PerUtil { public static final String RENDITIONS = "renditions"; public static final String METADATA = "metadata"; - public static final String PER_DATA = "per-data"; - public static final String IMAGE_WIDTH = "width"; - public static final String IMAGE_HEIGHT = "height"; public static final String PER_VENDOR = "headwire.com, Inc"; public static final String PER_PREFIX = "Peregrine: "; diff --git a/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java b/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java index 63438ecf94..fdf464b0d6 100644 --- a/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java +++ b/themes/themeclean/core/src/main/java/com/themeclean/models/MediablockModel.java @@ -10,6 +10,8 @@ import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; +import java.awt.Dimension; + /* //GEN[:DATA { @@ -396,7 +398,7 @@ public class MediablockModel extends AbstractComponent { @Inject @ImageInfo(name="imagesrc") - private String imageinfo; + private Dimension imageinfo; //GEN] @@ -516,7 +518,7 @@ public String getBottompadding() { return bottompadding; } - public String getImageinfo() { + public Dimension getImageinfo() { return imageinfo; } From cd9adf8457a3e10dad269f0bf420bf34b892f0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Mon, 7 Dec 2020 15:36:53 +0100 Subject: [PATCH 07/19] Extract PerAsset.getOrSaveAndGetDimension() --- .../resource/AdminResourceHandlerService.java | 24 +--- .../java/com/peregrine/adaption/PerAsset.java | 4 + .../peregrine/adaption/impl/PerAssetImpl.java | 51 +++++++- .../com/peregrine/assets/AssetConstants.java | 7 ++ .../model/impl/ImageInfoInjector.java | 51 +++----- .../model/impl/ImageInfoInjectorTest.java | 110 ------------------ 6 files changed, 75 insertions(+), 172 deletions(-) create mode 100644 platform/base/core/src/main/java/com/peregrine/assets/AssetConstants.java delete mode 100644 platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java diff --git a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java index 234d1e9caf..268ad3c709 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java @@ -114,9 +114,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; import javax.jcr.*; import javax.jcr.version.Version; import javax.jcr.version.VersionHistory; @@ -952,7 +949,7 @@ private void processNewAsset(PerAsset asset) throws IOException, RepositoryExcep } } // Obtain the Asset Dimension and store directly in the meta data folder - handleAssetDimensions(asset); + asset.getOrSaveAndGetDimension(); } catch (ImageProcessingException e) { logger.debug(EMPTY, e); } @@ -2315,25 +2312,6 @@ private Node createPageOrTemplate(Resource parent, String name, String templateC return newPage; } - public void handleAssetDimensions(PerAsset perAsset) throws RepositoryException, IOException { - InputStream is = perAsset.getRenditionStream((String) null); - // Ignore images that do not have a jcr:data element aka stream - if (is != null) { - ImageInputStream iis = ImageIO.createImageInputStream(is); - Iterator readers = ImageIO.getImageReaders(iis); - while (readers.hasNext()) { - ImageReader reader = readers.next(); - reader.setInput(iis); - int minIndex = reader.getMinIndex(); - int width = reader.getWidth(minIndex); - int height = reader.getHeight(minIndex); - perAsset.addTag("per-data", "width", width); - perAsset.addTag("per-data", "height", height); - break; - } - } - } - private String getPropsFromMap(Map source, String key, String defaultValue) { String answer = defaultValue; Object temp = source.get(key); diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java b/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java index e0e58d7974..3ce734a5fc 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java @@ -29,6 +29,8 @@ import org.apache.sling.api.resource.Resource; import javax.jcr.RepositoryException; +import java.awt.*; +import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -105,4 +107,6 @@ public void addTag(String category, String tag, Object value) * @return The value of the tag or null if not found */ public Object getTag(String category, String tag); + + Dimension getOrSaveAndGetDimension() throws RepositoryException, IOException; } diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java index c7d8d276b9..e165f4dc41 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java @@ -33,15 +33,22 @@ import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ValueMap; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; +import java.awt.*; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import static com.peregrine.assets.AssetConstants.*; import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; import static com.peregrine.commons.util.PerConstants.JCR_DATA; import static com.peregrine.commons.util.PerConstants.JCR_MIME_TYPE; @@ -51,6 +58,8 @@ import static com.peregrine.commons.util.PerConstants.SLING_FOLDER; import static com.peregrine.commons.util.PerUtil.METADATA; import static com.peregrine.commons.util.PerUtil.RENDITIONS; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; /** * Peregrine Asset Wrapper Object @@ -191,18 +200,21 @@ private Map getTags(Resource category) { @Override public Object getTag(String category, String tag) { - Object answer = null; - Resource categoryResource = null; + return getTag(category, tag, Object.class); + } + + private X getTag(String category, String tag, Class type) { try { - categoryResource = getCategoryResource(category, false); + Resource categoryResource = getCategoryResource(category, false); if(categoryResource != null) { ValueMap properties = categoryResource.getValueMap(); - answer = properties.get(PerUtil.adjustMetadataName(tag)); + return properties.get(PerUtil.adjustMetadataName(tag), type); } } catch(PersistenceException e) { // Ignore } - return answer; + + return null; } /** @@ -258,4 +270,33 @@ private Resource getOrCreateMetaData() throws PersistenceException { } return metadata; } + + public Dimension getOrSaveAndGetDimension() throws RepositoryException, IOException { + Integer width = getTag(NN_PER_DATA, PN_WIDTH, Integer.class); + Integer height = getTag(NN_PER_DATA, PN_HEIGHT, Integer.class); + if (nonNull(width) && nonNull(height)) { + return new Dimension(width, height); + } + + final InputStream is = getRenditionStream((String) null); + // Ignore images that do not have a jcr:data element aka stream + if (isNull(is)) { + return null; + } + + final ImageInputStream iis = ImageIO.createImageInputStream(is); + final Iterator readers = ImageIO.getImageReaders(iis); + if (readers.hasNext()) { + final ImageReader reader = readers.next(); + reader.setInput(iis); + final int minIndex = reader.getMinIndex(); + width = reader.getWidth(minIndex); + height = reader.getHeight(minIndex); + addTag(NN_PER_DATA, PN_WIDTH, width); + addTag(NN_PER_DATA, PN_HEIGHT, height); + return new Dimension(width, height); + } + + return null; + } } diff --git a/platform/base/core/src/main/java/com/peregrine/assets/AssetConstants.java b/platform/base/core/src/main/java/com/peregrine/assets/AssetConstants.java new file mode 100644 index 0000000000..c35039d749 --- /dev/null +++ b/platform/base/core/src/main/java/com/peregrine/assets/AssetConstants.java @@ -0,0 +1,7 @@ +package com.peregrine.assets; + +public class AssetConstants { + public static final String NN_PER_DATA = "per-data"; + public static final String PN_WIDTH = "width"; + public static final String PN_HEIGHT = "height"; +} diff --git a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java index 4f2e015b6e..0145e0d39a 100644 --- a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java +++ b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java @@ -16,29 +16,25 @@ */ package com.peregrine.model.impl; +import com.peregrine.adaption.PerAsset; import com.peregrine.model.api.ImageInfo; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Component; -import javax.imageio.ImageIO; +import javax.jcr.RepositoryException; import java.awt.Dimension; -import java.awt.image.BufferedImage; import java.io.IOException; -import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; import java.util.Optional; -import static com.peregrine.commons.util.PerConstants.*; import static java.util.Objects.isNull; -import static org.apache.commons.lang3.StringUtils.isBlank; @Component( service = Injector.class, @@ -52,8 +48,12 @@ public String getName() { } @Override - public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, - DisposalCallbackRegistry callbackRegistry + public Object getValue( + final Object adaptable, + final String name, + final Type type, + final AnnotatedElement element, + final DisposalCallbackRegistry callbackRegistry ) { if (isNull(adaptable) || type != Dimension.class) { return null; @@ -73,45 +73,28 @@ public Object getValue(Object adaptable, String name, Type type, AnnotatedElemen return null; } - final BufferedImage image = getImage(resource, ((ImageInfo) annotation).name()); - if (isNull(image)) { + final PerAsset asset = getImage(resource, ((ImageInfo) annotation).name()); + if (isNull(asset)) { return null; } - return new Dimension(image.getWidth(), image.getHeight()); + try { + return asset.getOrSaveAndGetDimension(); + } catch (final RepositoryException | IOException e) { + return null; + } } - private BufferedImage getImage(Resource resource, String imagePropertyName) { + private PerAsset getImage(final Resource resource, final String imagePropertyName) { return Optional.of(resource) .map(Resource::getValueMap) .map(map -> map.get(imagePropertyName, String.class)) .filter(StringUtils::isNotBlank) .filter(path -> !StringUtils.contains(path, ":/")) .map(resource.getResourceResolver()::getResource) - .map(r -> r.getChild(JCR_CONTENT)) - .map(this::getImage) + .map(r -> r.adaptTo(PerAsset.class)) .orElse(null); } - private BufferedImage getImage(final Resource content) { - final ValueMap properties = content.getValueMap(); - final String mimeType = properties.get(JCR_MIME_TYPE, String.class); - // SVG images are not handled by ImageIO so we ignore it here - if (isBlank(mimeType) || SVG_MIME_TYPE.equals(mimeType)) { - return null; - } - - final InputStream stream = properties.get(JCR_DATA, InputStream.class); - if (isNull(stream)) { - return null; - } - - try { - return ImageIO.read(stream); - } catch (IOException e) { - return null; - } - } - } diff --git a/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java b/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java deleted file mode 100644 index b46e4cbfbc..0000000000 --- a/platform/base/core/src/test/java/com/peregrine/model/impl/ImageInfoInjectorTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.peregrine.model.impl; - -import com.peregrine.model.api.ImageInfo; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.api.wrappers.ValueMapDecorator; -import org.apache.sling.models.spi.Injector; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; - -import java.awt.Dimension; -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.HashMap; - -import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; -import static com.peregrine.commons.util.PerConstants.JCR_DATA; -import static com.peregrine.commons.util.PerConstants.JCR_PRIMARY_TYPE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public final class ImageInfoInjectorTest { - - private static final String IMAGE_NAME = "013.jpg"; - private static final String IMAGE_PATH = "/content/unitTest/assets/images/" + IMAGE_NAME; - - private final Injector model = new ImageInfoInjector(); - - private final ResourceResolver resourceResolver = mock(ResourceResolver.class, "Test Resource Resolver"); - private final Resource testPageResource = createMockResource( - "test page", null, "per:Page", - "imagePath", IMAGE_PATH - ); - private final Resource assetTestResource = createMockResource( - IMAGE_NAME, null, "per:Asset" - ); - private final Resource assetContent = createMockResource( - JCR_CONTENT, assetTestResource, "per:AssetContent", - "jcr:mimeType", "image/jpeg" - ); - - @ImageInfo(name = "imagePath") - private Dimension imageInfo; - - private Field annotatedField; - - @Before - public void setup() throws NoSuchFieldException { - annotatedField = getClass().getDeclaredField("imageInfo"); - // Resource Resolver needs to return Asset - when(testPageResource.getResourceResolver()).thenReturn(resourceResolver); - when(resourceResolver.getResource(IMAGE_PATH)).thenReturn(assetTestResource); - } - - private Resource createMockResource(String name, Resource parent, String primaryType, Object...properties) { - Resource answer = name == null ? mock(Resource.class) : mock(Resource.class, name); - when(answer.getResourceResolver()).thenReturn(resourceResolver); - if(parent != null) { - when(parent.getParent()).thenReturn(parent); - when(parent.getChild(name)).thenReturn(answer); - } - HashMap values = new HashMap<>(); - if(primaryType != null) { - values.put(JCR_PRIMARY_TYPE, primaryType); - } - ValueMap vm; - if(properties != null) { - if(properties.length % 2 != 0) { - throw new IllegalArgumentException("Properties list must be on even number of entries but was: " + properties.length); - } - int count = properties.length / 2; - for(int i = 0; i < count; i++) { - values.put(properties[2 * i] + "", properties[(2*i)+1]); - } - vm = new ValueMapDecorator(values); - } else { - vm = new ValueMapDecorator(Collections.EMPTY_MAP); - } - when(answer.getValueMap()).thenReturn(vm); - when(answer.adaptTo(ValueMap.class)).thenReturn(vm); - return answer; - } - - private Dimension getValue(final Resource resource) { - return (Dimension) model.getValue(resource, null, Dimension.class, annotatedField, null); - } - - @Test - public void testImageDataFromMeta() { - final var map = new HashMap(); - map.put("jcr:mimeType", "image/jpeg"); - map.put(JCR_DATA, getClass().getResourceAsStream("/mj.jpg")); - when(assetContent.getValueMap()).thenReturn(new ValueMapDecorator(map)); - final var dimension = getValue(testPageResource); - assertEquals(614, dimension.getWidth(), 0.1); - assertEquals(410, dimension.getHeight(), 0.1); - } - - @Test - public void testImageWithNoData() { - assertNull(getValue(testPageResource)); - } - -} From eca7747cc3a636fca94b100193cfc71dc2f80d01 Mon Sep 17 00:00:00 2001 From: Cris Rockwell Date: Mon, 7 Dec 2020 13:02:15 -0500 Subject: [PATCH 08/19] added sling junit tests for perAssets --- .../example/assets/images/anchored.jpg | Bin 0 -> 122481 bytes .../example/assets/images/instagram.svg | 1 + .../peregrine/slingjunit/PerAssetJTest.java | 84 ++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg create mode 100644 slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b8cc523fdba6e032d753ec92bf42f4a1f6016815 GIT binary patch literal 122481 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 <#xjbV% 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ݛnoMYo64+Kdi#nH}*Jtj%2;Dggky? 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�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

pcp2xekXhs(L4*IY4>d}+eOqZ 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 F|JmXvVk-au literal 0 HcmV?d00001 diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg new file mode 100644 index 0000000000..89c89b76f8 --- /dev/null +++ b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java b/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java new file mode 100644 index 0000000000..f0e14457bf --- /dev/null +++ b/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java @@ -0,0 +1,84 @@ +package com.peregrine.slingjunit; + +import com.peregrine.adaption.PerAsset; +import com.peregrine.admin.resource.AdminResourceHandler; +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.junit.annotations.SlingAnnotationsTestRunner; +import org.apache.sling.junit.annotations.TestReference; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import java.awt.*; +import java.io.IOException; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(SlingAnnotationsTestRunner.class) +public class PerAssetJTest { + private final Logger logger = LoggerFactory.getLogger(getClass()); + @TestReference + AdminResourceHandler resourceManagement; + @TestReference + private ResourceResolverFactory resolverFactory; + private ResourceResolver resourceResolver; + private Resource resource; + private PerAsset perAsset; + private static String ASSET_PNG = "/content/example/assets/images/logo.png"; + private static String ASSET_JPG = "/content/example/assets/images/anchored.jpg"; + private static String ASSET_SVG = "/content/example/assets/images/instagram.svg"; + + @Test + public void dimensionsPng() { + resource = resourceResolver.getResource(ASSET_PNG); + perAsset = resource.adaptTo(PerAsset.class); + assertDimensions(); + } + + @Test + public void dimensionsJpg(){ + resource = resourceResolver.getResource(ASSET_JPG); + perAsset = resource.adaptTo(PerAsset.class); + assertDimensions(); + } + + @Test + public void dimensionsSvg(){ + resource = resourceResolver.getResource(ASSET_SVG); + perAsset = resource.adaptTo(PerAsset.class); + assertDimensions(); + } + + private void assertDimensions(){ + try { + Dimension dimension = perAsset.getOrSaveAndGetDimension(); + assertTrue(dimension.getHeight() > 0); + assertTrue(dimension.getWidth() > 0); + } catch (RepositoryException e) { + fail("could not save dimensions"); + } catch (IOException e) { + fail("could not save dimensions"); + } + } + + @Before + public void setUp() throws Exception { + resourceResolver = resolverFactory.getAdministrativeResourceResolver(null); + } + + @After + public void cleanup() { + resourceResolver.close(); + resourceManagement = null; + resolverFactory = null; + resource = null; + perAsset = null; + } +} From 10fe1ff3f6d0a4a92fe5409a89de6e2a3aa31b6e Mon Sep 17 00:00:00 2001 From: Cris Rockwell Date: Mon, 7 Dec 2020 14:14:50 -0500 Subject: [PATCH 09/19] added tests and test images for example site. SVG should be supported, but is not yet. --- .../resource/AdminResourceHandlerService.java | 2 +- .../java/com/peregrine/adaption/PerAsset.java | 13 +++++++++- .../peregrine/adaption/impl/PerAssetImpl.java | 23 ++++++++++-------- .../model/impl/ImageInfoInjector.java | 2 +- .../assets/images/anchored.jpg/.content.xml | 10 ++++++++ .../_jcr_content/_jcr_data.binary} | Bin .../_jcr_content/metadata/.content.xml | 3 +++ .../assets/images/instagram.svg/.content.xml | 10 ++++++++ .../_jcr_content/_jcr_data.binary} | 0 .../_jcr_content/metadata/.content.xml | 3 +++ .../peregrine/slingjunit/PerAssetJTest.java | 17 +++++++++++-- 11 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/.content.xml rename samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/{anchored.jpg => anchored.jpg/_jcr_content/_jcr_data.binary} (100%) create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/metadata/.content.xml create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/.content.xml rename samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/{instagram.svg => instagram.svg/_jcr_content/_jcr_data.binary} (100%) create mode 100644 samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/metadata/.content.xml diff --git a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java index 268ad3c709..9e8aa3757c 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java @@ -949,7 +949,7 @@ private void processNewAsset(PerAsset asset) throws IOException, RepositoryExcep } } // Obtain the Asset Dimension and store directly in the meta data folder - asset.getOrSaveAndGetDimension(); + asset.setDimension(); } catch (ImageProcessingException e) { logger.debug(EMPTY, e); } diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java b/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java index 3ce734a5fc..046432741b 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/PerAsset.java @@ -108,5 +108,16 @@ public void addTag(String category, String tag, Object value) */ public Object getTag(String category, String tag); - Dimension getOrSaveAndGetDimension() throws RepositoryException, IOException; + /** + * Saves dimensions based on the input stream (part of new asset processing) + * @throws RepositoryException + * @throws IOException + */ + void setDimension() throws RepositoryException, IOException; + + /** + * get dimension (or null) for asset + */ + Dimension getDimension() throws RepositoryException, IOException; + } diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java index e165f4dc41..031e501f05 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java @@ -271,17 +271,12 @@ private Resource getOrCreateMetaData() throws PersistenceException { return metadata; } - public Dimension getOrSaveAndGetDimension() throws RepositoryException, IOException { - Integer width = getTag(NN_PER_DATA, PN_WIDTH, Integer.class); - Integer height = getTag(NN_PER_DATA, PN_HEIGHT, Integer.class); - if (nonNull(width) && nonNull(height)) { - return new Dimension(width, height); - } + public void setDimension() throws RepositoryException, IOException { final InputStream is = getRenditionStream((String) null); // Ignore images that do not have a jcr:data element aka stream if (isNull(is)) { - return null; + return; } final ImageInputStream iis = ImageIO.createImageInputStream(is); @@ -290,13 +285,21 @@ public Dimension getOrSaveAndGetDimension() throws RepositoryException, IOExcept final ImageReader reader = readers.next(); reader.setInput(iis); final int minIndex = reader.getMinIndex(); - width = reader.getWidth(minIndex); - height = reader.getHeight(minIndex); + int width = reader.getWidth(minIndex); + int height = reader.getHeight(minIndex); addTag(NN_PER_DATA, PN_WIDTH, width); addTag(NN_PER_DATA, PN_HEIGHT, height); - return new Dimension(width, height); + return; } + } + + public Dimension getDimension() { + int width = getTag(NN_PER_DATA, PN_WIDTH, Integer.class); + int height = getTag(NN_PER_DATA, PN_HEIGHT, Integer.class); + if (nonNull(width) && nonNull(height)) { + return new Dimension(width, height); + } return null; } } diff --git a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java index 0145e0d39a..96cc1a51f3 100644 --- a/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java +++ b/platform/base/core/src/main/java/com/peregrine/model/impl/ImageInfoInjector.java @@ -79,7 +79,7 @@ public Object getValue( } try { - return asset.getOrSaveAndGetDimension(); + return asset.getDimension(); } catch (final RepositoryException | IOException e) { return null; } diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/.content.xml b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/.content.xml new file mode 100644 index 0000000000..e58ee4c84e --- /dev/null +++ b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/.content.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/_jcr_data.binary similarity index 100% rename from samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg rename to samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/_jcr_data.binary diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/metadata/.content.xml b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/metadata/.content.xml new file mode 100644 index 0000000000..491392d539 --- /dev/null +++ b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/anchored.jpg/_jcr_content/metadata/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/.content.xml b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/.content.xml new file mode 100644 index 0000000000..51caa68c46 --- /dev/null +++ b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/.content.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/_jcr_data.binary similarity index 100% rename from samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg rename to samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/_jcr_data.binary diff --git a/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/metadata/.content.xml b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/metadata/.content.xml new file mode 100644 index 0000000000..491392d539 --- /dev/null +++ b/samples/example-vue-site/ui.apps/src/main/content/jcr_root/content/example/assets/images/instagram.svg/_jcr_content/metadata/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java b/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java index f0e14457bf..5b310acfad 100644 --- a/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java +++ b/slingjunit.parent/core/src/main/java/com/peregrine/slingjunit/PerAssetJTest.java @@ -2,13 +2,16 @@ import com.peregrine.adaption.PerAsset; import com.peregrine.admin.resource.AdminResourceHandler; +import org.apache.sling.api.resource.PersistenceException; 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.junit.annotations.SlingAnnotationsTestRunner; import org.apache.sling.junit.annotations.TestReference; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; @@ -34,7 +37,7 @@ public class PerAssetJTest { private static String ASSET_PNG = "/content/example/assets/images/logo.png"; private static String ASSET_JPG = "/content/example/assets/images/anchored.jpg"; private static String ASSET_SVG = "/content/example/assets/images/instagram.svg"; - + private static String PER_DATA = "jcr:content/metadata/per-data"; @Test public void dimensionsPng() { resource = resourceResolver.getResource(ASSET_PNG); @@ -58,7 +61,10 @@ public void dimensionsSvg(){ private void assertDimensions(){ try { - Dimension dimension = perAsset.getOrSaveAndGetDimension(); + perAsset.setDimension(); + resourceResolver.refresh(); + resourceResolver.commit(); + Dimension dimension = perAsset.getDimension(); assertTrue(dimension.getHeight() > 0); assertTrue(dimension.getWidth() > 0); } catch (RepositoryException e) { @@ -75,6 +81,13 @@ public void setUp() throws Exception { @After public void cleanup() { + Resource perData = resource.getChild(PER_DATA); + try { + resourceResolver.delete(perData); + } catch (PersistenceException e) { + logger.error("could reset dimensions", e); + fail("could reset dimensions"); + } resourceResolver.close(); resourceManagement = null; resolverFactory = null; From 4bb3c0ebcdac72782609bb6100ff8174988eacf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Wed, 9 Dec 2020 12:55:44 +0100 Subject: [PATCH 10/19] Cover SVG case We use direct approach instead of a library since it's faster to implement at this point. Let's go through all the hassle to enlist a bigger lib like batik when there's actually a justification for such effort (apparently unfortunately we don't yet have a nice and quick ootb dependency bundles inclusion mechanism). --- .../resource/AdminResourceHandlerService.java | 5 +- .../peregrine/adaption/impl/PerAssetImpl.java | 136 ++++++++++++++++-- 2 files changed, 128 insertions(+), 13 deletions(-) diff --git a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java index 9e8aa3757c..0d3b904427 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java @@ -948,11 +948,12 @@ private void processNewAsset(PerAsset asset) throws IOException, RepositoryExcep addTagsToNewAsset(asset, directory, directoryName, selector); } } - // Obtain the Asset Dimension and store directly in the meta data folder - asset.setDimension(); } catch (ImageProcessingException e) { logger.debug(EMPTY, e); } + + // Obtain the Asset Dimension and store directly in the meta data folder + asset.setDimension(); } private void addTagsToNewAssetAsJson(PerAsset asset, Directory directory, String directoryName, ImageMetadataSelector selector) throws PersistenceException, RepositoryException { diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java index 031e501f05..60c9b21a45 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java @@ -41,14 +41,16 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import java.awt.*; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.io.InputStreamReader; +import java.util.*; +import java.util.stream.Collectors; -import static com.peregrine.assets.AssetConstants.*; +import static com.peregrine.assets.AssetConstants.NN_PER_DATA; +import static com.peregrine.assets.AssetConstants.PN_HEIGHT; +import static com.peregrine.assets.AssetConstants.PN_WIDTH; import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; import static com.peregrine.commons.util.PerConstants.JCR_DATA; import static com.peregrine.commons.util.PerConstants.JCR_MIME_TYPE; @@ -60,6 +62,7 @@ import static com.peregrine.commons.util.PerUtil.RENDITIONS; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static org.apache.commons.lang3.StringUtils.*; /** * Peregrine Asset Wrapper Object @@ -272,13 +275,121 @@ private Resource getOrCreateMetaData() throws PersistenceException { } public void setDimension() throws RepositoryException, IOException { - final InputStream is = getRenditionStream((String) null); // Ignore images that do not have a jcr:data element aka stream if (isNull(is)) { return; } + if (endsWithIgnoreCase(getName(), ".svg")) { + setSVGDimension(is); + return; + } + + setImageDimension(is); + } + + private void setSVGDimension(final InputStream is) throws RepositoryException, PersistenceException { + final var header = getSVGHeader(is); + if (isBlank(header)) { + return; + } + + final var widthProp = normalizeSVGAlphanumeric(extractSVGProperty(header, "width")); + final var heightProp = normalizeSVGAlphanumeric(extractSVGProperty(header, "height")); + final var viewBoxProp = extractSVGProperty(header, "viewBox"); + final var viewBox = parseRectangle(viewBoxProp); + if (!(isExtendedAlphanumeric(widthProp) && isExtendedAlphanumeric(heightProp)) && isNull(viewBox)) { + return; + } + + final int width = isExtendedAlphanumeric(widthProp) + ? Integer.parseInt(widthProp) + : (int) (viewBox.getWidth() - viewBox.getX()); + final int height = isExtendedAlphanumeric(heightProp) + ? Integer.parseInt(heightProp) + : (int) (viewBox.getHeight() - viewBox.getY()); + addDimensionTags(width, height); + } + + private String getSVGHeader(final InputStream is) { + final StringBuilder result = new StringBuilder(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + char c = '\0'; + while (!containsIgnoreCase(result, " readers = ImageIO.getImageReaders(iis); if (readers.hasNext()) { @@ -287,19 +398,22 @@ public void setDimension() throws RepositoryException, IOException { final int minIndex = reader.getMinIndex(); int width = reader.getWidth(minIndex); int height = reader.getHeight(minIndex); - addTag(NN_PER_DATA, PN_WIDTH, width); - addTag(NN_PER_DATA, PN_HEIGHT, height); - return; + addDimensionTags(width, height); } + } + private void addDimensionTags(final int width, final int height) throws PersistenceException, RepositoryException { + addTag(NN_PER_DATA, PN_WIDTH, width); + addTag(NN_PER_DATA, PN_HEIGHT, height); } public Dimension getDimension() { - int width = getTag(NN_PER_DATA, PN_WIDTH, Integer.class); - int height = getTag(NN_PER_DATA, PN_HEIGHT, Integer.class); + final Integer width = getTag(NN_PER_DATA, PN_WIDTH, Integer.class); + final Integer height = getTag(NN_PER_DATA, PN_HEIGHT, Integer.class); if (nonNull(width) && nonNull(height)) { return new Dimension(width, height); } + return null; } } From 16d461c807f716cfe0c11b1132945129d0b2f839 Mon Sep 17 00:00:00 2001 From: Felix Puetz Date: Wed, 9 Dec 2020 15:34:46 +0100 Subject: [PATCH 11/19] fix for #679 --- .../admin/components/contentview/template.vue | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue b/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue index 4267bc3989..3b32677911 100644 --- a/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue +++ b/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue @@ -150,6 +150,7 @@ export default { firstTime: [], selection: null }, + dynWatchers: [], toast: { templateComponent: null, missingEventPath: null @@ -549,6 +550,14 @@ export default { const dataInline = this.targetInline.split('.').slice(1) this.inline = dataInline.join('.') set(this.view, '/state/inline/doc', this.iframe.doc) + const modelPropName = this.getCurrentModelPropName() + this.dynWatchers.some((w, index) => { + if (w.modelPropName === modelPropName) { + w.unwatch() + this.dynWatchers.splice(index, 1) + return true + } + }) }, onInlineFocusOut(event) { @@ -558,6 +567,9 @@ export default { if (!isChromeBrowser() && event.target.innerHTML) { event.target.innerHTML = event.target.innerHTML.trim() } + const modelPropName = this.getCurrentModelPropName() + const unwatch = this.$watch(`node.${modelPropName}`, (val) => event.target.innerHTML = val) + this.dynWatchers.push({modelPropName, unwatch}) }, onInlineKeyDown(event) { @@ -911,6 +923,10 @@ export default { style.setAttribute('id', 'editing-extra-styles') }, + getCurrentModelPropName(vm = this) { + return vm.targetInline.split('.').slice(1)[0] + }, + isContentEditableOrNested(el) { const component = this.findComponentEl(el) let found = el From 48f5d804c724beea8ba5de4426bda172e10d3775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Mon, 14 Dec 2020 10:55:04 +0100 Subject: [PATCH 12/19] Fix missing property to save the cards change --- .../java/com/peregrine/nodetypes/models/AbstractComponent.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/base/core/src/main/java/com/peregrine/nodetypes/models/AbstractComponent.java b/platform/base/core/src/main/java/com/peregrine/nodetypes/models/AbstractComponent.java index 953afe7b03..5c15914dae 100644 --- a/platform/base/core/src/main/java/com/peregrine/nodetypes/models/AbstractComponent.java +++ b/platform/base/core/src/main/java/com/peregrine/nodetypes/models/AbstractComponent.java @@ -89,4 +89,7 @@ public List getExperiences() { return experiences; } + public String getName() { + return resource.getName(); + } } From c65a6fee7cf4892eda00c813181627b817055e40 Mon Sep 17 00:00:00 2001 From: Felix Puetz Date: Mon, 14 Dec 2020 13:33:33 +0100 Subject: [PATCH 13/19] update package-lock.json --- pagerenderer/vue/ui.apps/package-lock.json | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/pagerenderer/vue/ui.apps/package-lock.json b/pagerenderer/vue/ui.apps/package-lock.json index 1ca4dbf2c6..3a08c6b112 100644 --- a/pagerenderer/vue/ui.apps/package-lock.json +++ b/pagerenderer/vue/ui.apps/package-lock.json @@ -90,7 +90,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" } @@ -98,7 +98,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { "safer-buffer": "~2.1.0" } @@ -121,7 +121,7 @@ "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { "version": "0.18.1", @@ -143,7 +143,7 @@ "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=" + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" }, "bluebird": { "version": "3.5.5", @@ -190,7 +190,7 @@ "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "requires": { "source-map": "~0.6.0" }, @@ -198,14 +198,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" } @@ -297,7 +297,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" } @@ -339,7 +339,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", @@ -706,7 +706,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -793,7 +793,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } @@ -816,7 +816,7 @@ "postcss": { "version": "6.0.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ=", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "requires": { "chalk": "^2.4.1", "source-map": "^0.6.1", @@ -826,12 +826,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -850,7 +850,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } @@ -873,7 +873,7 @@ "postcss": { "version": "6.0.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ=", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "requires": { "chalk": "^2.4.1", "source-map": "^0.6.1", @@ -883,12 +883,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -972,7 +972,7 @@ "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=" + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" }, "regenerate-unicode-properties": { "version": "8.1.0", @@ -1011,7 +1011,7 @@ "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -1197,12 +1197,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "source-map": { "version": "0.5.7", @@ -1266,7 +1266,7 @@ "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -1327,7 +1327,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=" + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "uri-js": { "version": "4.2.2", @@ -1347,7 +1347,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "verror": { "version": "1.10.0", From 9f75593e0820e1ae03971358a471e7c88c347a9f Mon Sep 17 00:00:00 2001 From: Felix Puetz Date: Mon, 14 Dec 2020 13:34:06 +0100 Subject: [PATCH 14/19] watchers are now reset upon changing component to prevent "undefined" content --- .../jcr_root/apps/admin/components/contentview/template.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue b/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue index 3b32677911..bf26320f0c 100644 --- a/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue +++ b/admin-base/ui.apps/src/main/content/jcr_root/apps/admin/components/contentview/template.vue @@ -432,6 +432,9 @@ export default { }, updateSelectedComponent() { + this.dynWatchers.forEach((watcher) => { + watcher.unwatch() + }) $perAdminApp.action(this, 'showComponentEdit', this.path).then(() => { this.flushInlineState() return this.$nextTick() @@ -924,7 +927,7 @@ export default { }, getCurrentModelPropName(vm = this) { - return vm.targetInline.split('.').slice(1)[0] + return vm.targetInline.split('.').slice(1).join('.') }, isContentEditableOrNested(el) { From 0845e65c17cb672abd4dea71636246e5eaee6c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Tue, 15 Dec 2020 18:05:07 +0100 Subject: [PATCH 15/19] Use try-with-resources to auto-close InputStream --- .../peregrine/adaption/impl/PerAssetImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java index 60c9b21a45..543e5292b8 100644 --- a/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java +++ b/platform/base/core/src/main/java/com/peregrine/adaption/impl/PerAssetImpl.java @@ -275,18 +275,19 @@ private Resource getOrCreateMetaData() throws PersistenceException { } public void setDimension() throws RepositoryException, IOException { - final InputStream is = getRenditionStream((String) null); - // Ignore images that do not have a jcr:data element aka stream - if (isNull(is)) { - return; - } + try(final InputStream is = getRenditionStream((String) null)) { + // Ignore images that do not have a jcr:data element aka stream + if (isNull(is)) { + return; + } - if (endsWithIgnoreCase(getName(), ".svg")) { - setSVGDimension(is); - return; - } + if (endsWithIgnoreCase(getName(), ".svg")) { + setSVGDimension(is); + return; + } - setImageDimension(is); + setImageDimension(is); + } } private void setSVGDimension(final InputStream is) throws RepositoryException, PersistenceException { From c4392db64a96ca9b9f4d8b12c88fb31380437e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Mon, 21 Dec 2020 14:28:00 +0100 Subject: [PATCH 16/19] Remove missing props on update --- .../admin/resource/AdminResourceHandlerService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java index 234d1e9caf..225ba6704f 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java @@ -101,6 +101,7 @@ import com.peregrine.adaption.PerAsset; import com.peregrine.admin.models.Recyclable; import com.peregrine.commons.ResourceUtils; +import com.peregrine.commons.Strings; import com.peregrine.commons.util.PerUtil; import com.peregrine.rendition.BaseResourceHandler; import com.peregrine.replication.ImageMetadataSelector; @@ -2020,6 +2021,12 @@ private void updateResourceTree(Resource resource, Map propertie updateProperties.put(name, value); } } + + updateProperties.keySet().stream() + .filter(key -> !key.contains(Strings.COLON)) + .filter(key -> !properties.containsKey(key)) + .collect(Collectors.toSet()) + .forEach(updateProperties::remove); baseResourceHandler.updateModification(resource); } From e8c937ceb7910141c4223fb167877c2d16aec20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Weso=C5=82owski?= Date: Tue, 22 Dec 2020 16:01:22 +0100 Subject: [PATCH 17/19] Switch approach to special prop The previous way generated too much issues :( --- .../resource/AdminResourceHandlerService.java | 29 ++++++++++++------- .../admin/servlets/UpdateResourceServlet.java | 3 -- .../apps/field/material-range/template.vue | 13 +++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java index 225ba6704f..cb0801deba 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/resource/AdminResourceHandlerService.java @@ -101,7 +101,6 @@ import com.peregrine.adaption.PerAsset; import com.peregrine.admin.models.Recyclable; import com.peregrine.commons.ResourceUtils; -import com.peregrine.commons.Strings; import com.peregrine.commons.util.PerUtil; import com.peregrine.rendition.BaseResourceHandler; import com.peregrine.replication.ImageMetadataSelector; @@ -145,7 +144,8 @@ ) public class AdminResourceHandlerService implements AdminResourceHandler { - public static final String DELETION_PROPERTY_NAME = "_opDelete"; + public static final String PN_DELETE_NODE = "_opDelete"; + public static final String PN_DELETE_PROPS = "_opDeleteProps"; public static final String MODE_PROPERTY = "mode"; private static final String PARENT_NOT_FOUND = "Could not find %s Parent Resource. Path: '%s', name: '%s'"; @@ -2009,7 +2009,20 @@ private void updateResourceTree(Resource resource, Map propertie if (deleteIfContainsMarkerProperty(resource, properties)) { return; } + ModifiableValueMap updateProperties = getModifiableProperties(resource, false); + if (properties.containsKey(PN_DELETE_PROPS)) { + final Object value = properties.remove(PN_DELETE_PROPS); + if (value instanceof String) { + properties.remove(value); + updateProperties.remove(value); + } else if (value instanceof List) { + final var list = (List) value; + list.forEach(properties::remove); + list.forEach(updateProperties::remove); + } + } + for (Entry entry : properties.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); @@ -2022,17 +2035,12 @@ private void updateResourceTree(Resource resource, Map propertie } } - updateProperties.keySet().stream() - .filter(key -> !key.contains(Strings.COLON)) - .filter(key -> !properties.containsKey(key)) - .collect(Collectors.toSet()) - .forEach(updateProperties::remove); baseResourceHandler.updateModification(resource); } private boolean deleteIfContainsMarkerProperty(Resource resource, Map properties) throws ManagementException { - if (properties.containsKey(DELETION_PROPERTY_NAME)) { - Object value = properties.get(DELETION_PROPERTY_NAME); + if (properties.containsKey(PN_DELETE_NODE)) { + Object value = properties.get(PN_DELETE_NODE); if (value == null || Boolean.TRUE.toString().equalsIgnoreCase(value.toString())) { // This indicates that this node shall be removed try { @@ -2075,6 +2083,7 @@ private void applyChildProperties(Resource parent, String childName, Map childPr childProperties.remove(NAME); childProperties.remove(SLING_RESOURCE_TYPE); childProperties.remove(JCR_PRIMARY_TYPE); + childProperties.remove(PN_DELETE_PROPS); child = createNode(parent, childName, NT_UNSTRUCTURED, resourceType); // Now update the child with any remaining properties writeProperties(childProperties, child); @@ -2199,7 +2208,7 @@ private Resource handleExistingItem(Resource parent, Resource child, Map itemPro // Get index of the matching resource child to compare with the index in the list int index = getChildIndex(parent, child); String name = child.getName(); - if (getBoolean(itemProperties, DELETION_PROPERTY_NAME, false)) { + if (getBoolean(itemProperties, PN_DELETE_NODE, false)) { try { logger.trace("Remove List Child: '{}' ('{}')", name, child.getPath()); parent.getResourceResolver().delete(child); diff --git a/admin-base/core/src/main/java/com/peregrine/admin/servlets/UpdateResourceServlet.java b/admin-base/core/src/main/java/com/peregrine/admin/servlets/UpdateResourceServlet.java index d592f5dcc2..59f67c40ee 100644 --- a/admin-base/core/src/main/java/com/peregrine/admin/servlets/UpdateResourceServlet.java +++ b/admin-base/core/src/main/java/com/peregrine/admin/servlets/UpdateResourceServlet.java @@ -49,7 +49,6 @@ import java.io.IOException; import javax.servlet.Servlet; import org.apache.sling.api.resource.Resource; -import org.apache.sling.models.factory.ModelFactory; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -82,8 +81,6 @@ public class UpdateResourceServlet extends AbstractBaseServlet { public static final String FAILED_TO_UPDATE_PAGE = "Failed to Update Page"; - @Reference - ModelFactory modelFactory; @Reference AdminResourceHandler resourceManagement; diff --git a/admin-base/ui.apps/src/main/content/jcr_root/apps/field/material-range/template.vue b/admin-base/ui.apps/src/main/content/jcr_root/apps/field/material-range/template.vue index 5bd34b420a..a63ecf06aa 100644 --- a/admin-base/ui.apps/src/main/content/jcr_root/apps/field/material-range/template.vue +++ b/admin-base/ui.apps/src/main/content/jcr_root/apps/field/material-range/template.vue @@ -75,6 +75,19 @@ export default { watch: { value(val) { this.model[this.schema.model] = val + + let propsToRemove = this.model['_opDeleteProps'] || [] + if (val || val === 0 || val === "0") { + propsToRemove = propsToRemove.filter(x => x !== this.schema.model) + } else { + propsToRemove.push(this.schema.model) + } + + if (propsToRemove.length > 0) { + this.model['_opDeleteProps'] = propsToRemove + } else { + delete this.model['_opDeleteProps'] + } } }, created() { From c1ce707c71351580a499d99f6f208bc5f197af35 Mon Sep 17 00:00:00 2001 From: Ruben Reusser Date: Wed, 23 Dec 2020 07:06:09 -0800 Subject: [PATCH 18/19] make sure peregrine develop-sling12 builds with themecleanflex develop-sling12 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1f50e2fd20..5bea9436c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_script: script: - if [ ${TRAVIS_PULL_REQUEST} != "false" ]; then BUILD_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH; else BUILD_BRANCH=$TRAVIS_BRANCH; fi -- cd docker && ./builddocker.sh ${BUILD_BRANCH} develop +- cd docker && ./builddocker.sh ${BUILD_BRANCH} develop-sling12 - ./pushdocker-travis.sh after_script: From 9482392bb63b024b6746e816aae397f29f145703 Mon Sep 17 00:00:00 2001 From: Ruben Reusser Date: Mon, 4 Jan 2021 13:46:58 -0800 Subject: [PATCH 19/19] #691: allow read access to all tenants on /perapi instead of /perapi/admin --- ...ng.jcr.repoinit.RepositoryInitializer-peregrine.config | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/base/ui.apps/src/main/content/jcr_root/apps/runmodes/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-peregrine.config b/platform/base/ui.apps/src/main/content/jcr_root/apps/runmodes/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-peregrine.config index c14d7f0b49..60a3838855 100644 --- a/platform/base/ui.apps/src/main/content/jcr_root/apps/runmodes/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-peregrine.config +++ b/platform/base/ui.apps/src/main/content/jcr_root/apps/runmodes/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-peregrine.config @@ -72,20 +72,20 @@ scripts=[\ allow jcr:read on /content end +# Create Tenant Groups for Peregrine + create group all_tenants with path /home/groups/tenants + # set ACL's for all_tenants group on paths lower than root # Note: this replaces jcr:read for everyone on root set ACL for all_tenants allow jcr:read on /index.html allow jcr:read on /favicon.ico allow jcr:read on /robots.txt - allow jcr:read on /perapi/admin + allow jcr:read on /perapi allow jcr:read on /apps allow jcr:read on /i18n allow jcr:read on /content end -# Create Tenant Groups for Peregrine - create group all_tenants with path /home/groups/tenants - "\ ]