Skip to content

Commit

Permalink
AemObjectsReflectionToStringBuilder: support reflection-based toStrin…
Browse files Browse the repository at this point in the history
…g() methods with more compact/human-readable output of AEM-related objects (#1)
  • Loading branch information
stefanseifert authored Jun 18, 2024
1 parent b9eea4b commit 7b63c0f
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 5 deletions.
6 changes: 3 additions & 3 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
<body>

<release version="1.10.2" date="not released">
<action type="update" dev="sseifert">
Switch to AEM 6.5.17 as minimum version.
<release version="1.11.0" date="not released">
<action type="add" dev="sseifert" issue="1">
Add AemObjectsReflectionToStringBuilder to support reflection-based toString() methods with more compact/human-readable output of AEM-related objects.
</action>
</release>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<dependency>
<groupId>io.wcm</groupId>
<artifactId>io.wcm.sling.commons</artifactId>
<version>1.4.0</version>
<version>1.6.2</version>
<scope>compile</scope>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2024 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.wcm.commons.util;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;

import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;

/**
* Extends ReflectionToStringBuilder to provide custom handling for AEM-related objects
* (Resource, Page, Asset, ValueMap) for a more compact log output.
*/
public class AemObjectReflectionToStringBuilder extends ReflectionToStringBuilder {

private static final TypedValueProcessor[] PROCESSORS = {
new TypedValueProcessor<>(Resource.class, Resource::getPath),
new TypedValueProcessor<>(Page.class, Page::getPath),
new TypedValueProcessor<>(Asset.class, Asset::getPath),
new TypedValueProcessor<>(ValueMap.class, AemObjectReflectionToStringBuilder::filteredValueMap)
};

/**
* @param object Object to output
*/
public AemObjectReflectionToStringBuilder(Object object) {
super(object);
}

/**
* @param object Object to output
* @param style Style
*/
public AemObjectReflectionToStringBuilder(Object object, ToStringStyle style) {
super(object, style);
}

@Override
@SuppressWarnings({ "unchecked", "java:S3740" })
protected Object getValue(Field field) throws IllegalAccessException {
final Class<?> fieldType = field.getType();
// check if a dedicated processor is registered for the given field type
for (TypedValueProcessor item : PROCESSORS) {
if (item.type.isAssignableFrom(fieldType)) {
Object value = field.get(this.getObject());
if (value != null) {
return item.processor.apply(value);
}
}
}
return super.getValue(field);
}

/**
* Filter value map to exclude jcr:* properties and null values.
* @param props Value map
* @return Filtered value map, sorted by key
*/
public static Map<String, Object> filteredValueMap(ValueMap props) {
return props.entrySet().stream()
.filter(entry -> !entry.getKey().startsWith("jcr:") && entry.getValue() != null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (o1, o2) -> o1, TreeMap::new));
}

private static class TypedValueProcessor<T> {
private final Class<T> type;
private final Function<T, Object> processor;
TypedValueProcessor(Class<T> type, Function<T, Object> processor) {
this.type = type;
this.processor = processor;
}
}

}
2 changes: 1 addition & 1 deletion src/main/java/io/wcm/wcm/commons/util/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
/**
* Miscellaneous WCM helper classes.
*/
@org.osgi.annotation.versioning.Version("1.4.0")
@org.osgi.annotation.versioning.Version("1.5.0")
package io.wcm.wcm.commons.util;
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2024 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.wcm.commons.util;

import static com.day.cq.commons.jcr.JcrConstants.JCR_CREATED;
import static com.day.cq.commons.jcr.JcrConstants.JCR_PRIMARYTYPE;
import static com.day.cq.commons.jcr.JcrConstants.NT_UNSTRUCTURED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import io.wcm.wcm.commons.contenttype.ContentType;
import io.wcm.wcm.commons.testcontext.AppAemContext;

@ExtendWith(AemContextExtension.class)
class AemObjectReflectionToStringBuilderTest {

private final AemContext context = AppAemContext.newAemContext();

private static final ValueMap VALUEMAP_SAMPLE;
static {
final Map<String,Object> props = new HashMap<>();
props.put("prop1", "value1");
props.put(JCR_CREATED, new Date());
props.put(JCR_PRIMARYTYPE, NT_UNSTRUCTURED);
props.put("prop2", 5);
props.put("prop3", null);
VALUEMAP_SAMPLE = new ValueMapDecorator(props);
}

@Test
void testBuild() {
ClassWithFields obj = new ClassWithFields();
obj.prop1 = "value1";
obj.resource = context.create().resource("/content/resource1",
"prop2", "value2");
obj.page = context.create().page("/content/page1");
obj.asset = context.create().asset("/content/dam/asset1.jpg", 10, 10, ContentType.JPEG);
obj.props = VALUEMAP_SAMPLE;

assertEquals("[asset=/content/dam/asset1.jpg,"
+ "page=/content/page1,"
+ "prop1=value1,"
+ "props={prop1=value1, prop2=5},"
+ "resource=/content/resource1]",
new AemObjectReflectionToStringBuilder(obj, ToStringStyle.NO_CLASS_NAME_STYLE).build());
}

@Test
void testBuild_NullObjects() {
ClassWithFields obj = new ClassWithFields();

assertNotNull(new AemObjectReflectionToStringBuilder(obj).build());
}

@Test
void testFilteredValueMap() {
Map<String, Object> filtered = AemObjectReflectionToStringBuilder.filteredValueMap(VALUEMAP_SAMPLE);

assertEquals(Map.of("prop1", "value1", "prop2", 5), filtered);
}

@SuppressWarnings("unused")
@SuppressFBWarnings("URF_UNREAD_FIELD")
private static final class ClassWithFields {
String prop1;
Resource resource;
Page page;
Asset asset;
ValueMap props;
}

}

0 comments on commit 7b63c0f

Please sign in to comment.