diff --git a/xwiki-platform-core/pom.xml b/xwiki-platform-core/pom.xml index 7a6071500d646ade1b9df9625f0cccaed9ebaacd..dd537a0743c2ffd20735fbb7656913a5884362bf 100644 --- a/xwiki-platform-core/pom.xml +++ b/xwiki-platform-core/pom.xml @@ -575,6 +575,7 @@ <module>xwiki-platform-uiextension</module> <module>xwiki-platform-url</module> <module>xwiki-platform-velocity</module> + <module>xwiki-platform-vfs</module> <module>xwiki-platform-watchlist</module> <module>xwiki-platform-web</module> <module>xwiki-platform-webdav</module> diff --git a/xwiki-platform-core/xwiki-platform-vfs/pom.xml b/xwiki-platform-core/xwiki-platform-vfs/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..17dafdcacb42d567db737e72478290b87f56e8ca --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/pom.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + * 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. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-core</artifactId> + <version>7.4-SNAPSHOT</version> + </parent> + <artifactId>xwiki-platform-vfs</artifactId> + <name>XWiki Platform - VFS API</name> + <description>VFS API</description> + <properties> + <xwiki.jacoco.instructionRatio>0.70</xwiki.jacoco.instructionRatio> + </properties> + <dependencies> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-resource-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-url-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.xwiki.platform</groupId> + <artifactId>xwiki-platform-oldcore</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.xwiki.commons</groupId> + <artifactId>xwiki-commons-script</artifactId> + <version>${commons.version}</version> + </dependency> + <dependency> + <groupId>net.java.truevfs</groupId> + <artifactId>truevfs-profile-default</artifactId> + <version>0.11.0</version> + <type>pom</type> + </dependency> + <!-- Test Dependencies --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/AbstractContentResourceReferenceHandler.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/AbstractContentResourceReferenceHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..f0a15efac95b04d1cdc7cfc29b70246d662ebca3 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/AbstractContentResourceReferenceHandler.java @@ -0,0 +1,73 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.io.BufferedInputStream; +import java.io.InputStream; + +import javax.inject.Inject; + +import org.apache.commons.io.IOUtils; +import org.apache.tika.Tika; +import org.xwiki.container.Container; +import org.xwiki.container.Response; +import org.xwiki.resource.AbstractResourceReferenceHandler; +import org.xwiki.resource.ResourceReferenceHandlerException; +import org.xwiki.resource.ResourceType; + +/** + * Helper to implement {@link org.xwiki.resource.ResourceReferenceHandler} components that return content to the + * Container's output stream. + * + * @version $Id$ + * @since 7.4M2 + */ +public abstract class AbstractContentResourceReferenceHandler extends AbstractResourceReferenceHandler<ResourceType> +{ + @Inject + private Container container; + + /** + * Used to determine the Content Type of the requested resource files. + */ + private Tika tika = new Tika(); + + protected void serveResource(String resourceName, InputStream resourceStream) + throws ResourceReferenceHandlerException + { + // Make sure the resource stream supports mark & reset which is needed in order be able to detect the + // content type without affecting the stream (Tika may need to read a few bytes from the start of the + // stream, in which case it will mark & reset the stream). + InputStream markResetSupportingStream = resourceStream; + if (!resourceStream.markSupported()) { + markResetSupportingStream = new BufferedInputStream(resourceStream); + } + + try { + Response response = this.container.getResponse(); + response.setContentType(this.tika.detect(markResetSupportingStream, resourceName)); + IOUtils.copy(markResetSupportingStream, response.getOutputStream()); + } catch (Exception e) { + throw new ResourceReferenceHandlerException(String.format("Failed to read resource [%s]", resourceName), e); + } finally { + IOUtils.closeQuietly(markResetSupportingStream); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReference.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReference.java new file mode 100644 index 0000000000000000000000000000000000000000..d6ed67327cbe230bd7c92c37a7fd4fa5cabd4dc3 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReference.java @@ -0,0 +1,139 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.xwiki.resource.AbstractResourceReference; +import org.xwiki.resource.ResourceType; +import org.xwiki.text.XWikiToStringBuilder; + +/** + * Represents a reference to a VFS resource. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsResourceReference extends AbstractResourceReference +{ + /** + * Represents a VFS Resource Type. + */ + public static final ResourceType TYPE = new ResourceType("vfs"); + + private static final char RESOURCE_PATH_SEPARATOR = '/'; + + private URI uri; + + private List<String> pathSegments; + + /** + * @param uri the URI pointing to the archive (without the path inside the archive), + * e.g. {@code attach:space.page@attachment} + * @param pathSegments see {@link #getPathSegments()} + */ + public VfsResourceReference(URI uri, List<String> pathSegments) + { + setType(TYPE); + this.uri = uri; + this.pathSegments = new ArrayList<>(pathSegments); + } + + /** + * @return the URI to the VFS (e.g. {@code attach:space.page@file.zip}, {@code http://server/path/to/zip}) + */ + public URI getURI() + { + return this.uri; + } + + /** + * @return the list of segments pointing to the relative location of a resource in the VFS (e.g. {@code {"some", + * "directory", "file.txt"}} for {@code some/directory/file.txt} + */ + public List<String> getPathSegments() + { + return this.pathSegments; + } + + /** + * @return the String representation with "/" separating each VFS path segment, e.g. {@code some/directory/file.txt} + */ + public String getPath() + { + return StringUtils.join(getPathSegments(), RESOURCE_PATH_SEPARATOR); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(7, 7) + .append(getURI()) + .append(getPathSegments()) + .append(getType()) + .append(getParameters()) + .toHashCode(); + } + + @Override + public boolean equals(Object object) + { + if (object == null) { + return false; + } + if (object == this) { + return true; + } + if (object.getClass() != getClass()) { + return false; + } + VfsResourceReference rhs = (VfsResourceReference) object; + return new EqualsBuilder() + .append(getURI(), rhs.getURI()) + .append(getPathSegments(), rhs.getPathSegments()) + .append(getType(), rhs.getType()) + .append(getParameters(), rhs.getParameters()) + .isEquals(); + } + + @Override + public String toString() + { + ToStringBuilder builder = new XWikiToStringBuilder(this); + builder.append("uri", getURI()); + builder.append("path", getPath()); + builder.append("parameters", getParameters()); + return builder.toString(); + } + + /** + * @return the resource reference as a URI + */ + public URI toURI() + { + return URI.create(String.format("%s/%s", getURI().toString(), getPath())); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceHandler.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..7fba7c26dd72593590c86b3464bc10ff2529838e --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceHandler.java @@ -0,0 +1,136 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.component.manager.ComponentLookupException; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.component.phase.Initializable; +import org.xwiki.component.phase.InitializationException; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.resource.ResourceReference; +import org.xwiki.resource.ResourceReferenceHandlerChain; +import org.xwiki.resource.ResourceReferenceHandlerException; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.resource.ResourceType; +import org.xwiki.resource.SerializeResourceReferenceException; +import org.xwiki.resource.UnsupportedResourceReferenceException; +import org.xwiki.vfs.internal.attach.AttachDriver; + +import net.java.truevfs.access.TArchiveDetector; +import net.java.truevfs.access.TConfig; +import net.java.truevfs.access.TPath; + +/** + * Handles VFS Resource References by outputting in the Container's response the resource pointed to in the archive + * file defined in the URL. + * + * @version $Id$ + * @see VfsResourceReferenceResolver for the URL format handled + * @since 7.4M2 + */ +@Component +@Named("vfs") +@Singleton +public class VfsResourceReferenceHandler extends AbstractContentResourceReferenceHandler + implements Initializable +{ + @Inject + @Named("context") + private Provider<ComponentManager> componentManagerProvider; + + @Override + public List<ResourceType> getSupportedResourceReferences() + { + return Arrays.asList(VfsResourceReference.TYPE); + } + + @Override + public void initialize() throws InitializationException + { + // Register our Attach VFS Driver and inject a Component Manager in it. + TConfig config = TConfig.current(); + // Note: Make sure we add our own Archive Detector to the existing Detector so that all archive formats + // supported by TrueVFS are handled properly. + config.setArchiveDetector(new TArchiveDetector(config.getArchiveDetector(), "attach", + new AttachDriver(this.componentManagerProvider.get()))); + } + + @Override + public void handle(ResourceReference resourceReference, ResourceReferenceHandlerChain chain) + throws ResourceReferenceHandlerException + { + // This code only handles VFS Resource References. + VfsResourceReference vfsResourceReference = (VfsResourceReference) resourceReference; + + // Extract the asked resource from inside the zip and return its content for display. + try { + // We need to convert the VFS Resource Reference into a hierarchical URI supported by TrueVFS + URI trueVFSURI = convertResourceReference(vfsResourceReference); + + // We use TrueVFS. This line will automatically use the VFS Driver that matches the scheme passed in the URI + Path path = new TPath(trueVFSURI); + try (InputStream in = Files.newInputStream(path)) { + List<String> pathSegments = vfsResourceReference.getPathSegments(); + serveResource(pathSegments.get(pathSegments.size() - 1), in); + } + } catch (Exception e) { + throw new ResourceReferenceHandlerException( + String.format("Failed to extract resource [%s]", vfsResourceReference), e); + } + + // Be a good citizen, continue the chain, in case some lower-priority Handler has something to do for this + // Resource Reference. + chain.handleNext(vfsResourceReference); + } + + private URI convertResourceReference(VfsResourceReference reference) throws ResourceReferenceHandlerException + { + URI resultURI; + + try { + ResourceReferenceSerializer<VfsResourceReference, URI> serializer = + this.componentManagerProvider.get().getInstance(new DefaultParameterizedType(null, + ResourceReferenceSerializer.class, VfsResourceReference.class, URI.class), + String.format("truevfs/%s", reference.getURI().getScheme())); + resultURI = serializer.serialize(reference); + } catch (ComponentLookupException e) { + // No serializer exist, we just don't perform any conversion! + resultURI = reference.toURI(); + } catch (SerializeResourceReferenceException | UnsupportedResourceReferenceException e) { + throw new ResourceReferenceHandlerException( + String.format("Failed to convert VFS URI [%s] into a valid FS format", reference), e); + } + + return resultURI; + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceResolver.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..97158e554dee297ec36412c97193f2df9394b246 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceResolver.java @@ -0,0 +1,84 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.AttachmentReferenceResolver; +import org.xwiki.resource.CreateResourceReferenceException; +import org.xwiki.resource.ResourceType; +import org.xwiki.resource.UnsupportedResourceReferenceException; +import org.xwiki.url.ExtendedURL; +import org.xwiki.url.internal.AbstractResourceReferenceResolver; + +/** + * Transform VFS URLs into a typed Resource Reference. The URL format handled is {@code http://server/<servlet + * context>/vfs/<vfs reference as URI>/path/inside/zip}. For example: + * <ul> + * <li>{@code http://localhost:8080/xwiki/vfs/encoded(attach:space.page@attachment)/some/path/file.txt}.</li> + * <li>{@code http://localhost:8080/xwiki/vfs/encoded(http://server/path/to/zip)/some/path/file.txt}.</li> + * <li>{@code http://localhost:8080/xwiki/vfs/encoded(file://server/path/to/zip)/some/path/file.txt}.</li> + * </ul> + * + * @version $Id$ + * @since 7.4M2 + */ +@Component +@Named("vfs") +@Singleton +public class VfsResourceReferenceResolver extends AbstractResourceReferenceResolver +{ + @Inject + @Named("current") + private AttachmentReferenceResolver<String> referenceResolver; + + @Override + public VfsResourceReference resolve(ExtendedURL extendedURL, ResourceType resourceType, + Map<String, Object> parameters) throws CreateResourceReferenceException, UnsupportedResourceReferenceException + { + List<String> segments = extendedURL.getSegments(); + + // First segment is the url-encoded VFS reference, defined as URI + URI vfsUri; + try { + vfsUri = new URI(segments.get(0)); + } catch (URISyntaxException e) { + throw new CreateResourceReferenceException( + String.format("Invalid VFS URI [%s] for URL [%s]", segments.get(0), extendedURL)); + } + + // Other segments are the path to the archive resource + List<String> vfsPathSegments = new ArrayList<>(segments); + vfsPathSegments.remove(0); + + VfsResourceReference vfsReference = new VfsResourceReference(vfsUri, vfsPathSegments); + copyParameters(extendedURL, vfsReference); + return vfsReference; + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializer.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..8e30cea864a260b39f253dff89f65aed369b1b0a --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializer.java @@ -0,0 +1,72 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.resource.SerializeResourceReferenceException; +import org.xwiki.resource.UnsupportedResourceReferenceException; +import org.xwiki.url.ExtendedURL; +import org.xwiki.url.URLNormalizer; + +/** + * Converts a {@link VfsResourceReference} into a relative {@link ExtendedURL} (with the Context Path added). + * + * @version $Id$ + * @since 7.4M2 + */ +@Component +@Singleton +public class VfsResourceReferenceSerializer + implements ResourceReferenceSerializer<VfsResourceReference, ExtendedURL> +{ + @Inject + @Named("contextpath") + private URLNormalizer<ExtendedURL> extendedURLNormalizer; + + @Override + public ExtendedURL serialize(VfsResourceReference resourceReference) + throws SerializeResourceReferenceException, UnsupportedResourceReferenceException + { + List<String> segments = new ArrayList<>(); + + // Add the resource type segment. + segments.add("vfs"); + + // Add the VFS URI part + segments.add(resourceReference.getURI().toString()); + + // Add the VFS path + segments.addAll(resourceReference.getPathSegments()); + + // Add all optional parameters + ExtendedURL extendedURL = new ExtendedURL(segments, resourceReference.getParameters()); + + // Normalize the URL to add the Context Path since we want a full relative URL to be returned. + return this.extendedURLNormalizer.normalize(extendedURL); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachController.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachController.java new file mode 100644 index 0000000000000000000000000000000000000000..215f55ae05651d0702bc5793a9cde9c91ac7fc45 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachController.java @@ -0,0 +1,146 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.io.IOException; + +import org.xwiki.component.manager.ComponentManager; + +import net.java.truecommons.cio.Entry; +import net.java.truecommons.cio.Entry.Access; +import net.java.truecommons.cio.Entry.Type; +import net.java.truecommons.cio.InputSocket; +import net.java.truecommons.cio.OutputSocket; +import net.java.truecommons.shed.BitField; +import net.java.truevfs.kernel.spec.FsAbstractController; +import net.java.truevfs.kernel.spec.FsAccessOption; +import net.java.truevfs.kernel.spec.FsController; +import net.java.truevfs.kernel.spec.FsModel; +import net.java.truevfs.kernel.spec.FsNodeName; +import net.java.truevfs.kernel.spec.FsNodePath; +import net.java.truevfs.kernel.spec.FsReadOnlyFileSystemException; +import net.java.truevfs.kernel.spec.FsSyncOption; + +import static net.java.truecommons.cio.Entry.Access.READ; +import static net.java.truecommons.cio.Entry.Type.FILE; + +/** + * TrueVFS Controller for the Attach driver. + * + * @version $Id$ + * @since 7.4M2 + */ +public class AttachController extends FsAbstractController +{ + private static final BitField<Access> READ_ONLY = BitField.of(READ); + + private final AttachDriver driver; + + private ComponentManager componentManager; + + AttachController(AttachDriver driver, FsModel model, ComponentManager componentManager) + { + super(model); + this.driver = driver; + this.componentManager = componentManager; + } + + private AttachNode newEntry(FsNodeName name) + { + return new AttachNode(this, name); + } + + final FsNodePath resolve(FsNodeName name) + { + return getMountPoint().resolve(name); + } + + @Override + public FsController getParent() + { + return null; + } + + @Override + public AttachNode node(BitField<FsAccessOption> options, FsNodeName name) throws IOException + { + AttachNode entry = newEntry(name); + return entry.isType(FILE) ? entry : null; + } + + @Override + public void checkAccess(BitField<FsAccessOption> options, FsNodeName name, BitField<Access> types) + throws IOException + { + // We only support READ at the moment + if (!types.isEmpty() && !READ_ONLY.equals(types)) { + throw new FsReadOnlyFileSystemException(getMountPoint()); + } + } + + @Override + public void setReadOnly(BitField<FsAccessOption> options, FsNodeName name) throws IOException + { + // All the Nodes (attachments) are already readonly, no need to set anything! + } + + @Override + public boolean setTime(BitField<FsAccessOption> options, FsNodeName name, BitField<Access> types, long value) + throws IOException + { + throw new FsReadOnlyFileSystemException(getMountPoint()); + } + + @Override + public InputSocket<?> input(BitField<FsAccessOption> options, FsNodeName name) + { + return newEntry(name).input(options); + } + + @Override + public OutputSocket<?> output(BitField<FsAccessOption> options, FsNodeName name, Entry template) + { + return newEntry(name).output(options, template); + } + + @Override + public void make(final BitField<FsAccessOption> options, final FsNodeName name, Type type, Entry template) + throws IOException + { + throw new FsReadOnlyFileSystemException(getMountPoint()); + } + + @Override + public void unlink(BitField<FsAccessOption> options, FsNodeName name) throws IOException + { + throw new FsReadOnlyFileSystemException(getMountPoint()); + } + + @Override + public void sync(BitField<FsSyncOption> options) + { + // Empty + } + + ComponentManager getComponentManager() + { + return this.componentManager; + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachDriver.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachDriver.java new file mode 100644 index 0000000000000000000000000000000000000000..605c97d8ae11dbe8e9e7f12dd9995811e072f095 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachDriver.java @@ -0,0 +1,53 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import org.xwiki.component.manager.ComponentManager; + +import net.java.truevfs.kernel.spec.FsController; +import net.java.truevfs.kernel.spec.FsDriver; +import net.java.truevfs.kernel.spec.FsManager; +import net.java.truevfs.kernel.spec.FsModel; + +/** + * TrueVFS Driver for archives attached to wiki pages as attachments. + * + * @version $Id$ + * @since 7.4M2 + */ +public class AttachDriver extends FsDriver +{ + private ComponentManager componentManager; + + /** + * @param componentManager the Component Manager used to retrieve all other components since a TrueVFS driver is + * not a Component and thus cannot have dependency-injection. Thus we need to pass the Component Manager. + */ + public AttachDriver(ComponentManager componentManager) + { + this.componentManager = componentManager; + } + + @Override + public FsController newController(FsManager manager, FsModel model, FsController parent) + { + return new AttachController(this, model, this.componentManager); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachInputSocket.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachInputSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..f6feb543de10911ef506b7d4b9129497860a2a7f --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachInputSocket.java @@ -0,0 +1,100 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.SeekableByteChannel; + +import javax.annotation.concurrent.NotThreadSafe; + +import net.java.truecommons.cio.AbstractInputSocket; +import net.java.truecommons.cio.Entry; +import net.java.truecommons.cio.IoBuffer; +import net.java.truecommons.cio.IoSockets; +import net.java.truecommons.cio.OutputSocket; +import net.java.truecommons.io.ReadOnlyChannel; +import net.java.truecommons.shed.BitField; +import net.java.truevfs.kernel.spec.FsAccessOption; + +/** + * TrueVFS input socket for the Attach Driver. + * + * @version $Id$ + * @since 7.4M2 + */ +@NotThreadSafe +public class AttachInputSocket extends AbstractInputSocket<AttachNode> +{ + private final AttachNode entry; + + AttachInputSocket(BitField<FsAccessOption> options, AttachNode entry) + { + this.entry = entry; + } + + @Override + public AttachNode target() + { + return entry; + } + + @Override + public InputStream stream(final OutputSocket<? extends Entry> peer) throws IOException + { + return entry.newInputStream(); + } + + @Override + public SeekableByteChannel channel(final OutputSocket<? extends Entry> peer) throws IOException + { + final IoBuffer buffer = entry.getPool().allocate(); + try { + IoSockets.copy(entry.input(), buffer.output()); + } catch (final Throwable ex) { + try { + buffer.release(); + } catch (final Throwable ex2) { + ex.addSuppressed(ex2); + } + throw ex; + } + final class BufferReadOnlyChannel extends ReadOnlyChannel + { + private boolean closed; + + BufferReadOnlyChannel() throws IOException + { + super(buffer.input().channel(peer)); + } + + @Override + public void close() throws IOException + { + if (!closed) { + channel.close(); + closed = true; + buffer.release(); + } + } + } + return new BufferReadOnlyChannel(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachNode.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachNode.java new file mode 100644 index 0000000000000000000000000000000000000000..0d674bbadbd1f20489f67910de5d708fd9a29296 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachNode.java @@ -0,0 +1,192 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.Set; + +import javax.annotation.concurrent.Immutable; + +import com.xpn.xwiki.doc.XWikiAttachment; + +import net.java.truecommons.cio.Entry; +import net.java.truecommons.cio.InputSocket; +import net.java.truecommons.cio.IoBufferPool; +import net.java.truecommons.cio.IoEntry; +import net.java.truecommons.cio.OutputSocket; +import net.java.truecommons.shed.BitField; +import net.java.truevfs.kernel.spec.FsAbstractNode; +import net.java.truevfs.kernel.spec.FsAccessOption; +import net.java.truevfs.kernel.spec.FsNodeName; +import net.java.truevfs.kernel.spec.FsReadOnlyFileSystemException; +import net.java.truevfs.kernel.spec.sl.IoBufferPoolLocator; + +import static net.java.truecommons.cio.Entry.Access.READ; +import static net.java.truecommons.cio.Entry.Access.WRITE; +import static net.java.truecommons.cio.Entry.Size.DATA; +import static net.java.truecommons.cio.Entry.Type.FILE; +import static net.java.truevfs.kernel.spec.FsAccessOptions.NONE; + +/** + * Represents a TrueVFS Node inside an archive located in an attachment in a wiki page. + * + * @version $Id$ + * @since 7.4M2 + */ +@Immutable +public class AttachNode extends FsAbstractNode implements IoEntry<AttachNode> +{ + private final URI uri; + + private final AttachController controller; + + private final String name; + + private XWikiModelNode xwikiModelNode; + + AttachNode(final AttachController controller, final FsNodeName name) + { + assert null != controller; + this.controller = controller; + this.name = name.toString(); + this.uri = controller.resolve(name).getUri(); + this.xwikiModelNode = new XWikiModelNode(controller, name); + } + + IoBufferPool getPool() + { + return IoBufferPoolLocator.SINGLETON.get(); + } + + protected InputStream newInputStream() throws IOException + { + // Return the attachment as an input stream + try { + return this.xwikiModelNode.getAttachment().getContentInputStream(this.xwikiModelNode.getXWikiContext()); + } catch (Exception e) { + throw new IOException(String.format( + "Failed to get attachment content for attachment [%s] in URI [%s]", this.name, this.uri), e); + } + } + + protected OutputStream newOutputStream() throws IOException + { + throw new FsReadOnlyFileSystemException(this.controller.getMountPoint()); + } + + @Override + public String getName() + { + return name; + } + + @Override + public BitField<Type> getTypes() + { + // All Attach Driver Nodes are of type File, except if the Node doesn't exist, in which case we should return + // NO_TYPES + try { + return this.xwikiModelNode.getAttachment() != null ? FILE_TYPE : NO_TYPES; + } catch (IOException e) { + return NO_TYPES; + } + } + + @Override + public boolean isType(final Type type) + { + return type == FILE && getTypes().is(FILE); + } + + @Override + public long getSize(final Size type) + { + if (DATA != type) { + return UNKNOWN; + } + // Get the size of the attachment in bytes + try { + XWikiAttachment attachment = this.xwikiModelNode.getAttachment(); + return attachment.getContentSize(this.xwikiModelNode.getXWikiContext()); + } catch (Exception e) { + return UNKNOWN; + } + } + + @Override + @SuppressWarnings("deprecation") + public long getTime(Access type) + { + if (WRITE != type) { + return UNKNOWN; + } + // Get the last modified time for the attachment + try { + XWikiAttachment attachment = this.xwikiModelNode.getAttachment(); + return attachment.getDate().getTime(); + } catch (IOException e) { + return UNKNOWN; + } + } + + @Override + public Boolean isPermitted(final Access type, final Entity entity) + { + if (READ != type) { + return null; + } + return true; + } + + @Override + public Set<String> getMembers() + { + return null; + } + + @Override + public final InputSocket<AttachNode> input() + { + return input(NONE); + } + + /** + * @param options the options for accessing the file system node + * @return An input socket for reading this entry + */ + protected InputSocket<AttachNode> input(BitField<FsAccessOption> options) + { + return new AttachInputSocket(options, this); + } + + @Override + public final OutputSocket<AttachNode> output() + { + return output(NONE, null); + } + + protected OutputSocket<AttachNode> output(BitField<FsAccessOption> options, Entry template) + { + return new AttachOutputSocket(options, this, template); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachOutputSocket.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachOutputSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..26526fcde8019fdecec4af5a7b871a546d37586f --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/AttachOutputSocket.java @@ -0,0 +1,60 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.annotation.concurrent.NotThreadSafe; + +import net.java.truecommons.cio.AbstractOutputSocket; +import net.java.truecommons.cio.Entry; +import net.java.truecommons.cio.InputSocket; +import net.java.truecommons.shed.BitField; +import net.java.truevfs.kernel.spec.FsAccessOption; + +/** + * TrueVFS output socket for the Attach Driver. + * + * @version $Id$ + * @since 7.4M2 + */ +@NotThreadSafe +public class AttachOutputSocket extends AbstractOutputSocket<AttachNode> +{ + private final AttachNode entry; + + AttachOutputSocket(BitField<FsAccessOption> options, AttachNode entry, Entry template) + { + this.entry = entry; + } + + @Override + public AttachNode target() + { + return entry; + } + + @Override + public OutputStream stream(final InputSocket<? extends Entry> peer) throws IOException + { + return entry.newOutputStream(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/URIVfsResourceReferenceSerializer.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/URIVfsResourceReferenceSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..79c476f1261a1f9ceaea4d5305a9292e545a952a --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/URIVfsResourceReferenceSerializer.java @@ -0,0 +1,72 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.net.URI; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.AttachmentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.resource.SerializeResourceReferenceException; +import org.xwiki.resource.UnsupportedResourceReferenceException; +import org.xwiki.vfs.internal.VfsResourceReference; + +/** + * Converts a {@link VfsResourceReference} into a {@link URI} in a format compatible with TrueVFS. Specifically TrueVFS + * requires a hierarchical URI. We make the following type of transformation: + * <ul> + * <li>Example input: {@code attach:wiki:space.page@attachment/path/inside/archive}</li> + * <li>Example output: {@code attach://wiki:space.page/attachment/path/inside/archive}</li> + * </ul> + * + * @version $Id$ + * @since 7.4M2 + */ +@Component +@Named("truevfs/attach") +@Singleton +public class URIVfsResourceReferenceSerializer implements ResourceReferenceSerializer<VfsResourceReference, URI> +{ + @Inject + @Named("current") + private AttachmentReferenceResolver<String> attachmentResolver; + + @Inject + private EntityReferenceSerializer<String> documentSerializer; + + @Override + public URI serialize(VfsResourceReference reference) + throws SerializeResourceReferenceException, UnsupportedResourceReferenceException + { + AttachmentReference attachmentReference = + this.attachmentResolver.resolve(reference.getURI().getSchemeSpecificPart()); + String scheme = reference.getURI().getScheme(); + String documentRefefenceString = this.documentSerializer.serialize(attachmentReference.getDocumentReference()); + + return URI.create(String.format("%s://%s/%s/%s", scheme, documentRefefenceString, attachmentReference.getName(), + reference.getPath())); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/XWikiModelNode.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/XWikiModelNode.java new file mode 100644 index 0000000000000000000000000000000000000000..b553c350f2bed514fa74e5bc63077f15ab0594d5 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/internal/attach/XWikiModelNode.java @@ -0,0 +1,154 @@ +/* + * 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 org.xwiki.vfs.internal.attach; + +import java.io.IOException; +import java.net.URI; + +import javax.inject.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.doc.XWikiAttachment; +import com.xpn.xwiki.doc.XWikiDocument; + +import net.java.truevfs.kernel.spec.FsNodeName; + +/** + * Decorator for an {@code AttachNode} to provide all XWiki Model API to get the Attachment corresponding to the VFS + * node. + * + * @version $Id$ + * @since 7.4M2 + */ +public class XWikiModelNode +{ + private static final Logger LOGGER = LoggerFactory.getLogger(XWikiModelNode.class); + + private ComponentManager componentManager; + + private DocumentReference reference; + + private XWikiAttachment attachment; + + private XWikiContext xcontext; + + private URI uri; + + private String name; + + private ContextualAuthorizationManager authorizationManager; + + XWikiModelNode(AttachController controller, FsNodeName name) + { + this.name = name.toString(); + this.uri = controller.resolve(name).getUri(); + this.componentManager = controller.getComponentManager(); + } + + private ComponentManager getComponentManager() + { + return this.componentManager; + } + + /** + * @return the reference to the Document holding the archive attachment + * @throws IOException when an error accessing the Document occurs + */ + public DocumentReference getDocumentReference() throws IOException + { + if (this.reference == null) { + try { + // Use a default resolver (and not a current one) since we don't have any context, we're in a new + // request. + DocumentReferenceResolver<String> documentReferenceResolver = + getComponentManager().getInstance(DocumentReferenceResolver.TYPE_STRING); + this.reference = documentReferenceResolver.resolve(this.uri.getAuthority()); + } catch (Exception e) { + throw new IOException( + String.format("Failed to compute Document reference for [%s]", this.uri), e); + } + } + return this.reference; + } + + /** + * @return the current XWiki Context + * @throws IOException if an error occurs retrieving the context + */ + public XWikiContext getXWikiContext() throws IOException + { + if (this.xcontext == null) { + try { + Provider<XWikiContext> xcontextProvider = getComponentManager().getInstance( + new DefaultParameterizedType(null, Provider.class, XWikiContext.class)); + this.xcontext = xcontextProvider.get(); + } catch (Exception e) { + throw new IOException(String.format("Failed to get XWiki Context for [%s]", this.uri), e); + } + } + return this.xcontext; + } + + /** + * @return the archive attachment itself + * @throws IOException when an error accessing the Attachment occurs or if the current user doesn't have VIEW + * permission on the Document to which it's attached to + */ + public XWikiAttachment getAttachment() throws IOException + { + if (this.attachment == null) { + // Note that we check permission only once per Node for performance reason. As a consequence it's possible + // that a user who had permission at point A, and who's been denied it may still access the Node for some + // time. + checkViewPermission(); + try { + XWikiDocument document = getXWikiContext().getWiki().getDocument( + getDocumentReference(), getXWikiContext()); + this.attachment = document.getAttachment(this.name); + } catch (Exception e) { + throw new IOException(String.format("Failed to get Attachment for [%s]", this.uri), e); + } + } + return this.attachment; + } + + private void checkViewPermission() throws IOException + { + try { + if (this.authorizationManager == null) { + this.authorizationManager = getComponentManager().getInstance(ContextualAuthorizationManager.class); + } + } catch (Exception e) { + throw new IOException(String.format("Failed to check permission for [%s]", this.uri), e); + } + if (!this.authorizationManager.hasAccess(Right.VIEW, getDocumentReference())) { + throw new IOException(String.format("No View permission for document [%s]", getDocumentReference())); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/script/VfsScriptService.java b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/script/VfsScriptService.java new file mode 100644 index 0000000000000000000000000000000000000000..83e087c87e388613f5cdb85e4cca86df6192d176 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/java/org/xwiki/vfs/script/VfsScriptService.java @@ -0,0 +1,70 @@ +/* + * 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 org.xwiki.vfs.script; + +import java.net.URI; +import java.util.Arrays; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.xwiki.component.annotation.Component; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.script.service.ScriptService; +import org.xwiki.url.ExtendedURL; +import org.xwiki.vfs.internal.VfsResourceReference; + +/** + * Offers scripting APIs for the VFS module. + * + * @version $Id$ + * @since 7.4M2 + */ +@Component +@Named("vfs") +@Singleton +//@Unstable +public class VfsScriptService implements ScriptService +{ + @Inject + private ResourceReferenceSerializer<VfsResourceReference, ExtendedURL> serializer; + + /** + * Generate a VFS URL to access a resource inside an archive. + * + * @param resourceReference the string representation of a VFS resource reference which defines the location of an + * archive. For example {@code attach:space.page@my.zip}. + * @param pathInArchive the path of the resource inside the archive for which to generate a URL for. For example + * {@code /some/path/in/archive/test.txt}. + * @return a URL that can be used to access the content of a file inside an archive (ZIP, EAR, TAR.GZ, etc) + */ + public String url(String resourceReference, String pathInArchive) + { + try { + VfsResourceReference vfsResourceReference = new VfsResourceReference( + new URI(resourceReference), Arrays.asList(StringUtils.split(pathInArchive, "/"))); + return this.serializer.serialize(vfsResourceReference).toString(); + } catch (Exception e) { + return null; + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-vfs/src/main/resources/META-INF/components.txt new file mode 100644 index 0000000000000000000000000000000000000000..cfdb792343d452cd40d0e6d6e7480eafa6dfb12c --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/main/resources/META-INF/components.txt @@ -0,0 +1,5 @@ +org.xwiki.vfs.internal.VfsResourceReferenceResolver +org.xwiki.vfs.internal.VfsResourceReferenceSerializer +org.xwiki.vfs.internal.VfsResourceReferenceHandler +org.xwiki.vfs.script.VfsScriptService +org.xwiki.vfs.internal.attach.URIVfsResourceReferenceSerializer \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceHandlerTest.java b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..64bb80d6cef540cda6c5fa6d4d325dbd17dec716 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceHandlerTest.java @@ -0,0 +1,181 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.inject.Provider; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.container.Container; +import org.xwiki.container.Response; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.resource.ResourceReferenceHandlerChain; +import org.xwiki.resource.ResourceReferenceHandlerException; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.test.mockito.MockitoComponentMockingRule; + +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.doc.XWikiAttachment; +import com.xpn.xwiki.doc.XWikiDocument; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link VfsResourceReferenceHandler}. + * <p/> + * Note: We use a different URI for the various unit tests in this class since otherwise they're cached by + * TrueVFS. TODO: Find a way to flush TrueVFS caches. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsResourceReferenceHandlerTest +{ + @Rule + public MockitoComponentMockingRule<VfsResourceReferenceHandler> mocker = + new MockitoComponentMockingRule<>(VfsResourceReferenceHandler.class); + + private ByteArrayOutputStream baos; + + private DocumentReference documentReference; + + private VfsResourceReference reference; + + private void setUp(String wikiName, String spaceName, String pageName, String attachmentName, List<String> path) + throws Exception + { + Provider<ComponentManager> componentManagerProvider = this.mocker.registerMockComponent( + new DefaultParameterizedType(null, Provider.class, ComponentManager.class), "context"); + when(componentManagerProvider.get()).thenReturn(this.mocker); + + String attachmentReferenceAsString = + String.format("attach:%s:%s.%s@%s", wikiName, spaceName, pageName, attachmentName); + this.reference = new VfsResourceReference(URI.create(attachmentReferenceAsString), path); + + ResourceReferenceSerializer<VfsResourceReference, URI> serializer = mocker.registerMockComponent( + new DefaultParameterizedType(null, ResourceReferenceSerializer.class, VfsResourceReference.class, + URI.class), "truevfs/attach"); + String truevfsURIFragment = String.format("attach://%s:%s.%s/%s/%s", wikiName, spaceName, pageName, + attachmentName, StringUtils.join(path, '/')); + when(serializer.serialize(this.reference)).thenReturn(URI.create(truevfsURIFragment)); + + Provider<XWikiContext> xwikiContextProvider = mocker.registerMockComponent( + new DefaultParameterizedType(null, Provider.class, XWikiContext.class)); + XWikiContext xcontext = mock(XWikiContext.class); + when(xwikiContextProvider.get()).thenReturn(xcontext); + + XWiki xwiki = mock(XWiki.class); + when(xcontext.getWiki()).thenReturn(xwiki); + + DocumentReferenceResolver<String> documentReferenceResolver = + mocker.registerMockComponent(DocumentReferenceResolver.TYPE_STRING); + this.documentReference = new DocumentReference(wikiName, Arrays.asList(spaceName), pageName); + String documentReferenceAsString = String.format("%s:%s.%s", wikiName, spaceName, pageName); + when(documentReferenceResolver.resolve(documentReferenceAsString)).thenReturn(this.documentReference); + + XWikiDocument document = mock(XWikiDocument.class); + when(xwiki.getDocument(this.documentReference, xcontext)).thenReturn(document); + + XWikiAttachment attachment = mock(XWikiAttachment.class); + when(document.getAttachment(attachmentName)).thenReturn(attachment); + + when(attachment.getDate()).thenReturn(new Date()); + when(attachment.getContentSize(xcontext)).thenReturn(1000); + + when(attachment.getContentInputStream(xcontext)).thenReturn( + createZipInputStream(StringUtils.join(path, '/'), "success!")); + + Container container = this.mocker.getInstance(Container.class); + Response response = mock(Response.class); + when(container.getResponse()).thenReturn(response); + + this.baos = new ByteArrayOutputStream(); + when(response.getOutputStream()).thenReturn(this.baos); + } + + @Test + public void handleWhenNotPermitted() throws Exception + { + setUp("wiki2", "space2", "page2", "test.zip", Arrays.asList("test.txt")); + + // Disallow access to the document + ContextualAuthorizationManager authorizationManager = + this.mocker.registerMockComponent(ContextualAuthorizationManager.class); + when(authorizationManager.hasAccess(Right.VIEW, this.documentReference)).thenReturn(false); + + try { + this.mocker.getComponentUnderTest().handle(this.reference, mock(ResourceReferenceHandlerChain.class)); + fail("Should have raised an exception here"); + } catch (ResourceReferenceHandlerException expected) { + assertEquals("Failed to extract resource [uri = [attach:wiki2:space2.page2@test.zip], path = [test.txt], " + + "parameters = []]", expected.getMessage()); + // TODO: Find a better place to perform the permission check so that the error reported is better + assertEquals("NoSuchFileException: test.zip", ExceptionUtils.getRootCauseMessage(expected)); + } + } + + @Test + public void handleOk() throws Exception + { + setUp("wiki1", "space1", "page1", "test.zip", Arrays.asList("test.txt")); + + // Allow access to the document + ContextualAuthorizationManager authorizationManager = + this.mocker.registerMockComponent(ContextualAuthorizationManager.class); + when(authorizationManager.hasAccess(Right.VIEW, this.documentReference)).thenReturn(true); + + assertEquals(Arrays.asList(VfsResourceReference.TYPE), + this.mocker.getComponentUnderTest().getSupportedResourceReferences()); + this.mocker.getComponentUnderTest().handle(this.reference, mock(ResourceReferenceHandlerChain.class)); + + assertEquals("success!", this.baos.toString()); + } + + private InputStream createZipInputStream(String fileName, String content) throws Exception + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ZipOutputStream zos = new ZipOutputStream(baos)) { + ZipEntry entry = new ZipEntry(fileName); + zos.putNextEntry(entry); + zos.write(content.getBytes()); + zos.closeEntry(); + } + return new ByteArrayInputStream(baos.toByteArray()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceResolverTest.java b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceResolverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..72d2b9ef1094b9584915169d913d01ce66255eaf --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceResolverTest.java @@ -0,0 +1,60 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.url.ExtendedURL; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link VfsResourceReferenceResolver}. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsResourceReferenceResolverTest +{ + @Rule + public MockitoComponentMockingRule<VfsResourceReferenceResolver> mocker = + new MockitoComponentMockingRule<>(VfsResourceReferenceResolver.class); + + @Test + public void resolve() throws Exception + { + ExtendedURL extendedURL = new ExtendedURL( + Arrays.asList("attach:wiki:space.page@attachment", "path1", "path2", "test.txt"), + Collections.singletonMap("key", Arrays.asList("value"))); + + VfsResourceReference reference = this.mocker.getComponentUnderTest().resolve(extendedURL, + VfsResourceReference.TYPE, Collections.<String, Object>emptyMap()); + + VfsResourceReference expected = new VfsResourceReference(URI.create("attach:wiki:space.page@attachment"), + Arrays.asList("path1", "path2", "test.txt")); + expected.addParameter("key", "value"); + assertEquals(expected, reference); + } +} \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializerTest.java b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3cb4be9b341fbac8a052210bb347dd9b58be520a --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceSerializerTest.java @@ -0,0 +1,63 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.url.ExtendedURL; +import org.xwiki.url.URLNormalizer; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link VfsResourceReferenceSerializer}. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsResourceReferenceSerializerTest +{ + @Rule + public MockitoComponentMockingRule<VfsResourceReferenceSerializer> mocker = + new MockitoComponentMockingRule<>(VfsResourceReferenceSerializer.class); + + @Test + public void serialize() throws Exception + { + VfsResourceReference reference = new VfsResourceReference( + URI.create("attach:wiki:space.page@attachment"), Arrays.asList("path1", "path2", "test.txt")); + + ExtendedURL extendedURL = new ExtendedURL(Arrays.asList( + "vfs", "attach:wiki:space.page@attachment", "path1", "path2", "test.txt")); + + URLNormalizer<ExtendedURL> normalizer = this.mocker.registerMockComponent( + new DefaultParameterizedType(null, URLNormalizer.class, ExtendedURL.class), "contextpath"); + when(normalizer.normalize(extendedURL)).thenReturn(extendedURL); + + assertEquals("/vfs/attach%3Awiki%3Aspace.page%40attachment/path1/path2/test.txt", + this.mocker.getComponentUnderTest().serialize(reference).toString()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceTest.java b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a15690e9a906791968123ddd4d9ef77f19366eff --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/internal/VfsResourceReferenceTest.java @@ -0,0 +1,71 @@ +/* + * 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 org.xwiki.vfs.internal; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Unit tests for {@link VfsResourceReference}. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsResourceReferenceTest +{ + @Test + public void equality() + { + VfsResourceReference reference1 = + new VfsResourceReference(URI.create("scheme:specific"), Arrays.asList("a", "b")); + reference1.addParameter("key", "value"); + VfsResourceReference reference2 = + new VfsResourceReference(URI.create("scheme:specific"), Arrays.asList("a", "b")); + + assertNotEquals(reference1, reference2); + + reference2.addParameter("key", "value"); + assertEquals(reference1, reference2); + assertEquals(reference1.hashCode(), reference2.hashCode()); + } + + @Test + public void toURI() + { + VfsResourceReference reference = + new VfsResourceReference(URI.create("scheme:specific"), Arrays.asList("a", "b")); + + URI expected = URI.create("scheme:specific/a/b"); + assertEquals(expected, reference.toURI()); + } + + @Test + public void stringValue() + { + VfsResourceReference reference = + new VfsResourceReference(URI.create("scheme:specific"), Arrays.asList("a", "b")); + reference.addParameter("key", "value"); + assertEquals("uri = [scheme:specific], path = [a/b], parameters = [[key] = [[value]]]", reference.toString()); + } +} diff --git a/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/script/VfsScriptServiceTest.java b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/script/VfsScriptServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6232e410987f31dcbd81c4f48a97b3ac8dbadfa3 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-vfs/src/test/java/org/xwiki/vfs/script/VfsScriptServiceTest.java @@ -0,0 +1,62 @@ +/* + * 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 org.xwiki.vfs.script; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.resource.ResourceReferenceSerializer; +import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.url.ExtendedURL; +import org.xwiki.vfs.internal.VfsResourceReference; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link VfsScriptService}. + * + * @version $Id$ + * @since 7.4M2 + */ +public class VfsScriptServiceTest +{ + @Rule + public MockitoComponentMockingRule<VfsScriptService> mocker = + new MockitoComponentMockingRule<>(VfsScriptService.class); + + @Test + public void url() throws Exception + { + VfsResourceReference reference = new VfsResourceReference( + URI.create("attach:xwiki:space.page@attachment"), Arrays.asList("path1", "path2", "test.txt")); + + ResourceReferenceSerializer<VfsResourceReference, ExtendedURL> serializer = this.mocker.getInstance( + new DefaultParameterizedType(null, ResourceReferenceSerializer.class, VfsResourceReference.class, + ExtendedURL.class)); + when(serializer.serialize(reference)).thenReturn(new ExtendedURL(Arrays.asList("generated", "url"))); + + assertEquals("/generated/url", + this.mocker.getComponentUnderTest().url("attach:xwiki:space.page@attachment", "path1/path2/test.txt")); + } +}