diff --git a/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java b/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java index 48ca2c0d3..8795e34b0 100644 --- a/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java +++ b/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java @@ -384,7 +384,25 @@ public class CcmFiles { return getFileSystemAdapter().listFiles(getDataPath(path)); } + + public String getMimeType(final String path) throws FileAccessException { + + return getFileSystemAdapter().getMimeType(path); + } + public void copyFile(final String sourcePath, + final String targetPath) throws FileAccessException { + + getFileSystemAdapter().copy(sourcePath, targetPath, false); + } + + public void copyFile(final String sourcePath, + final String targetPath, + final boolean recursive) throws FileAccessException{ + + getFileSystemAdapter().copy(sourcePath, targetPath, recursive); + } + /** * Delete a file or directory. If the file is a directory the directory must * be empty. diff --git a/ccm-core/src/main/java/org/libreccm/files/FileSystemAdapter.java b/ccm-core/src/main/java/org/libreccm/files/FileSystemAdapter.java index 439c8ea31..52e40f121 100644 --- a/ccm-core/src/main/java/org/libreccm/files/FileSystemAdapter.java +++ b/ccm-core/src/main/java/org/libreccm/files/FileSystemAdapter.java @@ -135,6 +135,14 @@ public interface FileSystemAdapter { throws FileAccessException, InsufficientPermissionsException; + String getMimeType(String path) throws FileAccessException; + + long getSize(String path) throws FileAccessException; + + void copy(String sourcePath, + String targetPath, + boolean recursive) throws FileAccessException; + /** * checks if the provided path points to a directory. * diff --git a/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java b/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java index 22cb77ce4..783e91784 100644 --- a/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java +++ b/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java @@ -20,15 +20,31 @@ package org.libreccm.files; import org.libreccm.configuration.ConfigurationManager; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + import javax.annotation.PostConstruct; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; -import java.io.*; import java.nio.charset.Charset; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.rmi.UnexpectedException; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -190,6 +206,122 @@ public class NIOFileSystemAdapter implements FileSystemAdapter { return Files.exists(nioPath); } + @Override + public String getMimeType(final String path) throws FileAccessException { + + final Path nioPath = Paths.get(path); + try { + return Files.probeContentType(nioPath); + } catch (IOException ex) { + throw new FileAccessException(path, ex); + } + } + + @Override + public long getSize(final String path) throws FileAccessException { + + final Path nioPath = Paths.get(path); + try { + return Files.size(nioPath); + } catch (IOException ex) { + throw new FileAccessException(path, ex); + } + + } + + @Override + public void copy(final String sourcePath, + final String targetPath, + boolean recursive) throws FileAccessException { + + final Path nioSourcePath = Paths.get(sourcePath); + final Path nioTargetPath = Paths.get(targetPath); + + if (recursive) { + + try { + Files.walkFileTree( + nioTargetPath, + new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory( + final Path dir, + final BasicFileAttributes attrs) + throws IOException { + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile( + final Path file, + final BasicFileAttributes attrs) throws IOException { + + Files.copy( + file, + nioTargetPath + .resolve(nioSourcePath.relativize(file)), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING, + LinkOption.NOFOLLOW_LINKS); + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed( + final Path file, + final IOException ex) throws IOException { + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory( + final Path dir, + final IOException ex) + throws IOException { + + return FileVisitResult.CONTINUE; + } + + }); + } catch (IOException ex) { + throw new FileAccessException(targetPath, ex); + } +// ); +// source -> { +// +// Files.copy( +// source, +// nioTargetPath +// .resolve(nioSourcePath.relativize( +// source, +// StandardCopyOption.ATOMIC_MOVE, +// StandardCopyOption.COPY_ATTRIBUTES, +// StandardCopyOption.REPLACE_EXISTING, +// LinkOption.NOFOLLOW_LINKS))); +// } +// +// ); + + } else { + try { + Files.copy(nioSourcePath, + nioTargetPath, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING, + LinkOption.NOFOLLOW_LINKS); + } catch (IOException ex) { + throw new FileAccessException(sourcePath, ex); + } + } + + } + @Override public boolean isDirectory(final String path) throws FileAccessException, FileDoesNotExistException, diff --git a/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java new file mode 100644 index 000000000..253f84f8e --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2018 LibreCCM Foundation. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.theming; + +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.files.CcmFiles; +import org.libreccm.files.DirectoryNotEmptyException; +import org.libreccm.files.FileAccessException; +import org.libreccm.files.FileDoesNotExistException; +import org.libreccm.files.InsufficientPermissionsException; +import org.libreccm.theming.manifest.ThemeManifest; +import org.libreccm.theming.manifest.ThemeManifestUtil; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class FileSystemThemeProvider implements ThemeProvider { + + private static final long serialVersionUID = 1L; + + private static final String BASE_PATH = "/themes"; + private static final String DRAFT_THEMES_PATH = "/themes" + "/draft"; + private static final String LIVE_THEMES_PATH = "/themes" + "/live"; + + private static final String THEME_JSON = "%s/theme.json"; + private static final String THEME_XML = "%s/theme.xml"; + + @Inject + private CcmFiles ccmFiles; + + @Inject + private ThemeManifestUtil manifestUtil; + + @Inject + private ThemeFileInfoUtil themeFileInfoUtil; + + @Override + public List getThemes() { + + try { + + if (!ccmFiles.isDirectory(BASE_PATH) + || !ccmFiles.isDirectory(DRAFT_THEMES_PATH)) { + + return Collections.emptyList(); + } + + return ccmFiles + .listFiles(DRAFT_THEMES_PATH) + .stream() + .map(themePath -> readInfo(themePath)) + .filter(info -> info.isPresent()) + .map(info -> info.get()) + .collect(Collectors.toList()); + + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + + @Override + public List getLiveThemes() { + + try { + if (!ccmFiles.isDirectory(BASE_PATH) + || !ccmFiles.isDirectory(DRAFT_THEMES_PATH)) { + + return Collections.emptyList(); + } + + return ccmFiles + .listFiles(LIVE_THEMES_PATH) + .stream() + .map(themePath -> readInfo(themePath)) + .filter(info -> info.isPresent()) + .map(info -> info.get()) + .collect(Collectors.toList()); + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + + @Override + public Optional getThemeInfo(final String theme, + final ThemeVersion version) { + + final String themePath = createThemePath(theme, version); + return readInfo(themePath); + } + + @Override + public boolean providesTheme(final String theme, + final ThemeVersion version) { + + final String themePath = createThemePath(theme, version); + + try { + return ccmFiles.existsFile(themePath); + } catch (FileAccessException | InsufficientPermissionsException ex) { + throw new UnexpectedErrorException(ex); + } + } + + @Override + public List listThemeFiles(final String theme, + final ThemeVersion version, + final String path) { + + final String themePath = createThemePath(theme, version); + final String filePath = String.join(themePath, path, "/"); + + try { + + return ccmFiles + .listFiles(filePath) + .stream() + .map(currentPath -> buildThemeFileInfo(currentPath)) + .collect(Collectors.toList()); + + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + + @Override + public Optional getThemeFileAsStream( + final String theme, final ThemeVersion version, final String path) { + + final String themePath = createThemePath(theme, version); + final String filePath = String.join(theme, path, "/"); + + try { + if (ccmFiles.existsFile(path)) { + return Optional.empty(); + } else { + return Optional.of(ccmFiles.createInputStream(filePath)); + } + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + + @Override + public OutputStream getOutputStreamForThemeFile(final String theme, + final String path) { + + final String themePath = createThemePath(theme, ThemeVersion.DRAFT); + final String filePath = String.join(themePath, path, "/"); + + try { + + return ccmFiles.createOutputStream(filePath); + + } catch (FileAccessException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + + @Override + public void deleteThemeFile(final String theme, final String path) { + + final String themePath = createThemePath(theme, ThemeVersion.DRAFT); + final String filePath = String.join(themePath, path, "/"); + + try { + ccmFiles.deleteFile(filePath, true); + } catch (FileAccessException + | FileDoesNotExistException + | DirectoryNotEmptyException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + + } + + @Override + public boolean supportsChanges() { + return true; + } + + @Override + public boolean supportsDraftThemes() { + return true; + } + + @Override + public void publishTheme(final String theme) { + + final String draftThemePath = createThemePath(theme, + ThemeVersion.DRAFT); + final String liveThemePath = createThemePath(theme, + ThemeVersion.LIVE); + + try { + ccmFiles.copyFile(draftThemePath, liveThemePath, true); + } catch (FileAccessException ex) { + throw new UnexpectedErrorException(); + } + + } + + private String createThemePath(final String theme, + final ThemeVersion version) { + + switch (version) { + + case DRAFT: + return String.format(DRAFT_THEMES_PATH, theme); + case LIVE: + return String.format(LIVE_THEMES_PATH, theme); + default: + throw new IllegalArgumentException(String + .format("Illegal argument for ThemeVersion \"%s\".", + version)); + } + } + + private Optional readInfo(final String themePath) { + + final ThemeManifest manifest; + try { + final InputStream inputStream = ccmFiles + .createInputStream(String.format(THEME_JSON, + themePath)); + if (ccmFiles.existsFile(String.format(THEME_JSON, + themePath))) { + + manifest = manifestUtil.loadManifest(inputStream, "theme.json"); + } else if (ccmFiles.existsFile(String.format(THEME_XML, + themePath))) { + manifest = manifestUtil.loadManifest(inputStream, "theme.xml"); + } else { + return Optional.empty(); + } + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + + final ThemeInfo themeInfo = new ThemeInfo(); + themeInfo.setManifest(manifest); + + return Optional.of(themeInfo); + } + + private ThemeFileInfo buildThemeFileInfo(final String filePath) { + + final ThemeFileInfo fileInfo = new ThemeFileInfo(); + + try { + fileInfo.setDirectory(ccmFiles.isDirectory(filePath)); + fileInfo.setMimeType(ccmFiles.getMimeType(filePath)); + fileInfo.setName(filePath); + fileInfo.setWritable(true); + + return fileInfo; + } catch (FileAccessException + | FileDoesNotExistException + | InsufficientPermissionsException ex) { + + throw new UnexpectedErrorException(ex); + } + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java index fd5521ad5..20abbf3fb 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java @@ -51,8 +51,6 @@ import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; -import javax.json.JsonStructure; -import javax.json.JsonValue; /** * @@ -61,7 +59,7 @@ import javax.json.JsonValue; @RequestScoped public class StaticThemeProvider implements ThemeProvider { - private static final long serialVersionUID = 7174370298224448067L; + private static final long serialVersionUID = 1L; private static final Logger LOGGER = LogManager.getLogger( StaticThemeProvider.class); diff --git a/ccm-xafilesystemadapter/src/main/java/org/libreccm/files/XAFileSystemAdapter.java b/ccm-xafilesystemadapter/src/main/java/org/libreccm/files/XAFileSystemAdapter.java index 2f3b6618e..b6cc9d4ac 100644 --- a/ccm-xafilesystemadapter/src/main/java/org/libreccm/files/XAFileSystemAdapter.java +++ b/ccm-xafilesystemadapter/src/main/java/org/libreccm/files/XAFileSystemAdapter.java @@ -14,6 +14,7 @@ import org.xadisk.filesystem.exceptions.LockingFailedException; import org.xadisk.filesystem.exceptions.NoTransactionAssociatedException; import java.io.File; +import java.io.IOException; import javax.resource.ResourceException; @@ -23,26 +24,30 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Optional; +import javax.activation.MimetypesFileTypeMap; import javax.enterprise.context.RequestScoped; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.Transactional; /** - * An implementation of the {@link FileSystemAdapter} which uses XADisk to provides - * a transaction safe access to the file system. + * An implementation of the {@link FileSystemAdapter} which uses XADisk to + * provides a transaction safe access to the file system. * * This {@link FileSystemAdapter} requires that XADisk.rar is deployed into the * application server and that a resource adapter is configured to provided - * access to the file system. Please refer the documentation of this module - * for more information. + * access to the file system. Please refer the documentation of this module for + * more information. * * @see http://xadisk.java.net - * + * * @author Jens Pelzetter */ @RequestScoped @@ -136,8 +141,8 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (InterruptedException - | LockingFailedException - | NoTransactionAssociatedException ex) { + | LockingFailedException + | NoTransactionAssociatedException ex) { throw new FileAccessException(path, ex); } @@ -159,10 +164,10 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (org.xadisk.filesystem.exceptions.FileAlreadyExistsException - | FileNotExistsException - | LockingFailedException - | NoTransactionAssociatedException - | InterruptedException ex) { + | FileNotExistsException + | LockingFailedException + | NoTransactionAssociatedException + | InterruptedException ex) { throw new FileAccessException(path, ex); } } @@ -175,10 +180,10 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (FileNotExistsException - | FileUnderUseException - | InterruptedException - | LockingFailedException - | NoTransactionAssociatedException ex) { + | FileUnderUseException + | InterruptedException + | LockingFailedException + | NoTransactionAssociatedException ex) { throw new FileAccessException(path, ex); } @@ -198,17 +203,74 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (LockingFailedException - | NoTransactionAssociatedException - | InterruptedException ex) { + | NoTransactionAssociatedException + | InterruptedException ex) { throw new FileAccessException(path, ex); } } + @Override + public String getMimeType(final String path) throws FileAccessException { + + final File file = new File(path); + + return MimetypesFileTypeMap + .getDefaultFileTypeMap() + .getContentType(file); + } + + @Override + public long getSize(final String path) throws FileAccessException { + + final XADiskConnection connection = connect(); + + final File file = new File(path); + try { + return connection.getFileLength(file); + } catch (FileNotExistsException + | InsufficientPermissionOnFileException + | LockingFailedException + | NoTransactionAssociatedException + | InterruptedException ex) { + + throw new FileAccessException(path, ex); + } + } + + @Override + public void copy(final String sourcePath, + final String targetPath, + boolean recursive) throws FileAccessException { + + final XADiskConnection connection = connect(); + final File sourceFile = new File(sourcePath); + final File targetFile = new File(targetPath); + + try { + if (connection.fileExists(targetFile)) { + connection.deleteFile(targetFile); + connection.copyFile(sourceFile, targetFile); + } + connection.copyFile(sourceFile, targetFile); + } catch (org.xadisk.filesystem.exceptions.DirectoryNotEmptyException + | org.xadisk.filesystem.exceptions.FileAlreadyExistsException + | FileNotExistsException + | FileUnderUseException + | InsufficientPermissionOnFileException + | InterruptedException + | LockingFailedException + | NoTransactionAssociatedException ex) { + + throw new FileAccessException(targetPath, ex); + } + } + @Transactional(Transactional.TxType.REQUIRED) @Override - public boolean isDirectory(final String path) throws FileAccessException, - FileDoesNotExistException, - InsufficientPermissionsException { + public boolean isDirectory(final String path) + throws FileAccessException, + FileDoesNotExistException, + InsufficientPermissionsException { final XADiskConnection connection = connect(); final File file = new File(path); @@ -218,8 +280,8 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (LockingFailedException - | NoTransactionAssociatedException - | InterruptedException ex) { + | NoTransactionAssociatedException + | InterruptedException ex) { throw new FileAccessException(path, ex); } } @@ -240,9 +302,9 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (FileNotExistsException - | LockingFailedException - | InterruptedException - | NoTransactionAssociatedException ex) { + | LockingFailedException + | InterruptedException + | NoTransactionAssociatedException ex) { throw new FileAccessException(path, ex); } } @@ -264,8 +326,8 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (LockingFailedException - | NoTransactionAssociatedException - | InterruptedException ex) { + | NoTransactionAssociatedException + | InterruptedException ex) { throw new FileAccessException(path, ex); } @@ -295,9 +357,9 @@ public class XAFileSystemAdapter implements FileSystemAdapter { } catch (InsufficientPermissionOnFileException ex) { throw new InsufficientPermissionsException(path, ex); } catch (FileUnderUseException - | LockingFailedException - | NoTransactionAssociatedException - | InterruptedException ex) { + | LockingFailedException + | NoTransactionAssociatedException + | InterruptedException ex) { throw new FileAccessException(path, ex); } }