aboutsummaryrefslogtreecommitdiffstats
path: root/archiva-modules/archiva-base
diff options
context:
space:
mode:
authorMartin Stockhammer <martin_s@apache.org>2019-05-13 22:23:01 +0200
committerMartin Stockhammer <martin_s@apache.org>2019-05-13 22:23:01 +0200
commitf3aa14f3e2445e5404b9d254c9d3c3dd90221f6a (patch)
tree32f8015ad868ce6a372af859520bb8cdf3598f1e /archiva-modules/archiva-base
parent7adddbe141186225bc33e918727ceb3bed6646f3 (diff)
downloadarchiva-f3aa14f3e2445e5404b9d254c9d3c3dd90221f6a.tar.gz
archiva-f3aa14f3e2445e5404b9d254c9d3c3dd90221f6a.zip
Refactoring storage access and webdav
Diffstat (limited to 'archiva-modules/archiva-base')
-rw-r--r--archiva-modules/archiva-base/archiva-converter/src/main/java/org/apache/archiva/converter/legacy/LegacyConverterArtifactConsumer.java9
-rw-r--r--archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java61
-rw-r--r--archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RelocatablePath.java41
-rw-r--r--archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RequestPathMapper.java35
-rw-r--r--archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/StorageAsset.java131
-rw-r--r--archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java357
6 files changed, 631 insertions, 3 deletions
diff --git a/archiva-modules/archiva-base/archiva-converter/src/main/java/org/apache/archiva/converter/legacy/LegacyConverterArtifactConsumer.java b/archiva-modules/archiva-base/archiva-converter/src/main/java/org/apache/archiva/converter/legacy/LegacyConverterArtifactConsumer.java
index 13a79dd64..bddec98e5 100644
--- a/archiva-modules/archiva-base/archiva-converter/src/main/java/org/apache/archiva/converter/legacy/LegacyConverterArtifactConsumer.java
+++ b/archiva-modules/archiva-base/archiva-converter/src/main/java/org/apache/archiva/converter/legacy/LegacyConverterArtifactConsumer.java
@@ -19,6 +19,7 @@ package org.apache.archiva.converter.legacy;
* under the License.
*/
+import org.apache.archiva.common.filelock.FileLockManager;
import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
import org.apache.archiva.configuration.FileTypes;
@@ -71,6 +72,9 @@ public class LegacyConverterArtifactConsumer
@Inject
private FileTypes fileTypes;
+ @Inject
+ private FileLockManager fileLockManager;
+
private ArtifactFactory artifactFactory;
private ManagedRepositoryContent managedRepository;
@@ -93,11 +97,10 @@ public class LegacyConverterArtifactConsumer
}
@Override
- public void beginScan( org.apache.archiva.repository.ManagedRepository repository, Date whenGathered )
+ public void beginScan( ManagedRepository repository, Date whenGathered )
throws ConsumerException
{
- this.managedRepository = new ManagedDefaultRepositoryContent(artifactMappingProviders, fileTypes);
- this.managedRepository.setRepository( repository );
+ this.managedRepository = new ManagedDefaultRepositoryContent(repository, artifactMappingProviders, fileTypes, fileLockManager);
}
@Override
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
index 816b57766..37ba9cf94 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
@@ -23,9 +23,17 @@ import org.apache.archiva.model.ArchivaArtifact;
import org.apache.archiva.model.ArtifactReference;
import org.apache.archiva.model.ProjectReference;
import org.apache.archiva.model.VersionedReference;
+import org.apache.archiva.repository.content.StorageAsset;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.file.Path;
+import java.time.Instant;
+import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* ManagedRepositoryContent interface for interacting with a managed repository in an abstract way,
@@ -224,4 +232,57 @@ public interface ManagedRepositoryContent extends RepositoryContent
* @return the relative path to the artifact.
*/
String toPath( ArchivaArtifact reference );
+
+ /**
+ * Returns information about a specific storage asset.
+ * @param path
+ * @return
+ */
+ StorageAsset getAsset(String path);
+
+ /**
+ * Consumes the data and sets a lock for the file during the operation.
+ *
+ * @param asset
+ * @param consumerFunction
+ * @param readLock
+ * @throws IOException
+ */
+ void consumeData( StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException;
+
+ /**
+ * Adds a new asset to the underlying storage.
+ * @param path The path to the asset.
+ * @param container True, if the asset should be a container, false, if it is a file.
+ * @return
+ */
+ StorageAsset addAsset(String path, boolean container);
+
+ /**
+ * Removes the given asset from the storage.
+ *
+ * @param asset
+ * @throws IOException
+ */
+ void removeAsset(StorageAsset asset) throws IOException;
+
+ /**
+ * Moves the asset to the given location and returns the asset object for the destination.
+ *
+ * @param origin
+ * @param destination
+ * @return
+ */
+ StorageAsset moveAsset(StorageAsset origin, String destination) throws IOException;
+
+
+ /**
+ * Copies the given asset to the new destination.
+ *
+ * @param origin
+ * @param destination
+ * @return
+ * @throws IOException
+ */
+ StorageAsset copyAsset(StorageAsset origin, String destination) throws IOException;
}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RelocatablePath.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RelocatablePath.java
new file mode 100644
index 000000000..055abe48b
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RelocatablePath.java
@@ -0,0 +1,41 @@
+package org.apache.archiva.repository;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class RelocatablePath
+{
+
+ private final String path;
+ private final String originPath;
+ private final boolean relocated;
+
+ RelocatablePath(String path, String originPath) {
+ this.path = path;
+ this.originPath = originPath;
+ this.relocated = !path.equals(originPath);
+ }
+
+ RelocatablePath(String path) {
+ this.path = path;
+ this.originPath = path;
+ this.relocated = false;
+ }
+
+ public String getPath( )
+ {
+ return path;
+ }
+
+ public String getOriginPath( )
+ {
+ return originPath;
+ }
+
+ public boolean isRelocated( )
+ {
+ return relocated;
+ }
+
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RequestPathMapper.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RequestPathMapper.java
new file mode 100644
index 000000000..d06615fba
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RequestPathMapper.java
@@ -0,0 +1,35 @@
+package org.apache.archiva.repository;
+
+/**
+ *
+ * Maps request paths to native repository paths. Normally HTTP requests and the path in the repository
+ * storage should be identically.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface RequestPathMapper
+{
+ /**
+ * Maps a request path to a repository path. The request path should be relative
+ * to the repository. The resulting path should always start with a '/'.
+ * The returned object contains additional information, if this request
+ *
+ * @param requestPath
+ * @return
+ */
+ RelocatablePath relocatableRequestToRepository(String requestPath);
+
+
+ String requestToRepository(String requestPath);
+
+
+ /**
+ * Maps a repository path to a request path. The repository path is relative to the
+ * repository. The resulting path should always start with a '/'.
+ *
+ * @param repositoryPath
+ * @return
+ */
+ String repositoryToRequest(String repositoryPath);
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/StorageAsset.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/StorageAsset.java
new file mode 100644
index 000000000..008f096a3
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/StorageAsset.java
@@ -0,0 +1,131 @@
+package org.apache.archiva.repository.content;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A instance of this interface represents information about an specific asset in a repository.
+ * The asset may be an real artifact, a directory, or a virtual asset.
+ *
+ * Each asset has a unique path relative to the repository.
+ *
+ * The implementation may read the data directly from the filesystem or underlying storage implementation.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface StorageAsset
+{
+ /**
+ * Returns the complete path relative to the repository to the given asset.
+ *
+ * @return A path starting with '/' that uniquely identifies the asset in the repository.
+ */
+ String getPath();
+
+ /**
+ * Returns the name of the asset. It may be just the filename.
+ * @return
+ */
+ String getName();
+
+ /**
+ * Returns the time of the last modification.
+ *
+ * @return
+ */
+ Instant getModificationTime();
+
+ /**
+ * Returns true, if this asset is a container type and contains further child assets.
+ * @return
+ */
+ boolean isContainer();
+
+ /**
+ * List the child assets.
+ *
+ * @return The list of children. If there are no children, a empty list will be returned.
+ */
+ List<StorageAsset> list();
+
+ /**
+ * The size in bytes of the asset. If the asset does not have a size, -1 should be returned.
+ *
+ * @return The size if the asset has a size, otherwise -1
+ */
+ long getSize();
+
+ /**
+ * Returns the input stream of the artifact content.
+ * It will throw a IOException, if the stream could not be created.
+ * Implementations should create a new stream instance for each invocation.
+ *
+ * @return The InputStream representing the content of the artifact.
+ * @throws IOException
+ */
+ InputStream getData() throws IOException;
+
+ /**
+ *
+ * Returns an output stream where you can write data to the asset.
+ *
+ * @param replace If true, the original data will be replaced, otherwise the data will be appended.
+ * @return The OutputStream where the data can be written.
+ * @throws IOException
+ */
+ OutputStream writeData( boolean replace) throws IOException;
+
+ /**
+ * Replaces the content. The implementation may do an atomic move operation, or keep a backup. If
+ * the operation fails, the implementation should try to restore the old data, if possible.
+ *
+ * The original file may be deleted, if the storage was successful.
+ *
+ * @param newData Replaces the data by the content of the given file.
+ */
+ boolean storeDataFile( Path newData) throws IOException;
+
+ /**
+ * Returns true, if the asset exists.
+ *
+ * @return True, if the asset exists, otherwise false.
+ */
+ boolean exists();
+
+ /**
+ * Creates the asset in the underlying storage, if it does not exist.
+ */
+ void create() throws IOException;
+
+ /**
+ * Returns the real path to the asset, if it exist. Not all implementations may implement this method.
+ *
+ * @return The filesystem path to the asset.
+ * @throws UnsupportedOperationException
+ */
+ Path getFilePath() throws UnsupportedOperationException;
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java
new file mode 100644
index 000000000..ef3aad306
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java
@@ -0,0 +1,357 @@
+package org.apache.archiva.repository.content;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class FilesystemAsset implements StorageAsset
+{
+
+ private final static Logger log = LoggerFactory.getLogger( FilesystemAsset.class );
+
+ private final Path basePath;
+ private final Path assetPath;
+ private final Path completeAssetPath;
+
+ public String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
+ public String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
+
+ List<AclEntry> defaultFileAcls;
+ Set<PosixFilePermission> defaultPosixFilePermissions;
+ List<AclEntry> defaultDirectoryAcls;
+ Set<PosixFilePermission> defaultPosixDirectoryPermissions;
+
+ boolean supportsAcl = false;
+ boolean supportsPosix = false;
+
+ boolean directory = false;
+
+ public FilesystemAsset( Path basePath, String assetPath )
+ {
+ this.basePath = basePath;
+ this.assetPath = Paths.get( assetPath );
+ this.completeAssetPath = basePath.resolve( assetPath ).toAbsolutePath( );
+ init( );
+ }
+
+ public FilesystemAsset( Path basePath, String assetPath, boolean directory )
+ {
+ this.basePath = basePath;
+ this.assetPath = Paths.get( assetPath );
+ this.completeAssetPath = basePath.resolve( assetPath ).toAbsolutePath( );
+ this.directory = directory;
+ init( );
+ }
+
+ private void init( )
+ {
+ defaultFileAcls = new ArrayList<>( );
+ AclEntry.Builder aclBuilder = AclEntry.newBuilder( );
+ aclBuilder.setPermissions( AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
+ AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA );
+ aclBuilder.setType( AclEntryType.ALLOW );
+ defaultFileAcls.add( aclBuilder.build( ) );
+ AclEntry.Builder aclDirBuilder = AclEntry.newBuilder( );
+ aclDirBuilder.setPermissions( AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
+ AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
+ AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA );
+ aclDirBuilder.setType( AclEntryType.ALLOW );
+ defaultDirectoryAcls.add( aclDirBuilder.build( ) );
+
+ defaultPosixFilePermissions = PosixFilePermissions.fromString( DEFAULT_POSIX_FILE_PERMS );
+ defaultPosixDirectoryPermissions = PosixFilePermissions.fromString( DEFAULT_POSIX_DIR_PERMS );
+
+ try
+ {
+ supportsAcl = Files.getFileStore( completeAssetPath ).supportsFileAttributeView( AclFileAttributeView.class );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not check filesystem capabilities {}", e.getMessage( ) );
+ }
+ try
+ {
+ supportsPosix = Files.getFileStore( completeAssetPath ).supportsFileAttributeView( PosixFileAttributeView.class );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not check filesystem capabilities {}", e.getMessage( ) );
+ }
+
+ }
+
+
+ @Override
+ public String getPath( )
+ {
+ return assetPath.toString( );
+ }
+
+ @Override
+ public String getName( )
+ {
+ return assetPath.getFileName( ).toString( );
+ }
+
+ @Override
+ public Instant getModificationTime( )
+ {
+ try
+ {
+ return Files.getLastModifiedTime( completeAssetPath ).toInstant( );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not read modification time of {}", completeAssetPath );
+ return Instant.now( );
+ }
+ }
+
+ @Override
+ public boolean isContainer( )
+ {
+ return Files.isDirectory( completeAssetPath );
+ }
+
+ @Override
+ public List<StorageAsset> list( )
+ {
+ try
+ {
+ return Files.list( completeAssetPath ).map( p -> new FilesystemAsset( basePath, basePath.relativize( p ).toString( ) ) )
+ .collect( Collectors.toList( ) );
+ }
+ catch ( IOException e )
+ {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ @Override
+ public long getSize( )
+ {
+ try
+ {
+ return Files.size( completeAssetPath );
+ }
+ catch ( IOException e )
+ {
+ return -1;
+ }
+ }
+
+ @Override
+ public InputStream getData( ) throws IOException
+ {
+ return Files.newInputStream( completeAssetPath );
+ }
+
+ @Override
+ public OutputStream writeData( boolean replace ) throws IOException
+ {
+ OpenOption[] options;
+ if ( replace )
+ {
+ options = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE};
+ }
+ else
+ {
+ options = new OpenOption[]{StandardOpenOption.APPEND};
+ }
+ return Files.newOutputStream( completeAssetPath, options );
+ }
+
+ @Override
+ public boolean storeDataFile( Path newData ) throws IOException
+ {
+ final boolean createNew = !Files.exists( completeAssetPath );
+ Path backup = null;
+ if ( !createNew )
+ {
+ backup = findBackupFile( completeAssetPath );
+ }
+ try
+ {
+ if ( !createNew )
+ {
+ Files.move( completeAssetPath, backup );
+ }
+ Files.move( newData, completeAssetPath, StandardCopyOption.REPLACE_EXISTING );
+ setDefaultPermissions( completeAssetPath );
+ return true;
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not overwrite file {}", completeAssetPath );
+ // Revert if possible
+ if ( backup != null && Files.exists( backup ) )
+ {
+ Files.move( backup, completeAssetPath, StandardCopyOption.REPLACE_EXISTING );
+ }
+ throw e;
+ }
+ finally
+ {
+ if ( backup != null )
+ {
+ try
+ {
+ Files.deleteIfExists( backup );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not delete backup file {}", backup );
+ }
+ }
+ }
+
+ }
+
+ private void setDefaultPermissions(Path filePath) {
+ try
+ {
+ if ( supportsPosix )
+ {
+ Set<PosixFilePermission> perms;
+ if ( Files.isDirectory( filePath ) )
+ {
+ perms = defaultPosixFilePermissions;
+ }
+ else
+ {
+ perms = defaultPosixDirectoryPermissions;
+ }
+ Files.setPosixFilePermissions( filePath, perms );
+ }
+ else if ( supportsAcl )
+ {
+ List<AclEntry> perms;
+ if ( Files.isDirectory( filePath ) )
+ {
+ perms = defaultDirectoryAcls;
+ }
+ else
+ {
+ perms = defaultFileAcls;
+ }
+ AclFileAttributeView aclAttr = Files.getFileAttributeView( filePath, AclFileAttributeView.class );
+ aclAttr.setAcl( perms );
+ }
+ } catch (IOException e) {
+ log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
+ }
+ }
+
+ private Path findBackupFile( Path file )
+ {
+ String ext = ".bak";
+ Path backupPath = file.getParent( ).resolve( file.getFileName( ).toString( ) + ext );
+ int idx = 0;
+ while ( Files.exists( backupPath ) )
+ {
+ backupPath = file.getParent( ).resolve( file.getFileName( ).toString( ) + ext + idx++ );
+ }
+ return backupPath;
+ }
+
+ @Override
+ public boolean exists( )
+ {
+ return Files.exists( completeAssetPath );
+ }
+
+ @Override
+ public Path getFilePath( ) throws UnsupportedOperationException
+ {
+ return completeAssetPath;
+ }
+
+
+ public void setDefaultFileAcls( List<AclEntry> acl )
+ {
+ defaultFileAcls = acl;
+ }
+
+ public List<AclEntry> getDefaultFileAcls( )
+ {
+ return defaultFileAcls;
+ }
+
+ public void setDefaultPosixFilePermissions( Set<PosixFilePermission> perms )
+ {
+ defaultPosixFilePermissions = perms;
+ }
+
+ public Set<PosixFilePermission> getDefaultPosixFilePermissions( )
+ {
+ return defaultPosixFilePermissions;
+ }
+
+ public void setDefaultDirectoryAcls( List<AclEntry> acl )
+ {
+ defaultDirectoryAcls = acl;
+ }
+
+ public List<AclEntry> getDefaultDirectoryAcls( )
+ {
+ return defaultDirectoryAcls;
+ }
+
+ public void setDefaultPosixDirectoryPermissions( Set<PosixFilePermission> perms )
+ {
+ defaultPosixDirectoryPermissions = perms;
+ }
+
+ public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions( )
+ {
+ return defaultPosixDirectoryPermissions;
+ }
+
+ @Override
+ public void create( ) throws IOException
+ {
+ if ( !Files.exists( completeAssetPath ) )
+ {
+ if ( directory )
+ {
+ Files.createDirectories( completeAssetPath );
+ } else {
+ Files.createFile( completeAssetPath );
+ }
+ setDefaultPermissions( completeAssetPath );
+ }
+ }
+
+ @Override
+ public String toString( )
+ {
+ return assetPath.toString();
+ }
+
+
+}