From 7810ddca5d128481c9fe03199f6a4a822542e175 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Tue, 11 Aug 2026 23:26:38 +0200 Subject: [PATCH 1/2] CELDEV-1344 CelObjectFetcher --- .../celements/convert/bean/BeanConverter.java | 3 +- .../convert/bean/BeanFieldAccessor.java | 3 +- .../convert/bean/BeanXObjectConverter.java | 3 +- .../convert/bean/CelObjectBeanConverter.java | 73 +++++++ .../convert/bean/XObjectBeanConverter.java | 16 +- .../classes/AbstractClassDefConverter.java | 3 +- ...rter.java => AbstractObjectConverter.java} | 13 +- .../convert/classes/XObjectDeconverter.java | 3 +- .../access/DefaultModelAccessFacade.java | 44 +++-- .../model/access/IModelAccessFacade.java | 6 + .../field/AbstractDocumentFieldAccessor.java | 48 +++++ .../model/field/AbstractFieldAccessor.java | 19 -- .../field/AbstractObjectFieldAccessor.java | 80 ++++++++ .../model/field/CelDocumentFieldAccessor.java | 63 ++++++ .../model/field/CelObjectFieldAccessor.java | 48 +++++ .../celements/model/field/FieldAccessor.java | 12 +- .../model/field/XDocumentFieldAccessor.java | 55 ++---- .../model/field/XObjectFieldAccessor.java | 87 +++----- .../field/XObjectStringFieldAccessor.java | 15 +- .../model/object/AbstractObjectFetcher.java | 3 + .../model/object/cel/CelObjectBridge.java | 131 ++++++++++++ .../model/object/cel/CelObjectFetcher.java | 54 +++++ .../object/xwiki/XWikiEmptyObjectBridge.java | 34 ++-- .../model/object/xwiki/XWikiObjectBridge.java | 45 +++-- .../model/object/xwiki/XWikiObjectEditor.java | 6 +- .../object/xwiki/XWikiObjectFetcher.java | 19 +- .../main/resources/META-INF/components.txt | 5 - .../bean/CelObjectBeanConverterTest.java | 61 ++++++ .../access/DefaultModelAccessFacadeTest.java | 25 +++ .../access/object/CelObjectFetcherTest.java | 187 ++++++++++++++++++ .../model/access/object/QueryBuilderTest.java | 6 +- .../field/XDocumentFieldAccessorTest.java | 4 +- .../model/field/XObjectFieldAccessorTest.java | 5 +- ...alizedStringEntityReferenceSerializer.java | 21 ++ .../model/reference/DocumentReference.java | 12 +- .../model/reference/EntityReference.java | 48 ++--- ...tReferenceEntityReferenceResolverTest.java | 3 +- .../reference/AttachmentReferenceTest.java | 3 +- .../reference/DocumentReferenceTest.java | 29 ++- .../model/reference/EntityReferenceTest.java | 9 + .../ObjectPropertyReferenceTest.java | 3 +- .../model/reference/ObjectReferenceTest.java | 3 +- .../model/reference/SpaceReferenceTest.java | 3 +- .../java/com/xpn/xwiki/doc/CelDocument.java | 13 +- .../java/com/xpn/xwiki/doc/CelObject.java | 5 + .../com/xpn/xwiki/doc/XWikiAttachment.java | 12 ++ .../java/com/xpn/xwiki/doc/XWikiDocument.java | 47 ++--- .../com/xpn/xwiki/objects/BaseObject.java | 16 ++ .../com/xpn/xwiki/doc/CelDocumentTest.java | 29 ++- .../com/xpn/xwiki/doc/XWikiDocumentTest.java | 14 ++ 50 files changed, 1132 insertions(+), 317 deletions(-) create mode 100644 celements-model/src/main/java/com/celements/convert/bean/CelObjectBeanConverter.java rename celements-model/src/main/java/com/celements/convert/classes/{XObjectConverter.java => AbstractObjectConverter.java} (66%) create mode 100644 celements-model/src/main/java/com/celements/model/field/AbstractDocumentFieldAccessor.java delete mode 100644 celements-model/src/main/java/com/celements/model/field/AbstractFieldAccessor.java create mode 100644 celements-model/src/main/java/com/celements/model/field/AbstractObjectFieldAccessor.java create mode 100644 celements-model/src/main/java/com/celements/model/field/CelDocumentFieldAccessor.java create mode 100644 celements-model/src/main/java/com/celements/model/field/CelObjectFieldAccessor.java create mode 100644 celements-model/src/main/java/com/celements/model/object/cel/CelObjectBridge.java create mode 100644 celements-model/src/main/java/com/celements/model/object/cel/CelObjectFetcher.java create mode 100644 celements-model/src/test/java/com/celements/convert/bean/CelObjectBeanConverterTest.java create mode 100644 celements-model/src/test/java/com/celements/model/access/object/CelObjectFetcherTest.java create mode 100644 celements-reference/src/main/java/org/xwiki/model/internal/reference/LocalizedStringEntityReferenceSerializer.java diff --git a/celements-model/src/main/java/com/celements/convert/bean/BeanConverter.java b/celements-model/src/main/java/com/celements/convert/bean/BeanConverter.java index 1a1ed301d..d1d481ade 100644 --- a/celements-model/src/main/java/com/celements/convert/bean/BeanConverter.java +++ b/celements-model/src/main/java/com/celements/convert/bean/BeanConverter.java @@ -1,5 +1,7 @@ package com.celements.convert.bean; +import java.util.function.Supplier; + import javax.validation.constraints.NotNull; import org.xwiki.component.annotation.ComponentRole; @@ -9,7 +11,6 @@ import com.celements.common.reflect.ReflectiveInstanceSupplier; import com.celements.component.ComponentInstanceSupplier; import com.celements.convert.Converter; -import com.google.common.base.Supplier; /** *

diff --git a/celements-model/src/main/java/com/celements/convert/bean/BeanFieldAccessor.java b/celements-model/src/main/java/com/celements/convert/bean/BeanFieldAccessor.java index f9fb85146..f585b4927 100644 --- a/celements-model/src/main/java/com/celements/convert/bean/BeanFieldAccessor.java +++ b/celements-model/src/main/java/com/celements/convert/bean/BeanFieldAccessor.java @@ -11,7 +11,6 @@ import com.celements.model.classes.fields.ClassField; import com.celements.model.classes.fields.list.ListField; -import com.celements.model.field.AbstractFieldAccessor; import com.celements.model.field.FieldAccessException; import com.celements.model.field.FieldAccessor; import com.celements.model.field.FieldMissingException; @@ -23,7 +22,7 @@ * {{@link #getBeanMethodName(ClassField)} to check expected naming. */ @Component(BeanFieldAccessor.NAME) -public class BeanFieldAccessor extends AbstractFieldAccessor { +public class BeanFieldAccessor implements FieldAccessor { private static final Logger LOGGER = LoggerFactory.getLogger(BeanFieldAccessor.class); diff --git a/celements-model/src/main/java/com/celements/convert/bean/BeanXObjectConverter.java b/celements-model/src/main/java/com/celements/convert/bean/BeanXObjectConverter.java index 31c88c94d..c53536f73 100644 --- a/celements-model/src/main/java/com/celements/convert/bean/BeanXObjectConverter.java +++ b/celements-model/src/main/java/com/celements/convert/bean/BeanXObjectConverter.java @@ -1,5 +1,7 @@ package com.celements.convert.bean; +import java.util.function.Supplier; + import org.xwiki.component.annotation.Component; import org.xwiki.component.annotation.InstantiationStrategy; import org.xwiki.component.annotation.Requirement; @@ -7,7 +9,6 @@ import com.celements.convert.classes.XObjectDeconverter; import com.celements.model.field.FieldAccessor; -import com.google.common.base.Supplier; import com.xpn.xwiki.objects.BaseObject; /** diff --git a/celements-model/src/main/java/com/celements/convert/bean/CelObjectBeanConverter.java b/celements-model/src/main/java/com/celements/convert/bean/CelObjectBeanConverter.java new file mode 100644 index 000000000..e290f99be --- /dev/null +++ b/celements-model/src/main/java/com/celements/convert/bean/CelObjectBeanConverter.java @@ -0,0 +1,73 @@ +package com.celements.convert.bean; + +import static com.google.common.base.Preconditions.*; + +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.xwiki.component.annotation.ComponentRole; + +import com.celements.common.reflect.ReflectiveInstanceSupplier; +import com.celements.component.ComponentInstanceSupplier; +import com.celements.convert.classes.AbstractObjectConverter; +import com.celements.model.field.CelObjectFieldAccessor; +import com.celements.model.field.FieldAccessor; +import com.xpn.xwiki.doc.CelObject; + +/** Converts an immutable {@link CelObject} to a bean. */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class CelObjectBeanConverter extends AbstractObjectConverter implements + BeanClassDefConverter { + + private final FieldAccessor celObjectAccessor; + private final FieldAccessor beanAccessor; + private Supplier supplier; + + @Inject + public CelObjectBeanConverter( + CelObjectFieldAccessor celObjectAccessor, + BeanFieldAccessor beanAccessor) { + this.celObjectAccessor = celObjectAccessor; + this.beanAccessor = beanAccessor; + } + + @Override + public void initialize(Supplier instanceSupplier) { + supplier = checkNotNull(instanceSupplier); + } + + @Override + public void initialize(Class token) { + checkNotNull(token); + initialize(token.isAnnotationPresent(ComponentRole.class) + ? new ComponentInstanceSupplier<>(token) + : new ReflectiveInstanceSupplier<>(token)); + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public FieldAccessor getFromFieldAccessor() { + return celObjectAccessor; + } + + @Override + public FieldAccessor getToFieldAccessor() { + return beanAccessor; + } + + @Override + protected Supplier getInstanceSupplier() { + checkState(supplier != null, "not initialized"); + return supplier; + } + +} diff --git a/celements-model/src/main/java/com/celements/convert/bean/XObjectBeanConverter.java b/celements-model/src/main/java/com/celements/convert/bean/XObjectBeanConverter.java index afd7a0512..0386a3946 100644 --- a/celements-model/src/main/java/com/celements/convert/bean/XObjectBeanConverter.java +++ b/celements-model/src/main/java/com/celements/convert/bean/XObjectBeanConverter.java @@ -2,6 +2,8 @@ import static com.google.common.base.Preconditions.*; +import java.util.function.Supplier; + import org.xwiki.component.annotation.Component; import org.xwiki.component.annotation.ComponentRole; import org.xwiki.component.annotation.InstantiationStrategy; @@ -11,9 +13,9 @@ import com.celements.common.reflect.ReflectiveInstanceSupplier; import com.celements.component.ComponentInstanceSupplier; import com.celements.convert.classes.ClassDefinitionConverter; -import com.celements.convert.classes.XObjectConverter; +import com.celements.convert.classes.AbstractObjectConverter; import com.celements.model.field.FieldAccessor; -import com.google.common.base.Supplier; +import com.celements.model.field.XObjectFieldAccessor; import com.xpn.xwiki.objects.BaseObject; /** @@ -22,13 +24,16 @@ */ @Component(XObjectBeanConverter.NAME) @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP) -public class XObjectBeanConverter extends XObjectConverter implements +public class XObjectBeanConverter extends AbstractObjectConverter implements BeanClassDefConverter { public static final String NAME = "xobjectbean"; private Supplier supplier; + @Requirement(XObjectFieldAccessor.NAME) + private FieldAccessor xObjAccessor; + @Requirement(BeanFieldAccessor.NAME) private FieldAccessor beanAccessor; @@ -52,6 +57,11 @@ public String getName() { return NAME; } + @Override + public FieldAccessor getFromFieldAccessor() { + return xObjAccessor; + } + @Override public FieldAccessor getToFieldAccessor() { return beanAccessor; diff --git a/celements-model/src/main/java/com/celements/convert/classes/AbstractClassDefConverter.java b/celements-model/src/main/java/com/celements/convert/classes/AbstractClassDefConverter.java index 65e1d8e52..e017bdc03 100644 --- a/celements-model/src/main/java/com/celements/convert/classes/AbstractClassDefConverter.java +++ b/celements-model/src/main/java/com/celements/convert/classes/AbstractClassDefConverter.java @@ -3,6 +3,8 @@ import static com.celements.common.MoreObjectsCel.*; import static com.google.common.base.Preconditions.*; +import java.util.function.Supplier; + import javax.validation.constraints.NotNull; import org.slf4j.Logger; @@ -14,7 +16,6 @@ import com.celements.model.field.FieldAccessException; import com.celements.model.field.FieldAccessor; import com.celements.model.field.FieldMissingException; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; public abstract class AbstractClassDefConverter implements ClassDefinitionConverter { diff --git a/celements-model/src/main/java/com/celements/convert/classes/XObjectConverter.java b/celements-model/src/main/java/com/celements/convert/classes/AbstractObjectConverter.java similarity index 66% rename from celements-model/src/main/java/com/celements/convert/classes/XObjectConverter.java rename to celements-model/src/main/java/com/celements/convert/classes/AbstractObjectConverter.java index 2641e0a9d..04dcbb46c 100644 --- a/celements-model/src/main/java/com/celements/convert/classes/XObjectConverter.java +++ b/celements-model/src/main/java/com/celements/convert/classes/AbstractObjectConverter.java @@ -6,25 +6,14 @@ import com.celements.model.classes.ClassDefinition; import com.celements.model.classes.fields.ClassField; -import com.celements.model.field.FieldAccessor; -import com.celements.model.field.XObjectFieldAccessor; import com.celements.web.classes.oldcore.XWikiObjectClass; import com.google.common.collect.ImmutableList; -import com.xpn.xwiki.objects.BaseObject; -public abstract class XObjectConverter extends AbstractClassDefConverter { +public abstract class AbstractObjectConverter extends AbstractClassDefConverter { @Requirement(XWikiObjectClass.CLASS_DEF_HINT) private ClassDefinition xObjClassDef; - @Requirement(XObjectFieldAccessor.NAME) - private FieldAccessor xObjAccessor; - - @Override - public FieldAccessor getFromFieldAccessor() { - return xObjAccessor; - } - @Override protected ImmutableList.Builder> aggregateClassFields( ImmutableList.Builder> iter) { diff --git a/celements-model/src/main/java/com/celements/convert/classes/XObjectDeconverter.java b/celements-model/src/main/java/com/celements/convert/classes/XObjectDeconverter.java index 2fdd0970f..7ca0232f1 100644 --- a/celements-model/src/main/java/com/celements/convert/classes/XObjectDeconverter.java +++ b/celements-model/src/main/java/com/celements/convert/classes/XObjectDeconverter.java @@ -1,11 +1,12 @@ package com.celements.convert.classes; +import java.util.function.Supplier; + import org.xwiki.component.annotation.Requirement; import com.celements.model.field.FieldAccessor; import com.celements.model.field.XObjectFieldAccessor; import com.celements.model.object.xwiki.XWikiObjectSupplier; -import com.google.common.base.Supplier; import com.xpn.xwiki.objects.BaseObject; public abstract class XObjectDeconverter extends AbstractClassDefConverter { diff --git a/celements-model/src/main/java/com/celements/model/access/DefaultModelAccessFacade.java b/celements-model/src/main/java/com/celements/model/access/DefaultModelAccessFacade.java index 97ba8e9f4..a5ca12069 100644 --- a/celements-model/src/main/java/com/celements/model/access/DefaultModelAccessFacade.java +++ b/celements-model/src/main/java/com/celements/model/access/DefaultModelAccessFacade.java @@ -14,9 +14,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; @@ -152,6 +152,11 @@ public Document getApiDocument(XWikiDocument doc) throws NoAccessRightsException EAccessLevel.VIEW); } + @Override + public Optional getCelDocument(DocumentReference docRef) { + return getCelDocument(docRef, DEFAULT_LANG); + } + @Override public Optional getCelDocument(DocumentReference docRef, String lang) { checkNotNull(docRef); @@ -445,28 +450,37 @@ public boolean isTranslation(XWikiDocument doc) { @Override public Stream streamParents(XWikiDocument doc) { - return StreamEx.of(new Iterator() { + return streamParents(doc, XWikiDocument::getParentReference, this::getDocumentOpt); + } + + @Override + public Stream streamParents(CelDocument doc) { + return streamParents(doc, CelDocument::getParentReference, this::getCelDocument); + } + + private Stream streamParents(D doc, + Function parentRef, + Function> loader) { + return StreamEx.of(new Iterator() { - private XWikiDocument current = doc; + private D current = doc; private Set seen = new HashSet<>(); @Override public boolean hasNext() { - return (current != null) - && (current.getParentReference() != null) - && exists(current.getParentReference()); + return Optional.ofNullable(current) + .map(parentRef) + .filter(DefaultModelAccessFacade.this::exists) + .isPresent(); } @Override - public XWikiDocument next() { - try { - if (seen.add(current.getParentReference())) { - return current = getDocument(current.getParentReference()); - } else { - throw new IllegalStateException("cyclic parent referencing: " + seen); - } - } catch (DocumentNotExistsException | NullPointerException exc) { - throw new NoSuchElementException(exc.getClass().getSimpleName() + " " + exc.getMessage()); + public D next() { + DocumentReference ref = Optional.ofNullable(current).map(parentRef).orElseThrow(); + if (seen.add(ref)) { + return current = loader.apply(ref).orElseThrow(); + } else { + throw new IllegalStateException("cyclic parent referencing: " + seen); } } }); diff --git a/celements-model/src/main/java/com/celements/model/access/IModelAccessFacade.java b/celements-model/src/main/java/com/celements/model/access/IModelAccessFacade.java index 50b4bbcf1..436c04a55 100644 --- a/celements-model/src/main/java/com/celements/model/access/IModelAccessFacade.java +++ b/celements-model/src/main/java/com/celements/model/access/IModelAccessFacade.java @@ -51,6 +51,9 @@ XWikiDocument getDocument(@NotNull DocumentReference docRef, @Nullable String la @NotNull Optional getDocumentOpt(@NotNull DocumentReference docRef, @Nullable String lang); + @NotNull + Optional getCelDocument(@NotNull DocumentReference docRef); + @NotNull Optional getCelDocument(@NotNull DocumentReference docRef, @Nullable String lang); @@ -131,6 +134,9 @@ void deleteDocumentWithoutTranslations(@NotNull XWikiDocument doc, boolean totra @NotNull Stream streamParents(@NotNull XWikiDocument doc); + @NotNull + Stream streamParents(@NotNull CelDocument doc); + /** * @deprecated instead use {@link XWikiObjectFetcher} * @param docRef diff --git a/celements-model/src/main/java/com/celements/model/field/AbstractDocumentFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/AbstractDocumentFieldAccessor.java new file mode 100644 index 000000000..81341f173 --- /dev/null +++ b/celements-model/src/main/java/com/celements/model/field/AbstractDocumentFieldAccessor.java @@ -0,0 +1,48 @@ +package com.celements.model.field; + +import static com.celements.web.classes.oldcore.XWikiDocumentClass.*; +import static com.google.common.base.Preconditions.*; +import static com.google.common.base.Strings.*; +import static java.text.MessageFormat.*; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.celements.model.classes.fields.ClassField; + +public abstract class AbstractDocumentFieldAccessor implements FieldAccessor { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + @SuppressWarnings("unchecked") + public final Optional get(D doc, ClassField field) { + checkNotNull(doc); + checkField(field); + Function getter = getters().get(field.getName()); + if (getter == null) { + throw new FieldAccessException("undefined field: " + field); + } + Object value = getter.apply(doc); + if (value instanceof String) { + value = emptyToNull(value.toString().trim()); + } + logger.info("get: '{}' for '{}' from '{}'", value, field, doc); + return Optional.ofNullable((V) value); + } + + private void checkField(ClassField field) { + checkNotNull(field); + if (!CLASS_REF.equals(field.getClassReference())) { + throw new FieldAccessException(format("uneligible for [{0}], it is of class [{1}]", + CLASS_REF, field.getClassReference())); + } + } + + protected abstract Map> getters(); + +} diff --git a/celements-model/src/main/java/com/celements/model/field/AbstractFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/AbstractFieldAccessor.java deleted file mode 100644 index d3962e388..000000000 --- a/celements-model/src/main/java/com/celements/model/field/AbstractFieldAccessor.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.celements.model.field; - -import com.celements.model.classes.fields.ClassField; - -public abstract class AbstractFieldAccessor implements FieldAccessor { - - @Override - @Deprecated - public com.google.common.base.Optional getValue(T instance, ClassField field) { - return com.google.common.base.Optional.fromJavaUtil(get(instance, field)); - } - - @Override - @Deprecated - public boolean setValue(T instance, ClassField field, V value) { - return set(instance, field, value); - } - -} diff --git a/celements-model/src/main/java/com/celements/model/field/AbstractObjectFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/AbstractObjectFieldAccessor.java new file mode 100644 index 000000000..f04ccdf73 --- /dev/null +++ b/celements-model/src/main/java/com/celements/model/field/AbstractObjectFieldAccessor.java @@ -0,0 +1,80 @@ +package com.celements.model.field; + +import static com.celements.web.classes.oldcore.XWikiObjectClass.*; +import static com.google.common.base.Preconditions.*; +import static com.google.common.base.Strings.*; +import static java.text.MessageFormat.*; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xwiki.model.reference.ClassReference; + +import com.celements.model.classes.fields.ClassField; +import com.celements.model.classes.fields.CustomClassField; + +public abstract class AbstractObjectFieldAccessor implements FieldAccessor { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public final Optional get(O obj, ClassField field) { + checkNotNull(obj); + checkNotNull(field); + Optional value; + if (field.getClassReference().equals(CLASS_REF)) { + value = Optional.of(getObjectFieldValue(obj, field)); + } else { + checkClassRef(obj, field); + value = getRawValue(obj, field.getName()) + .map(val -> (val instanceof String str) ? emptyToNull(str.trim()) : val) + .flatMap(val -> resolvePropertyValue(field, val)); + } + logger.trace("get - obj [{}], field [{}], value [{}]", obj, field, value); + return value; + } + + protected abstract Optional getRawValue(O obj, String fieldName); + + private V getObjectFieldValue(O obj, ClassField field) { + Function getter = getters().get(field.getName()); + if (getter == null) { + throw new FieldAccessException("undefined field: " + field); + } + return field.getType().cast(getter.apply(obj)); + } + + protected abstract Map> getters(); + + private Optional resolvePropertyValue(ClassField field, Object value) { + try { + if (field instanceof CustomClassField) { + return ((CustomClassField) field).resolve(value); + } else { + return Optional.of(field.getType().cast(value)); + } + } catch (ClassCastException | IllegalArgumentException exc) { + throw new FieldAccessException(format("field [{0}] ill defined, expecting type [{1}], " + + "but got [{2}]", field, field.getType(), value.getClass()), exc); + } + } + + protected final void checkClassRef(O obj, ClassField field) { + checkNotNull(obj); + checkNotNull(field); + String objectTypeName = obj.getClass().getSimpleName(); + if (!field.getClassReference().isValidObjectClass()) { + throw new FieldAccessException(format( + "{0} uneligible for pseudo class field [{1}]", objectTypeName, field)); + } + ClassReference classRef = getObjectFieldValue(obj, FIELD_CLASS_REF); + if (!classRef.equals(field.getClassReference())) { + throw new FieldAccessException(format( + "{0} uneligible for [{1}], it is of class [{2}]", objectTypeName, field, classRef)); + } + } + +} diff --git a/celements-model/src/main/java/com/celements/model/field/CelDocumentFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/CelDocumentFieldAccessor.java new file mode 100644 index 000000000..3e93c57bb --- /dev/null +++ b/celements-model/src/main/java/com/celements/model/field/CelDocumentFieldAccessor.java @@ -0,0 +1,63 @@ +package com.celements.model.field; + +import static com.celements.web.classes.oldcore.XWikiDocumentClass.*; + +import java.time.Instant; +import java.util.Date; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +import org.springframework.stereotype.Component; + +import com.celements.model.classes.fields.ClassField; +import com.google.common.collect.ImmutableMap; +import com.xpn.xwiki.doc.CelDocument; + +import one.util.streamex.EntryStream; + +/** + * Read-only {@link FieldAccessor} for accessing {@link CelDocument} properties. + */ +@Component +public class CelDocumentFieldAccessor extends AbstractDocumentFieldAccessor { + + @Override + public String getName() { + return "CelDocumentFieldAccessor"; + } + + static final Map> GETTERS = EntryStream + .of(ImmutableMap., Function>builder() + .put(FIELD_DOC_REF, CelDocument::getDocumentReference) + .put(FIELD_PARENT_REF, CelDocument::getParentReference) + .put(FIELD_LANGUAGE, CelDocument::getLanguage) + .put(FIELD_DEFAULT_LANGUAGE, CelDocument::getDefaultLanguage) + .put(FIELD_TRANSLATION, CelDocument::isTrans) + .put(FIELD_CREATOR, CelDocument::getCreator) + .put(FIELD_AUTHOR, CelDocument::getAuthor) + .put(FIELD_CONTENT_AUTHOR, CelDocument::getContentAuthor) + .put(FIELD_CREATION_DATE, doc -> toDate(doc.getCreationDate())) + .put(FIELD_UPDATE_DATE, doc -> toDate(doc.getDate())) + .put(FIELD_CONTENT_UPDATE_DATE, doc -> toDate(doc.getContentUpdateDate())) + .put(FIELD_TITLE, CelDocument::getTitle) + .put(FIELD_CONTENT, CelDocument::getContent) + .build()) + .mapKeys(ClassField::getName) + .toImmutableMap(); + + @Override + protected Map> getters() { + return GETTERS; + } + + private static Date toDate(Instant instant) { + return Optional.ofNullable(instant).map(Date::from).orElse(null); + } + + @Override + public boolean set(CelDocument doc, ClassField field, V newValue) { + throw new UnsupportedOperationException("CelDocument is immutable"); + } + +} diff --git a/celements-model/src/main/java/com/celements/model/field/CelObjectFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/CelObjectFieldAccessor.java new file mode 100644 index 000000000..a5acf2d7a --- /dev/null +++ b/celements-model/src/main/java/com/celements/model/field/CelObjectFieldAccessor.java @@ -0,0 +1,48 @@ +package com.celements.model.field; + +import static com.celements.web.classes.oldcore.XWikiObjectClass.*; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +import org.springframework.stereotype.Component; +import org.xwiki.model.reference.ClassReference; + +import com.celements.model.classes.fields.ClassField; +import com.xpn.xwiki.doc.CelObject; +import com.xpn.xwiki.doc.CelProperty; + +/** + * Read-only {@link FieldAccessor} for accessing {@link CelObject} properties. + */ +@Component +public class CelObjectFieldAccessor extends AbstractObjectFieldAccessor { + + @Override + public String getName() { + return "CelObjectFieldAccessor"; + } + + static final Map> GETTERS = Map + .>of( + FIELD_DOC_REF.getName(), CelObject::getDocumentReference, + FIELD_CLASS_REF.getName(), obj -> new ClassReference(obj.getClassReference()), + FIELD_NUMBER.getName(), CelObject::getNumber); + + @Override + protected Map> getters() { + return GETTERS; + } + + @Override + protected Optional getRawValue(CelObject obj, String fieldName) { + return obj.getProperty(fieldName).map(CelProperty::getValue); + } + + @Override + public boolean set(CelObject obj, ClassField field, V newValue) { + throw new UnsupportedOperationException("CelObject is immutable"); + } + +} diff --git a/celements-model/src/main/java/com/celements/model/field/FieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/FieldAccessor.java index a8986e802..fd9fb298c 100644 --- a/celements-model/src/main/java/com/celements/model/field/FieldAccessor.java +++ b/celements-model/src/main/java/com/celements/model/field/FieldAccessor.java @@ -38,11 +38,15 @@ public interface FieldAccessor { @NotNull @Deprecated - com.google.common.base.Optional getValue(@NotNull T instance, @NotNull ClassField field) - throws FieldAccessException; + default com.google.common.base.Optional getValue(@NotNull T instance, + @NotNull ClassField field) throws FieldAccessException { + return com.google.common.base.Optional.fromJavaUtil(get(instance, field)); + } @Deprecated - boolean setValue(@NotNull T instance, @NotNull ClassField field, @Nullable V value) - throws FieldAccessException; + default boolean setValue(@NotNull T instance, @NotNull ClassField field, @Nullable V value) + throws FieldAccessException { + return set(instance, field, value); + } } diff --git a/celements-model/src/main/java/com/celements/model/field/XDocumentFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/XDocumentFieldAccessor.java index 2efb3443c..837fd13b5 100644 --- a/celements-model/src/main/java/com/celements/model/field/XDocumentFieldAccessor.java +++ b/celements-model/src/main/java/com/celements/model/field/XDocumentFieldAccessor.java @@ -1,19 +1,13 @@ package com.celements.model.field; import static com.celements.web.classes.oldcore.XWikiDocumentClass.*; -import static com.google.common.base.Preconditions.*; -import static com.google.common.base.Strings.*; -import static java.text.MessageFormat.*; import java.util.Map; -import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.ObjIntConsumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xwiki.component.annotation.Component; +import org.springframework.stereotype.Component; import org.xwiki.model.reference.EntityReference; import com.celements.model.classes.fields.ClassField; @@ -27,42 +21,22 @@ * {@link FieldAccessor} for accessing {@link XWikiDocument} properties */ @Component(XDocumentFieldAccessor.NAME) -public class XDocumentFieldAccessor extends AbstractFieldAccessor { +public class XDocumentFieldAccessor extends AbstractDocumentFieldAccessor { - private static final Logger LOGGER = LoggerFactory.getLogger(XDocumentFieldAccessor.class); - - public static final String NAME = "xdoc"; + public static final String NAME = "XDocumentFieldAccessor"; @Override public String getName() { return NAME; } - @Override - @SuppressWarnings("unchecked") - public Optional get(XWikiDocument doc, ClassField field) { - checkNotNull(doc); - checkField(field); - Function getter = GETTERS.get(field.getName()); - if (getter != null) { - Object value = getter.apply(doc); - if (value instanceof String) { - value = emptyToNull(value.toString().trim()); - } - LOGGER.info("get: '{}' for '{}' from '{}'", value, field, doc.getDocumentReference()); - return Optional.ofNullable((V) value); - } else { - throw new FieldAccessException("undefined field: " + field); - } - } - - static final Map> GETTERS = EntryStream - .of(ImmutableMap., Function>builder() + static final Map> GETTERS = EntryStream + .of(ImmutableMap., Function>builder() .put(FIELD_DOC_REF, XWikiDocument::getDocumentReference) .put(FIELD_PARENT_REF, XWikiDocument::getParentReference) .put(FIELD_LANGUAGE, XWikiDocument::getLanguage) .put(FIELD_DEFAULT_LANGUAGE, XWikiDocument::getDefaultLanguage) - .put(FIELD_TRANSLATION, doc -> doc.getTranslation() != 0) + .put(FIELD_TRANSLATION, XWikiDocument::isTrans) .put(FIELD_CREATOR, XWikiDocument::getCreator) .put(FIELD_AUTHOR, XWikiDocument::getAuthor) .put(FIELD_CONTENT_AUTHOR, XWikiDocument::getContentAuthor) @@ -75,6 +49,11 @@ public Optional get(XWikiDocument doc, ClassField field) { .mapKeys(ClassField::getName) .toImmutableMap(); + @Override + protected Map> getters() { + return GETTERS; + } + @Override public boolean set(XWikiDocument doc, ClassField field, V value) { if (!Objects.equal(value, get(doc, field).orElse(null))) { @@ -82,10 +61,10 @@ public boolean set(XWikiDocument doc, ClassField field, V value) { if (setter != null) { try { setter.accept(doc, value); - LOGGER.info("set: '{}' for '{}' from '{}'", value, field, doc.getDocumentReference()); + logger.info("set: '{}' for '{}' from '{}'", value, field, doc.getDocumentReference()); return true; } catch (ClassCastException cce) { - LOGGER.warn("set: illegal value '{}' for '{}' from '{}'", + logger.warn("set: illegal value '{}' for '{}' from '{}'", value, field, doc.getDocumentReference(), cce); } } else { @@ -113,14 +92,6 @@ public boolean set(XWikiDocument doc, ClassField field, V value) { .mapKeys(ClassField::getName) .toImmutableMap(); - private void checkField(ClassField field) { - checkNotNull(field); - if (!CLASS_REF.equals(field.getClassReference())) { - throw new FieldAccessException(format("uneligible for [{0}], it is of class [{1}]", - CLASS_REF, field.getClassReference())); - } - } - @SuppressWarnings("unchecked") private static BiConsumer asObj(BiConsumer c) { return (doc, value) -> c.accept(doc, (T) value); diff --git a/celements-model/src/main/java/com/celements/model/field/XObjectFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/XObjectFieldAccessor.java index 5cf7039e7..e78f425ba 100644 --- a/celements-model/src/main/java/com/celements/model/field/XObjectFieldAccessor.java +++ b/celements-model/src/main/java/com/celements/model/field/XObjectFieldAccessor.java @@ -1,16 +1,15 @@ package com.celements.model.field; import static com.celements.web.classes.oldcore.XWikiObjectClass.*; -import static com.google.common.base.Preconditions.*; import static java.text.MessageFormat.*; -import java.text.MessageFormat; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; import org.xwiki.model.reference.ClassReference; import com.celements.model.classes.fields.ClassField; @@ -21,60 +20,36 @@ * {@link FieldAccessor} for accessing {@link BaseObject} properties */ @Component(XObjectFieldAccessor.NAME) -public class XObjectFieldAccessor extends AbstractFieldAccessor { +public class XObjectFieldAccessor extends AbstractObjectFieldAccessor { - private static final Logger LOGGER = LoggerFactory.getLogger(XObjectFieldAccessor.class); + public static final String NAME = "XObjectFieldAccessor"; - public static final String NAME = "xobject"; + private final StringFieldAccessor strFieldAccessor; - @Requirement(XObjectStringFieldAccessor.NAME) - protected StringFieldAccessor strFieldAccessor; + @Inject + public XObjectFieldAccessor(XObjectStringFieldAccessor strFieldAccessor) { + this.strFieldAccessor = strFieldAccessor; + } @Override public String getName() { return NAME; } - @Override - public Optional get(BaseObject obj, ClassField field) { - Optional value; - if (field.getClassReference().equals(CLASS_REF)) { - value = Optional.of(getXObjFieldValue(obj, field)); - } else { - checkClassRef(obj, field); - return strFieldAccessor.get(obj, field.getName()) - .flatMap(val -> resolvePropertyValue(field, val)); - } - LOGGER.trace("get - obj [{}], field [{}], value [{}]", obj, field, value); - return value; - } + static final Map> GETTERS = Map + .>of( + FIELD_DOC_REF.getName(), BaseObject::getDocumentReference, + FIELD_CLASS_REF.getName(), obj -> new ClassReference(obj.getXClassReference()), + FIELD_NUMBER.getName(), BaseObject::getNumber); - @SuppressWarnings("unchecked") - private V getXObjFieldValue(BaseObject obj, ClassField field) { - V value; - if (field == FIELD_DOC_REF) { - value = (V) obj.getDocumentReference(); - } else if (field == FIELD_CLASS_REF) { - value = (V) new ClassReference(obj.getXClassReference()); - } else if (field == FIELD_NUMBER) { - value = (V) (Integer) obj.getNumber(); - } else { - throw new FieldAccessException("undefined field: " + field); - } - return value; + @Override + protected Map> getters() { + return GETTERS; } - private Optional resolvePropertyValue(ClassField field, Object value) { - try { - if (field instanceof CustomClassField) { - return ((CustomClassField) field).resolve(value); - } else { - return Optional.of(field.getType().cast(value)); - } - } catch (ClassCastException | IllegalArgumentException exc) { - throw new FieldAccessException(format("field [{0}] ill defined, expecting type [{1}], " - + "but got [{2}]", field, field.getType(), value.getClass()), exc); - } + @Override + protected Optional getRawValue(BaseObject obj, String fieldName) { + return strFieldAccessor.get(obj, fieldName); } @Override @@ -83,7 +58,7 @@ public boolean set(BaseObject obj, ClassField field, V newValue) { var serializeValue = serializePropertyValue(field, newValue).orElse(null); boolean dirty = strFieldAccessor.set(obj, field.getName(), serializeValue); if (dirty) { - LOGGER.debug("set - obj [{}], field [{}], newValue [{}]", obj, field, newValue); + logger.debug("set - obj [{}], field [{}], newValue [{}]", obj, field, newValue); } return dirty; } @@ -101,18 +76,4 @@ private Optional serializePropertyValue(ClassField field, T value) { } } - private void checkClassRef(BaseObject obj, ClassField field) { - checkNotNull(obj); - checkNotNull(field); - if (!field.getClassReference().isValidObjectClass()) { - throw new FieldAccessException(MessageFormat.format( - "BaseObject uneligible for pseudo class field [{0}]", field)); - } - ClassReference classRef = new ClassReference(obj.getXClassReference()); - if (!classRef.equals(field.getClassReference())) { - throw new FieldAccessException(MessageFormat.format( - "BaseObject uneligible for [{0}], it is of class [{1}]", field, classRef)); - } - } - } diff --git a/celements-model/src/main/java/com/celements/model/field/XObjectStringFieldAccessor.java b/celements-model/src/main/java/com/celements/model/field/XObjectStringFieldAccessor.java index 396ff8e69..2520b973a 100644 --- a/celements-model/src/main/java/com/celements/model/field/XObjectStringFieldAccessor.java +++ b/celements-model/src/main/java/com/celements/model/field/XObjectStringFieldAccessor.java @@ -5,10 +5,11 @@ import java.util.Collection; import java.util.Optional; +import javax.inject.Inject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; +import org.springframework.stereotype.Component; import org.xwiki.model.reference.ClassReference; import com.celements.model.context.ModelContext; @@ -26,10 +27,14 @@ public class XObjectStringFieldAccessor implements StringFieldAccessor { + + private final FieldAccessor celDocAccessor; + private final FieldAccessor celObjAccessor; + + @Inject + public CelObjectBridge( + CelDocumentFieldAccessor celDocAccessor, + CelObjectFieldAccessor celObjAccessor) { + this.celDocAccessor = celDocAccessor; + this.celObjAccessor = celObjAccessor; + } + + @Override + public Class getDocumentType() { + return CelDocument.class; + } + + @Override + public Class getObjectType() { + return CelObject.class; + } + + @Override + @Deprecated + public void checkDoc(CelDocument doc) { + // nothing to check on a document in general + } + + @Override + public DocumentReference getDocRef(CelDocument doc) { + return doc.getDocumentReference().withoutLocale(); + } + + @Override + public String getLanguage(CelDocument doc) { + return normalizeLang(doc.getLanguage()); + } + + @Override + public String getDefaultLanguage(CelDocument doc) { + return normalizeLang(doc.getDefaultLanguage()); + } + + private String normalizeLang(String lang) { + return "default".equals(lang) ? DEFAULT_LANG : Strings.nullToEmpty(lang); + } + + @Override + public FluentIterable getDocClasses(CelDocument doc) { + var classes = FluentIterable.from(assertDefaultDoc(doc).getXObjects()) + .transform(obj -> new ClassReference(obj.getClassReference())) + .toSet(); + return FluentIterable.from(classes); + } + + @Override + public FluentIterable getObjects(CelDocument doc, ClassIdentity classId) { + return FluentIterable.from(assertDefaultDoc(doc).getXObjects()) + .filter(obj -> classId.getClassReference().equals(obj.getClassReference())); + } + + private CelDocument.Default assertDefaultDoc(CelDocument doc) { + checkArgument(doc instanceof CelDocument.Default, + MessageFormat.format("object operations not allowed on translation [{0}] of doc [{1}]", + doc.getLanguage(), doc.getDocumentReference())); + return (CelDocument.Default) doc; + } + + @Override + public int getObjectNumber(CelObject obj) { + return obj.getNumber(); + } + + @Override + public ClassIdentity getObjectClass(CelObject obj) { + return new ClassReference(obj.getClassReference()); + } + + @Override + public CelObject cloneObject(CelObject obj) { + return obj; + } + + @Override + public CelObject createObject(CelDocument doc, ClassIdentity classId) { + throw new UnsupportedOperationException("CelDocument is immutable"); + } + + @Override + public boolean deleteObject(CelDocument doc, CelObject obj) { + throw new UnsupportedOperationException("CelDocument is immutable"); + } + + @Override + public FieldAccessor getDocumentFieldAccessor() { + return celDocAccessor; + } + + @Override + public FieldAccessor getObjectFieldAccessor() { + return celObjAccessor; + } + +} diff --git a/celements-model/src/main/java/com/celements/model/object/cel/CelObjectFetcher.java b/celements-model/src/main/java/com/celements/model/object/cel/CelObjectFetcher.java new file mode 100644 index 000000000..3d5d692e8 --- /dev/null +++ b/celements-model/src/main/java/com/celements/model/object/cel/CelObjectFetcher.java @@ -0,0 +1,54 @@ +package com.celements.model.object.cel; + +import javax.annotation.concurrent.NotThreadSafe; +import javax.validation.constraints.NotNull; + +import com.celements.model.object.AbstractObjectFetcher; +import com.celements.model.object.ObjectHandler; +import com.celements.spring.context.SpringContextProvider; +import com.xpn.xwiki.doc.CelDocument; +import com.xpn.xwiki.doc.CelObject; +import com.xpn.xwiki.doc.XWikiDocument; + +@NotThreadSafe +public class CelObjectFetcher extends + AbstractObjectFetcher { + + private static final CelDocument EMPTY_DOC = CelDocument.Default.from( + new XWikiDocument(EMPTY_DOC_REF)); + + public static CelObjectFetcher on(@NotNull CelDocument doc) { + return new CelObjectFetcher(doc); + } + + public static CelObjectFetcher from( + @NotNull ObjectHandler objHandler) { + return CelObjectFetcher.on(objHandler.getDocument()) + .withTranslation(objHandler.getTranslationDoc().orElse(null)) + .with(objHandler.getQuery()); + } + + public static CelObjectFetcher empty() { + return CelObjectFetcher.on(EMPTY_DOC); + } + + private CelObjectFetcher(CelDocument doc) { + super(doc); + } + + @Override + public CelObjectFetcher clone() { + return from(getThis()); + } + + @Override + protected CelObjectBridge getBridge() { + return SpringContextProvider.getBeanFactory().getBean(CelObjectBridge.class); + } + + @Override + protected CelObjectFetcher getThis() { + return this; + } + +} diff --git a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiEmptyObjectBridge.java b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiEmptyObjectBridge.java index bd5ac8fce..45a2e6216 100644 --- a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiEmptyObjectBridge.java +++ b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiEmptyObjectBridge.java @@ -1,19 +1,31 @@ package com.celements.model.object.xwiki; -import javax.annotation.concurrent.Immutable; - -import org.xwiki.component.annotation.Component; - -import com.celements.model.classes.ClassIdentity; -import com.google.common.collect.FluentIterable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.celements.model.classes.ClassIdentity; +import com.celements.model.context.ModelContext; +import com.celements.model.field.XDocumentFieldAccessor; +import com.celements.model.field.XObjectFieldAccessor; +import com.google.common.collect.FluentIterable; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; -@Immutable -@Component(XWikiEmptyObjectBridge.NAME) -public class XWikiEmptyObjectBridge extends XWikiObjectBridge { - - public static final String NAME = "xwikiempty"; +@Immutable +@Component(XWikiEmptyObjectBridge.NAME) +public class XWikiEmptyObjectBridge extends XWikiObjectBridge { + + public static final String NAME = "XWikiEmptyObjectBridge"; + + @Inject + public XWikiEmptyObjectBridge( + XDocumentFieldAccessor xDocAccessor, + XObjectFieldAccessor xObjAccessor, + ModelContext context) { + super(xDocAccessor, xObjAccessor, context); + } @Override public FluentIterable getDocClasses(XWikiDocument doc) { diff --git a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectBridge.java b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectBridge.java index e979405f2..50483cccc 100644 --- a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectBridge.java +++ b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectBridge.java @@ -6,13 +6,13 @@ import static com.google.common.base.Preconditions.*; import java.text.MessageFormat; -import java.util.List; - -import javax.annotation.concurrent.Immutable; - -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; -import org.xwiki.model.reference.ClassReference; +import java.util.List; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; +import org.xwiki.model.reference.ClassReference; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.WikiReference; @@ -32,19 +32,24 @@ import com.xpn.xwiki.objects.BaseObject; @Immutable -@Component(XWikiObjectBridge.NAME) -public class XWikiObjectBridge implements ObjectBridge { - - public static final String NAME = "xwiki"; - - @Requirement(XDocumentFieldAccessor.NAME) - private FieldAccessor xDocAccessor; - - @Requirement(XObjectFieldAccessor.NAME) - private FieldAccessor xObjAccessor; - - @Requirement - private ModelContext context; +@Component(XWikiObjectBridge.NAME) +public class XWikiObjectBridge implements ObjectBridge { + + public static final String NAME = "XWikiObjectBridge"; + + private final FieldAccessor xDocAccessor; + private final FieldAccessor xObjAccessor; + private final ModelContext context; + + @Inject + public XWikiObjectBridge( + XDocumentFieldAccessor xDocAccessor, + XObjectFieldAccessor xObjAccessor, + ModelContext context) { + this.xDocAccessor = xDocAccessor; + this.xObjAccessor = xObjAccessor; + this.context = context; + } @Override public Class getDocumentType() { diff --git a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectEditor.java b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectEditor.java index ca8c60009..84ce4cb3f 100644 --- a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectEditor.java +++ b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectEditor.java @@ -6,11 +6,10 @@ import javax.validation.constraints.NotNull; import com.celements.model.object.AbstractObjectEditor; -import com.celements.model.object.ObjectBridge; import com.celements.model.object.ObjectHandler; +import com.celements.spring.context.SpringContextProvider; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; -import com.xpn.xwiki.web.Utils; @NotThreadSafe public class XWikiObjectEditor extends @@ -43,7 +42,8 @@ public XWikiObjectFetcher fetch() { @Override protected XWikiObjectBridge getBridge() { - return (XWikiObjectBridge) Utils.getComponent(ObjectBridge.class, XWikiObjectBridge.NAME); + return SpringContextProvider.getBeanFactory() + .getBean(XWikiObjectBridge.NAME, XWikiObjectBridge.class); } @Override diff --git a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectFetcher.java b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectFetcher.java index 442b50dee..10e911163 100644 --- a/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectFetcher.java +++ b/celements-model/src/main/java/com/celements/model/object/xwiki/XWikiObjectFetcher.java @@ -1,18 +1,13 @@ package com.celements.model.object.xwiki; -import static com.google.common.base.MoreObjects.*; - import javax.annotation.concurrent.NotThreadSafe; import javax.validation.constraints.NotNull; -import org.xwiki.model.reference.DocumentReference; - import com.celements.model.object.AbstractObjectFetcher; -import com.celements.model.object.ObjectBridge; import com.celements.model.object.ObjectHandler; +import com.celements.spring.context.SpringContextProvider; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; -import com.xpn.xwiki.web.Utils; @NotThreadSafe public class XWikiObjectFetcher extends @@ -30,15 +25,15 @@ public static XWikiObjectFetcher from( } public static XWikiObjectFetcher empty() { - XWikiDocument dummyDoc = new XWikiDocument(new DocumentReference("$", "$", "$")); + XWikiDocument dummyDoc = new XWikiDocument(EMPTY_DOC_REF); return new XWikiObjectFetcher(dummyDoc, XWikiEmptyObjectBridge.NAME); } - private final String bridgeHint; + private final String bridgeBeanName; - private XWikiObjectFetcher(XWikiDocument doc, String bridgeHint) { + private XWikiObjectFetcher(XWikiDocument doc, String bridgeBeanName) { super(doc); - this.bridgeHint = bridgeHint; + this.bridgeBeanName = bridgeBeanName; } @Override @@ -53,8 +48,8 @@ protected XWikiObjectFetcher disableCloning() { @Override protected XWikiObjectBridge getBridge() { - return (XWikiObjectBridge) Utils.getComponent(ObjectBridge.class, firstNonNull(bridgeHint, - XWikiObjectBridge.NAME)); + return SpringContextProvider.getBeanFactory() + .getBean(bridgeBeanName, XWikiObjectBridge.class); } @Override diff --git a/celements-model/src/main/resources/META-INF/components.txt b/celements-model/src/main/resources/META-INF/components.txt index 1851228a7..4b2b9ac24 100644 --- a/celements-model/src/main/resources/META-INF/components.txt +++ b/celements-model/src/main/resources/META-INF/components.txt @@ -18,12 +18,9 @@ com.celements.store.DocumentCacheStoreListener com.celements.model.context.DefaultModelContext com.celements.model.util.DefaultModelUtils com.celements.model.access.DefaultXWikiDocumentCreator -com.celements.model.field.XObjectFieldAccessor -com.celements.model.field.XObjectStringFieldAccessor com.celements.convert.bean.BeanFieldAccessor com.celements.convert.bean.XObjectBeanConverter com.celements.convert.bean.BeanXObjectConverter -com.celements.model.object.xwiki.XWikiObjectBridge com.celements.query.QueryExecutionService com.celements.store.CelHibernateStore com.celements.web.classes.oldcore.XWikiGlobalRightsClass @@ -38,9 +35,7 @@ com.celements.web.classes.oldcore.XWikiUsersClass com.celements.web.classes.oldcore.XWikiGroupsClass com.celements.model.migration.DanglingObjectsMigration com.celements.web.classes.oldcore.XWikiDocumentClass -com.celements.model.field.XDocumentFieldAccessor com.celements.web.classes.oldcore.XWikiTagClass -com.celements.model.object.xwiki.XWikiEmptyObjectBridge com.celements.web.classes.oldcore.XWikiObjectClass com.celements.model.migration.InvalidXObjectsMigration com.celements.convert.bean.XDocBeanConversionLoader diff --git a/celements-model/src/test/java/com/celements/convert/bean/CelObjectBeanConverterTest.java b/celements-model/src/test/java/com/celements/convert/bean/CelObjectBeanConverterTest.java new file mode 100644 index 000000000..34ab9c814 --- /dev/null +++ b/celements-model/src/test/java/com/celements/convert/bean/CelObjectBeanConverterTest.java @@ -0,0 +1,61 @@ +package com.celements.convert.bean; + +import static com.celements.model.classes.TestClassDefinition.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.xwiki.model.reference.DocumentReference; + +import com.celements.common.test.AbstractComponentTest; +import com.celements.model.classes.ClassDefinition; +import com.celements.model.object.ObjectBean; +import com.xpn.xwiki.doc.CelObject; +import com.xpn.xwiki.objects.BaseObject; + +public class CelObjectBeanConverterTest extends AbstractComponentTest { + + private ClassDefinition classDef; + + @Before + public void prepareTest() throws Exception { + classDef = getBeanFactory().getBean(NAME, ClassDefinition.class); + } + + @Test + @SuppressWarnings("unchecked") + public void test_celObjectToBean() { + DocumentReference docRef = new DocumentReference("wiki", "space", "doc"); + BaseObject object = new BaseObject(); + object.setDocumentReference(docRef); + object.setXClassReference(CLASS_REF); + object.setNumber(3); + object.setStringValue(FIELD_MY_STRING.getName(), "value"); + + CelObjectBeanConverter converter = getBeanFactory().getBean( + CelObjectBeanConverter.class); + converter.initialize(classDef); + converter.initialize(TestBean.class); + TestBean bean = converter.apply(CelObject.from(object)); + + assertEquals(docRef, bean.getDocumentReference()); + assertEquals(CLASS_REF, bean.getClassReference()); + assertEquals(Integer.valueOf(3), bean.getNumber()); + assertEquals("value", bean.getMyString()); + } + + public static class TestBean extends ObjectBean { + + private String myString; + + public String getMyString() { + return myString; + } + + public void setMyString(String myString) { + this.myString = myString; + } + + } + +} diff --git a/celements-model/src/test/java/com/celements/model/access/DefaultModelAccessFacadeTest.java b/celements-model/src/test/java/com/celements/model/access/DefaultModelAccessFacadeTest.java index e18b66cb7..3be66be6b 100644 --- a/celements-model/src/test/java/com/celements/model/access/DefaultModelAccessFacadeTest.java +++ b/celements-model/src/test/java/com/celements/model/access/DefaultModelAccessFacadeTest.java @@ -813,6 +813,7 @@ public void test_deleteDocument_withTrans() throws Exception { List transDocs = transLangs.stream().map(lang -> { XWikiDocument transDoc = doc.clone(); transDoc.setLanguage(lang); + transDoc.setTranslation(1); return transDoc; }).toList(); for (XWikiDocument transDoc : transDocs) { @@ -1003,6 +1004,30 @@ public void test_streamParents_cyclic() throws Exception { verifyDefault(); } + @Test + public void test_streamParents_cel() throws Exception { + XWikiDocument pDoc = expectParent(doc, true); + XWikiDocument ppDoc = expectParent(pDoc, true); + expectParent(ppDoc, false); + replayDefault(); + List parents = modelAccess.streamParents(CelDocument.from(doc)).toList(); + verifyDefault(); + assertEquals(List.of(pDoc.getDocumentReference(), ppDoc.getDocumentReference()), + parents.stream().map(CelDocument::getDocumentReference).toList()); + } + + @Test + public void test_streamParents_cel_cyclic() throws Exception { + XWikiDocument pDoc = expectParent(doc, true); + pDoc.setParentReference((EntityReference) doc.getDocumentReference()); + expect(storeMock.exists(eqRefLang(doc), same(getXContext()))).andReturn(true); + expect(storeMock.loadXWikiDoc(eqRefLang(doc), same(getXContext()))).andReturn(doc); + replayDefault(); + Stream stream = modelAccess.streamParents(CelDocument.from(doc)); + assertThrows(IllegalStateException.class, stream::count); + verifyDefault(); + } + private XWikiDocument expectParent(XWikiDocument doc, boolean exists) throws Exception { DocumentReference parentDocRef = RefBuilder.from(doc.getDocumentReference()) .doc(doc.getDocumentReference().getName() + "-parent") diff --git a/celements-model/src/test/java/com/celements/model/access/object/CelObjectFetcherTest.java b/celements-model/src/test/java/com/celements/model/access/object/CelObjectFetcherTest.java new file mode 100644 index 000000000..306ce0dfd --- /dev/null +++ b/celements-model/src/test/java/com/celements/model/access/object/CelObjectFetcherTest.java @@ -0,0 +1,187 @@ +package com.celements.model.access.object; + +import static com.celements.model.classes.TestClassDefinition.*; +import static org.junit.Assert.*; + +import java.util.Date; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.xwiki.model.reference.ClassReference; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.WikiReference; + +import com.celements.common.test.AbstractComponentTest; +import com.celements.model.classes.fields.ClassField; +import com.celements.model.classes.fields.DateField; +import com.celements.model.object.cel.CelObjectBridge; +import com.celements.model.object.cel.CelObjectFetcher; +import com.celements.web.classes.oldcore.XWikiDocumentClass; +import com.celements.web.classes.oldcore.XWikiObjectClass; +import com.xpn.xwiki.doc.CelDocument; +import com.xpn.xwiki.doc.CelObject; +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.objects.BaseObject; + +public class CelObjectFetcherTest extends AbstractComponentTest { + + private static final ClassField FIELD_DATE = new DateField.Builder( + CLASS_REF, "date").build(); + + private WikiReference wikiRef; + private XWikiDocument doc; + + @Before + public void prepareTest() throws Exception { + wikiRef = new WikiReference("db"); + doc = new XWikiDocument(new DocumentReference(wikiRef.getName(), "space", "doc")); + } + + @Test + public void test_fetch_and_filter() { + addObj(CLASS_REF, FIELD_MY_STRING, "one"); + addObj(CLASS_REF, FIELD_MY_STRING, "two"); + addObj(new ClassReference("Classes", "Other"), null, null); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + + CelObject expected = celDoc.streamXObjects(CLASS_REF).findFirst().orElseThrow(); + CelObject fetched = CelObjectFetcher.on(celDoc) + .filter(FIELD_MY_STRING, "one") + .firstAssert(); + + assertSame(expected, fetched); + assertEquals(3, CelObjectFetcher.on(celDoc).count()); + assertEquals(2, CelObjectFetcher.on(celDoc).filter(CLASS_REF).count()); + assertEquals(List.of("one", "two"), + CelObjectFetcher.on(celDoc).fetchField(FIELD_MY_STRING).list()); + } + + @Test + public void test_empty() { + assertFalse(CelObjectFetcher.empty().filter(CLASS_REF).exists()); + } + + @Test + public void test_normalizes_string_fields() { + addObj(CLASS_REF, FIELD_MY_STRING, " one "); + addObj(CLASS_REF, FIELD_MY_STRING, " "); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + + assertEquals(List.of("one"), + CelObjectFetcher.on(celDoc).fetchField(FIELD_MY_STRING).list()); + assertEquals(1, CelObjectFetcher.on(celDoc).filter(FIELD_MY_STRING, "one").count()); + assertEquals(1, CelObjectFetcher.on(celDoc).filterAbsent(FIELD_MY_STRING).count()); + } + + @Test + public void test_fetchField_types_and_pseudo_fields() { + Date date = new Date(123456789L); + DocumentReference valueRef = new DocumentReference("db", "target", "doc"); + BaseObject obj = addObj(CLASS_REF, FIELD_MY_INT, 42); + obj.setDateValue(FIELD_DATE.getName(), date); + obj.setStringListValue(FIELD_MY_LIST_MS.getName(), List.of("one", "two")); + obj.setStringValue(FIELD_MY_DOCREF.getName(), "db:target.doc"); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + CelObjectFetcher fetcher = CelObjectFetcher.on(celDoc).filter(CLASS_REF); + CelObject celObj = fetcher.firstAssert(); + var objAccessor = getBeanFactory().getBean(CelObjectBridge.class).getObjectFieldAccessor(); + + assertEquals(Integer.valueOf(42), fetcher.fetchField(FIELD_MY_INT).findFirst().orElseThrow()); + assertEquals(date, fetcher.fetchField(FIELD_DATE).findFirst().orElseThrow()); + assertEquals(List.of("one", "two"), + fetcher.fetchField(FIELD_MY_LIST_MS).findFirst().orElseThrow()); + assertEquals(valueRef, fetcher.fetchField(FIELD_MY_DOCREF).findFirst().orElseThrow()); + assertEquals(doc.getDocumentReference(), + objAccessor.get(celObj, XWikiObjectClass.FIELD_DOC_REF).orElseThrow()); + assertEquals(CLASS_REF, + objAccessor.get(celObj, XWikiObjectClass.FIELD_CLASS_REF).orElseThrow()); + assertEquals(Integer.valueOf(0), + objAccessor.get(celObj, XWikiObjectClass.FIELD_NUMBER).orElseThrow()); + } + + @Test + public void test_fetchField_document_and_translation() { + Date date = new Date(123456789L); + doc.setTitle("default title"); + doc.setDate(date); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + XWikiDocument transDoc = new XWikiDocument(doc.getDocumentReference()); + transDoc.setTranslation(1); + transDoc.setLanguage("fr"); + transDoc.setTitle("titre"); + CelDocument.Translation celTransDoc = CelDocument.Translation.from(transDoc); + assertEquals(date, + CelObjectFetcher.on(celDoc).fetchField(XWikiDocumentClass.FIELD_UPDATE_DATE) + .findFirst().orElseThrow()); + assertEquals("titre", + CelObjectFetcher.on(celDoc).withTranslation(celTransDoc) + .fetchField(XWikiDocumentClass.FIELD_TITLE).findFirst().orElseThrow()); + } + + @Test + public void test_withTranslation_filters_objects() { + doc.setDefaultLanguage("en"); + addObj(CLASS_REF, FIELD_LANG, "en"); + addObj(CLASS_REF, FIELD_LANG, "fr"); + addObj(CLASS_REF, null, null); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + XWikiDocument transDoc = new XWikiDocument(doc.getDocumentReference()); + transDoc.setTranslation(1); + transDoc.setLanguage("fr"); + + List objects = CelObjectFetcher.on(celDoc) + .withTranslation(CelDocument.Translation.from(transDoc)) + .filter(CLASS_REF) + .list(); + + assertEquals(1, objects.size()); + assertEquals("fr", objects.get(0).getStringValue(FIELD_LANG.getName())); + } + + @Test + public void test_translation_cannot_own_objects() { + XWikiDocument transDoc = new XWikiDocument(doc.getDocumentReference()); + transDoc.setTranslation(1); + transDoc.setLanguage("fr"); + + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, + () -> CelObjectFetcher.on(CelDocument.Translation.from(transDoc)).count()); + + assertTrue(exc.getMessage().contains("[fr]")); + assertTrue(exc.getMessage().contains(doc.getDocumentReference().toString())); + } + + @Test + public void test_mutation_is_unsupported() { + addObj(CLASS_REF, FIELD_MY_STRING, "value"); + CelDocument.Default celDoc = CelDocument.Default.from(doc); + CelObject obj = celDoc.getXObjects().get(0); + CelObjectBridge bridge = getBeanFactory().getBean(CelObjectBridge.class); + + assertThrows(UnsupportedOperationException.class, + () -> bridge.createObject(celDoc, CLASS_REF)); + assertThrows(UnsupportedOperationException.class, + () -> bridge.deleteObject(celDoc, obj)); + assertThrows(UnsupportedOperationException.class, + () -> bridge.getDocumentFieldAccessor().set( + celDoc, XWikiDocumentClass.FIELD_TITLE, "changed")); + assertThrows(UnsupportedOperationException.class, + () -> bridge.getObjectFieldAccessor().set(obj, FIELD_MY_STRING, "changed")); + } + + private BaseObject addObj(ClassReference classRef, ClassField field, T value) { + BaseObject obj = new BaseObject(); + obj.setXClassReference(classRef.getDocRef(wikiRef)); + if (field != null) { + if (field.getType() == String.class) { + obj.setStringValue(field.getName(), (String) value); + } else if (field.getType() == Integer.class) { + obj.setIntValue(field.getName(), (Integer) value); + } + } + doc.addXObject(obj); + return obj; + } + +} diff --git a/celements-model/src/test/java/com/celements/model/access/object/QueryBuilderTest.java b/celements-model/src/test/java/com/celements/model/access/object/QueryBuilderTest.java index f49209b3c..3695571a4 100644 --- a/celements-model/src/test/java/com/celements/model/access/object/QueryBuilderTest.java +++ b/celements-model/src/test/java/com/celements/model/access/object/QueryBuilderTest.java @@ -16,7 +16,6 @@ import com.celements.common.test.ExceptionAsserter; import com.celements.model.classes.ClassDefinition; import com.celements.model.classes.fields.ClassField; -import com.celements.model.object.ObjectBridge; import com.celements.model.object.restriction.ClassRestriction; import com.celements.model.object.restriction.FieldAbsentRestriction; import com.celements.model.object.restriction.ObjectQuery; @@ -25,7 +24,6 @@ import com.celements.model.object.xwiki.XWikiObjectEditor; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; -import com.xpn.xwiki.web.Utils; public class QueryBuilderTest extends AbstractComponentTest { @@ -38,7 +36,7 @@ public class QueryBuilderTest extends AbstractComponentTest { public void prepareTest() throws Exception { wikiRef = new WikiReference("db"); doc = new XWikiDocument(new DocumentReference(wikiRef.getName(), "space", "doc")); - classRef = Utils.getComponent(ClassDefinition.class, NAME).getClassReference(); + classRef = getBeanFactory().getBean(NAME, ClassDefinition.class).getClassReference(); classRef2 = new ClassReference("class", "other"); } @@ -140,7 +138,7 @@ protected void execute() throws NullPointerException { } private XWikiObjectBridge getBridge() { - return (XWikiObjectBridge) Utils.getComponent(ObjectBridge.class, XWikiObjectBridge.NAME); + return getBeanFactory().getBean(XWikiObjectBridge.NAME, XWikiObjectBridge.class); } } diff --git a/celements-model/src/test/java/com/celements/model/field/XDocumentFieldAccessorTest.java b/celements-model/src/test/java/com/celements/model/field/XDocumentFieldAccessorTest.java index 5bc86108e..12bdb547c 100644 --- a/celements-model/src/test/java/com/celements/model/field/XDocumentFieldAccessorTest.java +++ b/celements-model/src/test/java/com/celements/model/field/XDocumentFieldAccessorTest.java @@ -14,7 +14,6 @@ import com.celements.model.classes.TestClassDefinition; import com.celements.model.classes.fields.ClassField; import com.xpn.xwiki.doc.XWikiDocument; -import com.xpn.xwiki.web.Utils; public class XDocumentFieldAccessorTest extends AbstractComponentTest { @@ -36,8 +35,7 @@ public class XDocumentFieldAccessorTest extends AbstractComponentTest { @Before public void prepareTest() throws Exception { - accessor = (XDocumentFieldAccessor) Utils.getComponent(FieldAccessor.class, - XDocumentFieldAccessor.NAME); + accessor = getBeanFactory().getBean(XDocumentFieldAccessor.class); } @Test diff --git a/celements-model/src/test/java/com/celements/model/field/XObjectFieldAccessorTest.java b/celements-model/src/test/java/com/celements/model/field/XObjectFieldAccessorTest.java index e06b630eb..68df81d4e 100644 --- a/celements-model/src/test/java/com/celements/model/field/XObjectFieldAccessorTest.java +++ b/celements-model/src/test/java/com/celements/model/field/XObjectFieldAccessorTest.java @@ -31,9 +31,8 @@ public class XObjectFieldAccessorTest extends AbstractComponentTest { @Before public void prepareTest() throws Exception { - accessor = (XObjectFieldAccessor) Utils.getComponent(FieldAccessor.class, - XObjectFieldAccessor.NAME); - testClassDef = Utils.getComponent(ClassDefinition.class, TestClassDefinition.NAME); + accessor = getBeanFactory().getBean(XObjectFieldAccessor.class); + testClassDef = getBeanFactory().getBean(TestClassDefinition.NAME, ClassDefinition.class); expectClass(testClassDef, getXContext().getWikiRef()); } diff --git a/celements-reference/src/main/java/org/xwiki/model/internal/reference/LocalizedStringEntityReferenceSerializer.java b/celements-reference/src/main/java/org/xwiki/model/internal/reference/LocalizedStringEntityReferenceSerializer.java new file mode 100644 index 000000000..00cd15f41 --- /dev/null +++ b/celements-reference/src/main/java/org/xwiki/model/internal/reference/LocalizedStringEntityReferenceSerializer.java @@ -0,0 +1,21 @@ +package org.xwiki.model.internal.reference; + +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.EntityReference; + +/** + * Extends the default representation with the locale of document references. + */ +public class LocalizedStringEntityReferenceSerializer + extends DefaultStringEntityReferenceSerializer { + + @Override + protected void serializeEntityReference(EntityReference currentReference, + StringBuilder representation, boolean isLastReference, Object... parameters) { + super.serializeEntityReference(currentReference, representation, isLastReference, parameters); + if (currentReference instanceof DocumentReference docRef) { + docRef.getLocale().ifPresent(x -> representation.append('(').append(x).append(')')); + } + } + +} diff --git a/celements-reference/src/main/java/org/xwiki/model/reference/DocumentReference.java b/celements-reference/src/main/java/org/xwiki/model/reference/DocumentReference.java index 91fff5f1b..3226d5ea3 100644 --- a/celements-reference/src/main/java/org/xwiki/model/reference/DocumentReference.java +++ b/celements-reference/src/main/java/org/xwiki/model/reference/DocumentReference.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Optional; import org.xwiki.model.EntityType; @@ -85,7 +86,7 @@ protected DocumentReference(EntityReference reference, EntityReference oldRefere * if the passed reference is not a valid document reference */ public DocumentReference(EntityReference reference, Locale locale) { - super(reference, asMap(EntryStream.of(LOCALE, locale))); + super(reference, Collections.singletonMap(LOCALE, locale)); } /** @@ -246,6 +247,15 @@ public Optional getLocale() { return getParameter(Locale.class, LOCALE); } + public DocumentReference withLocale(Locale locale) { + var isCurrentLocale = Objects.equals(locale, getLocale().orElse(null)); + return isCurrentLocale ? this : new DocumentReference(this, locale); + } + + public DocumentReference withoutLocale() { + return withLocale(null); + } + @Override public SpaceReference getParent() { return (SpaceReference) super.getParent(); diff --git a/celements-reference/src/main/java/org/xwiki/model/reference/EntityReference.java b/celements-reference/src/main/java/org/xwiki/model/reference/EntityReference.java index 4fad79ec7..53e58b142 100644 --- a/celements-reference/src/main/java/org/xwiki/model/reference/EntityReference.java +++ b/celements-reference/src/main/java/org/xwiki/model/reference/EntityReference.java @@ -35,6 +35,7 @@ import javax.validation.constraints.NotNull; import org.xwiki.model.EntityType; +import org.xwiki.model.internal.reference.LocalizedStringEntityReferenceSerializer; import com.celements.model.reference.RefBuilder; import com.google.common.base.Strings; @@ -54,6 +55,8 @@ */ public class EntityReference implements Serializable, Comparable { + protected static final LocalizedStringEntityReferenceSerializer SERIALIZER = new LocalizedStringEntityReferenceSerializer(); + private static final long serialVersionUID = 2L; /** @@ -101,12 +104,12 @@ public EntityReference(EntityReference reference, EntityReference parent) { } /** - * Clone an EntityReference, but add the specified parameters. + * Clone an EntityReference, but override the specified parameters. * * @param reference * the reference to clone * @param parameters - * additional parameters + * replacement parameters, a null value removes the parameter * @since 3.3M2 */ protected EntityReference(EntityReference reference, Map parameters) { @@ -197,9 +200,7 @@ protected EntityReference(String name, EntityType type, EntityReference parent, setName(name); setType(type); setParent(parent); - this.parameters = (parameters != null) - ? ImmutableMap.copyOf(parameters) - : ImmutableMap.of(); + this.parameters = (parameters != null) ? ImmutableMap.copyOf(parameters) : ImmutableMap.of(); } /** @@ -361,7 +362,7 @@ public Optional extractRef(@Nullable Class tok .flatMap(this::extractRef) .map(ref -> tryCast(ref, token) .orElseGet(() -> new RefBuilder().with(ref).buildOpt(token) - .orElse(null))); + .orElse(null))); } /** @@ -391,37 +392,12 @@ public EntityReference appendParent(EntityReference newParent) { @Override public String toString() { - StringBuilder sb = new StringBuilder(64); - sb.append("name = [") - .append(getName()) - .append("], type = [") - .append(getType()) - .append("], parent = [") - .append(getParent()) - .append(']'); - if (parameters.size() > 0) { - sb.append(" parameters = {"); - boolean first = true; - for (Map.Entry entry : parameters.entrySet()) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append(entry.getKey()) - .append(" = [") - .append(entry.getValue().toString()) - .append(']'); - } - sb.append('}'); - } - return sb.toString(); + return SERIALIZER.serialize(this); } @Override public boolean equals(Object obj) { - if (obj instanceof EntityReference) { - EntityReference other = (EntityReference) obj; + if (obj instanceof EntityReference other) { return Objects.equals(this.name, other.name) && Objects.equals(this.type, other.type) && Objects.equals(this.parent, other.parent) @@ -501,9 +477,11 @@ private static Map concatMaps( if ((map1 == null) || (map1.size() == 0)) { return map2; } else if ((map2 == null) || (map2.size() == 0)) { - return map1; + return asMap(EntryStream.of(map1)); } else { - return asMap(EntryStream.of(map1).append(EntryStream.of(map2))); + return asMap(EntryStream.of(map1) + .append(EntryStream.of(map2) + .filterKeys(key -> !map1.containsKey(key)))); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java b/celements-reference/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java index a71cc7a1d..96e1118c6 100644 --- a/celements-reference/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java @@ -127,8 +127,7 @@ public void test_resolveDocumentReference_whenInvalidReference() { fail("Should have thrown an exception here"); } catch (InvalidEntityReferenceException expected) { assertEquals( - "Invalid reference [name = [page], type = [DOCUMENT], parent = [name = [filename], " - + "type = [ATTACHMENT], parent = [null]]]", + "Invalid reference [filename.page]", expected.getMessage()); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/AttachmentReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/AttachmentReferenceTest.java index eea080a39..b5b8cc465 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/AttachmentReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/AttachmentReferenceTest.java @@ -62,8 +62,7 @@ public void testInvalidParentType() { fail("Should have thrown an exception here"); } catch (IllegalArgumentException expected) { assertEquals( - "Invalid parent reference [name = [wiki], type = [WIKI], parent = [null]] in an " - + "attachment reference", + "Invalid parent reference [wiki] in an attachment reference", expected.getMessage()); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/DocumentReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/DocumentReferenceTest.java index 3d6aa3179..392e89f70 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/DocumentReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/DocumentReferenceTest.java @@ -21,8 +21,11 @@ import static org.junit.Assert.*; +import java.io.Serializable; import java.util.Arrays; import java.util.List; +import java.util.Locale; +import java.util.Map; import org.junit.Test; import org.xwiki.model.EntityType; @@ -47,6 +50,29 @@ public void testConstructors() { new SpaceReference("space", new WikiReference("wiki")))); } + @Test + public void testCloneWithLocale() { + EntityReference reference = new EntityReference("page", EntityType.DOCUMENT, + new SpaceReference("space", new WikiReference("wiki")), + Map.of("custom", "value")); + + DocumentReference unlocalized = new DocumentReference(reference, null); + assertTrue(unlocalized.getLocale().isEmpty()); + assertEquals("value", unlocalized.getParameter(String.class, "custom").orElse(null)); + + DocumentReference english = new DocumentReference(unlocalized, Locale.ENGLISH); + assertEquals(Locale.ENGLISH, english.getLocale().orElse(null)); + assertEquals("value", english.getParameter(String.class, "custom").orElse(null)); + + DocumentReference german = new DocumentReference(english, Locale.GERMAN); + assertEquals(Locale.GERMAN, german.getLocale().orElse(null)); + + DocumentReference removed = german.withoutLocale(); + assertTrue(removed.getLocale().isEmpty()); + assertEquals("value", removed.getParameter(String.class, "custom").orElse(null)); + assertSame(unlocalized, unlocalized.withoutLocale()); + } + @Test public void testInvalidType() { try { @@ -76,8 +102,7 @@ public void testInvalidParentType() { fail("Should have thrown an exception here"); } catch (IllegalArgumentException expected) { assertEquals( - "Invalid parent reference [name = [wiki], type = [WIKI], parent = [null]] in a " - + "document reference", + "Invalid parent reference [wiki] in a document reference", expected.getMessage()); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java index 3ab5fd099..dafced3a6 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -46,6 +47,14 @@ */ public class EntityReferenceTest { + @Test + public void testToString() { + assertEquals("wiki:space.page", + new DocumentReference("wiki", "space", "page").toString()); + assertEquals("wiki:space.page(en)", + new DocumentReference("wiki", "space", "page", Locale.ENGLISH).toString()); + } + private Map getParamMap(int nb) { Map map = new HashMap<>(nb); for (int i = 0; i < nb; i++) { diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/ObjectPropertyReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/ObjectPropertyReferenceTest.java index d08b6e88c..bc37aefd3 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/ObjectPropertyReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/ObjectPropertyReferenceTest.java @@ -81,8 +81,7 @@ public void testInvalidParentType() { fail("Should have thrown exception"); } catch (IllegalArgumentException expected) { assertEquals( - "Invalid parent reference [name = [Space], type = [SPACE], parent = [null]] in an object " - + "property reference", + "Invalid parent reference [Space] in an object property reference", expected.getMessage()); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/ObjectReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/ObjectReferenceTest.java index fd93802b5..92c53ed9c 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/ObjectReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/ObjectReferenceTest.java @@ -78,8 +78,7 @@ public void testInvalidParentType() { fail("Should have thrown exception"); } catch (IllegalArgumentException expected) { assertEquals( - "Invalid parent reference [name = [Space], type = [SPACE], parent = [null]] in an object " - + "reference", + "Invalid parent reference [Space] in an object reference", expected.getMessage()); } } diff --git a/celements-reference/src/test/java/org/xwiki/model/reference/SpaceReferenceTest.java b/celements-reference/src/test/java/org/xwiki/model/reference/SpaceReferenceTest.java index 49a18f46a..158b6dc8b 100644 --- a/celements-reference/src/test/java/org/xwiki/model/reference/SpaceReferenceTest.java +++ b/celements-reference/src/test/java/org/xwiki/model/reference/SpaceReferenceTest.java @@ -61,8 +61,7 @@ public void testInvalidParentType() { fail("Should have thrown an exception here"); } catch (IllegalArgumentException expected) { assertEquals( - "Invalid parent reference [name = [whatever], type = [DOCUMENT], parent = [null]] " - + "in a space reference", + "Invalid parent reference [whatever] in a space reference", expected.getMessage()); } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelDocument.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelDocument.java index f1f8bec93..c94393006 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelDocument.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelDocument.java @@ -6,6 +6,7 @@ import java.time.Instant; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -65,7 +66,7 @@ public final IdVersion getIdVersion() { } public final String getLanguage() { - return getIdentity().language(); + return getDocumentReference().getLocale().map(Locale::toString).orElse(""); } public final String getDefaultLanguage() { @@ -152,11 +153,15 @@ public final String getValidationScript() { return metaData.validationScript(); } + @Override + public final String toString() { + return getDocRef().toString(); + } + public record Identity( long id, IdVersion idVersion, DocumentReference docRef, - String language, String version) { public Identity { @@ -168,8 +173,7 @@ public static Identity from(XWikiDocument doc) { return new Identity( doc.hasValidId() ? doc.getId() : 0, doc.hasValidId() ? doc.getIdVersion() : null, - doc.getDocumentReference(), - doc.getLanguage(), + doc.getDocRefWithLocale(), doc.getVersion()); } } @@ -276,6 +280,7 @@ public static Translation from(XWikiDocument doc) { private Translation(XWikiDocument doc) { super(doc); checkArgument(doc.isTrans(), "doc must have translation != 0"); + checkArgument(!doc.getLanguage().isEmpty(), "translation doc without language"); } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelObject.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelObject.java index 43f9498e4..fbb0d0b50 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelObject.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/CelObject.java @@ -101,4 +101,9 @@ public int getIntValue(String name) { public Instant getDateValue(String name) { return getProperty(name).map(CelProperty::getDateValue).orElse(null); } + + @Override + public String toString() { + return documentReference + "_" + classReference + "_" + number; + } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java index 4e1ea79ed..83fa61e18 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Optional; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; @@ -72,6 +73,17 @@ public class XWikiAttachment implements Cloneable { private boolean isMetaDataDirty = false; + public static XWikiAttachment from(XWikiDocument doc, CelAttachment catt) { + XWikiAttachment xatt = new XWikiAttachment(doc, catt.getFilename()); + xatt.setFilesize(catt.getFilesize()); + xatt.setAuthor(catt.getAuthor()); + xatt.setVersion(catt.getVersion()); + xatt.setComment(catt.getComment()); + xatt.setDate(Optional.ofNullable(catt.getDate()).map(Date::from).orElse(null)); + xatt.setMetaDataDirty(false); + return xatt; + } + public XWikiAttachment(XWikiDocument doc, String filename) { this(); setDoc(doc); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java index d389cfe4d..f4f4aabff 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java @@ -45,6 +45,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -428,13 +429,11 @@ public XWikiDocument(DocumentReference reference) { init(reference); } - private XWikiDocument(CelDocument celDocument) { - this(checkNotNull(celDocument).getDocumentReference()); - applyCelDocument(celDocument); - xClassCelDocument = celDocument instanceof CelDocument.Default defaultDocument - ? defaultDocument - : null; - originalCelDocument = celDocument; + private XWikiDocument(CelDocument doc) { + this(checkNotNull(doc).getDocRef().withoutLocale()); + applyCelDocument(doc); + xClassCelDocument = (doc instanceof CelDocument.Default defaultDoc) ? defaultDoc : null; + originalCelDocument = doc; } public static XWikiDocument from(CelDocument celDocument) { @@ -538,38 +537,13 @@ private void applyCelDocument(CelDocument celDocument) { private void applyCelDocument(CelDocument.Default celDocument) { celDocument.getXObjects().stream() - .map(XWikiDocument::materializeObject) + .map(BaseObject::from) .forEach(object -> setXObject(object.getNumber(), object)); setAttachmentList(celDocument.getAttachmentList().stream() - .map(this::materializeAttachment) + .map(att -> XWikiAttachment.from(this, att)) .collect(toCollection(ArrayList::new))); } - private static BaseObject materializeObject(CelObject celObject) { - BaseObject object = new BaseObject(); - object.setDocumentReference(celObject.getDocumentReference()); - object.setXClassReference(celObject.getClassReference()); - object.setNumber(celObject.getNumber()); - object.setGuid(celObject.getGuid()); - Optional.ofNullable(celObject.getIdVersion()) - .ifPresent(idVersion -> object.setId(celObject.getId(), idVersion)); - celObject.getProperties().stream() - .map(BaseProperty::from) - .forEach(property -> object.safeput(property.getName(), property)); - return object; - } - - private XWikiAttachment materializeAttachment(CelAttachment celAttachment) { - XWikiAttachment attachment = new XWikiAttachment(this, celAttachment.getFilename()); - attachment.setFilesize(celAttachment.getFilesize()); - attachment.setAuthor(celAttachment.getAuthor()); - attachment.setVersion(celAttachment.getVersion()); - attachment.setComment(celAttachment.getComment()); - attachment.setDate(Optional.ofNullable(celAttachment.getDate()).map(Date::from).orElse(null)); - attachment.setMetaDataDirty(false); - return attachment; - } - public XWikiStoreInterface getStore(XWikiContext context) { return context.getWiki().getStore(); } @@ -1059,6 +1033,11 @@ public DocumentReference getDocRef() { return documentReference; } + public DocumentReference getDocRefWithLocale() { + var locale = isTrans() ? Locale.forLanguageTag(getLanguage().replace('_', '-')) : null; + return getDocRef().withLocale(locale); + } + /** * @return the document's space + page name (eg "space.page") * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/objects/BaseObject.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/objects/BaseObject.java index fa9dd48a2..ebd495038 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/objects/BaseObject.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/objects/BaseObject.java @@ -39,6 +39,7 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.doc.CelObject; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.objects.classes.PropertyClass; @@ -46,6 +47,21 @@ public class BaseObject extends BaseCollection implements ObjectInterface, Seria private static final Logger LOGGER = LoggerFactory.getLogger(BaseObject.class); + public static BaseObject from(CelObject cobj) { + BaseObject xobj = new BaseObject(); + xobj.setDocumentReference(cobj.getDocumentReference()); + xobj.setXClassReference(cobj.getClassReference()); + xobj.setNumber(cobj.getNumber()); + xobj.setGuid(cobj.getGuid()); + if (cobj.getIdVersion() != null) { + xobj.setId(cobj.getId(), cobj.getIdVersion()); + } + cobj.getProperties().stream() + .map(BaseProperty::from) + .forEach(property -> xobj.safeput(property.getName(), property)); + return xobj; + } + private String guid = UUID.randomUUID().toString(); @Override diff --git a/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/CelDocumentTest.java b/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/CelDocumentTest.java index c68be945f..7e63b06d5 100644 --- a/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/CelDocumentTest.java +++ b/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/CelDocumentTest.java @@ -5,6 +5,7 @@ import java.lang.reflect.Field; import java.util.Date; import java.util.List; +import java.util.Locale; import org.junit.Test; import org.xwiki.model.reference.DocumentReference; @@ -30,6 +31,16 @@ public class CelDocumentTest extends AbstractComponentTest { + @Test + public void testToString() { + XWikiDocument document = new XWikiDocument( + new DocumentReference("wiki", "space", "page")); + document.setLanguage("de"); + document.setTranslation(1); + + assertEquals("wiki:space.page(de)", CelDocument.from(document).toString()); + } + @Test(expected = IllegalArgumentException.class) public void testTranslationRejectsDefaultDocument() { XWikiDocument document = new XWikiDocument( @@ -38,6 +49,15 @@ public void testTranslationRejectsDefaultDocument() { CelDocument.Translation.from(document); } + @Test(expected = IllegalArgumentException.class) + public void testTranslationRejectsMissingLanguage() { + XWikiDocument document = new XWikiDocument( + new DocumentReference("wiki", "space", "page")); + document.setTranslation(1); + + CelDocument.Translation.from(document); + } + @Test public void testGetXObjectsByClass() { DocumentReference documentReference = new DocumentReference("wiki", "space", "page"); @@ -127,7 +147,6 @@ public void testRoundTripAndIsolation() { assertSame(documentReference, identity.docRef()); assertEquals(42, identity.id()); assertEquals(IdVersion.CELEMENTS_3, identity.idVersion()); - assertEquals("", identity.language()); assertEquals("1.1", identity.version()); assertEquals("en", documentMeta.defaultLanguage()); assertEquals("content", documentMeta.content()); @@ -139,6 +158,8 @@ public void testRoundTripAndIsolation() { assertSame(documentReference, celDocument.getDocumentReference()); assertEquals("content", celDocument.getContent()); assertEquals("value", defaultDocument.getXObjects().get(0).getStringValue("string")); + assertEquals("wiki:space.page_XWiki.Class_2", + defaultDocument.getXObjects().get(0).toString()); assertEquals(new LocalDocumentReference(classReference), defaultDocument.getXObjects().get(0).getClassReference()); assertEquals("comment", defaultDocument.getAttachmentList().get(0).getComment()); @@ -218,6 +239,12 @@ public void testTranslationHasNoDefaultDocumentState() { CelDocument celDocument = CelDocument.from(source); assertTrue(celDocument instanceof CelDocument.Translation); + assertEquals(Locale.GERMAN, celDocument.getDocumentReference().getLocale().orElse(null)); + assertEquals("de", celDocument.getLanguage()); + + XWikiDocument materialized = XWikiDocument.from(celDocument); + assertEquals(source.getDocumentReference(), materialized.getDocumentReference()); + assertEquals("de", materialized.getLanguage()); } private void put(BaseObject object, String name, BaseProperty property, Object value) { diff --git a/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/XWikiDocumentTest.java b/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/XWikiDocumentTest.java index 42fe4afb8..1887cbdec 100644 --- a/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/XWikiDocumentTest.java +++ b/celements-xwiki-core/src/test/java/com/xpn/xwiki/doc/XWikiDocumentTest.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -213,6 +214,19 @@ public void test_constructor() { assertEquals("wiki", doc.getWikiName()); } + @Test + public void test_getDocRefWithLocale() { + DocumentReference localizedRef = new DocumentReference( + DOCWIKI, DOCSPACE, DOCNAME, Locale.ENGLISH); + XWikiDocument doc = new XWikiDocument(localizedRef); + + assertTrue(doc.getDocRefWithLocale().getLocale().isEmpty()); + + doc.setLanguage("de"); + doc.setTranslation(1); + assertEquals(Locale.GERMAN, doc.getDocRefWithLocale().getLocale().orElse(null)); + } + @Test public void test_getDisplayTitleWhenNoTitleAndNoContent() { this.document.setContent("Some content"); From 88c9b72517042200cb3e23b9cb3d19779b6c6fb0 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Sat, 15 Aug 2026 01:18:10 +0200 Subject: [PATCH 2/2] fix generateRandomString use SecureRandom --- celements-xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java index 51d6f9f03..d21fca45e 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWiki.java @@ -40,6 +40,7 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -185,6 +186,7 @@ public class XWiki implements EventListener { public static final String SERVLET_CONTEXT_KEY = "xwiki.instance"; protected static final Logger LOGGER = LoggerFactory.getLogger(XWiki.class); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); /** XWiki configuration loaded from xwiki.cfg. */ private XWikiConfig config; @@ -3011,7 +3013,7 @@ public void sendMessage(String sender, String recipient, String message, XWikiCo } public String generateRandomString(int size) { - return RandomStringUtils.randomAlphanumeric(size); + return RandomStringUtils.random(size, 0, 0, true, true, null, SECURE_RANDOM); } public String generateValidationKey(int size) {