diff --git a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java index 42248ed45..ebaa98f45 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java @@ -28,7 +28,6 @@ import org.reflections.scanners.ResourcesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -36,15 +35,13 @@ import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.Enumeration; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -54,6 +51,8 @@ import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; +import javax.json.JsonStructure; +import javax.json.JsonValue; /** * @@ -271,7 +270,7 @@ public class StaticThemeProvider implements ThemeProvider { "The name of the theme can't be empty."); } - final String[] pathTokens = path.split("/"); + final List pathTokens = Arrays.asList(path.split("/")); final String indexFilePath = String.format("/" + THEMES_PACKAGE @@ -281,11 +280,30 @@ public class StaticThemeProvider implements ThemeProvider { .getResourceAsStream(indexFilePath); final JsonReader reader = Json.createReader(stream); final JsonObject indexObj = reader.readObject(); - final JsonArray filesArray = indexObj.getJsonArray("files"); - filesArray - .forEach(value -> LOGGER.warn(value.toString())); - - throw new UnsupportedOperationException(); + final JsonArray currentDir = indexObj.getJsonArray("files"); + currentDir.forEach(value -> LOGGER.warn(value.toString())); + + final Optional targetFile = findFile(pathTokens, + currentDir); + + final List result; + if (targetFile.isPresent()) { + if (targetFile.get().getBoolean("isDirectory")) { + final JsonArray files = targetFile.get().getJsonArray("files"); + result = files + .stream() + .map(value -> generateFileInfo((JsonObject) value)) + .collect(Collectors.toList()); + } else { + final ThemeFileInfo fileInfo = generateFileInfo( + targetFile.get()); + result = new ArrayList<>(); + result.add(fileInfo); + } + } else { + result = Collections.emptyList(); + } + return result; } @Override @@ -379,4 +397,59 @@ public class StaticThemeProvider implements ThemeProvider { return Files.exists(manifestPathJson) || Files.exists(manifestPathXml); } + private Optional findFile(final List path, + final JsonArray currentDirectory) { + + Objects.requireNonNull(path); + Objects.requireNonNull(currentDirectory); + + if (path.isEmpty()) { + return Optional.empty(); + } + + final String fileName = path.get(0); + + final Optional fileData = currentDirectory + .stream() + .map(value -> (JsonObject) value) + .filter(value -> filterFileData(value, fileName)) + .findAny(); + if (path.size() == 1) { + return fileData; + } else { + + if (fileData.get().getBoolean("isDirectory")) { + return findFile(path.subList(1, path.size()), + fileData.get().getJsonArray("files")); + } else { + return Optional.empty(); + } + } + } + + private boolean filterFileData(final JsonObject fileData, + final String fileName) { + return fileData.getString("name").equals(fileName); + } + + private ThemeFileInfo generateFileInfo(final JsonObject fileData) { + + Objects.requireNonNull(fileData); + + final ThemeFileInfo fileInfo = new ThemeFileInfo(); + fileInfo.setName(fileData.getString("name")); + if (fileData.getBoolean("isDirectory")) { + fileInfo.setDirectory(true); + } else { + fileInfo.setDirectory(false); + fileInfo.setMimeType(fileData.getString("mimeType")); + } + fileInfo.setWritable(false); + if (fileData.getJsonNumber("size") != null) { + fileInfo.setSize(fileData.getJsonNumber("size").longValue()); + } + + return fileInfo; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java index c9a2e0866..689c6d379 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java +++ b/ccm-core/src/main/java/org/libreccm/theming/webdav/ThemeFiles.java @@ -41,6 +41,7 @@ import org.libreccm.theming.ThemeInfo; import org.libreccm.theming.ThemeVersion; import org.libreccm.theming.Themes; import org.libreccm.webdav.ResponseStatus; +import org.libreccm.webdav.xml.elements.Collection; import org.libreccm.webdav.xml.elements.HRef; import org.libreccm.webdav.xml.elements.MultiStatus; import org.libreccm.webdav.xml.elements.Prop; @@ -51,11 +52,12 @@ import org.libreccm.webdav.xml.properties.DisplayName; import org.libreccm.webdav.xml.properties.GetContentLength; import org.libreccm.webdav.xml.properties.GetContentType; -import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.OPTIONS; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; @@ -72,6 +74,12 @@ public class ThemeFiles { @Inject private Themes themes; + @Inject + private ServletContext servletContext; + + @Inject + private HttpServletRequest request; + @GET @Path("/{path}") public Response getFile(@PathParam("theme") final String theme, @@ -137,10 +145,15 @@ public class ThemeFiles { final List fileInfos = themes.listThemesFiles(themeInfo, path); final MultiStatus result; - if (fileInfos.size() == 1) { + if (fileInfos.isEmpty()) { + throw new NotFoundException(String.format( + "No file \"%s\" in theme \"%s\".", + path, + theme)); + } else if (fileInfos.size() == 1) { + + final ThemeFileInfo fileInfo = fileInfos.get(0); - final ThemeFileInfo fileInfo = fileInfos.get(1); - result = new MultiStatus(buildWebDavResponse(fileInfo, uriInfo)); } else { @@ -150,7 +163,7 @@ public class ThemeFiles { null, null, new PropStat(new Prop(new DisplayName(path), - org.libreccm.webdav.xml.elements.Collection.COLLECTION), + Collection.COLLECTION), new Status(javax.ws.rs.core.Response.Status.OK))); final List responses = new LinkedList<>(); @@ -158,13 +171,13 @@ public class ThemeFiles { final List fileResponses = fileInfos .stream() - .map(fileInfo -> buildWebDavResponse(fileInfo, uriInfo)) - .collect(Collectors.toList()); + .map(fileInfo -> buildWebDavResponse(path, fileInfo)) + .collect(Collectors.toList()); responses.addAll(fileResponses); - + result = new MultiStatus(responses.toArray(new WebDavResponse[]{})); } - + return Response .status(ResponseStatus.MULTI_STATUS) .entity(result) @@ -174,11 +187,18 @@ public class ThemeFiles { private WebDavResponse buildWebDavResponse(final ThemeFileInfo fileInfo, final UriInfo uriInfo) { - final PropStat propStat = new PropStat( - new Prop(new DisplayName(fileInfo.getName()), - new GetContentLength(fileInfo.getSize()), - new GetContentType(fileInfo.getMimeType())), - new Status(javax.ws.rs.core.Response.Status.OK)); + final PropStat propStat; + if (fileInfo.isDirectory()) { + propStat = new PropStat( + new Prop(new DisplayName(fileInfo.getName())), + new Status(javax.ws.rs.core.Response.Status.OK)); + } else { + propStat = new PropStat( + new Prop(new DisplayName(fileInfo.getName()), + new GetContentLength(fileInfo.getSize()), + new GetContentType(fileInfo.getMimeType())), + new Status(javax.ws.rs.core.Response.Status.OK)); + } final WebDavResponse response = new WebDavResponse( new HRef(uriInfo.getRequestUri()), @@ -189,6 +209,38 @@ public class ThemeFiles { return response; } + + private WebDavResponse buildWebDavResponse(final String basePath, + final ThemeFileInfo fileInfo) { + + final PropStat propStat; + if (fileInfo.isDirectory()) { + propStat = new PropStat( + new Prop(new DisplayName(fileInfo.getName())), + new Status(javax.ws.rs.core.Response.Status.OK)); + } else { + propStat = new PropStat( + new Prop(new DisplayName(fileInfo.getName()), + new GetContentLength(fileInfo.getSize()), + new GetContentType(fileInfo.getMimeType())), + new Status(javax.ws.rs.core.Response.Status.OK)); + } + + final WebDavResponse response = new WebDavResponse( + new HRef(String.format("%s://%s:%d%s/DAV/themes/%s/%s", + request.getScheme(), + request.getServerName(), + request.getServerPort(), + servletContext.getContextPath(), + basePath, + fileInfo.getName())), + null, + null, + null, + propStat); + + return response; + } private MediaType getMediaTypeFromPath(final String path) { diff --git a/ccm-core/src/main/java/org/libreccm/theming/webdav/WebDAV.java b/ccm-core/src/main/java/org/libreccm/theming/webdav/WebDAV.java index f79b2a846..6eb291480 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/webdav/WebDAV.java +++ b/ccm-core/src/main/java/org/libreccm/theming/webdav/WebDAV.java @@ -18,11 +18,17 @@ */ package org.libreccm.theming.webdav; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.webdav.xml.WebDavContextResolver; + import java.util.HashSet; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; +import javax.xml.bind.JAXBException; /** * @@ -31,6 +37,8 @@ import javax.ws.rs.core.Application; @ApplicationPath("/DAV/themes") public class WebDAV extends Application { + private static final Logger LOGGER = LogManager.getLogger(WebDAV.class); + @Override public Set> getClasses() { @@ -40,4 +48,20 @@ public class WebDAV extends Application { return classes; } + @Override + public Set getSingletons() { + + LOGGER.warn("Adding singletons..."); + + final HashSet singletons = new HashSet<>(); + try { + singletons.add(new WebDavContextResolver()); + } catch (JAXBException ex) { + throw new UnexpectedErrorException(ex); + } + + LOGGER.warn("Added singletons"); + return singletons; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/webdav/conditions/PropFindFiniteDepth.java b/ccm-core/src/main/java/org/libreccm/webdav/conditions/PropFindFiniteDepth.java index 31b91579a..0f34863ca 100644 --- a/ccm-core/src/main/java/org/libreccm/webdav/conditions/PropFindFiniteDepth.java +++ b/ccm-core/src/main/java/org/libreccm/webdav/conditions/PropFindFiniteDepth.java @@ -61,7 +61,7 @@ public final class PropFindFiniteDepth { // For unmarshalling only. } - private static PropFindFiniteDepth createInstance() { + private static PropFindFiniteDepth createSingleton() { return PROPFIND_FINITE_DEPTH; } diff --git a/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavContextResolver.java b/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavContextResolver.java index 49c198628..1140384e2 100644 --- a/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavContextResolver.java +++ b/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavContextResolver.java @@ -60,6 +60,12 @@ public class WebDavContextResolver implements ContextResolver { private final JAXBIntrospector introspector; + public WebDavContextResolver() throws JAXBException { + + this.context = WebDavJAXBContextBuilder.build(); + this.introspector = this.context.createJAXBIntrospector(); + } + /** * Creates an instance of this resolver, registering the provided custom XML * Elements and Properties. diff --git a/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavJAXBContextBuilder.java b/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavJAXBContextBuilder.java index 1f69d5d64..ed19073a1 100644 --- a/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavJAXBContextBuilder.java +++ b/ccm-core/src/main/java/org/libreccm/webdav/xml/WebDavJAXBContextBuilder.java @@ -48,6 +48,7 @@ import org.libreccm.webdav.xml.elements.PropStat; import org.libreccm.webdav.xml.elements.PropertyUpdate; import org.libreccm.webdav.xml.elements.Remove; import org.libreccm.webdav.xml.elements.ResponseDescription; +import org.libreccm.webdav.xml.elements.Set; import org.libreccm.webdav.xml.elements.Shared; import org.libreccm.webdav.xml.elements.Status; import org.libreccm.webdav.xml.elements.TimeOut; @@ -66,9 +67,7 @@ import org.libreccm.webdav.xml.properties.SupportedLock; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Set; -import javax.swing.text.Utilities; import javax.ws.rs.core.Response; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -148,7 +147,8 @@ final class WebDavJAXBContextBuilder { ResourceType.class, Response.class, ResponseDescription.class, - Set.class, Shared.class, + Set.class, + Shared.class, Status.class, SupportedLock.class, TimeOut.class, diff --git a/ccm-core/src/main/java/org/libreccm/webdav/xml/elements/Collection.java b/ccm-core/src/main/java/org/libreccm/webdav/xml/elements/Collection.java index e78d91a7e..d6a9cfc43 100644 --- a/ccm-core/src/main/java/org/libreccm/webdav/xml/elements/Collection.java +++ b/ccm-core/src/main/java/org/libreccm/webdav/xml/elements/Collection.java @@ -18,6 +18,9 @@ */ package org.libreccm.webdav.xml.elements; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + /** * WebDAV collection XML Element. * @@ -38,6 +41,9 @@ package org.libreccm.webdav.xml.elements; * Distributed Authoring and Versioning (WebDAV)" * */ +@XmlRootElement +@XmlType(factoryMethod = "createSingleton") + public final class Collection { private Collection() { diff --git a/ccm-core/src/main/resources/themes/ccm/theme-index.json b/ccm-core/src/main/resources/themes/ccm/theme-index.json index f798c9435..5cf7df714 100644 --- a/ccm-core/src/main/resources/themes/ccm/theme-index.json +++ b/ccm-core/src/main/resources/themes/ccm/theme-index.json @@ -57,6 +57,11 @@ "mimeType": "text/x-java-properties" } ] + }, + { + "name": "theme.json", + "isDirectory": false, + "mimeType": "application/json" } ] }