CCM NG: Next part of DatabaseThemeProvider

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5342 8810af33-2d31-482b-a856-94f89814c4df
jensp 2018-03-09 19:01:21 +00:00
parent 7878da2ab9
commit 386a7763b3
11 changed files with 888 additions and 68 deletions

View File

@ -92,14 +92,16 @@ public interface ThemeProvider extends Serializable {
* should throw an NullPointerException if {@code null} is
* provided as path.
*
* @return A list of all files in the provided directory. If there is 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.
* @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.
*
* @throws IllegalArgumentException If {@code theme} is an empty string,
* if there is no theme with the name provided by {@code theme} or
* if there is no file/directory with the provided path in the theme.
* @throws IllegalArgumentException If {@code theme} is an empty string, if
* there is no theme with the name provided
* by {@code theme} or if there is no
* file/directory with the provided path in
* the theme.
*/
List<ThemeFileInfo> listThemeFiles(String theme,
ThemeVersion version,

View File

@ -0,0 +1,245 @@
/*
* 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.db;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.theming.ThemeConstants;
import org.libreccm.theming.ThemeFileInfo;
import org.libreccm.theming.ThemeInfo;
import org.libreccm.theming.ThemeProvider;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.manifest.ThemeManifest;
import org.libreccm.theming.manifest.ThemeManifestUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
/**
* An implementation of {@link ThemeProvider} which serves themes from the
* database.
*
* Supports all operations.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class DatabaseThemeProvider implements ThemeProvider {
private static final long serialVersionUID = -8661840420214119753L;
@Inject
private ThemeFileManager fileManager;
@Inject
private ThemeFileRepository fileRepository;
@Inject
private ThemeManifestUtil manifestUtil;
@Inject
private ThemeManager themeManager;
@Inject
private ThemeRepository themeRepository;
@Override
@Transactional(Transactional.TxType.REQUIRED)
public List<ThemeInfo> getThemes() {
return themeRepository
.findAll(ThemeVersion.DRAFT)
.stream()
.map(this::createThemeInfo)
.sorted()
.collect(Collectors.toList());
}
@Override
public List<ThemeInfo> getLiveThemes() {
return themeRepository
.findAll(ThemeVersion.LIVE)
.stream()
.map(this::createThemeInfo)
.sorted()
.collect(Collectors.toList());
}
@Override
public Optional<ThemeInfo> getThemeInfo(final String themeName,
final ThemeVersion version) {
return themeRepository
.findThemeByName(themeName, version)
.map(this::createThemeInfo);
}
@Override
public boolean providesTheme(final String theme,
final ThemeVersion version) {
return themeRepository
.findThemeByName(theme, version)
.isPresent();
}
@Override
public List<ThemeFileInfo> listThemeFiles(final String themeName,
final ThemeVersion version,
final String path) {
final Theme theme = themeRepository
.findThemeByName(path, version)
.orElseThrow(() -> new IllegalArgumentException(String
.format("No Theme \"%s\" in the database.", themeName)));
final Optional<ThemeFile> themeFile = fileRepository
.findByPath(theme, path, version);
final List<ThemeFileInfo> result = new ArrayList<>();
if (themeFile.isPresent()) {
if (themeFile.get() instanceof DataFile) {
result.add(themeFile.map(this::createThemeFileInfo).get());
} else if (themeFile.get() instanceof Directory) {
final Directory directory = (Directory) themeFile.get();
result.addAll(directory
.getFiles()
.stream()
.map(this::createThemeFileInfo)
.collect(Collectors.toList()));
} else {
throw new IllegalArgumentException(String
.format("Unknown type \"%s\".",
themeFile.get().getClass().getName()));
}
}
return result;
}
@Override
public Optional<InputStream> getThemeFileAsStream(String theme,
ThemeVersion version,
String path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public OutputStream getOutputStreamForThemeFile(String theme, String path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public boolean supportsChanges() {
return true;
}
@Override
public boolean supportsDraftThemes() {
return true;
}
@Override
public void publishTheme(final String themeName) {
themeRepository
.findThemeByName(themeName, ThemeVersion.DRAFT)
.ifPresent(themeManager::publishTheme);
}
private ThemeInfo createThemeInfo(final Theme theme) {
Objects.requireNonNull(theme);
final Optional<ThemeFile> manifestFileJson = fileRepository
.findByNameAndParent(ThemeConstants.THEME_MANIFEST_JSON,
theme.getRootDirectory());
final Optional<ThemeFile> manifestFileXml = fileRepository
.findByNameAndParent(ThemeConstants.THEME_MANIFEST_XML,
theme.getRootDirectory());
final DataFile manifestFile;
final String filename;
if (manifestFileJson.isPresent()) {
manifestFile = (DataFile) manifestFileJson.get();
filename = ThemeConstants.THEME_MANIFEST_JSON;
} else if (manifestFileXml.isPresent()) {
manifestFile = (DataFile) manifestFileXml.get();
filename = ThemeConstants.THEME_MANIFEST_XML;
} else {
throw new IllegalArgumentException(String
.format("No manifest file found for theme \"%s\".",
theme.getName()));
}
try (final InputStream inputStream = new ByteArrayInputStream(
manifestFile.getData())) {
final ThemeManifest manifest = manifestUtil
.loadManifest(inputStream, filename);
final ThemeInfo themeInfo = new ThemeInfo();
themeInfo.setManifest(manifest);
themeInfo.setProvider(getClass());
themeInfo.setVersion(theme.getVersion());
return themeInfo;
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
private ThemeFileInfo createThemeFileInfo(final ThemeFile file) {
final ThemeFileInfo fileInfo = new ThemeFileInfo();
fileInfo.setName(file.getName());
fileInfo.setWritable(true);
if (file instanceof DataFile) {
final DataFile dataFile = (DataFile) file;
fileInfo.setDirectory(false);
fileInfo.setMimeType(dataFile.getType());
fileInfo.setSize(dataFile.getSize());
}
if (file instanceof Directory) {
fileInfo.setDirectory(true);
}
return fileInfo;
}
}

View File

@ -23,7 +23,6 @@ import org.libreccm.core.CoreConstants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.OneToMany;

View File

@ -19,6 +19,7 @@
package org.libreccm.theming.db;
import org.libreccm.core.CoreConstants;
import org.libreccm.theming.ThemeVersion;
import java.io.Serializable;
import java.util.Objects;
@ -43,6 +44,9 @@ import javax.persistence.Table;
@Entity
@Table(name = "THEMES", schema = CoreConstants.DB_SCHEMA)
@NamedQueries({
@NamedQuery(name = "Theme.findAllForVersion",
query = "SELECT t FROM Theme t WHERE t.version = :version")
,
@NamedQuery(name = "Theme.findByUuid",
query = "SELECT t FROM Theme t "
+ "WHERE t.uuid = :uuid "

View File

@ -19,6 +19,7 @@
package org.libreccm.theming.db;
import org.libreccm.core.CoreConstants;
import org.libreccm.theming.ThemeVersion;
import java.io.Serializable;
import java.util.Objects;
@ -88,6 +89,10 @@ public class ThemeFile implements Serializable {
@NotNull
private String path;
@ManyToOne
@JoinColumn(name = "THEME_ID")
private Theme theme;
@ManyToOne
@JoinColumn(name = "PARENT_DIRECTORY_ID")
private Directory parent;
@ -132,6 +137,14 @@ public class ThemeFile implements Serializable {
this.version = version;
}
public Theme getTheme() {
return theme;
}
protected void setTheme(final Theme theme) {
this.theme = theme;
}
public Directory getParent() {
return parent;
}
@ -147,6 +160,7 @@ public class ThemeFile implements Serializable {
hash = 37 * hash + Objects.hashCode(name);
hash = 37 * hash + Objects.hashCode(path);
hash = 37 * hash + Objects.hashCode(uuid);
hash = 37 * hash + Objects.hashCode(theme);
hash = 37 * hash + Objects.hashCode(parent);
return hash;
}
@ -178,6 +192,10 @@ public class ThemeFile implements Serializable {
if (!Objects.equals(uuid, other.getUuid())) {
return false;
}
if (!Objects.equals(theme, other.getTheme())) {
return false;
}
return Objects.equals(parent, other.getParent());
}
@ -195,13 +213,17 @@ public class ThemeFile implements Serializable {
+ "fileId = %d, "
+ "name = \"%s\", "
+ "path = \"%s\", "
+ "uuid = \"%s\"%s"
+ "uuid = \"%s\", "
+ "theme = \"%s\", "
+ "parent = \"%s\"%s"
+ " }",
super.toString(),
fileId,
name,
path,
uuid,
Objects.toString(theme),
Objects.toString(parent),
data);
}

View File

@ -19,10 +19,15 @@
package org.libreccm.theming.db;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.ThemingPrivileges;
import java.util.Date;
import java.util.Objects;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
/**
* Provides methods for managing the files of the theme stored in the database.
@ -35,32 +40,93 @@ public class ThemeFileManager {
@Inject
private ThemeFileRepository fileRepository;
@Inject
private ThemeRepository themeRepository;
/**
* Creates a new {@link DataFile}.
* Creates a new, empty {@link DataFile}.
*
* @param parent The directory in which the {@link DataFile} is created.
* @param theme The {@link Theme} to which the file belongs.
* @param parent The {@link Directory} in which the {@link DataFile} is
* created.
* @param name The name of the new {@link DataFile}.
*
* @return The new {@link DataFile}.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
public DataFile createDataFile(final Directory parent,
@Transactional(Transactional.TxType.REQUIRED)
public DataFile createDataFile(final Theme theme,
final Directory parent,
final String name) {
throw new UnsupportedOperationException();
Objects.requireNonNull(parent);
Objects.requireNonNull(name);
if (name.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of file can't be empty.");
}
final Date now = new Date();
final String path = String.join("/", parent.getPath(), name);
final DataFile dataFile = new DataFile();
dataFile.setCreationDate(now);
dataFile.setLastModified(now);
dataFile.setName(name);
dataFile.setParent(parent);
dataFile.setPath(path);
dataFile.setTheme(theme);
dataFile.setVersion(ThemeVersion.DRAFT);
parent.addFile(dataFile);
fileRepository.save(dataFile);
fileRepository.save(parent);
themeRepository.save(theme);
return dataFile;
}
/**
* Creates a new {@link Directory}.
*
* @param theme The {@link Theme} to which the file belongs.
* @param parent The parent directory of the new {@link Directory}.
* @param name The name of the new {@link Directory}
*
* @return The new {@link Directory}.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
public Directory createDirectory(final Directory parent,
@Transactional(Transactional.TxType.REQUIRED)
public Directory createDirectory(final Theme theme,
final Directory parent,
final String name) {
throw new UnsupportedOperationException();
Objects.requireNonNull(parent);
Objects.requireNonNull(name);
if (name.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of file can't be empty.");
}
final String path = String.join("/", parent.getPath(), name);
final Directory directory = new Directory();
directory.setName(name);
directory.setParent(parent);
directory.setPath(path);
directory.setTheme(theme);
directory.setVersion(ThemeVersion.DRAFT);
parent.addFile(directory);
fileRepository.save(directory);
fileRepository.save(parent);
themeRepository.save(theme);
return directory;
}
/**
@ -70,8 +136,33 @@ public class ThemeFileManager {
* @param file The {@link ThemeFile} to delete.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public void delete(final ThemeFile file) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
if (file instanceof DataFile) {
final Directory parent = file.getParent();
parent.removeFile(file);
fileRepository.delete(file);
fileRepository.save(parent);
} else if (file instanceof Directory) {
final Directory directory = (Directory) file;
if (directory.getFiles().isEmpty()) {
final Directory parent = file.getParent();
parent.removeFile(file);
fileRepository.delete(file);
fileRepository.save(parent);
} else {
throw new IllegalArgumentException(String
.format("File \"%s\" is a directory and not empty.",
directory.getPath()));
}
} else {
throw new IllegalArgumentException(String
.format("Don't know how handle file type \"%s\".",
file.getClass().getName()));
}
}
/**
@ -84,8 +175,28 @@ public class ThemeFileManager {
* @param file The {@link ThemeFile} to delete.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public void deleteRecursive(final ThemeFile file) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
if (file instanceof DataFile) {
delete(file);
} else if (file instanceof Directory) {
final Directory directory = (Directory) file;
directory
.getFiles()
.forEach(subFile -> deleteRecursive(subFile));
final Directory parent = file.getParent();
parent.removeFile(file);
fileRepository.delete(file);
fileRepository.save(parent);
} else {
throw new IllegalArgumentException(String
.format("Don't know how handle file type \"%s\".",
file.getClass().getName()));
}
}
/**
@ -98,8 +209,13 @@ public class ThemeFileManager {
* @return The newly created copy.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public ThemeFile copy(final ThemeFile file, final Directory target) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
return copy(file, target, file.getName());
}
/**
@ -112,10 +228,73 @@ public class ThemeFileManager {
* @return The copy.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public ThemeFile copy(final ThemeFile file,
final Directory target,
final String nameOfCopy) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
Objects.requireNonNull(nameOfCopy);
if (nameOfCopy.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the copy can't be empty.");
}
target
.getFiles()
.stream()
.filter(subFile -> subFile.getName().equals(nameOfCopy))
.findAny()
.ifPresent(subFile -> {
throw new IllegalArgumentException(String
.format("The target directory \"%s\"already contains a "
+ "file with name \"%s\".",
target.getPath(),
nameOfCopy));
});
if (file instanceof DataFile) {
final DataFile source = (DataFile) file;
final DataFile copy = new DataFile();
final Date now = new Date();
copy.setCreationDate(now);
copy.setData(source.getData());
copy.setLastModified(now);
copy.setName(nameOfCopy);
copy.setParent(target);
copy.setPath(String.join("/", target.getPath(), copy.getName()));
copy.setSize(source.getSize());
copy.setTheme(source.getTheme());
copy.setType(source.getType());
copy.setVersion(source.getVersion());
fileRepository.save(copy);
fileRepository.save(target);
themeRepository.save(copy.getTheme());
return copy;
} else if (file instanceof Directory) {
final Directory source = (Directory) file;
final Directory copy = new Directory();
copy.setName(nameOfCopy);
copy.setParent(target);
copy.setPath(String.join("/", target.getPath(), copy.getName()));
copy.setTheme(source.getTheme());
copy.setVersion(source.getVersion());
fileRepository.save(copy);
fileRepository.save(target);
return copy;
} else {
throw new IllegalArgumentException(String
.format("Don't know how handle file type \"%s\".",
file.getClass().getName()));
}
}
/**
@ -128,9 +307,14 @@ public class ThemeFileManager {
* @return The copy.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public ThemeFile copyRecursive(final ThemeFile file,
final Directory target) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
return copyRecursive(file, target, file.getName());
}
/**
@ -144,10 +328,32 @@ public class ThemeFileManager {
* @return The copy.
*/
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
@Transactional(Transactional.TxType.REQUIRED)
public ThemeFile copyRecursive(final ThemeFile file,
final Directory target,
final String nameOfCopy) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
Objects.requireNonNull(nameOfCopy);
if (nameOfCopy.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a file can't be empty.");
}
final ThemeFile copy = copy(file, target, nameOfCopy);
if (file instanceof Directory) {
final Directory source = (Directory) file;
final Directory copiedDirectory = (Directory) copy;
source
.getFiles()
.forEach(subFile -> copyRecursive(subFile, copiedDirectory));
}
return copy;
}
/**
@ -161,7 +367,11 @@ public class ThemeFileManager {
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
public ThemeFile move(final ThemeFile file,
final Directory target) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
return move(file, target, file.getName());
}
/**
@ -177,7 +387,42 @@ public class ThemeFileManager {
public ThemeFile move(final ThemeFile file,
final Directory target,
final String newName) {
throw new UnsupportedOperationException();
Objects.requireNonNull(file);
Objects.requireNonNull(target);
Objects.requireNonNull(newName);
if (newName.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a file can't be empty.");
}
target
.getFiles()
.stream()
.filter(subFile -> subFile.getName().equals(newName))
.findAny()
.ifPresent(subFile -> {
throw new IllegalArgumentException(String
.format("The target directory \"%s\"already contains a "
+ "file with name \"%s\".",
target.getPath(),
newName));
});
final Directory oldParent = file.getParent();
file.setName(newName);
oldParent.removeFile(file);
target.addFile(file);
file.setParent(target);
fileRepository.save(file);
fileRepository.save(oldParent);
fileRepository.save(target);
return file;
}
}

View File

@ -19,8 +19,17 @@
package org.libreccm.theming.db;
import org.libreccm.core.AbstractEntityRepository;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.theming.ThemeVersion;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;
import javax.enterprise.context.RequestScoped;
import javax.persistence.NoResultException;
@ -55,6 +64,33 @@ public class ThemeFileRepository extends AbstractEntityRepository<Long, ThemeFil
return entity.getFileId() == 0;
}
@Override
public void initNewEntity(final ThemeFile themeFile) {
if (themeFile.getUuid() == null || themeFile.getUuid().isEmpty()) {
themeFile.setUuid(UUID.randomUUID().toString());
}
}
@Override
public void save(final ThemeFile file) {
if (file instanceof DataFile) {
final DataFile dataFile = (DataFile) file;
dataFile.setLastModified(new Date());
try (final InputStream inputStream = new BufferedInputStream(
new ByteArrayInputStream(dataFile.getData()))) {
final String mimeType = URLConnection
.guessContentTypeFromStream(inputStream);
dataFile.setType(mimeType);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
super.save(file);
}
public Optional<ThemeFile> findByUuid(final String uuid,
final ThemeVersion version) {
@ -70,7 +106,8 @@ public class ThemeFileRepository extends AbstractEntityRepository<Long, ThemeFil
}
}
public Optional<ThemeFile> findByPath(final String path,
public Optional<ThemeFile> findByPath(final Theme theme,
final String path,
final ThemeVersion version) {
final TypedQuery<ThemeFile> query = getEntityManager()

View File

@ -19,11 +19,18 @@
package org.libreccm.theming.db;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.theming.ThemeConstants;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.ThemingPrivileges;
import org.libreccm.theming.manifest.ThemeManifest;
import org.libreccm.theming.manifest.ThemeManifestUtil;
import java.util.Objects;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
/**
* Provides methods for managing themes stored in the database.
@ -33,9 +40,15 @@ import javax.inject.Inject;
@RequestScoped
public class ThemeManager {
@Inject
private ThemeManifestUtil manifestUtil;
@Inject
private ThemeRepository themeRepository;
@Inject
private ThemeFileRepository themeFileRepository;
/**
* Creates a new theme, including the root directory and a theme manifest
* file.
@ -47,8 +60,44 @@ public class ThemeManager {
* @return The new theme.
*/
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
@Transactional(Transactional.TxType.REQUIRED)
public Theme createTheme(final String name) {
throw new UnsupportedOperationException();
Objects.requireNonNull(name);
if (name.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of a theme can't be empty.");
}
final Theme theme = new Theme();
theme.setName(name);
theme.setVersion(ThemeVersion.DRAFT);
final Directory root = new Directory();
root.setName(name);
root.setPath("/");
root.setTheme(theme);
final ThemeManifest manifest = new ThemeManifest();
manifest.setName(name);
final DataFile manifestFile = new DataFile();
manifestFile.setName(ThemeConstants.THEME_MANIFEST_JSON);
manifestFile.setPath(String.format("/%s",
ThemeConstants.THEME_MANIFEST_JSON));
manifestFile.setTheme(theme);
final String manifestData = manifestUtil
.serializeManifest(manifest, ThemeConstants.THEME_MANIFEST_JSON);
manifestFile.setData(manifestData.getBytes());
root.addFile(manifestFile);
themeRepository.save(theme);
themeFileRepository.save(root);
themeFileRepository.save(manifestFile);
return theme;
}
/**
@ -57,8 +106,71 @@ public class ThemeManager {
* @param theme The theme to delete.
*/
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
@Transactional(Transactional.TxType.REQUIRED)
public void deleteTheme(final Theme theme) {
throw new UnsupportedOperationException();
Objects.requireNonNull(theme);
if (isLive(theme)) {
throw new IllegalArgumentException(String
.format("The theme \"%s\" is live and can't be deleted.",
theme.getName()));
}
themeRepository.delete(theme);
}
/**
* Checks if a theme has a live version.
*
* @param theme The theme.
*
* @return {@code true} if there is a live version of the provided theme,
* {@code false} otherwise.
*/
@Transactional(Transactional.TxType.REQUIRED)
public boolean isLive(final Theme theme) {
Objects.requireNonNull(theme);
return themeRepository
.findThemeByUuid(theme.getUuid(), ThemeVersion.LIVE)
.isPresent();
}
@Transactional(Transactional.TxType.REQUIRED)
public Theme getDraftTheme(final Theme theme) {
Objects.requireNonNull(theme);
if (theme.getVersion() == ThemeVersion.DRAFT) {
return theme;
} else {
return themeRepository
.findThemeByUuid(theme.getUuid(), ThemeVersion.DRAFT)
.orElseThrow(() -> new IllegalArgumentException(String
.format("No draft theme with UUID \"%s\" in the database.",
theme.getUuid())));
}
}
/**
* Retrieves the live version of a theme.
*
* @param theme The theme.
*
* @return An {@link Optional} containing the live version of the provided
* theme or an empty {@link Optional} if the theme has no live
* version.
*/
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Theme> getLiveTheme(final Theme theme) {
Objects.requireNonNull(theme);
return themeRepository
.findThemeByUuid(theme.getUuid(), ThemeVersion.LIVE);
}
/**
@ -71,18 +183,149 @@ public class ThemeManager {
*
*/
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
@Transactional(Transactional.TxType.REQUIRED)
public void publishTheme(final Theme theme) {
Objects.requireNonNull(theme);
final Theme draftTheme;
if (theme.getVersion() == ThemeVersion.DRAFT) {
draftTheme = theme;
} else {
draftTheme = getDraftTheme(theme);
}
if (isLive(draftTheme)) {
unpublishTheme(draftTheme);
}
final Theme liveTheme = new Theme();
liveTheme.setName(draftTheme.getName());
liveTheme.setUuid(draftTheme.getUuid());
liveTheme.setVersion(ThemeVersion.LIVE);
final Directory liveRoot = new Directory();
liveRoot.setName(draftTheme.getRootDirectory().getName());
liveRoot.setPath(draftTheme.getRootDirectory().getPath());
liveRoot.setUuid(draftTheme.getRootDirectory().getUuid());
liveRoot.setTheme(theme);
liveRoot.setVersion(ThemeVersion.LIVE);
themeRepository.save(liveTheme);
themeFileRepository.save(liveRoot);
draftTheme
.getRootDirectory()
.getFiles()
.forEach(file -> publishFile(liveTheme, liveRoot, file));
throw new UnsupportedOperationException();
}
private void publishFile(final Theme liveTheme,
final Directory liveParent,
final ThemeFile draftFile) {
Objects.requireNonNull(liveParent);
Objects.requireNonNull(draftFile);
if (liveParent.getVersion() != ThemeVersion.LIVE) {
throw new IllegalArgumentException("Parent directory is not live.");
}
if (draftFile.getVersion() != ThemeVersion.DRAFT) {
throw new IllegalArgumentException("File to publish is not draft.");
}
if (draftFile instanceof Directory) {
final Directory draftDirectory = (Directory) draftFile;
final Directory liveDirectory = new Directory();
liveDirectory.setName(draftDirectory.getName());
liveDirectory.setPath(draftDirectory.getPath());
liveDirectory.setParent(liveParent);
liveDirectory.setUuid(draftDirectory.getUuid());
liveDirectory.setVersion(ThemeVersion.LIVE);
liveDirectory.setTheme(liveTheme);
themeFileRepository.save(liveDirectory);
draftDirectory
.getFiles()
.forEach(file -> publishFile(liveTheme, liveDirectory, file));
} else if (draftFile instanceof DataFile) {
final DataFile draftDataFile = (DataFile) draftFile;
final DataFile liveDataFile = new DataFile();
liveDataFile.setCreationDate(draftDataFile.getCreationDate());
liveDataFile.setData(draftDataFile.getData());
liveDataFile.setLastModified(draftDataFile.getLastModified());
liveDataFile.setName(draftDataFile.getName());
liveDataFile.setParent(liveParent);
liveDataFile.setPath(draftDataFile.getPath());
liveDataFile.setSize(draftDataFile.getSize());
liveDataFile.setType(draftDataFile.getType());
liveDataFile.setUuid(draftDataFile.getUuid());
liveDataFile.setTheme(liveTheme);
liveDataFile.setVersion(ThemeVersion.LIVE);
themeFileRepository.save(liveDataFile);
} else {
throw new IllegalArgumentException(String
.format("Don't know how handle file type \"%s\".",
draftFile.getClass().getName()));
}
}
/**
* Unpublishes a theme by deleting the live version of the theme.
* Unpublishes a theme by deleting the live version of the theme. If the
* theme is not published the method will return without doing anything.
*
* @param theme The theme to unpublish.
*/
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
@Transactional(Transactional.TxType.REQUIRED)
public void unpublishTheme(final Theme theme) {
throw new UnsupportedOperationException();
Objects.requireNonNull(theme);
if (!isLive(theme)) {
return;
}
final Theme liveTheme = getLiveTheme(theme).get();
final Directory liveRoot = liveTheme.getRootDirectory();
liveRoot
.getFiles()
.forEach(file -> unpublishFile(file));
}
private void unpublishFile(final ThemeFile themeFile) {
Objects.requireNonNull(themeFile);
if (themeFile.getVersion() != ThemeVersion.LIVE) {
throw new IllegalArgumentException(
"Only live files can be unpublished.");
}
if (themeFile instanceof DataFile) {
themeFileRepository.delete(themeFile);
} else if (themeFile instanceof Directory) {
final Directory directory = (Directory) themeFile;
directory
.getFiles()
.forEach(file -> unpublishFile(file));
themeFileRepository.delete(themeFile);
} else {
throw new IllegalArgumentException(String
.format("Don't know how handle file type \"%s\".",
themeFile.getClass().getName()));
}
}
}

View File

@ -19,8 +19,13 @@
package org.libreccm.theming.db;
import org.libreccm.core.AbstractEntityRepository;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.ThemingPrivileges;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.enterprise.context.RequestScoped;
import javax.persistence.NoResultException;
@ -57,8 +62,25 @@ public class ThemeRepository extends AbstractEntityRepository<Long, Theme> {
}
@Override
public void initNewEntity(final Theme theme) {
if (theme.getUuid() == null || theme.getUuid().isEmpty()) {
theme.setUuid(UUID.randomUUID().toString());
}
}
@Override
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void save(final Theme theme) {
super.save(theme);;
super.save(theme);
}
public List<Theme> findAll(final ThemeVersion version) {
final TypedQuery<Theme> query = getEntityManager()
.createNamedQuery("Theme.findAllForVersion", Theme.class);
query.setParameter("version", version);
return query.getResultList();
}
@Transactional(Transactional.TxType.REQUIRED)

View File

@ -1,30 +0,0 @@
/*
* 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.db;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public enum ThemeVersion {
DRAFT,
LIVE
}

View File

@ -32,6 +32,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
@ -60,7 +61,6 @@ public class ThemeManifestUtil implements Serializable {
public ThemeManifest loadManifest(final Path path) {
// final String pathStr = path.toString().toLowerCase(Locale.ROOT);
final BufferedReader reader;
try {
reader = Files.newBufferedReader(path, Charset.forName("UTF-8"));
@ -130,7 +130,38 @@ public class ThemeManifestUtil implements Serializable {
// return manifest;
}
private ThemeManifest parseManifest(final Reader reader, final String path) {
public String serializeManifest(final ThemeManifest manifest,
final String format) {
final ObjectMapper mapper;
switch (format) {
case THEME_MANIFEST_JSON:
mapper = new ObjectMapper();
break;
case THEME_MANIFEST_XML:
final JacksonXmlModule xmlModule = new JacksonXmlModule();
mapper = new XmlMapper(xmlModule);
break;
default:
throw new IllegalArgumentException(
"Unsupported format for ThemeManifest");
}
mapper.registerModule(new JaxbAnnotationModule());
final StringWriter writer = new StringWriter();
try {
mapper.writeValue(writer, manifest);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
return writer.toString();
}
private ThemeManifest parseManifest(final Reader reader,
final String path) {
final String pathStr = path.toLowerCase(Locale.ROOT);