From 3660b9a11c7042fc15e23ffde9166671230d0eed Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Sun, 11 Jan 2026 20:22:48 +0100 Subject: [PATCH 01/13] s3 test --- celements-xwiki-core/pom.xml | 5 +++ .../com/xpn/xwiki/doc/XWikiAttachment.java | 2 +- .../xpn/xwiki/doc/XWikiAttachmentContent.java | 6 ++-- .../store/XWikiHibernateAttachmentStore.java | 35 ++++++++++++++++++- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/celements-xwiki-core/pom.xml b/celements-xwiki-core/pom.xml index aab352607..f31570f9f 100644 --- a/celements-xwiki-core/pom.xml +++ b/celements-xwiki-core/pom.xml @@ -85,6 +85,11 @@ struts struts + + software.amazon.awssdk + s3 + 2.41.5 + 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..ec9dfe291 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 @@ -170,7 +170,7 @@ public int getContentSize(XWikiContext context) throws XWikiException { this.doc.loadAttachmentContent(this, context); } - return this.attachment_content.getSize(); + return (int) this.attachment_content.getSize(); } public String getFilename() { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java index 13945672b..372c8d059 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java @@ -251,14 +251,14 @@ public void setContent(InputStream is) throws IOException { IOUtils.copy(is, this.file.getOutputStream()); this.setContentDirty(true); - this.attachment.setFilesize(this.getSize()); + this.attachment.setFilesize((int) this.getSize()); } /** * @return the true size of the content of the attachment. * @since 2.3M2 */ - public int getSize() { - return (int) this.file.getSize(); + public long getSize() { + return this.file.getSize(); } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index ae7487cb8..7c19939fc 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -1,5 +1,7 @@ package com.xpn.xwiki.store; +import java.io.InputStream; +import java.net.URI; import java.util.List; import org.apache.commons.logging.Log; @@ -14,16 +16,38 @@ import com.xpn.xwiki.doc.XWikiAttachmentContent; import com.xpn.xwiki.doc.XWikiDocument; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; +import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + @Component public class XWikiHibernateAttachmentStore extends XWikiHibernateBaseStore implements XWikiAttachmentStoreInterface { private static final Log log = LogFactory.getLog(XWikiHibernateAttachmentStore.class); + private S3Client s3Client; + /** * Empty constructor needed for component manager. */ - public XWikiHibernateAttachmentStore() {} + public XWikiHibernateAttachmentStore() { + s3Client = S3Client.builder() + .endpointOverride(URI.create("https://fsn1.your-objectstorage.com")) + .region(Region.of("eu-central")) + .credentialsProvider(StaticCredentialsProvider + .create(AwsBasicCredentials.builder() + .accessKeyId("FIGYTJ2VE7JJ1K5BC3AS") + .secretAccessKey("H2di8Cgp8pMNZRAz4HCZLSG2K41F66tFcTT9697A") + .build())) + .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED) + .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED) + .build(); + } @Override public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext context, @@ -54,6 +78,15 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda context.setDatabase(attachdb); } + try (InputStream data = content.getContentInputStream()) { + s3Client.putObject(builder -> builder + .bucket("cel-filebase") + .key(db + ":" + content.getId()) + .contentLength(content.getSize()) + .contentType(attachment.getMimeType(context)), + RequestBody.fromInputStream(data, content.getSize())); + } + Query query = session.createQuery( "select attach.id from XWikiAttachmentContent as attach where attach.id = :id"); query.setLong("id", content.getId()); From d7c9e6a910ff3228e922683b02747b85d00258f4 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Sun, 11 Jan 2026 23:01:47 +0100 Subject: [PATCH 02/13] AttachmentContentStore --- celements-model/pom.xml | 3 +- celements-xwiki-core/pom.xml | 5 - .../com/celements/store/StoreFactory.java | 35 ++- .../src/main/java/com/xpn/xwiki/XWiki.java | 19 +- .../com/xpn/xwiki/doc/XWikiAttachment.java | 51 ++++- .../xwiki/store/AttachmentContentStore.java | 24 ++ .../HibernateAttachmentContentStore.java | 50 ++++ .../store/VoidAttachmentVersioningStore.java | 7 +- .../store/XWikiAttachmentStoreInterface.java | 7 + .../store/XWikiHibernateAttachmentStore.java | 214 +++++++----------- .../xwiki/store/XWikiHibernateBaseStore.java | 4 + .../VoidAttachmentVersioningStoreTest.java | 86 ------- .../xwiki/store/XWikiHibernateStoreTest.java | 6 +- 13 files changed, 257 insertions(+), 254 deletions(-) create mode 100644 celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java create mode 100644 celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java delete mode 100644 celements-xwiki-core/src/test/java/com/xpn/xwiki/store/VoidAttachmentVersioningStoreTest.java diff --git a/celements-model/pom.xml b/celements-model/pom.xml index 9a3468011..942fa784b 100644 --- a/celements-model/pom.xml +++ b/celements-model/pom.xml @@ -32,12 +32,13 @@ 4.0.0 celements-model 7.0-SNAPSHOT - Celements XWiki + Celements Model com.celements celements-config-source 7.0-SNAPSHOT + provided com.celements diff --git a/celements-xwiki-core/pom.xml b/celements-xwiki-core/pom.xml index f31570f9f..aab352607 100644 --- a/celements-xwiki-core/pom.xml +++ b/celements-xwiki-core/pom.xml @@ -85,11 +85,6 @@ struts struts - - software.amazon.awssdk - s3 - 2.41.5 - diff --git a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java index 50e478597..e89ba1c57 100644 --- a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java +++ b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java @@ -1,5 +1,7 @@ package com.celements.store; +import static com.celements.spring.context.SpringContextProvider.*; + import java.util.Optional; import org.xwiki.component.manager.ComponentLookupException; @@ -7,6 +9,10 @@ import org.xwiki.configuration.ConfigurationSource; import com.google.common.primitives.Ints; +import com.xpn.xwiki.store.AttachmentContentStore; +import com.xpn.xwiki.store.AttachmentVersioningStore; +import com.xpn.xwiki.store.HibernateAttachmentContentStore; +import com.xpn.xwiki.store.XWikiAttachmentStoreInterface; import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.web.Utils; @@ -28,6 +34,31 @@ public static Optional getRecycleBinStore() { return getOptionalStore(XWikiRecycleBinStoreInterface.class, "celements.store.recyclebin"); } + public static XWikiAttachmentStoreInterface getAttachmentStore() { + try { + String hint = getConfigSource().getProperty("celements.store.attachment"); + return getComponentManager().lookup(XWikiAttachmentStoreInterface.class, hint); + } catch (ComponentLookupException exc) { + throw new IllegalStateException("failed looking up attachment store", exc); + } + } + + public static AttachmentContentStore getAttachmentContentStore() { + String beanName = getConfigSource().getProperty("celements.store.attachment.content", + HibernateAttachmentContentStore.class.getName()); + return getBeanFactory().getBean(beanName, AttachmentContentStore.class); + } + + public static AttachmentVersioningStore getAttachmentVersioningStore() { + try { + var beanName = getConfigSource().getProperty("celements.store.attachment.versioning", ""); + return getComponentManager().lookup(AttachmentVersioningStore.class, + !beanName.isEmpty() ? beanName : "void"); + } catch (ComponentLookupException exc) { + throw new IllegalStateException("failed looking up attachment versioning store", exc); + } + } + private static Optional getOptionalStore(Class type, String key) { try { String enabled = getConfigSource().getProperty(key + ".enabled", "false").toLowerCase(); @@ -42,8 +73,8 @@ private static Optional getOptionalStore(Class type, String key) { } } - private static ConfigurationSource getConfigSource() throws ComponentLookupException { - return getComponentManager().lookup(ConfigurationSource.class, "allproperties"); + private static ConfigurationSource getConfigSource() { + return Utils.getComponent(ConfigurationSource.class, "allproperties"); } private static ComponentManager getComponentManager() { 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 058ed4a34..138ef0b64 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 @@ -184,9 +184,6 @@ public class XWiki implements EventListener { /** The attachment storage (excluding attachment history). */ private XWikiAttachmentStoreInterface attachmentStore; - /** Store for attachment archives. */ - private AttachmentVersioningStore attachmentVersioningStore; - /** Document versioning storage. */ private XWikiVersioningStoreInterface versioningStore; @@ -377,18 +374,12 @@ protected void initXWiki() throws XWikiException { "com.xpn.xwiki.criteria.impl.XWikiCriteriaServiceImpl", context)); LOGGER.trace("initialising AttachmentStore..."); - setAttachmentStore(Utils.getComponent(XWikiAttachmentStoreInterface.class, Param( - "xwiki.store.attachment.hint"))); + setAttachmentStore(StoreFactory.getAttachmentStore()); LOGGER.trace("initialising VersioningStore..."); setVersioningStore(Utils.getComponent(XWikiVersioningStoreInterface.class, Param( "xwiki.store.versioning.hint"))); - LOGGER.trace("initialising AttachmentVersioningStore..."); - setAttachmentVersioningStore(Utils.getComponent(AttachmentVersioningStore.class, - hasAttachmentVersioning(context) ? Param("xwiki.store.attachment.versioning.hint") - : "void")); - LOGGER.trace("initialising RecycleBinStore..."); StoreFactory.getRecycleBinStore().ifPresent(this::setRecycleBinStore); @@ -782,7 +773,7 @@ public XWikiAttachmentStoreInterface getAttachmentStore() { } public AttachmentVersioningStore getAttachmentVersioningStore() { - return this.attachmentVersioningStore; + return getAttachmentStore().getVersioningStore(); } public XWikiVersioningStoreInterface getVersioningStore() { @@ -2216,10 +2207,6 @@ public void setAttachmentStore(XWikiAttachmentStoreInterface attachmentStore) { this.attachmentStore = attachmentStore; } - public void setAttachmentVersioningStore(AttachmentVersioningStore avStore) { - this.attachmentVersioningStore = avStore; - } - public void setVersioningStore(XWikiVersioningStoreInterface versioningStore) { this.versioningStore = versioningStore; } @@ -5150,7 +5137,7 @@ public boolean hasVersioning(String fullName, XWikiContext context) { @Deprecated public boolean hasAttachmentVersioning(XWikiContext context) { - return ("1".equals(Param("xwiki.store.attachment.versioning", "1"))); + return getAttachmentStore().hasVersioningSupport(); } public String getExternalAttachmentURL(String fullName, String filename, XWikiContext context) { 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 ec9dfe291..84e4c96eb 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 @@ -21,6 +21,9 @@ package com.xpn.xwiki.doc; +import static com.celements.execution.XWikiExecutionProp.*; +import static com.celements.spring.context.SpringContextProvider.*; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -42,11 +45,14 @@ import org.dom4j.io.SAXReader; import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.Version; +import org.xwiki.context.Execution; +import com.celements.init.XWikiProvider; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.internal.xml.DOMXMLWriter; import com.xpn.xwiki.internal.xml.XMLWriter; +import com.xpn.xwiki.store.XWikiAttachmentStoreInterface; public class XWikiAttachment implements Cloneable { @@ -307,14 +313,14 @@ public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions, ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { XMLWriter wr = new XMLWriter(baos, - new OutputFormat("", true, context.getWiki().getEncoding())); + new OutputFormat("", true, getXContext().getWiki().getEncoding())); Document doc = new DOMDocument(); wr.writeDocumentStart(doc); - toXML(wr, bWithAttachmentContent, bWithVersions, context); + toXML(wr, bWithAttachmentContent, bWithVersions, getXContext()); wr.writeDocumentEnd(doc); byte[] array = baos.toByteArray(); baos = null; - return new String(array, context.getWiki().getEncoding()); + return new String(array, getXContext().getWiki().getEncoding()); } catch (IOException e) { e.printStackTrace(); return ""; @@ -655,7 +661,7 @@ public void setContent(InputStream is) throws IOException { public void loadContent(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { try { - context.getWiki().getAttachmentStore().loadAttachmentContent(this, context, true); + getAttachmentStore().loadAttachmentContent(this, context, true); } catch (Exception ex) { LOG.warn(String.format("Failed to load content for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", @@ -668,8 +674,8 @@ public void loadContent(XWikiContext context) throws XWikiException { public XWikiAttachmentArchive loadArchive(XWikiContext context) throws XWikiException { if (this.attachment_archive == null) { try { - this.attachment_archive = context.getWiki().getAttachmentVersioningStore().loadArchive(this, - context, true); + this.attachment_archive = getAttachmentStore().getVersioningStore() + .loadArchive(this, context, true); } catch (Exception ex) { LOG.warn(String.format("Failed to load archive for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", @@ -691,9 +697,14 @@ public void updateContentArchive(XWikiContext context) throws XWikiException { loadArchive(context).updateArchive(null, context); } + public String getMimeType() { + return getMimeType(getXContext()); + } + + @Deprecated public String getMimeType(XWikiContext context) { // Choose the right content type - String mimetype = context.getEngineContext().getMimeType(getFilename().toLowerCase()); + String mimetype = getXContext().getEngineContext().getMimeType(getFilename().toLowerCase()); if (mimetype != null) { return mimetype; } else { @@ -701,6 +712,11 @@ public String getMimeType(XWikiContext context) { } } + public boolean isImage() { + return isImage(getXContext()); + } + + @Deprecated public boolean isImage(XWikiContext context) { String contenttype = getMimeType(context); if (contenttype.startsWith("image/")) { @@ -719,4 +735,25 @@ public XWikiAttachment getAttachmentRevision(String rev, XWikiContext context) return loadArchive(context).getRevision(this, rev, context); } + @Override + public String toString() { + return "XWikiAttachment[" + + "doc=" + (getDoc() != null ? getDoc().getFullName() : "") + ", " + + "filename=" + getFilename() + ", " + + "version=" + getVersion() + "]"; + } + + private XWikiAttachmentStoreInterface getAttachmentStore() { + return getBeanFactory().getBean(XWikiProvider.class) + .get().orElseThrow(IllegalStateException::new) + .getAttachmentStore(); + } + + private XWikiContext getXContext() { + return getBeanFactory().getBean(Execution.class) + .getContext() + .get(XWIKI_CONTEXT) + .orElseThrow(IllegalStateException::new); + } + } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java new file mode 100644 index 000000000..8be7752e8 --- /dev/null +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java @@ -0,0 +1,24 @@ +package com.xpn.xwiki.store; + +import org.xwiki.component.annotation.ComponentRole; + +import com.xpn.xwiki.doc.XWikiAttachmentContent; + +@ComponentRole +public interface AttachmentContentStore { + + void saveContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; + + void loadContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; + + void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; + + public static class AttachmentContentStoreException extends Exception { + + private static final long serialVersionUID = 1L; + + public AttachmentContentStoreException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java new file mode 100644 index 000000000..56348fbef --- /dev/null +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java @@ -0,0 +1,50 @@ +package com.xpn.xwiki.store; + +import org.hibernate.HibernateException; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.stereotype.Component; + +import com.xpn.xwiki.doc.XWikiAttachmentContent; + +@Component +public class HibernateAttachmentContentStore extends XWikiHibernateBaseStore + implements AttachmentContentStore { + + @Override + public void saveContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + try { + Session session = getSession(); + Query query = session.createQuery( + "select attach.id from XWikiAttachmentContent as attach where attach.id = :id"); + query.setLong("id", content.getId()); + if (query.uniqueResult() == null) { + session.save(content); + } else { + session.update(content); + } + } catch (HibernateException e) { + throw new AttachmentContentStoreException("Failed saving attachment", e); + } + } + + @Override + public void loadContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + try { + Session session = getSession(); + session.load(content, Long.valueOf(content.getId())); + } catch (HibernateException e) { + throw new AttachmentContentStoreException("Failed loading attachment", e); + } + } + + @Override + public void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + try { + Session session = getSession(); + session.delete(content); + } catch (Exception e) { + throw new AttachmentContentStoreException("Failed deleting attachment", e); + } + } +} diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java index 1be831505..c24324d11 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java @@ -33,11 +33,8 @@ import com.xpn.xwiki.doc.XWikiAttachmentArchive; /** - * Void store for attachment versioning when it is disabled. ("xwiki.store.attachment.versioning=0" - * parameter is set in - * xwiki.cfg) It says what there is only one version of attachment - latest. It doesn't store - * anything. It is safe to - * use with any stores. + * Void store for attachment versioning when it is disabled. It says what there is only one version + * of attachment - latest. It doesn't store anything. It is safe to use with any stores. * * @version $Id$ * @since 1.4M2 diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java index da634e004..3c00113c4 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java @@ -57,4 +57,11 @@ void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpdate, XWi boolean bTransaction) throws XWikiException; void cleanUp(XWikiContext context); + + AttachmentContentStore getContentStore(); + + boolean hasVersioningSupport(); + + AttachmentVersioningStore getVersioningStore(); + } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 7c19939fc..e814ec47e 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -1,128 +1,87 @@ package com.xpn.xwiki.store; -import java.io.InputStream; -import java.net.URI; +import static com.celements.common.lambda.LambdaExceptionUtil.*; +import static com.celements.spring.context.SpringContextProvider.*; + import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hibernate.Query; import org.hibernate.Session; import org.xwiki.component.annotation.Component; +import com.celements.store.StoreFactory; +import com.google.common.base.Suppliers; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentContent; import com.xpn.xwiki.doc.XWikiDocument; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; -import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3Client; - @Component public class XWikiHibernateAttachmentStore extends XWikiHibernateBaseStore implements XWikiAttachmentStoreInterface { - private static final Log log = LogFactory.getLog(XWikiHibernateAttachmentStore.class); - - private S3Client s3Client; + private final Supplier contentStore = Suppliers + .memoize(StoreFactory::getAttachmentContentStore); + private final Supplier> contentStoreFallback = Suppliers + .memoize(() -> Optional.of( + getBeanFactory().getBean(HibernateAttachmentContentStore.class)) + .filter(store -> !(contentStore.get() instanceof HibernateAttachmentContentStore))); + private final Supplier versioningStore = Suppliers + .memoize(StoreFactory::getAttachmentVersioningStore); /** * Empty constructor needed for component manager. */ - public XWikiHibernateAttachmentStore() { - s3Client = S3Client.builder() - .endpointOverride(URI.create("https://fsn1.your-objectstorage.com")) - .region(Region.of("eu-central")) - .credentialsProvider(StaticCredentialsProvider - .create(AwsBasicCredentials.builder() - .accessKeyId("FIGYTJ2VE7JJ1K5BC3AS") - .secretAccessKey("H2di8Cgp8pMNZRAz4HCZLSG2K41F66tFcTT9697A") - .build())) - .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED) - .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED) - .build(); - } + public XWikiHibernateAttachmentStore() {} @Override public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext context, - boolean bTransaction) - throws XWikiException { + boolean bTransaction) throws XWikiException { saveAttachmentContent(attachment, true, context, bTransaction); } @Override public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpdate, - XWikiContext context, - boolean bTransaction) throws XWikiException { + XWikiContext context, boolean bTransaction) throws XWikiException { try { XWikiAttachmentContent content = attachment.getAttachment_content(); if (content.isContentDirty()) { attachment.updateContentArchive(context); } if (bTransaction) { - checkHibernate(context); - bTransaction = beginTransaction(context); + bTransaction = beginTransaction(); } - Session session = getSession(context); - String db = context.getDatabase(); String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); try { if (attachdb != null) { context.setDatabase(attachdb); } - - try (InputStream data = content.getContentInputStream()) { - s3Client.putObject(builder -> builder - .bucket("cel-filebase") - .key(db + ":" + content.getId()) - .contentLength(content.getSize()) - .contentType(attachment.getMimeType(context)), - RequestBody.fromInputStream(data, content.getSize())); - } - - Query query = session.createQuery( - "select attach.id from XWikiAttachmentContent as attach where attach.id = :id"); - query.setLong("id", content.getId()); - if (query.uniqueResult() == null) { - session.save(content); - } else { - session.update(content); - } - + getContentStore().saveContent(content); if (attachment.getAttachment_archive() == null) { attachment.loadArchive(context); } - context.getWiki().getAttachmentVersioningStore().saveArchive( - attachment.getAttachment_archive(), - context, false); - + getVersioningStore().saveArchive(attachment.getAttachment_archive(), context, false); if (parentUpdate) { context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, true); } - } finally { context.setDatabase(db); } - if (bTransaction) { - endTransaction(context, true); + endTransaction(true); } } catch (Exception e) { - Object[] args = { attachment.getFilename(), attachment.getDoc().getFullName() }; + Object[] args = { attachment }; throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_ATTACHMENT, - "Exception while saving attachment {0} of document {1}", e, args); + "Exception while saving {0}", e, args); } finally { try { if (bTransaction) { - endTransaction(context, false); + endTransaction(false); } } catch (Exception e) {} } @@ -131,15 +90,13 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda @Override public void saveAttachmentsContent(List attachments, XWikiDocument doc, - boolean bParentUpdate, - XWikiContext context, boolean bTransaction) throws XWikiException { + boolean bParentUpdate, XWikiContext context, boolean bTransaction) throws XWikiException { if (attachments == null) { return; } try { if (bTransaction) { - checkHibernate(context); - bTransaction = beginTransaction(context); + bTransaction = beginTransaction(); } for (XWikiAttachment att : attachments) { saveAttachmentContent(att, false, context, false); @@ -154,7 +111,7 @@ public void saveAttachmentsContent(List attachments, XWikiDocum } finally { try { if (bTransaction) { - endTransaction(context, false); + endTransaction(false); } } catch (Exception e) {} } @@ -163,15 +120,11 @@ public void saveAttachmentsContent(List attachments, XWikiDocum @Override public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext context, - boolean bTransaction) - throws XWikiException { + boolean bTransaction) throws XWikiException { try { if (bTransaction) { - checkHibernate(context); - bTransaction = beginTransaction(false, context); + bTransaction = beginTransaction(); } - Session session = getSession(context); - String db = context.getDatabase(); String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); try { @@ -180,28 +133,28 @@ public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext conte } XWikiAttachmentContent content = new XWikiAttachmentContent(attachment); attachment.setAttachment_content(content); - session.load(content, new Long(content.getId())); - - // Hibernate calls setContent which causes isContentDirty to be true. This is not what we - // want. + try { + getContentStore().loadContent(content); + } catch (Exception e) { + contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.loadContent(content))); + contentStoreFallback.get().orElseThrow(() -> e); + } content.setContentDirty(false); - } finally { context.setDatabase(db); } - if (bTransaction) { - endTransaction(context, false, false); + endTransaction(false); } } catch (Exception e) { - Object[] args = { attachment.getFilename(), attachment.getDoc().getFullName() }; + Object[] args = { attachment }; throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_ATTACHMENT, - "Exception while loading attachment {0} of document {1}", e, args); + "Exception while loading {0}", e, args); } finally { try { if (bTransaction) { - endTransaction(context, false, false); + endTransaction(false); } } catch (Exception e) {} } @@ -209,96 +162,95 @@ public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext conte @Override public void deleteXWikiAttachment(XWikiAttachment attachment, XWikiContext context, - boolean bTransaction) - throws XWikiException { + boolean bTransaction) throws XWikiException { deleteXWikiAttachment(attachment, true, context, bTransaction); } @Override public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpdate, - XWikiContext context, - boolean bTransaction) throws XWikiException { + XWikiContext context, boolean bTransaction) throws XWikiException { try { if (bTransaction) { - checkHibernate(context); - bTransaction = beginTransaction(context); + bTransaction = beginTransaction(); } - - Session session = getSession(context); - + Session session = getSession(); String db = context.getDatabase(); String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); try { if (attachdb != null) { context.setDatabase(attachdb); } - - // Delete the three attachment entries + // delete attachment content try { loadAttachmentContent(attachment, context, false); + var content = attachment.getAttachment_content(); try { - session.delete(attachment.getAttachment_content()); + getContentStore().deleteContent(content); } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Error deleting attachment content " + attachment.getFilename() + " of doc " - + attachment.getDoc().getFullName()); - } + logger.info("Error deleting content for {}", attachment); } - } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Error loading attachment content when deleting attachment " - + attachment.getFilename() + " of doc " + attachment.getDoc().getFullName()); + try { + contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.deleteContent(content))); + } catch (Exception e) { + logger.info("Error deleting content for {}", attachment); } + } catch (Exception e) { + logger.warn("Error loading content when deleting {}", attachment); } - - context.getWiki().getAttachmentVersioningStore().deleteArchive(attachment, context, false); - + // delete attachment archive + getVersioningStore().deleteArchive(attachment, context, false); + // delete attachment meta data try { session.delete(attachment); } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Error deleting attachment meta data " + attachment.getFilename() + " of doc " - + attachment.getDoc().getFullName()); - } + logger.warn("Error deleting meta data for {}", attachment); } - } finally { context.setDatabase(db); } - + // update parent document try { if (parentUpdate) { - List list = attachment.getDoc().getAttachmentList(); - for (int i = 0; i < list.size(); i++) { - XWikiAttachment attach = list.get(i); - if (attachment.getFilename().equals(attach.getFilename())) { - list.remove(i); - break; + var iter = attachment.getDoc().getAttachmentList().iterator(); + while (iter.hasNext()) { + if (attachment.getFilename().equals(iter.next().getFilename())) { + iter.remove(); } } context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, false); } } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Error updating document when deleting attachment " + attachment.getFilename() - + " of doc " + attachment.getDoc().getFullName()); - } + logger.warn("Error updating document when deleting {}", attachment); } - if (bTransaction) { - endTransaction(context, true); + endTransaction(true); } } catch (Exception e) { - Object[] args = { attachment.getFilename(), attachment.getDoc().getFullName() }; + Object[] args = { attachment }; throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_DELETING_ATTACHMENT, - "Exception while deleting attachment {0} of document {1}", e, args); + "Exception while deleting {0}", e, args); } finally { try { if (bTransaction) { - endTransaction(context, false); + endTransaction(false); } } catch (Exception e) {} } } + + @Override + public AttachmentContentStore getContentStore() { + return contentStore.get(); + } + + @Override + public boolean hasVersioningSupport() { + return getVersioningStore().getClass() != VoidAttachmentVersioningStore.class; + } + + @Override + public AttachmentVersioningStore getVersioningStore() { + return versioningStore.get(); + } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java index d0cf6d7a3..782ab17a4 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java @@ -24,6 +24,7 @@ import org.xwiki.component.annotation.Requirement; import org.xwiki.component.phase.Initializable; import org.xwiki.component.phase.InitializationException; +import org.xwiki.configuration.ConfigurationSource; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.model.reference.WikiReference; @@ -55,6 +56,9 @@ public class XWikiHibernateBaseStore implements Initializable { @Requirement protected XWikiConfigSource xwikiCfg; + @Requirement("allproperties") + protected ConfigurationSource cfgSrc; + private String hibpath = DEFAULT_CFG_PATH; /** diff --git a/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/VoidAttachmentVersioningStoreTest.java b/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/VoidAttachmentVersioningStoreTest.java deleted file mode 100644 index 2a7881d0d..000000000 --- a/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/VoidAttachmentVersioningStoreTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package com.xpn.xwiki.store; - -import org.suigeneris.jrcs.rcs.Version; - -import com.xpn.xwiki.XWiki; -import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.doc.XWikiAttachment; -import com.xpn.xwiki.doc.XWikiAttachmentArchive; -import com.xpn.xwiki.doc.XWikiDocument; -import com.xpn.xwiki.store.VoidAttachmentVersioningStore.VoidAttachmentArchive; -import com.xpn.xwiki.test.AbstractBridgedXWikiComponentTestCase; - -/** - * Unit tests for {@link VoidAttachmentVersioningStore} and {@link VoidAttachmentArchive}. - * - * @version $Id$ - */ -public class VoidAttachmentVersioningStoreTest extends AbstractBridgedXWikiComponentTestCase { - - AttachmentVersioningStore store; - - @Override - protected void setUp() throws Exception { - super.setUp(); - XWiki xwiki = new XWiki(false); - getContext().setWiki(xwiki); - - this.store = getComponentManager().lookup(AttachmentVersioningStore.class, "void"); - xwiki.setAttachmentVersioningStore(this.store); - } - - public void testStore() throws XWikiException { - // is store correctly inited? - assertEquals(VoidAttachmentVersioningStore.class, this.store.getClass()); - // create doc, attachment & attachment archive - XWikiDocument doc = new XWikiDocument("Main", "Test"); - XWikiAttachment attachment = new XWikiAttachment(doc, "filename"); - attachment.setContent(new byte[] { 1 }); - attachment.updateContentArchive(getContext()); - // is archive correctly inited and cloneable? - this.store.saveArchive(attachment.getAttachment_archive(), this.getContext(), true); - XWikiAttachmentArchive archive = this.store.loadArchive(attachment, this.getContext(), false); - assertEquals(VoidAttachmentArchive.class, archive.getClass()); - assertEquals(VoidAttachmentArchive.class, archive.clone().getClass()); - assertEquals(archive, this.store.loadArchive(attachment, this.getContext(), true)); - - this.store.deleteArchive(attachment, getContext(), true); - } - - public void testHistory() throws XWikiException { - XWikiDocument doc = new XWikiDocument("Main", "Test"); - XWikiAttachment attachment = new XWikiAttachment(doc, "filename"); - // 1.1 - attachment.setContent(new byte[] { 1 }); - attachment.updateContentArchive(this.getContext()); - assertEquals(attachment, attachment.getAttachmentRevision("1.1", this.getContext())); - // 1.2 - attachment.setContent(new byte[] { 2 }); - attachment.updateContentArchive(this.getContext()); - assertEquals(attachment, attachment.getAttachmentRevision("1.2", this.getContext())); - // there should be only 1.2 version. - assertNull(attachment.getAttachmentRevision("1.1", this.getContext())); - assertNull(attachment.getAttachmentRevision("1.3", this.getContext())); - assertEquals(1, attachment.getVersions().length); - assertEquals(new Version(1, 2), attachment.getVersions()[0]); - } -} diff --git a/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java b/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java index f931e1294..1e2f2f718 100644 --- a/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java +++ b/celements-xwiki-core/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java @@ -29,6 +29,8 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.xwiki.configuration.ConfigurationSource; +import org.xwiki.test.MockConfigurationSource; import com.xpn.xwiki.XWikiConstant; import com.xpn.xwiki.test.AbstractComponentTest; @@ -44,7 +46,9 @@ public class XWikiHibernateStoreTest extends AbstractComponentTest { XWikiHibernateStore store; @Before - public void setUp() { + public void setUp() throws Exception { + var cfgSrc = new MockConfigurationSource(); + registerComponentMock(ConfigurationSource.class, "allproperties", cfgSrc); store = Utils.getComponent(XWikiHibernateStore.class); store.setPath("whatever"); } From e720575cdfcdc6b20ffc7dcceca7e1907dee2c6e Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Tue, 13 Jan 2026 19:28:24 +0100 Subject: [PATCH 03/13] fix archive/recyclebin for different content storage --- .../com/celements/store/StoreFactory.java | 5 +- .../store/att/AttachmentContentPolicy.java | 57 ++++++++++++++++ .../src/main/java/com/xpn/xwiki/XWiki.java | 4 +- .../com/xpn/xwiki/api/DeletedAttachment.java | 6 +- .../com/xpn/xwiki/api/DeletedDocument.java | 6 +- .../com/xpn/xwiki/doc/DeletedAttachment.java | 12 ++-- .../com/xpn/xwiki/doc/XWikiAttachment.java | 66 +++++++++---------- .../xpn/xwiki/doc/XWikiAttachmentArchive.java | 35 ++++++---- .../xpn/xwiki/doc/XWikiAttachmentContent.java | 28 +------- .../com/xpn/xwiki/doc/rcs/XWikiPatch.java | 6 +- .../xpn/xwiki/doc/rcs/XWikiRCSArchive.java | 15 ++--- .../xwiki/store/AttachmentContentStore.java | 5 ++ .../store/AttachmentVersioningStore.java | 2 + .../HibernateAttachmentContentStore.java | 10 +++ .../store/VoidAttachmentVersioningStore.java | 14 ++-- .../store/XWikiAttachmentStoreInterface.java | 2 - .../store/XWikiHibernateAttachmentStore.java | 11 +--- .../store/XWikiHibernateVersioningStore.java | 6 +- .../HibernateAttachmentRecycleBinStore.java | 7 +- .../HibernateAttachmentVersioningStore.java | 15 +++-- .../XWikiHibernateMigrationManager.java | 6 +- 21 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java diff --git a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java index e89ba1c57..75d830132 100644 --- a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java +++ b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java @@ -45,15 +45,14 @@ public static XWikiAttachmentStoreInterface getAttachmentStore() { public static AttachmentContentStore getAttachmentContentStore() { String beanName = getConfigSource().getProperty("celements.store.attachment.content", - HibernateAttachmentContentStore.class.getName()); + HibernateAttachmentContentStore.STORE_NAME); return getBeanFactory().getBean(beanName, AttachmentContentStore.class); } public static AttachmentVersioningStore getAttachmentVersioningStore() { try { - var beanName = getConfigSource().getProperty("celements.store.attachment.versioning", ""); return getComponentManager().lookup(AttachmentVersioningStore.class, - !beanName.isEmpty() ? beanName : "void"); + getConfigSource().getProperty("celements.store.attachment.versioning", "default")); } catch (ComponentLookupException exc) { throw new IllegalStateException("failed looking up attachment versioning store", exc); } diff --git a/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java b/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java new file mode 100644 index 000000000..5ab1f0b5c --- /dev/null +++ b/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java @@ -0,0 +1,57 @@ +package com.celements.store.att; + +import javax.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.celements.init.XWikiProvider; +import com.xpn.xwiki.store.HibernateAttachmentContentStore; + +/** + * Policy component that decides whether attachment binary content should be embedded into + * XML-based structures produced by XWiki, namely the attachment archive (RCS/XML history) and the + * attachment recycle bin entry (deleted attachment XML snapshot). + *

+ * The decision is based on the currently active {@code AttachmentContentStore} implementation. + * When the legacy Hibernate content store is used, attachment content is expected to be present in + * the serialized XML (RCS archive and recycle bin) because the Hibernate-based implementations + * historically persisted/consumed the bytes from there. + *

+ * When an alternative content storage is used (for example object storage), embedding raw bytes + * into these XML payloads is typically undesirable (size, duplication) and may be incompatible with + * the alternative store's lookup strategy. In that case, content should be excluded and retrieved + * via the configured {@code AttachmentContentStore} instead. + */ +@Component +public class AttachmentContentPolicy { + + private final XWikiProvider xwikiProvider; + + @Inject + public AttachmentContentPolicy(XWikiProvider xwikiProvider) { + this.xwikiProvider = xwikiProvider; + } + + /** + * Determines whether attachment content should be embedded into the attachment archive XML. + */ + public boolean includeInArchive() { + return isHibernateAttachmentContentStore(); + } + + /** + * Determines whether attachment content should be embedded into recycle bin XML. + */ + public boolean includeInRecycleBin() { + return isHibernateAttachmentContentStore(); + } + + private boolean isHibernateAttachmentContentStore() { + return xwikiProvider.get().orElseThrow(IllegalStateException::new) + .getAttachmentStore() + .getContentStore() + .getStoreName() + .equals(HibernateAttachmentContentStore.STORE_NAME); + } + +} 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 138ef0b64..9d400c07e 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 @@ -375,6 +375,8 @@ protected void initXWiki() throws XWikiException { LOGGER.trace("initialising AttachmentStore..."); setAttachmentStore(StoreFactory.getAttachmentStore()); + getAttachmentStore().getContentStore(); // force init + getAttachmentStore().getVersioningStore(); // force init LOGGER.trace("initialising VersioningStore..."); setVersioningStore(Utils.getComponent(XWikiVersioningStoreInterface.class, Param( @@ -5137,7 +5139,7 @@ public boolean hasVersioning(String fullName, XWikiContext context) { @Deprecated public boolean hasAttachmentVersioning(XWikiContext context) { - return getAttachmentStore().hasVersioningSupport(); + return getAttachmentStore().getVersioningStore().hasVersioning(); } public String getExternalAttachmentURL(String fullName, String filename, XWikiContext context) { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedAttachment.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedAttachment.java index 02c005d30..d707e6df2 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedAttachment.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedAttachment.java @@ -22,8 +22,8 @@ import java.util.Calendar; import java.util.Date; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.xpn.xwiki.XWikiConfig; import com.xpn.xwiki.XWikiContext; @@ -47,7 +47,7 @@ public class DeletedAttachment extends Api { /** Logging helper object. */ - private static final Log LOG = LogFactory.getLog(DeletedAttachment.class); + private static final Logger LOG = LoggerFactory.getLogger(DeletedAttachment.class); /** The internal object wrapped by this API. */ private final com.xpn.xwiki.doc.DeletedAttachment deletedAttachment; diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedDocument.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedDocument.java index 2476d82d6..4cb8500fd 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedDocument.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/DeletedDocument.java @@ -22,8 +22,8 @@ import java.util.Calendar; import java.util.Date; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.xpn.xwiki.XWikiConfig; import com.xpn.xwiki.XWikiContext; @@ -40,7 +40,7 @@ public class DeletedDocument extends Api { /** Logging helper object. */ - private static final Log LOG = LogFactory.getLog(DeletedDocument.class); + private static final Logger LOG = LoggerFactory.getLogger(DeletedDocument.class); /** * The internal object wrapped by this API. diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/DeletedAttachment.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/DeletedAttachment.java index 5bcd77a98..0f9da79c4 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/DeletedAttachment.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/DeletedAttachment.java @@ -73,20 +73,20 @@ protected DeletedAttachment() {} * User which deleted the attachment. * @param deleteDate * Date of delete action. - * @param context - * The current context. Used for determining the encoding. + * @param bWithAttachmentContent + * True to include the attachment content in the recycle bin * @throws XWikiException * If the attachment cannot be exported to XML. */ public DeletedAttachment(XWikiAttachment attachment, String deleter, Date deleteDate, - XWikiContext context) + boolean bWithAttachmentContent) throws XWikiException { this.docId = attachment.getDocId(); this.docName = attachment.getDoc().getFullName(); this.filename = attachment.getFilename(); this.deleter = deleter; this.date = deleteDate; - setAttachment(attachment, context); + setAttachment(attachment, bWithAttachmentContent); } /** @@ -233,9 +233,9 @@ protected void setXml(String xml) { * @throws XWikiException * if an exception occurs during the XML export */ - protected void setAttachment(XWikiAttachment attachment, XWikiContext context) + protected void setAttachment(XWikiAttachment attachment, boolean bWithAttachmentContent) throws XWikiException { - setXml(attachment.toStringXML(true, true, context)); + setXml(attachment.toStringXML(bWithAttachmentContent, true)); } /** 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 84e4c96eb..e3b32216d 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 @@ -24,6 +24,7 @@ import static com.celements.execution.XWikiExecutionProp.*; import static com.celements.spring.context.SpringContextProvider.*; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -31,11 +32,10 @@ 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; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; @@ -43,6 +43,8 @@ import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.Version; import org.xwiki.context.Execution; @@ -56,7 +58,7 @@ public class XWikiAttachment implements Cloneable { - private static final Log LOG = LogFactory.getLog(XWikiAttachment.class); + private static final Logger LOG = LoggerFactory.getLogger(XWikiAttachment.class); private XWikiDocument doc; @@ -288,6 +290,12 @@ public void setMetaDataDirty(boolean metaDataDirty) { this.isMetaDataDirty = metaDataDirty; } + @Deprecated + public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions, + XWikiContext context) throws XWikiException { + return toStringXML(bWithAttachmentContent, bWithVersions); + } + /** * Retrieve an attachment as an XML string. You should prefer * {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, com.xpn.xwiki.XWikiContext) @@ -303,8 +311,7 @@ public void setMetaDataDirty(boolean metaDataDirty) { * @throws XWikiException * when an error occurs during wiki operations */ - public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions, - XWikiContext context) + public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions) throws XWikiException { // This is very bad. baos holds the entire attachment on the heap, then it makes a copy when // toByteArray @@ -470,17 +477,14 @@ public void fromXML(Element docel) throws XWikiException { setAuthor(docel.element("author").getText()); setVersion(docel.element("version").getText()); setComment(docel.element("comment").getText()); - String sdate = docel.element("date").getText(); Date date = new Date(Long.parseLong(sdate)); setDate(date); - - Element contentel = docel.element("content"); - if (contentel != null) { - String base64content = contentel.getText(); - byte[] content = Base64.decodeBase64(base64content.getBytes()); - setContent(content); - } + Optional.ofNullable(docel.element("content")) + .map(el -> el.getText().trim()) + .filter(content -> !content.isEmpty()) + .map(content -> Base64.decodeBase64(content.getBytes())) + .ifPresent(this::setContent); Element archiveel = docel.element("versions"); if (archiveel != null) { String archive = archiveel.getText(); @@ -609,16 +613,16 @@ public synchronized List getVersionList() throws XWikiException { * * @param data * a byte array with the binary content of the attachment - * @deprecated use {@link #setContent(java.io.InputStream, int)} instead + * @deprecated use {@link #setContent(java.io.InputStream)} instead */ @Deprecated public void setContent(byte[] data) { - if (this.attachment_content == null) { - this.attachment_content = new XWikiAttachmentContent(); - this.attachment_content.setAttachment(this); + data = (data != null) ? data : new byte[0]; + try (InputStream is = new ByteArrayInputStream(data)) { + setContent(is); + } catch (IOException e) { + throw new RuntimeException("This should never happen", e); } - - this.attachment_content.setContent(data); } /** @@ -630,15 +634,11 @@ public void setContent(byte[] data) { * the length in byte to read * @throws IOException * when an error occurs during streaming operation - * @since 2.3M2 + * @deprecated use {@link #setContent(java.io.InputStream)} instead */ + @Deprecated public void setContent(InputStream is, int length) throws IOException { - if (this.attachment_content == null) { - this.attachment_content = new XWikiAttachmentContent(); - this.attachment_content.setAttachment(this); - } - - this.attachment_content.setContent(is, length); + setContent(is); } /** @@ -651,11 +651,11 @@ public void setContent(InputStream is, int length) throws IOException { * @since 2.6M1 */ public void setContent(InputStream is) throws IOException { - if (this.attachment_content == null) { - this.attachment_content = new XWikiAttachmentContent(this); + if (attachment_content == null) { + attachment_content = new XWikiAttachmentContent(); + attachment_content.setAttachment(this); } - - this.attachment_content.setContent(is); + attachment_content.setContent(is); } public void loadContent(XWikiContext context) throws XWikiException { @@ -691,10 +691,7 @@ public void updateContentArchive(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { return; } - - // XWikiAttachmentArchive no longer uses the byte array passed as it's first parameter making it - // redundant. - loadArchive(context).updateArchive(null, context); + loadArchive(context).updateArchive(); } public String getMimeType() { @@ -731,7 +728,6 @@ public XWikiAttachment getAttachmentRevision(String rev, XWikiContext context) if (StringUtils.equals(rev, this.getVersion())) { return this; } - return loadArchive(context).getRevision(this, rev, context); } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java index 292cb9327..ba9694b08 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java @@ -21,22 +21,25 @@ package com.xpn.xwiki.doc; +import static com.celements.spring.context.SpringContextProvider.*; + import java.io.ByteArrayInputStream; import java.util.Date; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.Version; import org.suigeneris.jrcs.rcs.impl.Node; import org.suigeneris.jrcs.util.ToString; +import com.celements.store.att.AttachmentContentPolicy; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; public class XWikiAttachmentArchive implements Cloneable { - private static final Log LOG = LogFactory.getLog(XWikiAttachmentArchive.class); + private static final Logger LOG = LoggerFactory.getLogger(XWikiAttachmentArchive.class); private XWikiAttachment attachment; @@ -87,13 +90,6 @@ public void setRCSArchive(Archive archive) { } public byte[] getArchive() throws XWikiException { - return getArchive(null); - } - - public byte[] getArchive(XWikiContext context) throws XWikiException { - if ((this.archive == null) && (context != null)) { - updateArchive(this.attachment.getContent(context), context); - } if (this.archive == null) { return new byte[0]; } else { @@ -129,11 +125,12 @@ public void setArchive(byte[] data) throws XWikiException { * the XWikiContext for the request used to load the correct attachment content from the * database. */ - public void updateArchive(byte[] data, XWikiContext context) throws XWikiException { + public void updateArchive() throws XWikiException { try { this.attachment.incrementVersion(); this.attachment.setDate(new Date()); - String sdata = this.attachment.toStringXML(true, false, context); + boolean includeContent = getAttachmentContentPolicy().includeInArchive(); + String sdata = this.attachment.toStringXML(includeContent, false); Object[] lines = ToString.stringToArray(sdata); if (this.archive != null) { @@ -196,6 +193,16 @@ public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWiki revattach.fromXML(scontent); revattach.setDoc(attachment.getDoc()); revattach.setVersion(rev); + /* + * If the RCS archive is loaded from Hibernate (legacy), the content is already injected + * above by fromXML. + * If an alternative content storage is used (e.g. S3), this loads the content here. + * It is expected that AttachmentContentStore.loadContent impl can resolve the correct + * revision based solely on attachment metadata (doc, filename, version). + */ + if (revattach.getAttachment_content() == null) { + revattach.loadContent(context); + } return revattach; } catch (Exception e) { Object[] args = { attachment.getFilename() }; @@ -204,4 +211,8 @@ public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWiki "Exception while manipulating the archive for file {0}", e, args); } } + + private AttachmentContentPolicy getAttachmentContentPolicy() { + return getBeanFactory().getBean(AttachmentContentPolicy.class); + } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java index 372c8d059..764c19c7e 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java @@ -160,13 +160,9 @@ public byte[] getContent() { */ @Deprecated public void setContent(byte[] content) { - try { - byte[] internalContent = {}; - if (content != null) { - internalContent = content; - } - - this.setContent(new ByteArrayInputStream(internalContent)); + content = (content != null) ? content : new byte[0]; + try (InputStream in = new ByteArrayInputStream(content)) { + this.setContent(in); } catch (IOException e) { throw new RuntimeException("Failed to copy data to storage.", e); } @@ -220,23 +216,6 @@ public InputStream getContentInputStream() { } } - /** - * Set the content of the attachment from a portion of an InputStream. - * - * @param is - * the input stream that will be read - * @param len - * the number of bytes to read from the beginning of the stream - * @throws IOException - * when an error occurs during streaming operation - * @since 2.3M2 - */ - public void setContent(InputStream is, int len) throws IOException { - // TODO Fix so this sends a EOS when the limit is reached. - // this.setContent(new LimitedInputStream(is, ((long) len))); - this.setContent(is); - } - /** * Set the content of the attachment from an InputStream. * @@ -250,7 +229,6 @@ public void setContent(InputStream is) throws IOException { this.newFileItem(); IOUtils.copy(is, this.file.getOutputStream()); this.setContentDirty(true); - this.attachment.setFilesize((int) this.getSize()); } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiPatch.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiPatch.java index 92ed6cd85..80939cd06 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiPatch.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiPatch.java @@ -23,8 +23,8 @@ import java.util.Arrays; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.suigeneris.jrcs.util.ToString; import com.xpn.xwiki.XWikiContext; @@ -41,7 +41,7 @@ public class XWikiPatch { /** Logger. */ - private static final Log LOG = LogFactory.getLog(XWikiPatch.class); + private static final Logger LOG = LoggerFactory.getLogger(XWikiPatch.class); /** string serialization for patch. */ private String content; diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiRCSArchive.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiRCSArchive.java index 515d2f8d5..486cd8a5b 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiRCSArchive.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/rcs/XWikiRCSArchive.java @@ -24,13 +24,12 @@ import java.util.BitSet; import java.util.Collection; import java.util.Date; -import java.util.Iterator; import java.util.List; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.net.URLCodec; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.suigeneris.jrcs.diff.PatchFailedException; import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.InvalidFileFormatException; @@ -56,7 +55,7 @@ public class XWikiRCSArchive extends Archive { /** logger. */ - private static final Log LOG = LogFactory.getLog(XWikiRCSArchive.class); + private static final Logger LOG = LoggerFactory.getLogger(XWikiRCSArchive.class); /** * Used to serialize {@link XWikiDocumentArchive}. @@ -85,8 +84,8 @@ public XWikiRCSArchive(Collection nodeInfos, XWikiContext cont nodes.put(node.getVersion(), node); } XWikiJRCSNode last = null; - for (Iterator it = nodes.keySet().iterator(); it.hasNext();) { - Version ver = (Version) it.next(); + for (Object element : nodes.keySet()) { + Version ver = (Version) element; XWikiJRCSNode node = (XWikiJRCSNode) nodes.get(ver); if (last != null) { last.setRCSNext(node); @@ -288,8 +287,8 @@ public void patch(List original, boolean annotate) public Collection getNodes(long docId) throws NodeNotFoundException, InvalidFileFormatException, PatchFailedException { Collection result = new ArrayList(nodes.size()); - for (Iterator it = nodes.values().iterator(); it.hasNext();) { - XWikiJRCSNode node = new XWikiJRCSNode((Node) it.next()); + for (Object element : nodes.values()) { + XWikiJRCSNode node = new XWikiJRCSNode((Node) element); XWikiRCSNodeInfo nodeInfo = new XWikiRCSNodeInfo(); nodeInfo.setId(new XWikiRCSNodeId(docId, node.getVersion())); nodeInfo.setDiff(node.isDiff()); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java index 8be7752e8..18335a149 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java @@ -1,5 +1,7 @@ package com.xpn.xwiki.store; +import javax.validation.constraints.NotEmpty; + import org.xwiki.component.annotation.ComponentRole; import com.xpn.xwiki.doc.XWikiAttachmentContent; @@ -7,6 +9,9 @@ @ComponentRole public interface AttachmentContentStore { + @NotEmpty + String getStoreName(); + void saveContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; void loadContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java index b3994f2aa..bd9df4a79 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java @@ -35,6 +35,8 @@ @ComponentRole public interface AttachmentVersioningStore { + boolean hasVersioning(); + /** * Load attachment archive from store. * diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java index 56348fbef..f41cfcf09 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java @@ -1,5 +1,7 @@ package com.xpn.xwiki.store; +import javax.inject.Named; + import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; @@ -8,9 +10,17 @@ import com.xpn.xwiki.doc.XWikiAttachmentContent; @Component +@Named(HibernateAttachmentContentStore.STORE_NAME) public class HibernateAttachmentContentStore extends XWikiHibernateBaseStore implements AttachmentContentStore { + public static final String STORE_NAME = "store.attachment.content.hibernate"; + + @Override + public String getStoreName() { + return STORE_NAME; + } + @Override public void saveContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { try { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java index c24324d11..6ee742048 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java @@ -57,9 +57,11 @@ public VoidAttachmentVersioningStore(XWikiContext context) {} */ public VoidAttachmentVersioningStore() {} - /** - * {@inheritDoc} - */ + @Override + public boolean hasVersioning() { + return false; + } + @Override public void deleteArchive(XWikiAttachment attachment, XWikiContext context, boolean transaction) throws XWikiException { @@ -116,7 +118,7 @@ public VoidAttachmentArchive(XWikiAttachment attachment) { * {@inheritDoc} */ @Override - public void updateArchive(byte[] data, XWikiContext context) throws XWikiException { + public void updateArchive() throws XWikiException { getAttachment().incrementVersion(); getAttachment().setDate(new Date()); } @@ -133,8 +135,8 @@ public void setArchive(byte[] data) throws XWikiException { * {@inheritDoc} */ @Override - public byte[] getArchive(XWikiContext context) throws XWikiException { - String sdata = getAttachment().toStringXML(true, false, context); + public byte[] getArchive() throws XWikiException { + String sdata = getAttachment().toStringXML(true, false); Object[] lines = ToString.stringToArray(sdata); Archive archive = new Archive(lines, getAttachment().getFilename(), getAttachment().getVersion()); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java index 3c00113c4..8cb020649 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiAttachmentStoreInterface.java @@ -60,8 +60,6 @@ void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpdate, XWi AttachmentContentStore getContentStore(); - boolean hasVersioningSupport(); - AttachmentVersioningStore getVersioningStore(); } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index e814ec47e..8fcbcf41d 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -25,9 +25,9 @@ public class XWikiHibernateAttachmentStore extends XWikiHibernateBaseStore private final Supplier contentStore = Suppliers .memoize(StoreFactory::getAttachmentContentStore); private final Supplier> contentStoreFallback = Suppliers - .memoize(() -> Optional.of( - getBeanFactory().getBean(HibernateAttachmentContentStore.class)) - .filter(store -> !(contentStore.get() instanceof HibernateAttachmentContentStore))); + .memoize(() -> Optional.of(getBeanFactory() + .getBean(HibernateAttachmentContentStore.STORE_NAME, AttachmentContentStore.class)) + .filter(store -> !store.getStoreName().equals(contentStore.get().getStoreName()))); private final Supplier versioningStore = Suppliers .memoize(StoreFactory::getAttachmentVersioningStore); @@ -244,11 +244,6 @@ public AttachmentContentStore getContentStore() { return contentStore.get(); } - @Override - public boolean hasVersioningSupport() { - return getVersioningStore().getClass() != VoidAttachmentVersioningStore.class; - } - @Override public AttachmentVersioningStore getVersioningStore() { return versioningStore.get(); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateVersioningStore.java index ba46725eb..88c72a128 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateVersioningStore.java @@ -24,11 +24,11 @@ import java.util.Iterator; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.suigeneris.jrcs.rcs.Version; import org.xwiki.component.annotation.Component; @@ -50,7 +50,7 @@ public class XWikiHibernateVersioningStore extends XWikiHibernateBaseStore implements XWikiVersioningStoreInterface { /** Logger. */ - private static final Log LOG = LogFactory.getLog(XWikiHibernateVersioningStore.class); + private static final Logger LOG = LoggerFactory.getLogger(XWikiHibernateVersioningStore.class); /** Colon symbol. */ private static final String COLON = ":"; diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentRecycleBinStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentRecycleBinStore.java index 9cfbb1c21..d615ee82d 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentRecycleBinStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentRecycleBinStore.java @@ -19,6 +19,8 @@ */ package com.xpn.xwiki.store.hibernate; +import static com.celements.spring.context.SpringContextProvider.*; + import java.util.Date; import java.util.List; @@ -30,6 +32,7 @@ import org.hibernate.criterion.Restrictions; import org.xwiki.component.annotation.Component; +import com.celements.store.att.AttachmentContentPolicy; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.DeletedAttachment; @@ -69,8 +72,10 @@ public HibernateAttachmentRecycleBinStore() {} public void saveToRecycleBin(XWikiAttachment attachment, String deleter, Date date, XWikiContext context, boolean bTransaction) throws XWikiException { + boolean includeContent = getBeanFactory().getBean(AttachmentContentPolicy.class) + .includeInRecycleBin(); final DeletedAttachment trashAtachment = new DeletedAttachment(attachment, deleter, date, - context); + includeContent); executeWrite(context, bTransaction, session -> { session.save(trashAtachment); return null; diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java index 76855034c..a607db3f4 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java @@ -19,9 +19,9 @@ */ package com.xpn.xwiki.store.hibernate; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.hibernate.ObjectNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.Component; import com.xpn.xwiki.XWikiContext; @@ -42,16 +42,19 @@ public class HibernateAttachmentVersioningStore extends XWikiHibernateBaseStore implements AttachmentVersioningStore { /** logger. */ - private static final Log LOG = LogFactory.getLog(HibernateAttachmentVersioningStore.class); + private static final Logger LOG = LoggerFactory + .getLogger(HibernateAttachmentVersioningStore.class); /** * Empty constructor needed for component manager. */ public HibernateAttachmentVersioningStore() {} - /** - * {@inheritDoc} - */ + @Override + public boolean hasVersioning() { + return true; + } + @Override public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, XWikiContext context, boolean bTransaction) throws XWikiException { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/migration/hibernate/XWikiHibernateMigrationManager.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/migration/hibernate/XWikiHibernateMigrationManager.java index 0aa42379e..298189a10 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/migration/hibernate/XWikiHibernateMigrationManager.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/migration/hibernate/XWikiHibernateMigrationManager.java @@ -22,8 +22,8 @@ import java.util.ArrayList; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; @@ -40,7 +40,7 @@ public class XWikiHibernateMigrationManager extends AbstractXWikiMigrationManager { /** logger */ - protected static final Log LOG = LogFactory.getLog(XWikiHibernateMigrationManager.class); + protected static final Logger LOG = LoggerFactory.getLogger(XWikiHibernateMigrationManager.class); /** {@inheritDoc} */ public XWikiHibernateMigrationManager(XWikiContext context) throws XWikiException { From 0c6479412cc1fca63ba655914dc370d8bc9f93f7 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Tue, 13 Jan 2026 23:31:22 +0100 Subject: [PATCH 04/13] remove xcontexts --- .../java/com/xpn/xwiki/api/Attachment.java | 4 +-- .../com/xpn/xwiki/doc/XWikiAttachment.java | 22 +++++++-------- .../xpn/xwiki/doc/XWikiAttachmentArchive.java | 25 ++++++++--------- .../java/com/xpn/xwiki/doc/XWikiDocument.java | 13 ++++++--- .../store/AttachmentVersioningStore.java | 14 +++------- .../store/VoidAttachmentVersioningStore.java | 17 ++++-------- .../store/XWikiHibernateAttachmentStore.java | 8 +++--- .../xwiki/store/XWikiHibernateBaseStore.java | 5 ++++ .../HibernateAttachmentVersioningStore.java | 27 ++++++++----------- 9 files changed, 61 insertions(+), 74 deletions(-) diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/Attachment.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/Attachment.java index 262e92dec..0d698df60 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/Attachment.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/api/Attachment.java @@ -141,7 +141,7 @@ public String getContentAsString(String charset) throws XWikiException { } public Version[] getVersions() throws XWikiException { - attachment.loadArchive(getXWikiContext()); + attachment.loadArchive(); return attachment.getVersions(); } @@ -150,7 +150,7 @@ public Version[] getVersions() throws XWikiException { * @throws XWikiException */ public List getVersionList() throws XWikiException { - attachment.loadArchive(getXWikiContext()); + attachment.loadArchive(); return attachment.getVersionList(); } 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 e3b32216d..8d9f3e972 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 @@ -401,10 +401,9 @@ public void toXML(XMLWriter wr, boolean bWithAttachmentContent, boolean bWithVer if (bWithAttachmentContent) { el = new DOMElement("content"); // We need to make sure content is loaded - loadContent(context); - XWikiAttachmentContent acontent = getAttachment_content(); + XWikiAttachmentContent acontent = loadContent(); if (acontent != null) { - wr.writeBase64(el, getAttachment_content().getContentInputStream()); + wr.writeBase64(el, acontent.getContentInputStream()); } else { el.addText(""); wr.write(el); @@ -413,7 +412,7 @@ public void toXML(XMLWriter wr, boolean bWithAttachmentContent, boolean bWithVer if (bWithVersions) { // We need to make sure content is loaded - XWikiAttachmentArchive aarchive = loadArchive(context); + XWikiAttachmentArchive aarchive = loadArchive(); if (aarchive != null) { el = new DOMElement("versions"); try { @@ -658,10 +657,10 @@ public void setContent(InputStream is) throws IOException { attachment_content.setContent(is); } - public void loadContent(XWikiContext context) throws XWikiException { + public XWikiAttachmentContent loadContent() throws XWikiException { if (this.attachment_content == null) { try { - getAttachmentStore().loadAttachmentContent(this, context, true); + getAttachmentStore().loadAttachmentContent(this, getXContext(), true); } catch (Exception ex) { LOG.warn(String.format("Failed to load content for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", @@ -669,13 +668,14 @@ public void loadContent(XWikiContext context) throws XWikiException { ex.getMessage())); } } + return this.attachment_content; } - public XWikiAttachmentArchive loadArchive(XWikiContext context) throws XWikiException { + public XWikiAttachmentArchive loadArchive() throws XWikiException { if (this.attachment_archive == null) { try { this.attachment_archive = getAttachmentStore().getVersioningStore() - .loadArchive(this, context, true); + .loadArchive(this, true); } catch (Exception ex) { LOG.warn(String.format("Failed to load archive for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", @@ -687,11 +687,11 @@ public XWikiAttachmentArchive loadArchive(XWikiContext context) throws XWikiExce return this.attachment_archive; } - public void updateContentArchive(XWikiContext context) throws XWikiException { + public void updateContentArchive() throws XWikiException { if (this.attachment_content == null) { return; } - loadArchive(context).updateArchive(); + loadArchive().updateArchive(); } public String getMimeType() { @@ -728,7 +728,7 @@ public XWikiAttachment getAttachmentRevision(String rev, XWikiContext context) if (StringUtils.equals(rev, this.getVersion())) { return this; } - return loadArchive(context).getRevision(this, rev, context); + return loadArchive().getRevision(rev); } @Override diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java index ba9694b08..b3951e727 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java @@ -34,7 +34,6 @@ import org.suigeneris.jrcs.util.ToString; import com.celements.store.att.AttachmentContentPolicy; -import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; public class XWikiAttachmentArchive implements Cloneable { @@ -165,17 +164,16 @@ public Version[] getVersions() { return versions; } - public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWikiContext context) - throws XWikiException { - try { - Archive archive = getRCSArchive(); - - if (archive == null) { - return null; - } + public XWikiAttachment getRevision(String rev) throws XWikiException { + if ((rev == null) || (archive == null)) { + return null; + } + return getRevision(archive.getRevisionVersion(rev)); + } - Version v = archive.getRevisionVersion(rev); - if (v == null) { + public XWikiAttachment getRevision(Version v) throws XWikiException { + try { + if ((v == null) || (archive == null)) { return null; } Object[] lines = archive.getRevision(v); @@ -187,12 +185,11 @@ public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWiki content.append("\n"); } } - String scontent = content.toString(); XWikiAttachment revattach = new XWikiAttachment(); revattach.fromXML(scontent); revattach.setDoc(attachment.getDoc()); - revattach.setVersion(rev); + revattach.setVersion(v.toString()); /* * If the RCS archive is loaded from Hibernate (legacy), the content is already injected * above by fromXML. @@ -201,7 +198,7 @@ public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWiki * revision based solely on attachment metadata (doc, filename, version). */ if (revattach.getAttachment_content() == null) { - revattach.loadContent(context); + revattach.loadContent(); } return revattach; } catch (Exception e) { 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 66432ef77..c332f2b1e 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 @@ -3428,10 +3428,15 @@ public void copyAttachments(XWikiDocument sourceDocument) { setContentDirty(true); } + @Deprecated public void loadAttachments(XWikiContext context) throws XWikiException { + loadAttachments(); + } + + public void loadAttachments() throws XWikiException { for (XWikiAttachment attachment : getAttachmentList()) { - attachment.loadContent(context); - attachment.loadArchive(context); + attachment.loadContent(); + attachment.loadArchive(); } } @@ -5795,8 +5800,8 @@ private void refactorDocumentLinks(DocumentReference oldDocumentReference, */ public XWikiDocument copyDocument(DocumentReference newDocumentReference, XWikiContext context) throws XWikiException { - loadAttachments(context); - loadArchive(context); + loadAttachments(); + getDocumentArchive(); XWikiDocument newdoc = duplicate(newDocumentReference); newdoc.setOriginalDocument(null); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java index bd9df4a79..baf47cd75 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentVersioningStore.java @@ -21,7 +21,6 @@ import org.xwiki.component.annotation.ComponentRole; -import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentArchive; @@ -43,15 +42,12 @@ public interface AttachmentVersioningStore { * @return attachment archive. not null. return empty archive if it is not exist in store. * @param attachment * The attachment of archive. - * @param context - * The current context. * @param bTransaction * Should use old transaction (false) or create new (true). * @throws XWikiException * If an error occurs. */ - XWikiAttachmentArchive loadArchive(XWikiAttachment attachment, XWikiContext context, - boolean bTransaction) + XWikiAttachmentArchive loadArchive(XWikiAttachment attachment, boolean bTransaction) throws XWikiException; /** @@ -59,14 +55,12 @@ XWikiAttachmentArchive loadArchive(XWikiAttachment attachment, XWikiContext cont * * @param archive * The attachment archive to save. - * @param context - * The current context. * @param bTransaction * Should use old transaction (false) or create new (true). * @throws XWikiException * If an error occurs. */ - void saveArchive(XWikiAttachmentArchive archive, XWikiContext context, boolean bTransaction) + void saveArchive(XWikiAttachmentArchive archive, boolean bTransaction) throws XWikiException; /** @@ -74,13 +68,11 @@ void saveArchive(XWikiAttachmentArchive archive, XWikiContext context, boolean b * * @param attachment * The attachment to delete. - * @param context - * The current context. * @param bTransaction * Should use old transaction (false) or create new (true). * @throws XWikiException * If an error occurs. */ - void deleteArchive(XWikiAttachment attachment, XWikiContext context, boolean bTransaction) + void deleteArchive(XWikiAttachment attachment, boolean bTransaction) throws XWikiException; } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java index 6ee742048..93c4af4f9 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/VoidAttachmentVersioningStore.java @@ -63,26 +63,19 @@ public boolean hasVersioning() { } @Override - public void deleteArchive(XWikiAttachment attachment, XWikiContext context, boolean transaction) + public void deleteArchive(XWikiAttachment attachment, boolean transaction) throws XWikiException { // Don't do anything since it's a void implementation. } - /** - * {@inheritDoc} - */ @Override - public void saveArchive(XWikiAttachmentArchive archive, XWikiContext context, boolean transaction) + public void saveArchive(XWikiAttachmentArchive archive, boolean transaction) throws XWikiException { // Don't do anything since it's a void implementation. } - /** - * {@inheritDoc} - */ @Override - public XWikiAttachmentArchive loadArchive(XWikiAttachment attachment, XWikiContext context, - boolean transaction) + public XWikiAttachmentArchive loadArchive(XWikiAttachment attachment, boolean transaction) throws XWikiException { XWikiAttachmentArchive archive = attachment.getAttachment_archive(); if (!(archive instanceof VoidAttachmentArchive)) { @@ -163,9 +156,9 @@ public Version[] getVersions() { * {@inheritDoc} */ @Override - public XWikiAttachment getRevision(XWikiAttachment attachment, String rev, XWikiContext context) + public XWikiAttachment getRevision(Version v) throws XWikiException { - return (attachment.getVersion().equals(rev)) ? attachment : null; + return (getAttachment().getVersion().equals(v.toString())) ? getAttachment() : null; } /** diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 8fcbcf41d..880e884d0 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -48,7 +48,7 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda try { XWikiAttachmentContent content = attachment.getAttachment_content(); if (content.isContentDirty()) { - attachment.updateContentArchive(context); + attachment.updateContentArchive(); } if (bTransaction) { bTransaction = beginTransaction(); @@ -61,9 +61,9 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda } getContentStore().saveContent(content); if (attachment.getAttachment_archive() == null) { - attachment.loadArchive(context); + attachment.loadArchive(); } - getVersioningStore().saveArchive(attachment.getAttachment_archive(), context, false); + getVersioningStore().saveArchive(attachment.getAttachment_archive(), false); if (parentUpdate) { context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, true); } @@ -198,7 +198,7 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpda logger.warn("Error loading content when deleting {}", attachment); } // delete attachment archive - getVersioningStore().deleteArchive(attachment, context, false); + getVersioningStore().deleteArchive(attachment, false); // delete attachment meta data try { session.delete(attachment); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java index 782ab17a4..0ca542bf0 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateBaseStore.java @@ -1,5 +1,6 @@ package com.xpn.xwiki.store; +import static com.celements.execution.XWikiExecutionProp.*; import static com.celements.logging.LogUtils.*; import static com.google.common.base.Preconditions.*; @@ -1010,6 +1011,10 @@ protected final ExecutionContext getEContext() { return execution.getContext(); } + protected XWikiContext getXContext() { + return getEContext().get(XWIKI_CONTEXT).orElseThrow(IllegalStateException::new); + } + protected WikiReference getWikiRef(XWikiContext context) { return Strings.isNullOrEmpty(context.getDatabase()) ? null : new WikiReference(context.getDatabase()); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java index a607db3f4..42e6229c1 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.Component; -import com.xpn.xwiki.XWikiContext; +import com.celements.execution.XWikiExecutionProp; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentArchive; @@ -56,12 +56,13 @@ public boolean hasVersioning() { } @Override - public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, XWikiContext context, - boolean bTransaction) throws XWikiException { + public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, boolean bTransaction) + throws XWikiException { try { final XWikiAttachmentArchive archive = new XWikiAttachmentArchive(); archive.setAttachment(attachment); - executeRead(context, bTransaction, session -> { + var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + executeRead(wiki, bTransaction, session -> { try { session.load(archive, archive.getId()); } catch (ObjectNotFoundException e) { @@ -79,28 +80,22 @@ public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, XWik } } - /** - * {@inheritDoc} - */ @Override - public void saveArchive(final XWikiAttachmentArchive archive, XWikiContext context, - boolean bTransaction) + public void saveArchive(final XWikiAttachmentArchive archive, boolean bTransaction) throws XWikiException { - executeWrite(context, bTransaction, session -> { + var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + executeWrite(wiki, bTransaction, session -> { session.saveOrUpdate(archive); return null; }); } - /** - * {@inheritDoc} - */ @Override - public void deleteArchive(final XWikiAttachment attachment, final XWikiContext context, - boolean bTransaction) + public void deleteArchive(final XWikiAttachment attachment, boolean bTransaction) throws XWikiException { try { - executeWrite(context, bTransaction, session -> { + var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + executeWrite(wiki, bTransaction, session -> { XWikiAttachmentArchive archive = new XWikiAttachmentArchive(); archive.setAttachment(attachment); session.delete(archive); From 05488357f5f8111cdd128b54ea3a501cffcae949 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 15 Jan 2026 14:48:00 +0100 Subject: [PATCH 05/13] address PR comments --- .../main/java/com/xpn/xwiki/doc/XWikiAttachment.java | 5 ++--- .../com/xpn/xwiki/doc/XWikiAttachmentContent.java | 11 ++++++++--- .../xwiki/store/XWikiHibernateAttachmentStore.java | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) 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 8d9f3e972..1362c1c98 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 @@ -177,8 +177,7 @@ public int getContentSize(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { this.doc.loadAttachmentContent(this, context); } - - return (int) this.attachment_content.getSize(); + return this.attachment_content.getSize(); } public String getFilename() { @@ -620,7 +619,7 @@ public void setContent(byte[] data) { try (InputStream is = new ByteArrayInputStream(data)) { setContent(is); } catch (IOException e) { - throw new RuntimeException("This should never happen", e); + throw new RuntimeException("Failed to set attachment content from byte array", e); } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java index 764c19c7e..322bd5837 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java @@ -229,14 +229,19 @@ public void setContent(InputStream is) throws IOException { this.newFileItem(); IOUtils.copy(is, this.file.getOutputStream()); this.setContentDirty(true); - this.attachment.setFilesize((int) this.getSize()); + this.attachment.setFilesize(this.getSize()); } /** * @return the true size of the content of the attachment. * @since 2.3M2 */ - public long getSize() { - return this.file.getSize(); + public int getSize() { + long size = file.getSize(); + if (size <= Integer.MAX_VALUE) { + return (int) size; + } else { + throw new IllegalStateException("Attachment size " + size + " exceeds max value"); + } } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 880e884d0..a30d1fd85 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -215,6 +215,7 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpda while (iter.hasNext()) { if (attachment.getFilename().equals(iter.next().getFilename())) { iter.remove(); + break; } } context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, false); From 99d70972acb66d1b092199a7a678fe38a3a1b01f Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 15 Jan 2026 16:19:42 +0100 Subject: [PATCH 06/13] getRevision fix for current version --- .../com/xpn/xwiki/doc/XWikiAttachment.java | 16 ++++++++------ .../xpn/xwiki/doc/XWikiAttachmentArchive.java | 22 +++++++++---------- .../store/XWikiHibernateAttachmentStore.java | 1 + 3 files changed, 20 insertions(+), 19 deletions(-) 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 1362c1c98..9cd498a45 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 @@ -32,10 +32,10 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; @@ -564,7 +564,6 @@ public void setArchive(Archive archive) { this.attachment_archive = new XWikiAttachmentArchive(); this.attachment_archive.setAttachment(this); } - this.attachment_archive.setRCSArchive(archive); } @@ -573,8 +572,9 @@ public void setArchive(String data) throws XWikiException { this.attachment_archive = new XWikiAttachmentArchive(); this.attachment_archive.setAttachment(this); } - - this.attachment_archive.setArchive(data.getBytes()); + if (data != null) { + this.attachment_archive.setArchive(data.getBytes()); + } } public synchronized Version[] getVersions() { @@ -653,7 +653,9 @@ public void setContent(InputStream is) throws IOException { attachment_content = new XWikiAttachmentContent(); attachment_content.setAttachment(this); } - attachment_content.setContent(is); + if (is != null) { + attachment_content.setContent(is); + } } public XWikiAttachmentContent loadContent() throws XWikiException { @@ -680,9 +682,9 @@ public XWikiAttachmentArchive loadArchive() throws XWikiException { + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", getFilename(), (this.doc != null) ? this.doc.getFullName() : "", ex.getMessage())); + setArchive((String) null); } } - return this.attachment_archive; } @@ -724,7 +726,7 @@ public boolean isImage(XWikiContext context) { public XWikiAttachment getAttachmentRevision(String rev, XWikiContext context) throws XWikiException { - if (StringUtils.equals(rev, this.getVersion())) { + if (Objects.equals(rev, this.getVersion())) { return this; } return loadArchive().getRevision(rev); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java index b3951e727..d37f9528e 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/doc/XWikiAttachmentArchive.java @@ -25,6 +25,9 @@ import java.io.ByteArrayInputStream; import java.util.Date; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -173,21 +176,16 @@ public XWikiAttachment getRevision(String rev) throws XWikiException { public XWikiAttachment getRevision(Version v) throws XWikiException { try { - if ((v == null) || (archive == null)) { + if (Objects.equals(v, attachment.getRCSVersion())) { + return attachment; + } else if ((v == null) || (archive == null)) { return null; } - Object[] lines = archive.getRevision(v); - StringBuffer content = new StringBuffer(); - for (int i = 0; i < lines.length; i++) { - String line = lines[i].toString(); - content.append(line); - if (i != (lines.length - 1)) { - content.append("\n"); - } - } - String scontent = content.toString(); + String xml = Stream.of(archive.getRevision(v)) + .map(Object::toString) + .collect(Collectors.joining("\n")); XWikiAttachment revattach = new XWikiAttachment(); - revattach.fromXML(scontent); + revattach.fromXML(xml); revattach.setDoc(attachment.getDoc()); revattach.setVersion(v.toString()); /* diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index a30d1fd85..052937705 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -136,6 +136,7 @@ public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext conte try { getContentStore().loadContent(content); } catch (Exception e) { + // content load failed, try legacy fallback store first before rethrowing contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.loadContent(content))); contentStoreFallback.get().orElseThrow(() -> e); } From 6fcfc568d1fe43212444b91383c45dd1ffae2818 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Fri, 16 Jan 2026 14:04:29 +0100 Subject: [PATCH 07/13] add logging --- .../com/xpn/xwiki/store/HibernateAttachmentContentStore.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java index f41cfcf09..ffb9ce0da 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java @@ -23,6 +23,7 @@ public String getStoreName() { @Override public void saveContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + logger.info("saveContent - {}", content.getAttachment()); try { Session session = getSession(); Query query = session.createQuery( @@ -40,6 +41,7 @@ public void saveContent(XWikiAttachmentContent content) throws AttachmentContent @Override public void loadContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + logger.info("loadContent - {}", content.getAttachment()); try { Session session = getSession(); session.load(content, Long.valueOf(content.getId())); @@ -50,6 +52,7 @@ public void loadContent(XWikiAttachmentContent content) throws AttachmentContent @Override public void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + logger.info("deleteContent - {}", content.getAttachment()); try { Session session = getSession(); session.delete(content); From 580cb72e3dc2855849e5f79c9ca2b3534d362acf Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Fri, 16 Jan 2026 17:31:20 +0100 Subject: [PATCH 08/13] XWA throw out XWE --- .../com/xpn/xwiki/doc/XWikiAttachment.java | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) 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 9cd498a45..61ff2abd0 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 @@ -660,30 +660,15 @@ public void setContent(InputStream is) throws IOException { public XWikiAttachmentContent loadContent() throws XWikiException { if (this.attachment_content == null) { - try { - getAttachmentStore().loadAttachmentContent(this, getXContext(), true); - } catch (Exception ex) { - LOG.warn(String.format("Failed to load content for attachment [%s@%s]. " - + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", - getFilename(), (this.doc != null) ? this.doc.getFullName() : "", - ex.getMessage())); - } + getAttachmentStore().loadAttachmentContent(this, getXContext(), true); } return this.attachment_content; } public XWikiAttachmentArchive loadArchive() throws XWikiException { if (this.attachment_archive == null) { - try { - this.attachment_archive = getAttachmentStore().getVersioningStore() - .loadArchive(this, true); - } catch (Exception ex) { - LOG.warn(String.format("Failed to load archive for attachment [%s@%s]. " - + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", - getFilename(), (this.doc != null) ? this.doc.getFullName() : "", - ex.getMessage())); - setArchive((String) null); - } + this.attachment_archive = getAttachmentStore().getVersioningStore() + .loadArchive(this, true); } return this.attachment_archive; } From c564b0117036d4bb76185cf373b4d7204184aa66 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 22 Jan 2026 19:47:22 +0100 Subject: [PATCH 09/13] beginTransaction with db --- .../store/XWikiHibernateAttachmentStore.java | 118 +++++++++--------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 052937705..467b234c7 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -45,30 +45,27 @@ public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext conte @Override public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpdate, XWikiContext context, boolean bTransaction) throws XWikiException { + String currentDb = context.getDatabase(); try { XWikiAttachmentContent content = attachment.getAttachment_content(); if (content.isContentDirty()) { attachment.updateContentArchive(); } + var attWiki = Optional.ofNullable(attachment.getDoc()) + .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + if (attWiki != null) { + context.setDatabase(attWiki.getName()); + } if (bTransaction) { - bTransaction = beginTransaction(); + bTransaction = beginTransaction(attWiki); } - String db = context.getDatabase(); - String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); - try { - if (attachdb != null) { - context.setDatabase(attachdb); - } - getContentStore().saveContent(content); - if (attachment.getAttachment_archive() == null) { - attachment.loadArchive(); - } - getVersioningStore().saveArchive(attachment.getAttachment_archive(), false); - if (parentUpdate) { - context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, true); - } - } finally { - context.setDatabase(db); + getContentStore().saveContent(content); + if (attachment.getAttachment_archive() == null) { + attachment.loadArchive(); + } + getVersioningStore().saveArchive(attachment.getAttachment_archive(), false); + if (parentUpdate) { + context.getWiki().getStore().saveXWikiDoc(attachment.getDoc(), context, true); } if (bTransaction) { endTransaction(true); @@ -79,6 +76,7 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_ATTACHMENT, "Exception while saving {0}", e, args); } finally { + context.setDatabase(currentDb); try { if (bTransaction) { endTransaction(false); @@ -121,29 +119,26 @@ public void saveAttachmentsContent(List attachments, XWikiDocum @Override public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext context, boolean bTransaction) throws XWikiException { + String currentDb = context.getDatabase(); try { + var attWiki = Optional.ofNullable(attachment.getDoc()) + .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + if (attWiki != null) { + context.setDatabase(attWiki.getName()); + } if (bTransaction) { - bTransaction = beginTransaction(); + bTransaction = beginTransaction(attWiki); } - String db = context.getDatabase(); - String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); + XWikiAttachmentContent content = new XWikiAttachmentContent(attachment); + attachment.setAttachment_content(content); try { - if (attachdb != null) { - context.setDatabase(attachdb); - } - XWikiAttachmentContent content = new XWikiAttachmentContent(attachment); - attachment.setAttachment_content(content); - try { - getContentStore().loadContent(content); - } catch (Exception e) { - // content load failed, try legacy fallback store first before rethrowing - contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.loadContent(content))); - contentStoreFallback.get().orElseThrow(() -> e); - } - content.setContentDirty(false); - } finally { - context.setDatabase(db); + getContentStore().loadContent(content); + } catch (Exception e) { + // content load failed, try legacy fallback store first before rethrowing + contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.loadContent(content))); + contentStoreFallback.get().orElseThrow(() -> e); } + content.setContentDirty(false); if (bTransaction) { endTransaction(false); } @@ -153,6 +148,7 @@ public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext conte XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_ATTACHMENT, "Exception while loading {0}", e, args); } finally { + context.setDatabase(currentDb); try { if (bTransaction) { endTransaction(false); @@ -170,44 +166,41 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, XWikiContext conte @Override public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpdate, XWikiContext context, boolean bTransaction) throws XWikiException { + String currentDb = context.getDatabase(); try { + var attWiki = Optional.ofNullable(attachment.getDoc()) + .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + if (attWiki != null) { + context.setDatabase(attWiki.getName()); + } if (bTransaction) { - bTransaction = beginTransaction(); + bTransaction = beginTransaction(attWiki); } Session session = getSession(); - String db = context.getDatabase(); - String attachdb = (attachment.getDoc() == null) ? null : attachment.getDoc().getDatabase(); + // delete attachment content try { - if (attachdb != null) { - context.setDatabase(attachdb); - } - // delete attachment content + loadAttachmentContent(attachment, context, false); + var content = attachment.getAttachment_content(); try { - loadAttachmentContent(attachment, context, false); - var content = attachment.getAttachment_content(); - try { - getContentStore().deleteContent(content); - } catch (Exception e) { - logger.info("Error deleting content for {}", attachment); - } - try { - contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.deleteContent(content))); - } catch (Exception e) { - logger.info("Error deleting content for {}", attachment); - } + getContentStore().deleteContent(content); } catch (Exception e) { - logger.warn("Error loading content when deleting {}", attachment); + logger.info("Error deleting content for {}", attachment); } - // delete attachment archive - getVersioningStore().deleteArchive(attachment, false); - // delete attachment meta data try { - session.delete(attachment); + contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.deleteContent(content))); } catch (Exception e) { - logger.warn("Error deleting meta data for {}", attachment); + logger.info("Error deleting content for {}", attachment); } - } finally { - context.setDatabase(db); + } catch (Exception e) { + logger.warn("Error loading content when deleting {}", attachment); + } + // delete attachment archive + getVersioningStore().deleteArchive(attachment, false); + // delete attachment meta data + try { + session.delete(attachment); + } catch (Exception e) { + logger.warn("Error deleting meta data for {}", attachment); } // update parent document try { @@ -233,6 +226,7 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpda XWikiException.ERROR_XWIKI_STORE_HIBERNATE_DELETING_ATTACHMENT, "Exception while deleting {0}", e, args); } finally { + context.setDatabase(currentDb); try { if (bTransaction) { endTransaction(false); From 6e03b0adc13812e4c09f0f7495b6754396984fe5 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 22 Jan 2026 23:25:19 +0100 Subject: [PATCH 10/13] deleteContent for XWikiAttachment --- .../com/xpn/xwiki/doc/XWikiAttachment.java | 29 ++++++++++++++ .../xwiki/store/AttachmentContentStore.java | 3 ++ .../HibernateAttachmentContentStore.java | 14 +++++-- .../store/XWikiHibernateAttachmentStore.java | 38 ++++++++----------- 4 files changed, 58 insertions(+), 26 deletions(-) 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 61ff2abd0..3e7edff4b 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 @@ -48,6 +48,9 @@ import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.Version; import org.xwiki.context.Execution; +import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.WikiReference; import com.celements.init.XWikiProvider; import com.xpn.xwiki.XWikiContext; @@ -74,6 +77,8 @@ public class XWikiAttachment implements Cloneable { private Date date; + private AttachmentReference attRef; + private XWikiAttachmentContent attachment_content; private XWikiAttachmentArchive attachment_archive; @@ -189,6 +194,7 @@ public void setFilename(String filename) { if (!filename.equals(this.filename)) { setMetaDataDirty(true); this.filename = filename; + setAttachmentReference(); } } @@ -250,6 +256,29 @@ public XWikiDocument getDoc() { public void setDoc(XWikiDocument doc) { this.doc = doc; + setAttachmentReference(); + } + + public AttachmentReference getAttachmentReference() { + return attRef; + } + + private void setAttachmentReference() { + if ((doc != null) && (filename != null)) { + attRef = new AttachmentReference(filename, doc.getDocumentReference()); + } + } + + public DocumentReference getDocumentReference() { + return Optional.ofNullable(getAttachmentReference()) + .map(AttachmentReference::getDocumentReference) + .orElse(null); + } + + public WikiReference getWikiReference() { + return Optional.ofNullable(getDocumentReference()) + .map(DocumentReference::getWikiReference) + .orElse(null); } public Date getDate() { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java index 18335a149..a2d1f5e23 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/AttachmentContentStore.java @@ -4,6 +4,7 @@ import org.xwiki.component.annotation.ComponentRole; +import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentContent; @ComponentRole @@ -16,6 +17,8 @@ public interface AttachmentContentStore { void loadContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; + void deleteContent(XWikiAttachment attachment) throws AttachmentContentStoreException; + void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException; public static class AttachmentContentStoreException extends Exception { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java index ffb9ce0da..caa559c60 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java @@ -7,6 +7,7 @@ import org.hibernate.Session; import org.springframework.stereotype.Component; +import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentContent; @Component @@ -51,13 +52,20 @@ public void loadContent(XWikiAttachmentContent content) throws AttachmentContent } @Override - public void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { - logger.info("deleteContent - {}", content.getAttachment()); + public void deleteContent(XWikiAttachment attachment) throws AttachmentContentStoreException { + logger.info("deleteContent - {}", attachment); try { Session session = getSession(); - session.delete(content); + session.createQuery("delete from " + XWikiAttachmentContent.class.getName() + " where id = ?") + .setLong(0, attachment.getId()) + .executeUpdate(); } catch (Exception e) { throw new AttachmentContentStoreException("Failed deleting attachment", e); } } + + @Override + public void deleteContent(XWikiAttachmentContent content) throws AttachmentContentStoreException { + deleteContent(content.getAttachment()); + } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 467b234c7..801e200ce 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Stream; import org.hibernate.Session; import org.xwiki.component.annotation.Component; @@ -51,8 +52,7 @@ public void saveAttachmentContent(XWikiAttachment attachment, boolean parentUpda if (content.isContentDirty()) { attachment.updateContentArchive(); } - var attWiki = Optional.ofNullable(attachment.getDoc()) - .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + var attWiki = attachment.getWikiReference(); if (attWiki != null) { context.setDatabase(attWiki.getName()); } @@ -94,7 +94,9 @@ public void saveAttachmentsContent(List attachments, XWikiDocum } try { if (bTransaction) { - bTransaction = beginTransaction(); + bTransaction = beginTransaction(attachments.stream() + .map(XWikiAttachment::getWikiReference) + .findFirst().orElse(null)); } for (XWikiAttachment att : attachments) { saveAttachmentContent(att, false, context, false); @@ -121,8 +123,7 @@ public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext conte boolean bTransaction) throws XWikiException { String currentDb = context.getDatabase(); try { - var attWiki = Optional.ofNullable(attachment.getDoc()) - .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + var attWiki = attachment.getWikiReference(); if (attWiki != null) { context.setDatabase(attWiki.getName()); } @@ -168,8 +169,7 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpda XWikiContext context, boolean bTransaction) throws XWikiException { String currentDb = context.getDatabase(); try { - var attWiki = Optional.ofNullable(attachment.getDoc()) - .map(doc -> doc.getDocRef().getWikiReference()).orElse(null); + var attWiki = attachment.getWikiReference(); if (attWiki != null) { context.setDatabase(attWiki.getName()); } @@ -178,22 +178,14 @@ public void deleteXWikiAttachment(XWikiAttachment attachment, boolean parentUpda } Session session = getSession(); // delete attachment content - try { - loadAttachmentContent(attachment, context, false); - var content = attachment.getAttachment_content(); - try { - getContentStore().deleteContent(content); - } catch (Exception e) { - logger.info("Error deleting content for {}", attachment); - } - try { - contentStoreFallback.get().ifPresent(rethrowConsumer(s -> s.deleteContent(content))); - } catch (Exception e) { - logger.info("Error deleting content for {}", attachment); - } - } catch (Exception e) { - logger.warn("Error loading content when deleting {}", attachment); - } + Stream.of(Optional.of(getContentStore()), contentStoreFallback.get()) + .flatMap(Optional::stream).forEach(store -> { + try { + store.deleteContent(attachment); + } catch (Exception e) { + logger.info("Error deleting content for {}", attachment); + } + }); // delete attachment archive getVersioningStore().deleteArchive(attachment, false); // delete attachment meta data From b3a22886077d75d79e13065e40fffb297e26a4af Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 22 Jan 2026 23:40:21 +0100 Subject: [PATCH 11/13] move HibernateAttachmentContentStore --- .../src/main/java/com/celements/store/StoreFactory.java | 2 +- .../java/com/celements/store/att/AttachmentContentPolicy.java | 2 +- .../com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java | 1 + .../{ => hibernate}/HibernateAttachmentContentStore.java | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) rename celements-xwiki-core/src/main/java/com/xpn/xwiki/store/{ => hibernate}/HibernateAttachmentContentStore.java (94%) diff --git a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java index 75d830132..d08522448 100644 --- a/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java +++ b/celements-xwiki-core/src/main/java/com/celements/store/StoreFactory.java @@ -11,10 +11,10 @@ import com.google.common.primitives.Ints; import com.xpn.xwiki.store.AttachmentContentStore; import com.xpn.xwiki.store.AttachmentVersioningStore; -import com.xpn.xwiki.store.HibernateAttachmentContentStore; import com.xpn.xwiki.store.XWikiAttachmentStoreInterface; import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface; import com.xpn.xwiki.store.XWikiStoreInterface; +import com.xpn.xwiki.store.hibernate.HibernateAttachmentContentStore; import com.xpn.xwiki.web.Utils; public final class StoreFactory { diff --git a/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java b/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java index 5ab1f0b5c..feb22e5e0 100644 --- a/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java +++ b/celements-xwiki-core/src/main/java/com/celements/store/att/AttachmentContentPolicy.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Component; import com.celements.init.XWikiProvider; -import com.xpn.xwiki.store.HibernateAttachmentContentStore; +import com.xpn.xwiki.store.hibernate.HibernateAttachmentContentStore; /** * Policy component that decides whether attachment binary content should be embedded into diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java index 801e200ce..57dd425fc 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateAttachmentStore.java @@ -18,6 +18,7 @@ import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentContent; import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.store.hibernate.HibernateAttachmentContentStore; @Component public class XWikiHibernateAttachmentStore extends XWikiHibernateBaseStore diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentContentStore.java similarity index 94% rename from celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java rename to celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentContentStore.java index caa559c60..6f3463129 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/HibernateAttachmentContentStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentContentStore.java @@ -1,4 +1,4 @@ -package com.xpn.xwiki.store; +package com.xpn.xwiki.store.hibernate; import javax.inject.Named; @@ -9,6 +9,8 @@ import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentContent; +import com.xpn.xwiki.store.AttachmentContentStore; +import com.xpn.xwiki.store.XWikiHibernateBaseStore; @Component @Named(HibernateAttachmentContentStore.STORE_NAME) From f6a13a3dd2c5dcf911cdb40fedf1ebf100bf5a92 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Thu, 22 Jan 2026 23:46:48 +0100 Subject: [PATCH 12/13] fix getAttachmentReference --- .../java/com/xpn/xwiki/doc/XWikiAttachment.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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 3e7edff4b..566bbb62b 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 @@ -53,6 +53,7 @@ import org.xwiki.model.reference.WikiReference; import com.celements.init.XWikiProvider; +import com.google.common.base.Strings; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.internal.xml.DOMXMLWriter; @@ -77,8 +78,6 @@ public class XWikiAttachment implements Cloneable { private Date date; - private AttachmentReference attRef; - private XWikiAttachmentContent attachment_content; private XWikiAttachmentArchive attachment_archive; @@ -194,7 +193,6 @@ public void setFilename(String filename) { if (!filename.equals(this.filename)) { setMetaDataDirty(true); this.filename = filename; - setAttachmentReference(); } } @@ -256,17 +254,13 @@ public XWikiDocument getDoc() { public void setDoc(XWikiDocument doc) { this.doc = doc; - setAttachmentReference(); } public AttachmentReference getAttachmentReference() { - return attRef; - } - - private void setAttachmentReference() { - if ((doc != null) && (filename != null)) { - attRef = new AttachmentReference(filename, doc.getDocumentReference()); + if ((doc != null) && !Strings.isNullOrEmpty(filename)) { + return new AttachmentReference(filename, doc.getDocumentReference()); } + return null; } public DocumentReference getDocumentReference() { From d9d543fedbc81858e0415a06c08d02f023cab238 Mon Sep 17 00:00:00 2001 From: Marc Sladek Date: Fri, 23 Jan 2026 00:33:19 +0100 Subject: [PATCH 13/13] print xwiki cfg --- .../com/celements/init/CelementsBootstrap.java | 3 ++- .../src/main/java/com/xpn/xwiki/XWiki.java | 18 ++++++++++++++++++ .../HibernateAttachmentVersioningStore.java | 7 +++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/celements-xwiki-core/src/main/java/com/celements/init/CelementsBootstrap.java b/celements-xwiki-core/src/main/java/com/celements/init/CelementsBootstrap.java index 9737991d0..ff99adf41 100644 --- a/celements-xwiki-core/src/main/java/com/celements/init/CelementsBootstrap.java +++ b/celements-xwiki-core/src/main/java/com/celements/init/CelementsBootstrap.java @@ -2,6 +2,7 @@ import static com.celements.common.lambda.LambdaExceptionUtil.*; import static com.celements.execution.XWikiExecutionProp.*; +import static com.celements.logging.LogUtils.*; import static com.celements.spring.context.SpringContextProvider.*; import static com.google.common.base.Preconditions.*; @@ -98,7 +99,7 @@ public void onApplicationEvent(CelementsStartedEvent event) { XWiki xwiki = bootstrapXWiki(); // make XWiki available to all requests via servlet context, see {@link XWikiProvider} xwikiFuture.complete(xwiki); - LOGGER.info("XWiki initialised"); + LOGGER.info("XWiki initialised: {}", defer(xwiki::printConfig)); } catch (Exception exc) { xwikiFuture.completeExceptionally(exc); LOGGER.error("Celements bootstrap failed"); 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 9d400c07e..455d875f9 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 @@ -50,6 +50,7 @@ import java.util.ListIterator; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.TimeZone; @@ -407,6 +408,23 @@ protected void initXWiki() throws XWikiException { LOGGER.debug("XWiki init done"); } + public String printConfig() { + return "XWiki " + + "[ mainStore=" + getStore().getClass().getName() + + ", notCacheStore=" + getNotCacheStore().getClass().getName() + + ", versioningStore=" + getVersioningStore().getClass().getName() + + ", recycleBinStore=" + Optional.ofNullable(getRecycleBinStore()) + .map(s -> s.getClass().getName()).orElse("none") + + ", attachmentStore=" + getAttachmentStore().getClass().getName() + + ", attachmentContentStore=" + getAttachmentStore().getContentStore().getClass().getName() + + ", attachmentVersioningStore=" + getAttachmentStore().getVersioningStore() + .getClass().getName() + + ", attachmentRecycleBinStore=" + Optional.ofNullable(getAttachmentRecycleBinStore()) + .map(s -> s.getClass().getName()).orElse("none") + + ", renderingEngine=" + getRenderingEngine().getClass().getName() + + "]"; + } + /** * Ensure that mandatory classes (ie classes XWiki needs to work properly) exist and create them * if they don't exist. diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java index 42e6229c1..6429e4f4e 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/hibernate/HibernateAttachmentVersioningStore.java @@ -24,7 +24,6 @@ import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.Component; -import com.celements.execution.XWikiExecutionProp; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiAttachmentArchive; @@ -61,7 +60,7 @@ public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, bool try { final XWikiAttachmentArchive archive = new XWikiAttachmentArchive(); archive.setAttachment(attachment); - var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + var wiki = attachment.getWikiReference(); executeRead(wiki, bTransaction, session -> { try { session.load(archive, archive.getId()); @@ -83,7 +82,7 @@ public XWikiAttachmentArchive loadArchive(final XWikiAttachment attachment, bool @Override public void saveArchive(final XWikiAttachmentArchive archive, boolean bTransaction) throws XWikiException { - var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + var wiki = archive.getAttachment().getWikiReference(); executeWrite(wiki, bTransaction, session -> { session.saveOrUpdate(archive); return null; @@ -94,7 +93,7 @@ public void saveArchive(final XWikiAttachmentArchive archive, boolean bTransacti public void deleteArchive(final XWikiAttachment attachment, boolean bTransaction) throws XWikiException { try { - var wiki = getEContext().get(XWikiExecutionProp.WIKI).orElseThrow(); + var wiki = attachment.getWikiReference(); executeWrite(wiki, bTransaction, session -> { XWikiAttachmentArchive archive = new XWikiAttachmentArchive(); archive.setAttachment(attachment);