CcM NG: First part for ThemeManager UI

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5691 8810af33-2d31-482b-a856-94f89814c4df
ccm-docs
jensp 2018-09-13 18:15:56 +00:00
parent d166fa9bf9
commit ac86ccab91
10 changed files with 470 additions and 53 deletions

View File

@ -434,14 +434,6 @@ public class NIOFileSystemAdapter implements FileSystemAdapter {
throw new InsufficientPermissionsException(path);
}
try {
if (Files.isDirectory(nioPath) && Files.list(nioPath).count() > 0) {
throw new DirectoryNotEmptyException(path);
}
} catch (IOException ex) {
throw new FileAccessException(path, ex);
}
if (recursively && Files.isDirectory(nioPath)) {
final List<String> files;
try {
@ -455,7 +447,11 @@ public class NIOFileSystemAdapter implements FileSystemAdapter {
for (final String file : files) {
deleteFile(file, recursively);
}
deleteFile(path);
} else {
deleteFile(path);
}
}

View File

@ -22,15 +22,20 @@ import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.files.CcmFiles;
import org.libreccm.files.DirectoryNotEmptyException;
import org.libreccm.files.FileAccessException;
import org.libreccm.files.FileAlreadyExistsException;
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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -84,8 +89,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
.collect(Collectors.toList());
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -96,7 +101,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
try {
if (!ccmFiles.isDirectory(BASE_PATH)
|| !ccmFiles.isDirectory(DRAFT_THEMES_PATH)) {
|| !ccmFiles.isDirectory(LIVE_THEMES_PATH)) {
return Collections.emptyList();
}
@ -109,8 +114,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
.map(info -> info.get())
.collect(Collectors.toList());
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -137,6 +142,86 @@ public class FileSystemThemeProvider implements ThemeProvider {
}
}
@Override
public ThemeInfo createTheme(final String themeName) {
Objects.requireNonNull(themeName);
if (themeName.isEmpty() || themeName.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a theme can't be empty.");
}
try {
ccmFiles.createDirectory(String.format(DRAFT_THEMES_PATH + "/%s",
themeName));
} catch (FileAccessException
| FileAlreadyExistsException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
final ThemeManifest manifest = new ThemeManifest();
manifest.setName(themeName);
final OutputStream outputStream;
try {
outputStream = ccmFiles.createOutputStream(
String.format(DRAFT_THEMES_PATH + "/%s/"
+ ThemeConstants.THEME_MANIFEST_JSON,
themeName));
} catch (FileAccessException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
try(final OutputStreamWriter writer = new OutputStreamWriter(
outputStream, StandardCharsets.UTF_8)) {
writer
.append(manifestUtil
.serializeManifest(manifest,
ThemeConstants.THEME_MANIFEST_JSON));
writer.flush();
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
return getThemeInfo(themeName, ThemeVersion.DRAFT).get();
}
@Override
public void deleteTheme(final String themeName) {
Objects.requireNonNull(themeName);
if (themeName.isEmpty() || themeName.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a theme can't be empty.");
}
final Optional<ThemeInfo> liveTheme = getLiveThemes()
.stream()
.filter(theme -> theme.getName().equals(themeName))
.findAny();
if (liveTheme.isPresent()) {
throw new IllegalArgumentException(String
.format("The theme \"%s\" is live and can't be deleted.",
themeName));
}
try {
ccmFiles.deleteFile(String.format(DRAFT_THEMES_PATH + "/%s",
themeName),
true);
} catch (FileAccessException
| FileDoesNotExistException
| DirectoryNotEmptyException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
}
@Override
public List<ThemeFileInfo> listThemeFiles(final String theme,
final ThemeVersion version,
@ -154,8 +239,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
.collect(Collectors.toList());
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -166,7 +251,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
final String theme, final ThemeVersion version, final String path) {
final String themePath = createThemePath(theme, version);
final String filePath = String.join(theme, path, "/");
final String filePath = String.join(themePath, path, "/");
try {
if (ccmFiles.existsFile(path)) {
@ -175,8 +260,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
return Optional.of(ccmFiles.createInputStream(filePath));
}
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -194,7 +279,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
return ccmFiles.createOutputStream(filePath);
} catch (FileAccessException
| InsufficientPermissionsException ex) {
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -209,9 +294,9 @@ public class FileSystemThemeProvider implements ThemeProvider {
try {
ccmFiles.deleteFile(filePath, true);
} catch (FileAccessException
| FileDoesNotExistException
| DirectoryNotEmptyException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| DirectoryNotEmptyException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -258,9 +343,9 @@ public class FileSystemThemeProvider implements ThemeProvider {
switch (version) {
case DRAFT:
return String.format(DRAFT_THEMES_PATH, theme);
return String.format(DRAFT_THEMES_PATH + "/%s", theme);
case LIVE:
return String.format(LIVE_THEMES_PATH, theme);
return String.format(LIVE_THEMES_PATH + "/%s", theme);
default:
throw new IllegalArgumentException(String
.format("Illegal argument for ThemeVersion \"%s\".",
@ -286,8 +371,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
return Optional.empty();
}
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@ -310,8 +395,8 @@ public class FileSystemThemeProvider implements ThemeProvider {
return fileInfo;
} catch (FileAccessException
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
| FileDoesNotExistException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}

View File

@ -254,6 +254,22 @@ public class StaticThemeProvider implements ThemeProvider {
return manifestJsonUrl != null || manifestXmlUrl != null;
}
@Override
public ThemeInfo createTheme(final String theme) {
throw new UnsupportedOperationException(String.format(
"The ThemeProvider %s does support the creation of new themes.",
getClass().getName()));
}
@Override
public void deleteTheme(final String theme) {
throw new UnsupportedOperationException(String.format(
"The ThemeProvider %s does support the deltion of themes.",
getClass().getName()));
}
@Override
public List<ThemeFileInfo> listThemeFiles(final String theme,
final ThemeVersion version,

View File

@ -79,6 +79,36 @@ public interface ThemeProvider extends Serializable {
*/
boolean providesTheme(String theme, ThemeVersion version);
/**
* Creates a new theme.
*
* The theme should be empty besides the manifest file. If a theme with the
* provided name already exists implementations should throw an
* {@link IllegalArgumentException}.
*
* {@code ThemeProvider} implementations which do not support the the
* creation of new themes the implementation of the method should throw a
* {@link UnsupportedOperationException}.
*
* @param themeName The name of the new theme.
*
* @return The {@link ThemeInfo} about the new theme.
*/
ThemeInfo createTheme(String themeName);
/**
* Deletes a theme and all its content.
*
* If the is live implementations should throw an exception.
*
* {@code ThemeProvider} implementations which do not support the the
* deletion of themes the implementation of the method should throw a
* {@link UnsupportedOperationException}.
*
* @param themeName The theme to delete.
*/
void deleteTheme(String themeName);
/**
* List all files in a theme at the specified path.
*

View File

@ -199,15 +199,16 @@ public class Themes implements Serializable {
* List all files in a theme at the specified path
*
* @param theme The theme from which the file is retrieved.
* @param path The path of the file relative to the root directory of the
* @param path The path of the file relative to the root directory of the
* theme.
*
* @return A list of all files in the provided directory. If there is no
* such path in the theme the list is empty. If the path is the path
* of a file and not a directory the list should have one element,
* the data about the file itself.
*/
public List<ThemeFileInfo> listThemesFiles(final ThemeInfo theme,
final String path) {
public List<ThemeFileInfo> listThemeFiles(final ThemeInfo theme,
final String path) {
final Instance<? extends ThemeProvider> forTheme = providers.select(
theme.getProvider());
@ -229,8 +230,8 @@ public class Themes implements Serializable {
public void deleteThemeFile(final ThemeInfo theme,
final String path) {
final Instance<? extends ThemeProvider> forTheme = providers.select(
theme.getProvider());
final Instance<? extends ThemeProvider> forTheme = providers
.select(theme.getProvider());
if (forTheme.isUnsatisfied()) {
LOGGER.error("ThemeProvider \"{}\" not found.",

View File

@ -112,6 +112,39 @@ public class DatabaseThemeProvider implements ThemeProvider {
.isPresent();
}
@Override
public ThemeInfo createTheme(final String themeName) {
Objects.requireNonNull(themeName);
if (themeName.isEmpty() || themeName.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a theme can't be empty.");
}
final Theme theme = themeManager.createTheme(themeName);
return createThemeInfo(theme);
}
@Override
public void deleteTheme(final String themeName) {
Objects.requireNonNull(themeName);
if (themeName.isEmpty() || themeName.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a theme can't be empty.");
}
final Theme theme = themeRepository
.findThemeByName(themeName, ThemeVersion.DRAFT)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No theme with name \"%s\" is managed by this provider.",
themeName)));
themeManager.deleteTheme(theme);
}
@Override
public List<ThemeFileInfo> listThemeFiles(final String themeName,
final ThemeVersion version,
@ -270,7 +303,7 @@ public class DatabaseThemeProvider implements ThemeProvider {
if (file instanceof DataFile) {
fileManager.delete(file);
} else if(file instanceof Directory) {
} else if (file instanceof Directory) {
fileManager.deleteRecursive(file);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.manager;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
/**
* JAX-RS application for managing themes.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationPath("/thememanager")
public class ThemeManager extends Application {
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(Themes.class);
return classes;
}
}

View File

@ -0,0 +1,210 @@
/*
* 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.manager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.theming.ThemeInfo;
import org.libreccm.theming.ThemeProvider;
import org.libreccm.theming.ThemeVersion;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonWriter;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static javassist.CtClass.*;
import static org.reflections.util.Utils.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path("/")
public class Themes implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LogManager.getLogger(Themes.class);
@Inject
@Any
private Instance<ThemeProvider> providers;
@Inject
private Themes themes;
@GET
@Path("/providers")
@Produces(MediaType.APPLICATION_JSON)
public String getThemeProviders() {
final List<ThemeProvider> providersList = new ArrayList<>();
providers
.forEach(provider -> providersList.add(provider));
final JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder();
providersList
.stream()
.filter(provider -> provider.supportsChanges()
&& provider.supportsDraftThemes())
.map(this::getProviderName)
.forEach(jsonArrayBuilder::add);
final StringWriter writer = new StringWriter();
final JsonWriter jsonWriter = Json.createWriter(writer);
jsonWriter.writeArray(jsonArrayBuilder.build());
return writer.toString();
}
@GET
@Path("/themes")
@Produces(MediaType.APPLICATION_JSON)
public List<ThemeInfo> getAvailableThemes() {
final List<ThemeInfo> availableThemes = new ArrayList<>();
for (final ThemeProvider provider : providers) {
if (provider.supportsChanges() && provider.supportsDraftThemes()) {
availableThemes.addAll(provider.getThemes());
}
}
return availableThemes;
}
@GET
@Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON)
public ThemeInfo getTheme(@PathParam("theme") final String themeName) {
for (final ThemeProvider provider : providers) {
if (provider.providesTheme(themeName, ThemeVersion.DRAFT)) {
return provider
.getThemeInfo(themeName, ThemeVersion.DRAFT)
.orElseThrow(() -> new WebApplicationException(
Response.Status.NOT_FOUND));
}
}
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
@PUT
@Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("unchecked")
public ThemeInfo createTheme(
@PathParam("theme") final String themeName,
@QueryParam("provider") final String providerName) {
Objects.requireNonNull(themeName);
Objects.requireNonNull(providerName);
if (themeName.isEmpty() || themeName.matches("\\s*")) {
throw new WebApplicationException("No name for new theme provided.",
Response.Status.BAD_REQUEST);
}
if (providerName.isEmpty() || providerName.matches("\\s*")) {
throw new WebApplicationException(
"No provider for new theme provided.",
Response.Status.BAD_REQUEST);
}
final Class<ThemeProvider> providerClass;
try {
providerClass = (Class<ThemeProvider>) Class.forName(providerName);
} catch (ClassNotFoundException ex) {
throw new WebApplicationException(
String.format("No provider with name \"%s\" available.",
providerName),
Response.Status.INTERNAL_SERVER_ERROR);
}
final ThemeProvider provider = providers.select(providerClass).get();
return provider.createTheme(themeName);
}
@DELETE
@Path("/themes/{theme}")
public void deleteTheme(@PathParam("theme") final String themeName) {
Objects.requireNonNull(themeName);
final List<ThemeProvider> providersList = new ArrayList<>();
providers
.forEach(provider -> providersList.add(provider));
final Optional<ThemeProvider> provider = providersList
.stream()
.filter(current -> current.providesTheme(themeName,
ThemeVersion.DRAFT))
.findAny();
if (provider.isPresent()) {
provider.get().deleteTheme(themeName);
} else {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}
private String getProviderName(final ThemeProvider provider) {
if (provider
.getClass()
.getCanonicalName()
.toLowerCase()
.contains("$proxy")) {
final String name = provider.getClass().getCanonicalName();
return name.substring(0, name.toLowerCase().indexOf("$proxy"));
} else {
return provider.getClass().getName();
}
}
}

View File

@ -21,6 +21,7 @@ package org.libreccm.theming.manifest;
import static org.libreccm.theming.ThemeConstants.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
@ -149,6 +150,7 @@ public class ThemeManifestUtil implements Serializable {
}
mapper.registerModule(new JaxbAnnotationModule());
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
final StringWriter writer = new StringWriter();
try {

View File

@ -202,7 +202,7 @@ public class ThemeFiles {
"No theme with name \"%s\" exists.",
theme)));
final List<ThemeFileInfo> fileInfos = themes.listThemesFiles(themeInfo,
final List<ThemeFileInfo> fileInfos = themes.listThemeFiles(themeInfo,
path);
final MultiStatus result;
if (fileInfos.isEmpty()) {