parent
30cd9bae25
commit
de29bb6a3a
|
|
@ -0,0 +1,595 @@
|
||||||
|
/*
|
||||||
|
* 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.api.themes;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.libreccm.security.AuthorizationRequired;
|
||||||
|
import org.libreccm.security.RequiresPrivilege;
|
||||||
|
import org.libreccm.theming.ThemeFileInfo;
|
||||||
|
import org.libreccm.theming.ThemeInfo;
|
||||||
|
import org.libreccm.theming.ThemeProvider;
|
||||||
|
import org.libreccm.theming.ThemeVersion;
|
||||||
|
import org.libreccm.theming.ThemingPrivileges;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.enterprise.inject.Any;
|
||||||
|
import javax.enterprise.inject.Instance;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.BadRequestException;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/providers")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
public List<String> getThemeProviders() {
|
||||||
|
|
||||||
|
return providers
|
||||||
|
.stream()
|
||||||
|
.filter(
|
||||||
|
provider -> provider.supportsChanges()
|
||||||
|
&& provider.supportsDraftThemes()
|
||||||
|
)
|
||||||
|
.map(this::getProviderName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/themes")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
public List<ThemeInfo> getAvailableThemes() {
|
||||||
|
return providers
|
||||||
|
.stream()
|
||||||
|
.filter(provider -> provider.supportsChanges()
|
||||||
|
&& provider.supportsDraftThemes()
|
||||||
|
)
|
||||||
|
.map(ThemeProvider::getThemes)
|
||||||
|
.flatMap(themes -> themes.stream())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/themes/{theme}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public ThemeInfo getTheme(@PathParam("theme") final String themeName) {
|
||||||
|
return providers
|
||||||
|
.stream()
|
||||||
|
.filter(
|
||||||
|
provider -> provider.providesTheme(
|
||||||
|
themeName, ThemeVersion.DRAFT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.findAny()
|
||||||
|
.map(
|
||||||
|
provider -> provider.getThemeInfo(
|
||||||
|
themeName, ThemeVersion.DRAFT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found.", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found.", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("/themes/{theme}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
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 BadRequestException("No name for new theme provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName.isEmpty() || providerName.matches("\\s*")) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"No provider for new theme provided."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
public Response deleteTheme(@PathParam("theme") final String themeName) {
|
||||||
|
Objects.requireNonNull(themeName);
|
||||||
|
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
provider.deleteTheme(themeName);
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/themes/{theme}/live")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
public Response publishTheme(@PathParam("theme") final String themeName) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
provider.publishTheme(themeName);
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("/themes/{theme}/live")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
||||||
|
public Response unPublishTheme(@PathParam("theme") final String themeName) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
provider.unpublishTheme(themeName);
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/themes/{theme}/files/")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response getThemeRootDir(@PathParam("theme") final String themeName) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeFileInfo fileInfo = provider
|
||||||
|
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, "/")
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new WebApplicationException(
|
||||||
|
String.format(
|
||||||
|
"File \"/\" in theme %s is not a directory.",
|
||||||
|
themeName),
|
||||||
|
Response.Status.INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fileInfo.isDirectory()) {
|
||||||
|
return Response
|
||||||
|
.ok(
|
||||||
|
provider.listThemeFiles(
|
||||||
|
themeName, ThemeVersion.DRAFT, "/")
|
||||||
|
)
|
||||||
|
.type(MediaType.APPLICATION_JSON)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
throw new WebApplicationException(
|
||||||
|
String.format(
|
||||||
|
"File \"/\" in theme %s is not a directory.",
|
||||||
|
themeName),
|
||||||
|
Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/themes/{theme}/files/{path:.+}")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response getThemeFile(
|
||||||
|
@PathParam("theme") final String themeName,
|
||||||
|
@PathParam("path") final String path
|
||||||
|
) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme \"%s\" does not exist.", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeFileInfo fileInfo = provider
|
||||||
|
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, path)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"File \"%s\" does not exist in theme %s.",
|
||||||
|
path,
|
||||||
|
themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fileInfo.isDirectory()) {
|
||||||
|
return Response
|
||||||
|
.ok(
|
||||||
|
provider.listThemeFiles(themeName, ThemeVersion.DRAFT, path)
|
||||||
|
)
|
||||||
|
.type(MediaType.APPLICATION_JSON)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
final InputStream inputStream = provider
|
||||||
|
.getThemeFileAsStream(themeName, ThemeVersion.DRAFT, path)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"File \"%s\" does not exist in theme %s.",
|
||||||
|
path,
|
||||||
|
themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response
|
||||||
|
.ok(inputStream)
|
||||||
|
.type(fileInfo.getMimeType())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("/themes/{theme}/files/{path:.+}")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response createOrUpdateThemeFile(
|
||||||
|
@PathParam("theme") final String themeName,
|
||||||
|
@PathParam("path") final String path,
|
||||||
|
final byte[] data
|
||||||
|
) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Optional<ThemeFileInfo> fileInfo = provider
|
||||||
|
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, "/");
|
||||||
|
|
||||||
|
if (fileInfo.isPresent() && fileInfo.get().isDirectory()) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
String.format(
|
||||||
|
"File %s already exists in theme %s and is a directory.",
|
||||||
|
path,
|
||||||
|
themeName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final OutputStream outputStream = provider.getOutputStreamForThemeFile(
|
||||||
|
themeName, path
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
outputStream.write(data);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new WebApplicationException(
|
||||||
|
String.format(
|
||||||
|
"Failed to create/update file %s in theme %s.",
|
||||||
|
path,
|
||||||
|
themeName
|
||||||
|
),
|
||||||
|
Response.Status.INTERNAL_SERVER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("/themes/{theme}/files/{path:.+}")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response deleteThemeFile(
|
||||||
|
@PathParam("theme") final String themeName,
|
||||||
|
@PathParam("path") final String path,
|
||||||
|
@QueryParam("recursive") final boolean recursive
|
||||||
|
) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme \"%s\" does not exist.", themeName
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
final ThemeFileInfo fileInfo = provider
|
||||||
|
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, path)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"File \"%s\" does not exist in theme %s.",
|
||||||
|
path,
|
||||||
|
themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fileInfo.isDirectory()) {
|
||||||
|
final List<ThemeFileInfo> files = provider
|
||||||
|
.listThemeFiles(themeName, ThemeVersion.DRAFT, path);
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
provider.deleteThemeFile(themeName, path);
|
||||||
|
return Response.ok().build();
|
||||||
|
} else if (recursive) {
|
||||||
|
for (final ThemeFileInfo file : files) {
|
||||||
|
provider.deleteThemeFile(
|
||||||
|
themeName, String.format("%s/%s", path, file.getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
provider.deleteThemeFile(themeName, path);
|
||||||
|
return Response.ok().build();
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException(
|
||||||
|
String.format(
|
||||||
|
"Directory %s of theme %s is not empty.",
|
||||||
|
path, themeName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
provider.deleteThemeFile(themeName, path);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/themes/{theme}/@download")
|
||||||
|
@Produces("application/zip")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response downloadTheme(final String themeName) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||||
|
addFilesInDirectoryToZip(provider, themeName, "/", zos);
|
||||||
|
|
||||||
|
final byte[] result = baos.toByteArray();
|
||||||
|
|
||||||
|
return Response
|
||||||
|
.ok(result)
|
||||||
|
.build();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new WebApplicationException(
|
||||||
|
String.format(
|
||||||
|
"Error zipping theme %s.", themeName
|
||||||
|
),
|
||||||
|
ex,
|
||||||
|
Response.Status.INTERNAL_SERVER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/themes/{theme}/@update")
|
||||||
|
@Consumes("application/zip")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
||||||
|
public Response updateTheme(
|
||||||
|
final String themeName, final byte[] updatedTheme
|
||||||
|
) {
|
||||||
|
final ThemeProvider provider = findProvider(themeName)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"Theme %s not found", themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read every file from ZIP
|
||||||
|
// Try to find file in theme
|
||||||
|
// Create or update file in theme
|
||||||
|
// remove files not in ZIP from theme
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ThemeProvider> findProvider(final String forTheme) {
|
||||||
|
|
||||||
|
final List<ThemeProvider> providersList = new ArrayList<>();
|
||||||
|
providers
|
||||||
|
.forEach(provider -> providersList.add(provider));
|
||||||
|
|
||||||
|
return providersList
|
||||||
|
.stream()
|
||||||
|
.filter(current -> current.providesTheme(forTheme,
|
||||||
|
ThemeVersion.DRAFT))
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFilesInDirectoryToZip(
|
||||||
|
final ThemeProvider provider,
|
||||||
|
final String themeName,
|
||||||
|
final String directoryPath,
|
||||||
|
final ZipOutputStream zipOutputStream
|
||||||
|
) throws IOException {
|
||||||
|
final List<ThemeFileInfo> files = provider
|
||||||
|
.listThemeFiles(themeName, ThemeVersion.DRAFT, directoryPath
|
||||||
|
);
|
||||||
|
for (ThemeFileInfo file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
final String dirPath = String.format(
|
||||||
|
"%s/%s", directoryPath, file.getName()
|
||||||
|
);
|
||||||
|
final ZipEntry entry = new ZipEntry(dirPath);
|
||||||
|
zipOutputStream.putNextEntry(entry);
|
||||||
|
addFilesInDirectoryToZip(
|
||||||
|
provider, themeName, dirPath, zipOutputStream
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final String filePath = String.format(
|
||||||
|
"%s/%s", directoryPath, file.getName()
|
||||||
|
);
|
||||||
|
final ZipEntry entry = new ZipEntry(filePath);
|
||||||
|
zipOutputStream.putNextEntry(entry);
|
||||||
|
|
||||||
|
try (InputStream inputStream = getFileInputStream(
|
||||||
|
provider, themeName, filePath
|
||||||
|
)) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length = inputStream.read(buffer);
|
||||||
|
while (length != -1) {
|
||||||
|
zipOutputStream.write(buffer);
|
||||||
|
length = inputStream.read(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipOutputStream.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getFileInputStream(
|
||||||
|
final ThemeProvider provider, final String themeName,
|
||||||
|
final String filePath
|
||||||
|
) {
|
||||||
|
return provider
|
||||||
|
.getThemeFileAsStream(themeName, ThemeVersion.DRAFT, filePath)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException(
|
||||||
|
String.format(
|
||||||
|
"File %s not found in theme %s.",
|
||||||
|
filePath,
|
||||||
|
themeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,11 @@
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
* MA 02110-1301 USA
|
* MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
package org.libreccm.theming.manager;
|
package org.libreccm.api.themes;
|
||||||
|
|
||||||
|
import org.libreccm.api.CorsFilter;
|
||||||
|
import org.libreccm.api.DefaultResponseHeaders;
|
||||||
|
import org.libreccm.api.PreflightRequestFilter;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -29,16 +33,17 @@ import javax.ws.rs.core.Application;
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
*/
|
*/
|
||||||
@ApplicationPath("/thememanager")
|
@ApplicationPath("/api/themes")
|
||||||
public class ThemeManager extends Application {
|
public class ThemesApi extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Class<?>> getClasses() {
|
public Set<Class<?>> getClasses() {
|
||||||
|
|
||||||
final Set<Class<?>> classes = new HashSet<>();
|
final Set<Class<?>> classes = new HashSet<>();
|
||||||
|
classes.add(CorsFilter.class);
|
||||||
|
classes.add(DefaultResponseHeaders.class);
|
||||||
|
classes.add(PreflightRequestFilter.class);
|
||||||
classes.add(Themes.class);
|
classes.add(Themes.class);
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,392 +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.manager;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.libreccm.security.AuthorizationRequired;
|
|
||||||
import org.libreccm.security.RequiresPrivilege;
|
|
||||||
import org.libreccm.theming.ThemeFileInfo;
|
|
||||||
import org.libreccm.theming.ThemeInfo;
|
|
||||||
import org.libreccm.theming.ThemeProvider;
|
|
||||||
import org.libreccm.theming.ThemeVersion;
|
|
||||||
import org.libreccm.theming.ThemingPrivileges;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
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.POST;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @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)
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
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)
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
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)
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
|
|
||||||
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")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
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}")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
public void deleteTheme(@PathParam("theme") final String themeName) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(themeName);
|
|
||||||
|
|
||||||
final Optional<ThemeProvider> provider = findProvider(themeName);
|
|
||||||
|
|
||||||
if (provider.isPresent()) {
|
|
||||||
|
|
||||||
provider.get().deleteTheme(themeName);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/themes/{theme}/live")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
public void publishTheme(@PathParam("theme") final String themeName) {
|
|
||||||
|
|
||||||
final Optional<ThemeProvider> provider = findProvider(themeName);
|
|
||||||
|
|
||||||
if (provider.isPresent()) {
|
|
||||||
|
|
||||||
provider.get().publishTheme(themeName);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@DELETE
|
|
||||||
@Path("/themes/{theme}/live")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
public void unPublishTheme(@PathParam("theme") final String themeName) {
|
|
||||||
|
|
||||||
final Optional<ThemeProvider> provider = findProvider(themeName);
|
|
||||||
|
|
||||||
if (provider.isPresent()) {
|
|
||||||
|
|
||||||
provider.get().unpublishTheme(themeName);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/themes/{theme}/files/")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
public Response getThemeRootDir(@PathParam("theme") final String themeName) {
|
|
||||||
|
|
||||||
final Optional<ThemeProvider> provider = findProvider(themeName);
|
|
||||||
|
|
||||||
if (provider.isPresent()) {
|
|
||||||
|
|
||||||
final Optional<ThemeFileInfo> fileInfo = provider
|
|
||||||
.get()
|
|
||||||
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, "/");
|
|
||||||
|
|
||||||
if (fileInfo.isPresent()) {
|
|
||||||
|
|
||||||
if (fileInfo.get().isDirectory()) {
|
|
||||||
|
|
||||||
final List<ThemeFileInfo> fileInfos = provider
|
|
||||||
.get()
|
|
||||||
.listThemeFiles(themeName, ThemeVersion.DRAFT, "/");
|
|
||||||
|
|
||||||
return Response
|
|
||||||
.ok(fileInfos)
|
|
||||||
.type(MediaType.APPLICATION_JSON)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
throw new WebApplicationException(
|
|
||||||
String.format(
|
|
||||||
"File \"/\" in theme %s is not a directory.",
|
|
||||||
themeName),
|
|
||||||
Response.Status.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return Response
|
|
||||||
.status(Response.Status.NOT_FOUND)
|
|
||||||
.entity(String.format(
|
|
||||||
"File \"/\" does not exist in theme %s.",
|
|
||||||
themeName))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return Response
|
|
||||||
.status(Response.Status.NOT_FOUND)
|
|
||||||
.entity(String.format("Theme \"%s\" does not exist.",
|
|
||||||
themeName))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/themes/{theme}/files/{path:.+}")
|
|
||||||
@AuthorizationRequired
|
|
||||||
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
|
|
||||||
public Response getThemeFile(@PathParam("theme") final String themeName,
|
|
||||||
@PathParam("path") final String path) {
|
|
||||||
|
|
||||||
final Optional<ThemeProvider> provider = findProvider(themeName);
|
|
||||||
|
|
||||||
if (provider.isPresent()) {
|
|
||||||
|
|
||||||
final Optional<ThemeFileInfo> fileInfo = provider
|
|
||||||
.get()
|
|
||||||
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, path);
|
|
||||||
|
|
||||||
if (fileInfo.isPresent()) {
|
|
||||||
|
|
||||||
final ThemeFileInfo themeFileInfo = fileInfo.get();
|
|
||||||
|
|
||||||
if (themeFileInfo.isDirectory()) {
|
|
||||||
|
|
||||||
final List<ThemeFileInfo> fileInfos = provider
|
|
||||||
.get()
|
|
||||||
.listThemeFiles(themeName, ThemeVersion.DRAFT, path);
|
|
||||||
|
|
||||||
return Response
|
|
||||||
.ok(fileInfos)
|
|
||||||
.type(MediaType.APPLICATION_JSON)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
final Optional<InputStream> inputStream = provider
|
|
||||||
.get()
|
|
||||||
.getThemeFileAsStream(themeName,
|
|
||||||
ThemeVersion.DRAFT,
|
|
||||||
path);
|
|
||||||
|
|
||||||
if (inputStream.isPresent()) {
|
|
||||||
|
|
||||||
final InputStream inStream = inputStream.get();
|
|
||||||
return Response
|
|
||||||
.ok(inStream)
|
|
||||||
.type(themeFileInfo.getMimeType())
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
return Response
|
|
||||||
.status(Response.Status.NOT_FOUND)
|
|
||||||
.entity(String.format(
|
|
||||||
"File \"%s\" does not exist in theme %s.",
|
|
||||||
path,
|
|
||||||
themeName))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Response
|
|
||||||
.status(Response.Status.NOT_FOUND)
|
|
||||||
.entity(String.format(
|
|
||||||
"File \"%s\" does not exist in theme %s.",
|
|
||||||
path,
|
|
||||||
themeName))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return Response
|
|
||||||
.status(Response.Status.NOT_FOUND)
|
|
||||||
.entity(String.format("Theme \"%s\" does not exist.",
|
|
||||||
themeName))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ThemeProvider> findProvider(final String forTheme) {
|
|
||||||
|
|
||||||
final List<ThemeProvider> providersList = new ArrayList<>();
|
|
||||||
providers
|
|
||||||
.forEach(provider -> providersList.add(provider));
|
|
||||||
|
|
||||||
return providersList
|
|
||||||
.stream()
|
|
||||||
.filter(current -> current.providesTheme(forTheme,
|
|
||||||
ThemeVersion.DRAFT))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue