This is an automated email from the ASF dual-hosted git repository.
elecharny pushed a commit to branch 1.2.X
in repository https://gitbox.apache.org/repos/asf/mina-ftpserver.git
The following commit(s) were added to refs/heads/1.2.X by this push:
new 4ff3bc9a o Applied Filip Cichy patch on in-memory filesystem with
minor modifications (mainly a missing @param, static order and a few NL) o
Bumped up dependencies and plugins o Added missing package-info
4ff3bc9a is described below
commit 4ff3bc9a7898f9a15dc13416d8defbd9ed97dcf6
Author: emmanuel lecharny <[email protected]>
AuthorDate: Wed Mar 4 05:52:15 2026 +0100
o Applied Filip Cichy patch on in-memory filesystem with minor
modifications (mainly a missing @param, static order and a few NL)
o Bumped up dependencies and plugins
o Added missing package-info
---
core/pom.xml | 4 +-
.../inmemoryfs/InMemoryFileSystemFactory.java | 60 +++
.../inmemoryfs/impl/InMemoryFileSystemView.java | 225 +++++++++
.../inmemoryfs/impl/InMemoryFtpFile.java | 534 +++++++++++++++++++++
.../filesystem/inmemoryfs/impl/package-info.java | 23 +
.../filesystem/inmemoryfs/package-info.java | 22 +
.../inmemory/impl/InMemoryFileSystemViewTest.java | 101 ++++
.../inmemory/impl/InMemoryFtpFileTest.java | 274 +++++++++++
examples/ftpserver-osgi-ftplet-service/pom.xml | 2 +-
examples/ftpserver-osgi-spring-service/pom.xml | 2 +-
ftplet-api/pom.xml | 2 +-
pom.xml | 15 +-
12 files changed, 1252 insertions(+), 12 deletions(-)
diff --git a/core/pom.xml b/core/pom.xml
index 598e7f44..d89aa62b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -50,7 +50,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>5.1.4</version>
+ <version>${maven-bundle-plugin-version}</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -84,7 +84,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
- <version>1.2.1</version>
+ <version>1.3.0</version>
<executions>
<execution>
<phase>generate-resources</phase>
diff --git
a/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/InMemoryFileSystemFactory.java
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/InMemoryFileSystemFactory.java
new file mode 100644
index 00000000..fd1280b6
--- /dev/null
+++
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/InMemoryFileSystemFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ftpserver.filesystem.inmemoryfs;
+
+import org.apache.ftpserver.filesystem.inmemoryfs.impl.InMemoryFileSystemView;
+import org.apache.ftpserver.ftplet.FileSystemFactory;
+import org.apache.ftpserver.ftplet.FileSystemView;
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.User;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Factory class for creating instances of an in-memory file system.
+ * <p>
+ * * This implementation provides a lightweight and non-persistent file system
+ * * stored entirely in memory, suitable for testing or temporary data
storage.
+ * </p>
+ */
+public class InMemoryFileSystemFactory implements FileSystemFactory {
+
+ private static final Map<String, InMemoryFileSystemView> filesystemMap =
new ConcurrentHashMap<>();
+
+ /**
+ * Create the appropriate user file system view.
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized FileSystemView createFileSystemView(User user) throws
FtpException {
+ Objects.requireNonNull(user);
+
+ if (filesystemMap.containsKey(user.getName())) {
+ return filesystemMap.get(user.getName());
+ }
+
+ InMemoryFileSystemView view = new InMemoryFileSystemView(user);
+ filesystemMap.put(user.getName(), view);
+
+ return view;
+ }
+}
diff --git
a/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFileSystemView.java
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFileSystemView.java
new file mode 100644
index 00000000..f585c96e
--- /dev/null
+++
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFileSystemView.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ftpserver.filesystem.inmemoryfs.impl;
+
+import org.apache.ftpserver.ftplet.FileSystemView;
+import org.apache.ftpserver.ftplet.FtpFile;
+import org.apache.ftpserver.ftplet.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * View representation of the in-memory file system.
+ * <p>
+ * Provides mechanisms to navigate and manage the structure of the in-memory
file system.
+ * </p>
+ *
+ * <p>
+ * This class is designed to support operations such as listing files,
managing directories,
+ * and accessing file properties within a non-persistent in-memory environment.
+ * </p>
+ */
+public class InMemoryFileSystemView implements FileSystemView {
+
+ public static final String PARENT_DIRECTORY = "..";
+ public static final String CURRENT_DIRECTORY = ".";
+ public static final String CURRENT = "./";
+ private static final Logger LOG =
LoggerFactory.getLogger(InMemoryFileSystemView.class);
+ private final InMemoryFtpFile homeDirectory;
+ private InMemoryFtpFile workingDirectory;
+
+ /**
+ * Default constructor for the factory.
+ * @param user The acting User
+ */
+ public InMemoryFileSystemView(User user) {
+ validateUser(user);
+ this.homeDirectory =
InMemoryFtpFile.createRoot(user.getHomeDirectory());
+ this.workingDirectory = homeDirectory;
+ }
+
+ /**
+ * validates whether the user is eligible to create an instance
+ */
+ private void validateUser(User user) {
+ if (user == null) {
+ throw new IllegalArgumentException("user cannot be null");
+ }
+
+ if (user.getHomeDirectory() == null) {
+ throw new IllegalArgumentException("User home directory cannot be
null");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FtpFile getHomeDirectory() {
+ return homeDirectory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FtpFile getWorkingDirectory() {
+ return workingDirectory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean changeWorkingDirectory(String path) {
+ LOG.debug("changing working directory from {} to path {}",
workingDirectory.getAbsolutePath(), path);
+
+ if (isPathNotMatchingCurrentWorkingDirPath(path)) {
+ InMemoryFtpFile mayBeWorkingDirectory;
+
+ if (isAbsolute(path)) {
+ mayBeWorkingDirectory = homeDirectory.find(path);
+ } else {
+ mayBeWorkingDirectory = workingDirectory.find(path);
+ }
+
+ if (mayBeWorkingDirectory.doesExist() &&
mayBeWorkingDirectory.isDirectory()) {
+ workingDirectory = mayBeWorkingDirectory;
+ LOG.debug("changed working directory to {}",
workingDirectory.getAbsolutePath());
+ return true;
+ }
+
+ LOG.info("changing working directory failed - cannot find file by
path {}", path);
+
+ return false;
+ }
+
+ //path is same as current working directory
+ return true;
+ }
+
+ /**
+ *
+ * @return true if path is absolute, otherwise false
+ */
+ private boolean isPathNotMatchingCurrentWorkingDirPath(String dir) {
+ return !workingDirectory.getAbsolutePath().equals(dir);
+ }
+
+ /**
+ * Retrieves a file or directory within the in-memory file system based on
the given path.
+ * <p>
+ * The method resolves paths using the following rules:
+ * <ul>
+ * <li>If the path starts with "../", it navigates to the parent
directory.</li>
+ * <li>If the path starts with "./", it remains in the current
directory.</li>
+ * <li>Otherwise, it searches for the file or directory in the current
directory.</li>
+ * </ul>
+ * </p>
+ *
+ * @param path the path to the file or directory
+ * @param file the current file or directory context
+ * @return the resolved file or directory, or {@code null} if not found
+ */
+ private FtpFile getFile(String path, InMemoryFtpFile file) {
+ path = removeSlash(path);
+
+ if (path.startsWith(PARENT_DIRECTORY)) {
+ if (path.equals(PARENT_DIRECTORY)) {
+ return file.getParent();
+ } else {
+ if (file.getParent() == null) {
+ return null;
+ }
+
+ return getFile(path.substring(2), (InMemoryFtpFile)
file.getParent());
+ }
+ } else if (path.startsWith(CURRENT_DIRECTORY)) {
+ if (path.equals(CURRENT_DIRECTORY)) {
+ return file;
+ } else if (path.startsWith(CURRENT)) {
+ if (path.equals(CURRENT)) {
+ return file;
+ } else {
+ return getFile(path.substring(2), file);
+ }
+ }
+ }
+
+ return file.find(path);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FtpFile getFile(String path) {
+ if (isAbsolute(path)) {
+ return getFile(path, homeDirectory);
+ } else {
+ return getFile(path, workingDirectory);
+ }
+ }
+
+ /**
+ * check if path is absolute
+ * @return true if it is, otherwise false
+ */
+ private boolean isAbsolute(String path) {
+ return path.startsWith("/");
+ }
+
+ /**
+ * Removes the leading slash ('/') from the given path, if present.
+ * <p>
+ * For example:
+ * <ul>
+ * <li>If the input path is "/dir1/dir2", the returned path will be
"dir1/dir2".</li>
+ * <li>If the input path is "./dir1/dir2", the returned path remains
"./dir1/dir2".</li>
+ * </ul>
+ * </p>
+ *
+ * @param path the input path from which to remove the leading slash
+ * @return the path without the leading slash if it starts with '/',
otherwise the original path
+ */
+ private String removeSlash(String path) {
+ if (path.startsWith("/")) {
+ return path.substring(1);
+ }
+
+ return path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRandomAccessible() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dispose() {
+ }
+}
+
diff --git
a/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFtpFile.java
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFtpFile.java
new file mode 100644
index 00000000..7bbf4eae
--- /dev/null
+++
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/InMemoryFtpFile.java
@@ -0,0 +1,534 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ftpserver.filesystem.inmemoryfs.impl;
+
+import org.apache.ftpserver.ftplet.FtpFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * This class wraps in-memory file object.
+ */
+public class InMemoryFtpFile implements FtpFile {
+
+ public static final String SLASH = "/";
+ private static final Logger LOG =
LoggerFactory.getLogger(InMemoryFtpFile.class);
+ private boolean isExist = false;
+ private boolean isFolder = true;
+ private String name;
+ private Map<String, InMemoryFtpFile> children;
+ private InMemoryFtpFile parent;
+ private long lastModify = System.currentTimeMillis();
+
+ private ByteArrayOutputStream data = new ByteArrayOutputStream();
+
+ /**
+ * default constructor
+ * @param name filename
+ * @param parent file, null if file is root
+ * @throws IllegalArgumentException 'name' is empty or null
+ */
+ private InMemoryFtpFile(String name, InMemoryFtpFile parent) {
+ if (name == null || name.isEmpty()) {
+ throw new IllegalArgumentException("null or empty file name");
+ }
+
+ this.name = name;
+ this.children = new ConcurrentHashMap<>();
+ this.parent = parent;
+ LOG.debug("created file in path {}", this.getAbsolutePath());
+ }
+
+ /**
+ * Creates a root file object for the in-memory file system.
+ *
+ * @param name The name of the root directory.
+ * @return The created root file object.
+ */
+ static InMemoryFtpFile createRoot(String name) {
+ InMemoryFtpFile file = new InMemoryFtpFile(name, null);
+ file.isExist = true;
+ file.isFolder = true;
+
+ return file;
+ }
+
+ /**
+ * Creates a leaf file object within the given parent directory.
+ *
+ * @param name The name of the leaf file.
+ * @param parent The parent directory for the leaf file.
+ * @return The created leaf file object.
+ * @throws NullPointerException If the `parent` parameter is null.
+ * @throws IllegalArgumentException If the `name` parameter is null or
empty.
+ */
+ private static InMemoryFtpFile createLeaf(String name, InMemoryFtpFile
parent) {
+ Objects.requireNonNull(parent);
+
+ return new InMemoryFtpFile(name, parent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getAbsolutePath() {
+ if (isRoot()) {
+ return name;
+ }
+
+ return parent.getAbsolutePath(name);
+ }
+
+ /**
+ * Returns the absolute path of the given child path within this directory.
+ * This method is used internally to construct the absolute path for child
files or directories.
+ *
+ * @param childPath The relative path of the child.
+ * @return The absolute path of the child.
+ */
+ private String getAbsolutePath(String childPath) {
+ if (isRoot()) {
+ return getName() + childPath;
+ }
+
+ return parent.getAbsolutePath(name + SLASH + childPath);
+ }
+
+ /**
+ * Check is file root file.
+ * @return <code>true</code> if file is root
+ */
+ private boolean isRoot() {
+ return parent == null;
+ }
+
+ /**
+ * This method returns a list of all child files and directories that
currently exist within this directory.
+ *
+ * @return A list of child files and directories.
+ */
+ protected List<InMemoryFtpFile> getChildren() {
+ return
children.values().stream().filter(InMemoryFtpFile::doesExist).collect(Collectors.toList());
+ }
+
+ /**
+ * This method searches for the specified file or directory within the
current directory and its subdirectories.
+ * It supports relative paths, including ".." for navigating to the parent
directory.
+ *
+ * @param path The relative path of the file or directory to find.
+ * @return The found file object, or null if the file or directory does
not exist.
+ */
+ public InMemoryFtpFile find(String path) {
+ if (path.startsWith(SLASH)) {
+ path = path.substring(1);
+ }
+
+ if (path.isEmpty()) {
+ return this;
+ }
+
+ if (isGoUpPath(path)) {
+ if (isRoot()) {
+ return this;
+ }
+
+ return parent.find(path.substring(2));
+ }
+
+ String[] splitPath = path.split(SLASH, 2);
+
+ if (isMultiFolderPath(splitPath)) {
+ InMemoryFtpFile file = this.find(splitPath[0]);
+
+ return file.find(splitPath[1]);
+ } else {
+ InMemoryFtpFile leaf = InMemoryFtpFile.createLeaf(path, this);
+ InMemoryFtpFile existingFile =
children.putIfAbsent(leaf.getName(), leaf);
+
+ return existingFile == null ? leaf : existingFile;
+ }
+ }
+
+ /**
+ * Checks if the given path represents a multi-level directory path.
+ *
+ * A multi-level directory path contains more than one directory level
+ * (e.g., "dir1/dir2/file.txt").
+ *
+ * @param splitPath An array of path components obtained by splitting the
original path.
+ * @return `true` if the path contains more than one directory level,
`false` otherwise.
+ */
+ private boolean isMultiFolderPath(String[] splitPath) {
+ return splitPath.length > 1;
+ }
+
+ /**
+ * Checks if the given path represents a request to navigate to the parent
directory.
+ *
+ * @param path The path to be checked.
+ * @return `true` if the path starts with ".." (indicating a parent
directory request), `false` otherwise.
+ */
+ private boolean isGoUpPath(String path) {
+ return path.startsWith("..");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isHidden() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isDirectory() {
+ return isRoot() || isFolder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isFile() {
+ return !isRoot() && !isFolder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean doesExist() {
+ return isExist;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isReadable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isWritable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRemovable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getOwnerName() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getGroupName() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getLinkCount() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getLastModified() {
+ return lastModify;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setLastModified(long time) {
+ lastModify = time;
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getSize() {
+ return data.size();
+ }
+
+ /**
+ * There is no physicl file in in-memory filesystem.
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public Object getPhysicalFile() {
+ throw new UnsupportedOperationException("physical file not available
in memory file system");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean mkdir() {
+ LOG.debug("creating folder at path {}", getAbsolutePath());
+
+ if (isExist && isFolder) {
+ return false;
+ }
+
+ if (!isRoot()) {
+ isExist = true;
+ isFolder = true;
+ parent.mkdir();
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean delete() {
+ LOG.debug("deleting file at path {}", getAbsolutePath());
+ isExist = false;
+ this.children = null;
+ removeFromParent();
+ this.parent = null;
+
+ return true;
+ }
+
+ /**
+ * This method removes the entry for this file or directory from the
`children` map of its parent.
+ */
+ private void removeFromParent() {
+ parent.children.remove(getName());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean move(FtpFile destination) {
+ LOG.debug("moving file from {} to {}", getAbsolutePath(),
destination.getAbsolutePath());
+ removeFromParent();
+ InMemoryFtpFile inMemoryDestination = (InMemoryFtpFile) destination;
+ clone(inMemoryDestination);
+ inMemoryDestination.parent.getChildren().add(inMemoryDestination);
+
+ return true;
+ }
+
+ /**
+ * Creates a shallow copy of this InMemoryFtpFile object into the provided
target object.
+ *
+ * This method copies the data references, flags, and parent reference
from the current object to the target object.
+ * It does not create deep copies of child objects or data.
+ *
+ * @param target The target InMemoryFtpFile object to copy the data into.
+ */
+ private void clone(InMemoryFtpFile target) {
+ target.data = this.data;
+ target.isFolder = this.isFolder;
+ target.children = this.children;
+ target.isExist = this.isExist;
+ target.parent = this.parent;
+ target.name = this.name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<? extends FtpFile> listFiles() {
+ if (isDirectory()) {
+ return
children.values().stream().filter(InMemoryFtpFile::doesExist).collect(Collectors.toList());
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OutputStream createOutputStream(long offset) {
+ isFolder = false;
+ isExist = true;
+ if (offset != 0) {
+ throw new IllegalArgumentException("offset must be equal 0");
+ }
+
+ return new ByteArrayOutputStreamWrapper(data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public InputStream createInputStream(long offset) throws IOException {
+ validateInputStream(offset);
+
+ return new ByteArrayInputStream(data.toByteArray());
+ }
+
+ /**
+ * This method checks if the current object is a directory and if the
specified offset is valid.
+ *
+ * @param offset The desired offset for the input stream.
+ * @throws IOException If the current object is a directory.
+ * @throws IllegalArgumentException If the offset is not equal to 0.
+ */
+ private void validateInputStream(long offset) throws IOException {
+ if (isFolder) {
+ throw new IOException("cannot read bytes from folder");
+ }
+
+ if (offset != 0) {
+ throw new IllegalArgumentException("offset must be equal 0");
+ }
+ }
+
+ /**
+ * Returns the parent directory of this file or directory.
+ *
+ * @return The parent directory object, or null if this is the root
directory.
+ */
+ public FtpFile getParent() {
+ return parent;
+ }
+
+ /**
+ * Two InMemoryFtpFile objects are considered equal if they have the same
absolute path.
+ *
+ * @param o The object to compare with.
+ * @return `true` if the objects are equal, `false` otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ InMemoryFtpFile file = (InMemoryFtpFile) o;
+
+ return Objects.equals(getAbsolutePath(), file.getAbsolutePath());
+ }
+
+ /**
+ * Returns the hash code of this InMemoryFtpFile object.
+ *
+ * The hash code is calculated based on the absolute path of the file or
directory.
+ *
+ * @return The hash code of this object.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(getAbsolutePath());
+ }
+
+ /**
+ * This class allows the ByteArrayOutputStream to be used as an
OutputStream
+ * by delegating all write operations to the underlying
ByteArrayOutputStream.
+ */
+ private static class ByteArrayOutputStreamWrapper extends OutputStream {
+
+ private final ByteArrayOutputStream byteArrayOutputStream;
+
+ /**
+ * Default Constructor.
+ */
+ ByteArrayOutputStreamWrapper(ByteArrayOutputStream
byteArrayOutputStream) {
+ this.byteArrayOutputStream = byteArrayOutputStream;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void write(int b) {
+ byteArrayOutputStream.write(b);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void write(byte[] b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void write(byte[] b, int off, int len) {
+ byteArrayOutputStream.write(b, off, len);
+ }
+ }
+}
+
diff --git
a/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/package-info.java
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/package-info.java
new file mode 100644
index 00000000..c813b130
--- /dev/null
+++
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/impl/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+package org.apache.ftpserver.filesystem.inmemoryfs.impl;
+
diff --git
a/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/package-info.java
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/package-info.java
new file mode 100644
index 00000000..06e15fb9
--- /dev/null
+++
b/core/src/main/java/org/apache/ftpserver/filesystem/inmemoryfs/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+package org.apache.ftpserver.filesystem.inmemoryfs;
diff --git
a/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFileSystemViewTest.java
b/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFileSystemViewTest.java
new file mode 100644
index 00000000..250cefdc
--- /dev/null
+++
b/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFileSystemViewTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ftpserver.filesystem.inmemory.impl;
+
+import junit.framework.TestCase;
+import org.apache.ftpserver.filesystem.inmemoryfs.impl.InMemoryFileSystemView;
+import org.apache.ftpserver.usermanager.impl.BaseUser;
+
+public class InMemoryFileSystemViewTest extends TestCase {
+ private final String HOME_DIR = "/";
+ protected BaseUser user = new BaseUser();
+
+ public void testShouldNotCreateViewIfUserIsNull() {
+ try {
+ new InMemoryFileSystemView(null);
+ fail("user should not be null");
+ } catch (Exception ex) {
+ assertTrue(ex instanceof IllegalArgumentException);
+ }
+ }
+
+ public void testShouldNotCreateViewIfUserIsHasNoHomeDir() {
+ try {
+ user.setHomeDirectory(null);
+ new InMemoryFileSystemView(user);
+ fail("user home directory should not be ull");
+ } catch (Exception ex) {
+ assertTrue(ex instanceof IllegalArgumentException);
+ }
+ }
+
+ public void testHomeDir() throws Exception {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ assertEquals(HOME_DIR, view.getHomeDirectory().getAbsolutePath());
+ }
+
+ public void testWorkingDir() throws Exception {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ assertEquals(HOME_DIR, view.getWorkingDirectory().getAbsolutePath());
+ }
+
+ public void testChangeWorkingDir() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile("1").mkdir();
+
+ view.changeWorkingDirectory("1");
+
+ assertEquals("1", view.getWorkingDirectory().getName());
+ }
+
+ public void testChangeWorkingDirToGoUp() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile("1").mkdir();
+
+ view.changeWorkingDirectory("1");
+
+ assertEquals("1", view.getWorkingDirectory().getName());
+
+ view.changeWorkingDirectory("..");
+ assertEquals("/", view.getWorkingDirectory().getAbsolutePath());
+ }
+
+ public void testChangeWorkingDirToGoUp2Times() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile("1/2").mkdir();
+
+ view.changeWorkingDirectory("1/2");
+
+ assertEquals("2", view.getWorkingDirectory().getName());
+
+ assertEquals("/", view.getFile("../..").getAbsolutePath());
+ }
+
+ public void testGetCurrentDir() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ assertEquals("/", view.getFile(".").getAbsolutePath());
+ }
+
+ private InMemoryFileSystemView getInMemoryFileSystemView() {
+ user.setHomeDirectory(HOME_DIR);
+ InMemoryFileSystemView view = new InMemoryFileSystemView(user);
+ return view;
+ }
+}
+
diff --git
a/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFtpFileTest.java
b/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFtpFileTest.java
new file mode 100644
index 00000000..4c217be8
--- /dev/null
+++
b/core/src/test/java/org/apache/ftpserver/filesystem/inmemory/impl/InMemoryFtpFileTest.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ftpserver.filesystem.inmemory.impl;
+
+import junit.framework.TestCase;
+import org.apache.ftpserver.filesystem.inmemoryfs.impl.InMemoryFileSystemView;
+import org.apache.ftpserver.filesystem.inmemoryfs.impl.InMemoryFtpFile;
+import org.apache.ftpserver.ftplet.FtpFile;
+import org.apache.ftpserver.usermanager.impl.BaseUser;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+public class InMemoryFtpFileTest extends TestCase {
+
+ public static final String FILE1_NAME = "1";
+ public static final String FILE2_NAME = "2";
+ public static final String FILE3_NAME = "3";
+ protected BaseUser user = new BaseUser();
+ private final static String HOME_DIR = "/";
+
+ public void testGetAbsolutePath() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ FtpFile file2 = view.getFile(FILE1_NAME + "/" + FILE2_NAME);
+ assertEquals(HOME_DIR + FILE1_NAME, file1.getAbsolutePath());
+ assertEquals(HOME_DIR + FILE1_NAME + "/" + FILE2_NAME,
file2.getAbsolutePath());
+ }
+
+ public void testFindFile() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+
+ InMemoryFtpFile file = (InMemoryFtpFile) view.getFile(FILE1_NAME);
+ file.mkdir();
+
+ InMemoryFtpFile subFile1 = (InMemoryFtpFile)
view.getFile(file.getAbsolutePath() + "/" + FILE2_NAME);
+ subFile1.mkdir();
+
+ assertEquals(file, file.find(""));
+ assertEquals(subFile1, file.find(subFile1.getName()));
+ }
+
+ public void testGetName() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file = view.getFile(FILE1_NAME);
+
+ assertEquals(FILE1_NAME, file.getName());
+ }
+
+ public void testIsDirectory() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file = view.getFile(FILE1_NAME);
+
+ assertTrue(file.isDirectory());
+ assertFalse(file.isFile());
+ }
+
+ public void testIsFile() throws Exception{
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file = view.getFile(FILE1_NAME);
+ file.createOutputStream(0).close(); //setting that file is file, not
directory
+ assertFalse(file.isDirectory());
+ assertTrue(file.isFile());
+ }
+ public void testModificationTime() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+
+ long time = System.currentTimeMillis();
+
+ file1.setLastModified(time);
+
+ assertEquals(time, file1.getLastModified());
+ }
+
+ public void testGetSize() throws Exception {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file = view.getFile(FILE1_NAME);
+
+ OutputStream outputStream = file.createOutputStream(0);
+ String data = "Test";
+ outputStream.write(data.getBytes());
+ outputStream.close();
+
+ assertEquals(data.getBytes().length, file.getSize());
+ }
+
+ public void testDeleteLeaf() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ view.changeWorkingDirectory(FILE1_NAME);
+ FtpFile file2 = view.getFile(FILE1_NAME + "/" + FILE2_NAME);
+ file2.mkdir();
+
+ assertTrue(file2.delete());
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+ assertEquals(0, file1.listFiles().size());
+ }
+
+ public void testDeleteRoot() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ file1.mkdir();
+ view.changeWorkingDirectory(FILE1_NAME);
+
+ assertTrue(file1.delete());
+
+ assertEquals(0, view.getHomeDirectory().listFiles().size());
+ }
+
+ public void testWriteAndRead() throws Exception {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file = view.getFile(FILE1_NAME);
+
+ OutputStream outputStream = file.createOutputStream(0);
+ String input_data = "Hello, World!";
+ outputStream.write(input_data.getBytes());
+ outputStream.close();
+
+ InputStream inputStream = file.createInputStream(0);
+ StringBuilder output_data = new StringBuilder();
+ int data;
+ while ((data = inputStream.read()) != -1) {
+ output_data.append((char) data);
+ }
+ inputStream.close();
+
+ assertEquals(input_data, output_data.toString());
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ }
+
+ public void testMove() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ FtpFile file2 = view.getFile(FILE2_NAME);
+
+ file1.move(file2);
+
+ assertEquals(file1.getAbsolutePath(), file2.getAbsolutePath());
+ assertEquals(file1.getName(), file2.getName());
+ assertEquals(file1.getSize(), file2.getSize());
+ }
+
+ public void testListFiles() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ file1.mkdir();
+ FtpFile file2 = view.getFile(FILE1_NAME + "/" + FILE2_NAME);
+ file2.mkdir();
+
+ List<FtpFile> result = (List<FtpFile>) file1.listFiles();
+ assertEquals(1, result.size());
+ assertEquals(file2, result.get(0));
+ }
+
+ public void testDeleteWithFilesInside() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ file1.mkdir();
+
+ FtpFile file2 = view.getFile(FILE1_NAME + "/" + FILE2_NAME);
+ file2.mkdir();
+
+ file1.delete();
+ assertEquals(0, view.getHomeDirectory().listFiles().size());
+ }
+
+ public void testMultipleWorkingDirectoryChange() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ FtpFile file1 = view.getFile(FILE1_NAME);
+ file1.mkdir();
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ FtpFile file2 = view.getFile(FILE1_NAME + "/" + FILE2_NAME);
+ file2.mkdir();
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ view.changeWorkingDirectory(FILE1_NAME);
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ view.changeWorkingDirectory(FILE2_NAME);
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ view.changeWorkingDirectory("..");
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ view.changeWorkingDirectory("..");
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size());
+ assertEquals(1, file1.listFiles().size());
+ assertEquals(0, file2.listFiles().size());
+ }
+
+ public void testCreateDirectoryByMultiDirectoryPath() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile(FILE1_NAME + "/" + FILE2_NAME).mkdir();
+
+ assertEquals(1, view.getWorkingDirectory().listFiles().size());
+ assertTrue(view.getFile(FILE1_NAME).doesExist() &&
view.getFile(FILE1_NAME).isDirectory());
+
+ view.changeWorkingDirectory(FILE1_NAME);
+
+ assertEquals(1, view.getWorkingDirectory().listFiles().size());
+ assertTrue(view.getFile(FILE2_NAME).doesExist() &&
view.getFile(FILE2_NAME).isDirectory());
+
+ view.changeWorkingDirectory(FILE2_NAME);
+
+ assertEquals(0, view.getWorkingDirectory().listFiles().size());
+ }
+
+ public void testCWDToNotExisingDirectoryNotChangingIt() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile(FILE1_NAME + "/" + FILE2_NAME + "/" + FILE3_NAME).mkdir();
+ view.changeWorkingDirectory(FILE1_NAME + "/" + FILE2_NAME + "/" +
FILE3_NAME);
+
+ assertEquals(FILE3_NAME, view.getWorkingDirectory().getName());
+
+ view.changeWorkingDirectory("not_exist");
+
+ assertEquals(FILE3_NAME, view.getWorkingDirectory().getName());
+ }
+
+ public void testCreateDirectoryByMultiDirectoryAbsolutePath() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile(HOME_DIR + "/" + FILE1_NAME + "/" + FILE2_NAME + "/" +
FILE3_NAME).mkdir();
+
+ assertEquals(1, view.getHomeDirectory().listFiles().size()); // home
+ assertEquals(1,
view.getHomeDirectory().listFiles().get(0).listFiles().size()); //1
+ assertEquals(1,
view.getHomeDirectory().listFiles().get(0).listFiles().get(0).listFiles().size());
//2
+ assertEquals(0,
view.getHomeDirectory().listFiles().get(0).listFiles().get(0).listFiles().get(0).listFiles().size());
//3
+ }
+
+ public void testGoUpDirectory() {
+ InMemoryFileSystemView view = getInMemoryFileSystemView();
+ view.getFile(HOME_DIR + "/" + FILE1_NAME + "/" + FILE2_NAME + "/" +
FILE3_NAME).mkdir();
+
+ view.changeWorkingDirectory(HOME_DIR + "/" + FILE1_NAME + "/" +
FILE2_NAME + "/" + FILE3_NAME);
+ view.changeWorkingDirectory("..");
+ assertEquals(FILE2_NAME, view.getWorkingDirectory().getName());
+ }
+
+ private InMemoryFileSystemView getInMemoryFileSystemView() {
+ user.setHomeDirectory(HOME_DIR);
+ return new InMemoryFileSystemView(user);
+ }
+}
+
diff --git a/examples/ftpserver-osgi-ftplet-service/pom.xml
b/examples/ftpserver-osgi-ftplet-service/pom.xml
index 9ea08b1b..dd254342 100644
--- a/examples/ftpserver-osgi-ftplet-service/pom.xml
+++ b/examples/ftpserver-osgi-ftplet-service/pom.xml
@@ -51,7 +51,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>5.1.4</version>
+ <version>${maven-bundle-plugin-version}</version>
<extensions>true</extensions>
<configuration>
<instructions>
diff --git a/examples/ftpserver-osgi-spring-service/pom.xml
b/examples/ftpserver-osgi-spring-service/pom.xml
index f7d24f5f..93411ca9 100644
--- a/examples/ftpserver-osgi-spring-service/pom.xml
+++ b/examples/ftpserver-osgi-spring-service/pom.xml
@@ -45,7 +45,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>5.1.4</version>
+ <version>${maven-bundle-plugin-version}</version>
<extensions>true</extensions>
<configuration>
<instructions>
diff --git a/ftplet-api/pom.xml b/ftplet-api/pom.xml
index ecd732a7..560ef840 100644
--- a/ftplet-api/pom.xml
+++ b/ftplet-api/pom.xml
@@ -36,7 +36,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>5.1.4</version>
+ <version>${maven-bundle-plugin-version}</version>
<extensions>true</extensions>
<configuration>
<instructions>
diff --git a/pom.xml b/pom.xml
index 4324c6ff..acd743e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
<parent>
<artifactId>apache</artifactId>
<groupId>org.apache</groupId>
- <version>33</version>
+ <version>37</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -151,14 +151,14 @@
<!-- doclint>none</doclint-->
<!-- Set versions for depending jars -->
- <commons.codec.version>1.17.2</commons.codec.version>
- <commons.net.version>3.11.1</commons.net.version>
+ <commons.codec.version>1.21.0</commons.codec.version>
+ <commons.net.version>3.12.0</commons.net.version>
<ftpserver.version>${project.version}</ftpserver.version>
<hsqldb.version>1.8.0.10</hsqldb.version>
<jcl.over.slf4j.version>1.7.36</jcl.over.slf4j.version>
<junit.version>4.13.2</junit.version>
- <log4j.version>2.24.3</log4j.version>
- <mina.core.version>2.2.4</mina.core.version>
+ <log4j.version>2.25.3</log4j.version>
+ <mina.core.version>2.2.5</mina.core.version>
<osgi.r4.version>1.0</osgi.r4.version>
<slf4j.api.version>1.7.36</slf4j.api.version>
<slf4j.log4j12.version>1.7.36</slf4j.log4j12.version>
@@ -175,7 +175,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Maven plugin version -->
- <maven-javadoc-plugin-version>3.11.2</maven-javadoc-plugin-version>
+ <maven-javadoc-plugin-version>3.12.0</maven-javadoc-plugin-version>
+ <maven-bundle-plugin-version>6.0.2</maven-bundle-plugin-version>
<version.site.plugin>4.0.0-M16</version.site.plugin>
</properties>
@@ -353,7 +354,7 @@
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
- <version>0.23.1</version>
+ <version>0.25.4</version>
<configuration>
<oldVersion>
<dependency>