CcmNG: ThemeProvider serving themes from the file system using CcmFiles

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5678 8810af33-2d31-482b-a856-94f89814c4df
jensp 2018-08-26 15:21:37 +00:00
parent 9fdf1bdaa0
commit afc596e2ea
6 changed files with 562 additions and 34 deletions

View File

@ -385,6 +385,24 @@ public class CcmFiles {
return getFileSystemAdapter().listFiles(getDataPath(path)); 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 * Delete a file or directory. If the file is a directory the directory must
* be empty. * be empty.

View File

@ -135,6 +135,14 @@ public interface FileSystemAdapter {
throws FileAccessException, throws FileAccessException,
InsufficientPermissionsException; 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. * checks if the provided path points to a directory.
* *

View File

@ -20,15 +20,31 @@ package org.libreccm.files;
import org.libreccm.configuration.ConfigurationManager; 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.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -190,6 +206,122 @@ public class NIOFileSystemAdapter implements FileSystemAdapter {
return Files.exists(nioPath); 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<Path>() {
@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 @Override
public boolean isDirectory(final String path) throws FileAccessException, public boolean isDirectory(final String path) throws FileAccessException,
FileDoesNotExistException, FileDoesNotExistException,

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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<ThemeInfo> 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<ThemeInfo> 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<ThemeInfo> 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<ThemeFileInfo> 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<InputStream> 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<ThemeInfo> 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);
}
}
}

View File

@ -51,8 +51,6 @@ import javax.json.Json;
import javax.json.JsonArray; import javax.json.JsonArray;
import javax.json.JsonObject; import javax.json.JsonObject;
import javax.json.JsonReader; import javax.json.JsonReader;
import javax.json.JsonStructure;
import javax.json.JsonValue;
/** /**
* *
@ -61,7 +59,7 @@ import javax.json.JsonValue;
@RequestScoped @RequestScoped
public class StaticThemeProvider implements ThemeProvider { public class StaticThemeProvider implements ThemeProvider {
private static final long serialVersionUID = 7174370298224448067L; private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LogManager.getLogger( private static final Logger LOGGER = LogManager.getLogger(
StaticThemeProvider.class); StaticThemeProvider.class);

View File

@ -14,6 +14,7 @@ import org.xadisk.filesystem.exceptions.LockingFailedException;
import org.xadisk.filesystem.exceptions.NoTransactionAssociatedException; import org.xadisk.filesystem.exceptions.NoTransactionAssociatedException;
import java.io.File; import java.io.File;
import java.io.IOException;
import javax.resource.ResourceException; import javax.resource.ResourceException;
@ -23,23 +24,27 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; 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.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.activation.MimetypesFileTypeMap;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.naming.InitialContext; import javax.naming.InitialContext;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
* An implementation of the {@link FileSystemAdapter} which uses XADisk to provides * An implementation of the {@link FileSystemAdapter} which uses XADisk to
* a transaction safe access to the file system. * provides a transaction safe access to the file system.
* *
* This {@link FileSystemAdapter} requires that XADisk.rar is deployed into the * This {@link FileSystemAdapter} requires that XADisk.rar is deployed into the
* application server and that a resource adapter is configured to provided * application server and that a resource adapter is configured to provided
* access to the file system. Please refer the documentation of this module * access to the file system. Please refer the documentation of this module for
* for more information. * more information.
* *
* @see http://xadisk.java.net * @see http://xadisk.java.net
* *
@ -204,9 +209,66 @@ public class XAFileSystemAdapter implements FileSystemAdapter {
} }
} }
@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) @Transactional(Transactional.TxType.REQUIRED)
@Override @Override
public boolean isDirectory(final String path) throws FileAccessException, public boolean isDirectory(final String path)
throws FileAccessException,
FileDoesNotExistException, FileDoesNotExistException,
InsufficientPermissionsException { InsufficientPermissionsException {