diff --git a/elemental-media-type/elemental-media-type-api/src/main/java/xyz/elemental/mediatype/MediaTypeAlias.java b/elemental-media-type/elemental-media-type-api/src/main/java/xyz/elemental/mediatype/MediaTypeAlias.java new file mode 100644 index 0000000000..2f61ab3ed9 --- /dev/null +++ b/elemental-media-type/elemental-media-type-api/src/main/java/xyz/elemental/mediatype/MediaTypeAlias.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to Elemental by + * Evolved Binary, for the benefit of the Elemental Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to Elemental, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in Elemental. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * ===================================================================== + * + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * 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; version 2.1. + * + * 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 xyz.elemental.mediatype; + +/** + * Information about a Media Type Alias. (aka MIME Type) + * + * @author Adam Retter + */ +public interface MediaTypeAlias extends MediaType { + + /** + * Get the alias identifier of the Media Type. + * + * For example {@code text/xml}. + * + * @return the identifier of the alias of the Media Type + */ + String getAliasIdentifier(); +} diff --git a/elemental-media-type/elemental-media-type-impl/pom.xml b/elemental-media-type/elemental-media-type-impl/pom.xml index 00e40a6d3b..a9e53d2994 100644 --- a/elemental-media-type/elemental-media-type-impl/pom.xml +++ b/elemental-media-type/elemental-media-type-impl/pom.xml @@ -80,6 +80,11 @@ jcip-annotations + + com.evolvedbinary.j8fu + j8fu + + org.slf4j slf4j-api diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliasImpl.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliasImpl.java new file mode 100644 index 0000000000..ece719ee77 --- /dev/null +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliasImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to Elemental by + * Evolved Binary, for the benefit of the Elemental Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to Elemental, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in Elemental. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * ===================================================================== + * + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * 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; version 2.1. + * + * 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 xyz.elemental.mediatype.impl; + +import net.jcip.annotations.NotThreadSafe; +import xyz.elemental.mediatype.MediaType; +import xyz.elemental.mediatype.MediaTypeAlias; +import xyz.elemental.mediatype.StorageType; + +import javax.annotation.Nullable; + +/** + * Immutable implementation of a Media Type Alias. + * + * @author Adam Retter + */ +public class MediaTypeAliasImpl implements MediaTypeAlias { + + private final String aliasIdentifier; + private final MediaType target; + + private MediaTypeAliasImpl(final String aliasIdentifier, final MediaType target) { + this.aliasIdentifier = aliasIdentifier; + this.target = target; + } + + @Override + public String getAliasIdentifier() { + return aliasIdentifier; + } + + @Override + public String getIdentifier() { + return target.getIdentifier(); + } + + @Nullable + @Override + public String[] getKnownFileExtensions() { + return target.getKnownFileExtensions(); + } + + @Override + public StorageType getStorageType() { + return target.getStorageType(); + } + + /** + * Construct a new Media Type Alias. + * + * @param aliasIdentifier the Media Type alias identifier + * + * @return a media type alias builder. + */ + public static MediaTypeAliasImpl.Builder builder(final String aliasIdentifier) { + return MediaTypeAliasImpl.Builder.alias(aliasIdentifier); + } + + /** + * Builder pattern which allows us to + * ultimately construct an Immutable MediaTypeImpl. + */ + @NotThreadSafe + public static class Builder { + private final String aliasIdentifier; + private @Nullable MediaType target; + + private Builder(final String aliasIdentifier) { + this.aliasIdentifier = aliasIdentifier; + } + + /** + * Initiate the build of a MediaTypeImpl. + * + * @param aliasIdentifier the Media Type alias identifier + * @return a Media Type Alias builder + */ + static MediaTypeAliasImpl.Builder alias(final String aliasIdentifier) { + return new MediaTypeAliasImpl.Builder(aliasIdentifier); + } + + /** + * Set the target for the Media Type alias. + * + * @param target a media type. + * @return this + */ + public MediaTypeAliasImpl.Builder of(final MediaType target) { + this.target = target; + return this; + } + + /** + * Build the Immutable MediaTypeAlias. + * + * @return an immutable MediaTypeAlias. + */ + public MediaTypeAlias build() { + if (target == null) { + throw new IllegalStateException("A target must be set for the alias"); + } + + return new MediaTypeAliasImpl(aliasIdentifier, target); + } + } +} diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliaser.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliaser.java new file mode 100644 index 0000000000..318fe65915 --- /dev/null +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeAliaser.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to Elemental by + * Evolved Binary, for the benefit of the Elemental Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to Elemental, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in Elemental. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * ===================================================================== + * + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * 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; version 2.1. + * + * 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 xyz.elemental.mediatype.impl; + +import com.evolvedbinary.j8fu.tuple.Tuple2; +import io.lacuna.bifurcan.IList; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.LinearMap; +import net.jcip.annotations.NotThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import xyz.elemental.mediatype.impl.MediaTypeConfigUtil.MediaTypeConfigSource; +import xyz.elemental.mediatype.impl.configuration.MediaTypeAlias; +import xyz.elemental.mediatype.impl.configuration.MediaTypeAliases; + +import javax.annotation.Nullable; +import java.nio.file.Path; + +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; +import static xyz.elemental.mediatype.impl.MediaTypeConfigUtil.findConfigSources; +import static xyz.elemental.mediatype.impl.MediaTypeConfigUtil.parseConfigSources; + +/** + * Maps Media Type Aliases to Media Types. + *

Aliases file search order.

+ * The MediaTypeAliaser looks in various places in the user's + * system for media type aliases files. When requests are made + * to resolve media type aliases to media types, it searches + * aliases files in the following order: + *

    + *
  1. The file media-type-aliases.xml from within the user's home directory: + *
      + *
    • Linux/Unix: $XDG_CONFIG_HOME/elemental/media-type-aliases.xml. If $XDG_CONFIG_HOME is not set then, ~/.config/elemental/media-type-aliases.xml
    • + *
    • macOS: $XDG_CONFIG_HOME/elemental/media-type-aliases.xml. If $XDG_CONFIG_HOME is not set then, ~/Library/Preferences/xyz.elemental/media-type-aliases.xml
    • + *
    • Windows: %APPDATA%/Elemental/media-type-aliases.xml. If %APPDATA% is not set then, %USERPROFILE%/AppData/Local/Elemental/media-type-aliases.xml
    • + *
    + *
  2. + *
  3. One or more files named media-type-aliases.xml in the Application's config directory(s).
  4. + *
  5. The file media-type-aliases.xml on the classpath in the package xyz.elemental.mediatype.
  6. + *
+ * + * @author Adam Retter + */ +@NotThreadSafe +public class MediaTypeAliaser { + + private static final Logger LOG = LoggerFactory.getLogger(MediaTypeAliaser.class); + + private static final String MEDIA_TYPE_ALIASES_FILENAME = "media-type-aliases.xml"; + + final IMap aliases; + + public MediaTypeAliaser(@Nullable final Path... configDirs) { + final IList aliasesFileSources = findConfigSources(MediaTypeAliaser.class, MEDIA_TYPE_ALIASES_FILENAME, configDirs); + final IList> aliases = parseConfigSources(MediaTypeAliases.class, aliasesFileSources, MediaTypeAliaser::parseAliases); + this.aliases = toMap(aliases); + } + + private static void parseAliases(final String configLocation, final MediaTypeAliases mediaTypeAliases, final IList> aliases) { + if (mediaTypeAliases.getMediaTypeAlias() == null) { + LOG.error("No aliases found in {} skipping...", configLocation); + return; + } + + for (final MediaTypeAlias alias : mediaTypeAliases.getMediaTypeAlias()) { + aliases.addLast(Tuple(alias.getAlias(), alias.getTarget())); + } + } + + private static IMap toMap(final IList> list) { + final IMap map = new LinearMap<>((int) list.size()); + for (final Tuple2 entry : list) { + map.put(entry._1, entry._2); + } + return map.forked(); + } + + /** + * Given a Media Type alias identifier try and resolve its Media Type identifier. + * + * @param mediaTypeAliasIdentifier the identifier of the Media Type alias. + * + * @return aliased Media Type Identifier, or null if there is no such alias. + */ + public @Nullable String resolveAlias(final String mediaTypeAliasIdentifier) { + return resolveAlias(mediaTypeAliasIdentifier, null); + } + + /** + * Given a Media Type alias identifier try and resolve its Media Type identifier. + * + * @param mediaTypeAliasIdentifier the identifier of the Media Type alias. + * @param defaultMediaTypeIdentifier the default Media Type identifier to return + * if a Media Type identifier cannot be resolved for the Media Type alias identifier. + * + * @return aliased Media Type Identifier, or {@code defaultMediaTypeIdentifier} if there is no such alias. + */ + public @Nullable String resolveAlias(final String mediaTypeAliasIdentifier, @Nullable final String defaultMediaTypeIdentifier) { + return aliases.get(mediaTypeAliasIdentifier, defaultMediaTypeIdentifier); + } +} diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeConfigUtil.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeConfigUtil.java new file mode 100644 index 0000000000..066a12a8cf --- /dev/null +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeConfigUtil.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to Elemental by + * Evolved Binary, for the benefit of the Elemental Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to Elemental, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in Elemental. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * ===================================================================== + * + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * 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; version 2.1. + * + * 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 xyz.elemental.mediatype.impl; + +import com.evolvedbinary.j8fu.function.TriConsumer; +import io.lacuna.bifurcan.IList; +import io.lacuna.bifurcan.LinearList; +import io.lacuna.bifurcan.List; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Unmarshaller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Utilities for working with Media Type config files. + * + * @author Adam Retter + */ +public class MediaTypeConfigUtil { + + private static final Logger LOG = LoggerFactory.getLogger(MediaTypeConfigUtil.class); + + /** + * Searches in various places in the user's + * system for media type config files in the following order: + *
    + *
  1. The file configFilename from within the user's home directory: + *
      + *
    • Linux/Unix: $XDG_CONFIG_HOME/elemental/configFilename. If $XDG_CONFIG_HOME is not set then, ~/.config/elemental/configFilename
    • + *
    • macOS: $XDG_CONFIG_HOME/elemental/configFilename. If $XDG_CONFIG_HOME is not set then, ~/Library/Preferences/xyz.elemental/configFilename
    • + *
    • Windows: %APPDATA%/Elemental/configFilename. If %APPDATA% is not set then, %USERPROFILE%/AppData/Local/Elemental/configFilename
    • + *
    + *
  2. + *
  3. One or more files named configFilename in the Application's config directory(s).
  4. + *
  5. The file configFilename on the classpath in the package xyz.elemental.mediatype.
  6. + *
+ * + * @param configManager The class that is calling this function - used for improved log messages, and finding the classpath. + * @param configFilename The filename of the config files to search for. + * @param configDirs Any application specific config directories to search within. + * + * @return a list of config sources. + */ + static IList findConfigSources(final Class configManager, final String configFilename, final Path... configDirs) { + final LinearList configSources = new LinearList<>(); + + LOG.trace("{}: load HOME", configManager.getSimpleName()); + @Nullable final Path userConfigFolder = PathUtil.getUserConfigFolder(); + if (userConfigFolder != null) { + final Path mappingsFile = userConfigFolder.resolve(configFilename); + if (!Files.exists(mappingsFile)) { + LOG.trace("No {} found at: {}, skipping...", configFilename, mappingsFile.toAbsolutePath()); + } else { + configSources.addLast(new MediaTypeConfigSource(mappingsFile)); + } + } + + LOG.trace("{}: load application", configManager.getSimpleName()); + if (configDirs != null) { + for (final Path configDir : configDirs) { + final Path mappingsFile = configDir.resolve(configFilename); + if (!Files.exists(mappingsFile)) { + LOG.warn("No custom {} found at: {}, skipping...", configFilename, mappingsFile.toAbsolutePath()); + } else { + configSources.addLast(new MediaTypeConfigSource(mappingsFile)); + } + } + } + + LOG.trace("{}: load classpath from xyz.elemental.mediatype", configManager.getSimpleName()); + final String classPathLocationStr = "xyz/elemental/mediatype/" + configFilename; + @Nullable final URL url = MediaTypeMapper.class.getClassLoader().getResource(classPathLocationStr); + if (url == null) { + LOG.trace("No {} found on classpath from xyz.elemental.mediatype, skipping...", configFilename); + } else { + final InputStream is = configManager.getClassLoader().getResourceAsStream(classPathLocationStr); + configSources.addLast(new MediaTypeConfigSource(url.toString(), is)); + } + + return configSources.forked(); + } + + /** + * Parses config sources and generates a list of objects. + * + * @param configClass the JAXB object class that we will unmarshall each config sources. + * @param configSources the config sources. + * @param parseFn a Function that converts an object of type C into one or more result objects of type U. + * + * @param The type of the JAB object to unmarshall. + * @param The type of the result object. + * + * @return the list of configured objects. + */ + @SuppressWarnings("unchecked") + static IList parseConfigSources(final Class configClass, final IList configSources, final TriConsumer> parseFn) { + try { + assert (!configSources.isLinear()); + + final JAXBContext context; + final Unmarshaller unmarshaller; + try { + context = JAXBContext.newInstance(configClass); + unmarshaller = context.createUnmarshaller(); + } catch (final JAXBException e) { + LOG.error("Unable to instantiate JAXB Unmarshaller: {}", e.getMessage(), e); + return List.EMPTY; + } + + final LinearList results = new LinearList<>(); + for (final MediaTypeConfigSource configSource : configSources) { + if (configSource.path != null && !Files.exists(configSource.path)) { + LOG.warn("Config path {} does not exist, skipping...", configSource.path); + continue; + } + + try { + final C config; + if (configSource.path != null) { + config = (C) unmarshaller.unmarshal(configSource.path.toUri().toURL()); + } else { + config = (C) unmarshaller.unmarshal(configSource.is); + } + if (config == null) { + LOG.error("No config found in {} skipping...", configSource.location); + continue; + } + + parseFn.accept(configSource.location, config, results); + + } catch (final MalformedURLException | JAXBException e) { + @Nullable String message = e.getMessage(); + if (message == null) { + @Nullable final Throwable cause = e.getCause(); + if (cause != null) { + message = cause.getMessage(); + } + } + LOG.error("Skipping {} due to error: {}", configSource.location, message, e); + } + } + + return results.forked(); + + } finally { + for (final MediaTypeConfigSource configSource : configSources) { + if (configSource.is != null) { + try { + configSource.is.close(); + } catch (final IOException e) { + LOG.warn("Unable to close config file source: {} ", configSource.location, e); + } + } + } + } + } + + /** + * Some source of configuration information. + * + * Either {@link #path} or {@link #is} will be set, but never both. + */ + static class MediaTypeConfigSource { + private final String location; + + @Nullable private final Path path; + @Nullable private final InputStream is; + + private MediaTypeConfigSource(final Path path) { + this.location = path.normalize().toAbsolutePath().toString(); + this.path = path; + this.is = null; + } + + private MediaTypeConfigSource(final String location, final InputStream is) { + this.location = location; + this.is = is; + this.path = null; + } + } +} diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeImpl.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeImpl.java index 21ac682c9d..ea8d302372 100644 --- a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeImpl.java +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeImpl.java @@ -115,6 +115,7 @@ private Builder(final String identifier, final StorageType storageType) { * * @param identifier the Media Type identifier * @param storageType the database storage that should be used for resources of this Media Type + * @return a Media Type builder */ static Builder forMediaType(final String identifier, final StorageType storageType) { return new Builder(identifier, storageType); diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeMapper.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeMapper.java index cc1786a2b3..76f2ae384e 100644 --- a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeMapper.java +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeMapper.java @@ -37,30 +37,24 @@ package xyz.elemental.mediatype.impl; import io.lacuna.bifurcan.IList; -import io.lacuna.bifurcan.LinearList; import net.jcip.annotations.NotThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.elemental.mediatype.StorageType; +import xyz.elemental.mediatype.impl.MediaTypeConfigUtil.MediaTypeConfigSource; import xyz.elemental.mediatype.impl.configuration.MediaType; import xyz.elemental.mediatype.impl.configuration.MediaTypeMappings; import xyz.elemental.mediatype.impl.configuration.Storage; import javax.annotation.Nullable; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.Unmarshaller; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static xyz.elemental.mediatype.impl.MediaTypeConfigUtil.findConfigSources; +import static xyz.elemental.mediatype.impl.MediaTypeConfigUtil.parseConfigSources; + /** * Maps Media Types to database Storage Types. *

Mappings file search order.

@@ -92,153 +86,61 @@ public class MediaTypeMapper { private final Function[] matchers; public MediaTypeMapper(@Nullable final Path... configDirs) { - final IList mappingsFileSources = getMappingsFileSources(configDirs); - this.matchers = loadMatchers(mappingsFileSources); + final IList mappingsFileSources = findConfigSources(MediaTypeMapper.class, MEDIA_TYPE_MAPPINGS_FILENAME, configDirs); + final IList> matchers = parseConfigSources(MediaTypeMappings.class, mappingsFileSources, MediaTypeMapper::mappingsToMatchers); + this.matchers = matchers.toArray(Function[]::new); } - private static IList getMappingsFileSources(final Path... configDirs) { - final LinearList mappingsFileSources = new LinearList<>(); - - LOG.trace("MediaTypeMapper: load HOME"); - @Nullable final Path userConfigFolder = PathUtil.getUserConfigFolder(); - if (userConfigFolder != null) { - final Path mappingsFile = userConfigFolder.resolve(MEDIA_TYPE_MAPPINGS_FILENAME); - if (!Files.exists(mappingsFile)) { - LOG.trace("No media-type-mappings.xml found at: {}, skipping...", mappingsFile.toAbsolutePath()); - } else { - mappingsFileSources.addLast(new MappingsFileSource(mappingsFile)); - } - } - - LOG.trace("MediaTypeMapper: load application"); - if (configDirs != null) { - for (final Path configDir : configDirs) { - final Path mappingsFile = configDir.resolve(MEDIA_TYPE_MAPPINGS_FILENAME); - if (!Files.exists(mappingsFile)) { - LOG.warn("No custom media-type-mappings.xml found at: {}, skipping...", mappingsFile.toAbsolutePath()); - } else { - mappingsFileSources.addLast(new MappingsFileSource(mappingsFile)); - } - } - } - - LOG.trace("ApplicationMimetypesFileTypeMap: load classpath from xyz.elemental.mediatype"); - final String classPathLocationStr = "xyz/elemental/mediatype/" + MEDIA_TYPE_MAPPINGS_FILENAME; - @Nullable final URL url = MediaTypeMapper.class.getClassLoader().getResource(classPathLocationStr); - if (url == null) { - LOG.trace("No media-type-mappings.xml found on classpath from xyz.elemental.mediatype, skipping..."); - } else { - final InputStream is = MediaTypeMapper.class.getClassLoader().getResourceAsStream(classPathLocationStr); - mappingsFileSources.addLast(new MappingsFileSource(url.toString(), is)); + private static void mappingsToMatchers(final String configLocation, final MediaTypeMappings mediaTypeMappings, final IList> matchers) { + if (mediaTypeMappings.getStorage() == null || mediaTypeMappings.getStorage().isEmpty()) { + LOG.error("No mappings found in {} skipping...", configLocation); + return; } - return mappingsFileSources.forked(); - } - - @SuppressWarnings("unchecked") - private static Function[] loadMatchers(final IList mappingsFileSources) { - try { - assert (!mappingsFileSources.isLinear()); - - final JAXBContext context; - final Unmarshaller unmarshaller; - try { - context = JAXBContext.newInstance(MediaTypeMappings.class); - unmarshaller = context.createUnmarshaller(); - } catch (final JAXBException e) { - LOG.error("Unable to instantiate JAXB Unmarshaller: {}", e.getMessage(), e); - return new Function[0]; - } - - final LinearList> matchersList = new LinearList<>(); - for (final MappingsFileSource mappingsFileSource : mappingsFileSources) { - if (mappingsFileSource.path != null && !Files.exists(mappingsFileSource.path)) { - LOG.warn("Mappings path {} does not exist, skipping...", mappingsFileSource.path); - continue; - } - - try { - final MediaTypeMappings mappings; - if (mappingsFileSource.path != null) { - mappings = (MediaTypeMappings) unmarshaller.unmarshal(mappingsFileSource.path.toUri().toURL()); - } else { - mappings = (MediaTypeMappings) unmarshaller.unmarshal(mappingsFileSource.is); - } - if (mappings == null || mappings.getStorage() == null || mappings.getStorage().isEmpty()) { - LOG.error("No mappings found in {} skipping...", mappingsFileSource.location); - continue; - } - - for (final Storage storage : mappings.getStorage()) { - for (final MediaType mediaType : storage.getMediaType()) { - - final StorageType storageType = toStorageType(storage.getType()); - final Function matcher; - switch (mediaType.getMatch()) { - case STARTS_WITH: - matcher = identifier -> { - if (identifier.startsWith(mediaType.getValue())) { - return storageType; - } else { - return null; - } - }; - break; - - case FULL: - matcher = identifier -> { - if (identifier.equals(mediaType.getValue())) { - return storageType; - } else { - return null; - } - }; - break; - - case PATTERN: - final Pattern pattern = Pattern.compile(mediaType.getValue()); - final Matcher patternMatcher = pattern.matcher(""); - matcher = identifier -> { - patternMatcher.reset(identifier); - if (patternMatcher.matches()) { - return storageType; - } else { - return null; - } - }; - break; - - default: - throw new IllegalArgumentException(); + for (final Storage storage : mediaTypeMappings.getStorage()) { + for (final MediaType mediaType : storage.getMediaType()) { + + final StorageType storageType = toStorageType(storage.getType()); + final Function matcher; + switch (mediaType.getMatch()) { + case STARTS_WITH: + matcher = identifier -> { + if (identifier.startsWith(mediaType.getValue())) { + return storageType; + } else { + return null; } + }; + break; + + case FULL: + matcher = identifier -> { + if (identifier.equals(mediaType.getValue())) { + return storageType; + } else { + return null; + } + }; + break; + + case PATTERN: + final Pattern pattern = Pattern.compile(mediaType.getValue()); + final Matcher patternMatcher = pattern.matcher(""); + matcher = identifier -> { + patternMatcher.reset(identifier); + if (patternMatcher.matches()) { + return storageType; + } else { + return null; + } + }; + break; - matchersList.addLast(matcher); - } - } - - } catch (final MalformedURLException | JAXBException e) { - @Nullable String message = e.getMessage(); - if (message == null) { - @Nullable final Throwable cause = e.getCause(); - if (cause != null) { - message = cause.getMessage(); - } - } - LOG.error("Skipping {} due to error: {}", mappingsFileSource.location, message, e); + default: + throw new IllegalArgumentException(); } - } - - return matchersList.toArray(Function[]::new); - } finally { - for (final MappingsFileSource mappingsFileSource : mappingsFileSources) { - if (mappingsFileSource.is != null) { - try { - mappingsFileSource.is.close(); - } catch (final IOException e) { - LOG.warn("Unable to close mappings file source: {} ", mappingsFileSource.location, e); - } - } + matchers.addLast(matcher); } } } @@ -287,26 +189,4 @@ public StorageType resolveStorageType(final String mediaTypeIdentifier) { return defaultStorageType; } - - private static class MappingsFileSource { - private final String location; - - /** - * Either {@link #path} or {@link #is} will be set, but never both. - */ - @Nullable private final Path path; - @Nullable private final InputStream is; - - private MappingsFileSource(final Path path) { - this.location = path.normalize().toAbsolutePath().toString(); - this.path = path; - this.is = null; - } - - private MappingsFileSource(final String location, final InputStream is) { - this.location = location; - this.is = is; - this.path = null; - } - } } diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverFactoryImpl.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverFactoryImpl.java index 016a73f8e2..bfcac15c46 100644 --- a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverFactoryImpl.java +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverFactoryImpl.java @@ -62,7 +62,8 @@ public MediaTypeResolver newMediaTypeResolver() { public MediaTypeResolver newMediaTypeResolver(@Nullable final Path... configDirs) { final ApplicationMimetypesFileTypeMap mimetypesFileTypeMap = new ApplicationMimetypesFileTypeMap( configDirs); + final MediaTypeAliaser mediaTypeAliaser = new MediaTypeAliaser(configDirs); final MediaTypeMapper mediaTypeMapper = new MediaTypeMapper(configDirs); - return new MediaTypeResolverImpl(mimetypesFileTypeMap, mediaTypeMapper); + return new MediaTypeResolverImpl(mimetypesFileTypeMap, mediaTypeAliaser, mediaTypeMapper); } } diff --git a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverImpl.java b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverImpl.java index c03d19a750..87dfef9d54 100644 --- a/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverImpl.java +++ b/elemental-media-type/elemental-media-type-impl/src/main/java/xyz/elemental/mediatype/impl/MediaTypeResolverImpl.java @@ -37,6 +37,7 @@ package xyz.elemental.mediatype.impl; import com.sun.activation.registries.MimeTypeEntry; +import io.lacuna.bifurcan.IEntry; import io.lacuna.bifurcan.IList; import io.lacuna.bifurcan.LinearMap; import jakarta.activation.MimetypesFileTypeMap; @@ -44,6 +45,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.elemental.mediatype.MediaType; +import xyz.elemental.mediatype.MediaTypeAlias; import xyz.elemental.mediatype.MediaTypeResolver; import xyz.elemental.mediatype.StorageType; @@ -70,15 +72,17 @@ public class MediaTypeResolverImpl implements MediaTypeResolver { private final io.lacuna.bifurcan.IMap extensionsIndex; private final io.lacuna.bifurcan.IMap identifiersIndex; + private final io.lacuna.bifurcan.IMap aliasIdentifiersIndex; private final MediaType defaultMediaType; - public MediaTypeResolverImpl(final ApplicationMimetypesFileTypeMap fileTypeMap, final MediaTypeMapper mediaTypeMapper) { + public MediaTypeResolverImpl(final ApplicationMimetypesFileTypeMap fileTypeMap, final MediaTypeAliaser mediaTypeAliaser, final MediaTypeMapper mediaTypeMapper) { final IList>> allEntries = fileTypeMap.getAllEntries(); if (allEntries == null) { LOG.warn("Could not load file type maps. No mime types are known to the system!"); this.extensionsIndex = io.lacuna.bifurcan.Map.empty(); this.identifiersIndex = io.lacuna.bifurcan.Map.empty(); + this.aliasIdentifiersIndex = io.lacuna.bifurcan.Map.empty(); this.defaultMediaType = null; return; } @@ -119,10 +123,28 @@ public MediaTypeResolverImpl(final ApplicationMimetypesFileTypeMap fileTypeMap, this.extensionsIndex = mutExtensionsIndex.mapValues((k, v) -> v.build()).forked(); this.identifiersIndex = mutIdentifiersIndex.mapValues((k, v) -> v.build()).forked(); - assert(!extensionsIndex.isLinear()); assert(!identifiersIndex.isLinear()); + final LinearMap mutAliasIdentifiersIndex = new LinearMap<>((int) mediaTypeAliaser.aliases.size()); + for (final IEntry alias : mediaTypeAliaser.aliases.entries()) { + final String aliasIdentifier = alias.key(); + final String aliasTo = alias.value(); + @Nullable final MediaType aliasTarget = identifiersIndex.get(aliasTo, null); + if (aliasTarget == null) { + LOG.warn("No Media Type definition found for alias: {}. Alias will be ignored!", aliasIdentifier); + continue; + } + + final MediaTypeAliasImpl.Builder mediaTypeAliasBuilder = MediaTypeAliasImpl.builder(aliasIdentifier) + .of(aliasTarget); + + mutAliasIdentifiersIndex.put(aliasIdentifier, mediaTypeAliasBuilder); + } + + this.aliasIdentifiersIndex = mutAliasIdentifiersIndex.mapValues((k, v) -> v.build()).forked(); + assert(!aliasIdentifiersIndex.isLinear()); + this.defaultMediaType = identifiersIndex.get(APPLICATION_OCTET_STREAM) .orElseGet(() -> MediaTypeImpl.Builder.forMediaType(APPLICATION_OCTET_STREAM, StorageType.BINARY).build()); } @@ -167,12 +189,21 @@ public MediaTypeResolverImpl(final ApplicationMimetypesFileTypeMap fileTypeMap, } @Override - public @Nullable MediaType fromString(@Nullable final String mediaType) { + public @Nullable MediaType fromString(@Nullable String mediaType) { if (mediaType == null) { return null; } - return identifiersIndex.get(mediaType.toLowerCase(), null); + mediaType = mediaType.toLowerCase(); + + // 1. try and resolve any alias first + @Nullable final MediaType aliasTarget = aliasIdentifiersIndex.get(mediaType, null); + if (aliasTarget != null) { + return aliasTarget; + } + + // 2. if it is not an alias, then resolve the media type itself + return identifiersIndex.get(mediaType, null); } @Override diff --git a/elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-mappings.xjb b/elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-configuration.xjb similarity index 84% rename from elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-mappings.xjb rename to elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-configuration.xjb index 7df9688282..429902145f 100644 --- a/elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-mappings.xjb +++ b/elemental-media-type/elemental-media-type-impl/src/main/xjb/media-type-configuration.xjb @@ -38,9 +38,18 @@ --> + + + + + \ No newline at end of file diff --git a/elemental-media-type/elemental-media-type-impl/src/main/xsd/media-type-aliases.xsd b/elemental-media-type/elemental-media-type-impl/src/main/xsd/media-type-aliases.xsd new file mode 100644 index 0000000000..0c0815838b --- /dev/null +++ b/elemental-media-type/elemental-media-type-impl/src/main/xsd/media-type-aliases.xsd @@ -0,0 +1,93 @@ + + + + + + + Defined Media Types as aliases to other Media Types. + + + + + + + + + + + + Each Media Type alias should only appear once + + + + + + + + + Creates a Media Type Alias + + + + + + Optional documentation describing the purpose of the alias + + + + + + The Media Type alias identifier for the Media Type + + + + + The identifier of the Media Type that is being aliased + + + + + + diff --git a/elemental-media-type/elemental-media-type-impl/src/test/java/xyz/elemental/mediatype/impl/MediaTypeResolverImplTest.java b/elemental-media-type/elemental-media-type-impl/src/test/java/xyz/elemental/mediatype/impl/MediaTypeResolverImplTest.java index f90a90d06e..0578507750 100644 --- a/elemental-media-type/elemental-media-type-impl/src/test/java/xyz/elemental/mediatype/impl/MediaTypeResolverImplTest.java +++ b/elemental-media-type/elemental-media-type-impl/src/test/java/xyz/elemental/mediatype/impl/MediaTypeResolverImplTest.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import xyz.elemental.mediatype.MediaType; +import xyz.elemental.mediatype.MediaTypeAlias; import xyz.elemental.mediatype.MediaTypeResolver; import xyz.elemental.mediatype.StorageType; @@ -49,28 +50,29 @@ import java.nio.file.Paths; import java.util.Arrays; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.*; public class MediaTypeResolverImplTest { // TODO(AR) if an explicit content type is provided, e.g. HTTP PUT, store the mime type with the document data??? what if its not provided, lookup and store, or lookup on retrieval? + private static MediaTypeAliaser MEDIA_TYPE_ALIASER = null; private static MediaTypeMapper MEDIA_TYPE_MAPPER = null; private static MediaTypeResolver DEFAULT_MEDIA_RESOLVER = null; private static MediaTypeResolver APPLICATION_MEDIA_RESOLVER = null; @BeforeAll public static void setupMediaResolvers() throws URISyntaxException { + @Nullable final URL mediaTypeAliases = MediaTypeResolverImplTest.class.getResource("media-type-aliases.xml"); + assertNotNull(mediaTypeAliases); @Nullable final URL mediaTypeMappings = MediaTypeResolverImplTest.class.getResource("media-type-mappings.xml"); assertNotNull(mediaTypeMappings); final Path configDir = Paths.get(mediaTypeMappings.toURI()).getParent(); + MEDIA_TYPE_ALIASER = new MediaTypeAliaser(configDir); MEDIA_TYPE_MAPPER = new MediaTypeMapper(configDir); final ApplicationMimetypesFileTypeMap defaultMimetypesFileTypeMap = new ApplicationMimetypesFileTypeMap((Path[]) null); - DEFAULT_MEDIA_RESOLVER = new MediaTypeResolverImpl(defaultMimetypesFileTypeMap, MEDIA_TYPE_MAPPER); + DEFAULT_MEDIA_RESOLVER = new MediaTypeResolverImpl(defaultMimetypesFileTypeMap, MEDIA_TYPE_ALIASER, MEDIA_TYPE_MAPPER); APPLICATION_MEDIA_RESOLVER = new MediaTypeResolverFactoryImpl().newMediaTypeResolver(configDir); } @@ -485,6 +487,14 @@ public void defaultResolveXmlIdentifier() { // + // + @Test + public void defaultResolveTextXmlAliasIdentifier() { + assertDefaultResolveFromAliasIdentifier("text/xml", MediaType.APPLICATION_XML, new String[] {"xsl", "xml"}, StorageType.XML); + } + // + + // @Test public void applicationResolveDitaExtension() { @@ -693,6 +703,19 @@ public void applicationResolveXqueryXIdentifier() { // + // + @Test + public void applicationResolveTextXmlAliasIdentifier() { + assertApplicationResolveFromAliasIdentifier("text/xml", MediaType.APPLICATION_XML, new String[] {"fo", "nvdl", "rng", "stx", "xconf", "xml", "xsd", "xsl"}, StorageType.XML); + } + + @Test + public void applicationResolveApplicationAdamQueryAliasIdentifier() { + assertApplicationResolveFromAliasIdentifier("application/adam-query", MediaType.APPLICATION_XQUERY, new String[] {"xq", "xql", "xqm", "xquery", "xqws", "xqy"}, StorageType.BINARY); + } + // + + /** * Check that multiple levels of mime.types files * yield correct lookups via. both @@ -720,7 +743,7 @@ public void resolveFromCorrectLevel() throws URISyntaxException { assertEquals(MediaType.APPLICATION_XML, mimetypesFileTypeMap.getContentType("something.xsd")); assertEquals("test/x.xsl+xml", mimetypesFileTypeMap.getContentType("something.xsl")); - final MediaTypeResolverImpl specificMediaTypeResolver = new MediaTypeResolverImpl(mimetypesFileTypeMap, MEDIA_TYPE_MAPPER); + final MediaTypeResolverImpl specificMediaTypeResolver = new MediaTypeResolverImpl(mimetypesFileTypeMap, MEDIA_TYPE_ALIASER, MEDIA_TYPE_MAPPER); assertResolveFromFileName(specificMediaTypeResolver, "something.xadam", "test/extensible-markup-language", new String[] {"xml", "xadam"}, StorageType.BINARY); assertResolveFromFileName(specificMediaTypeResolver, "something.xconf", "test/prs.existdb.collection-config+xml", new String[] {"xconf"}, StorageType.XML); @@ -815,12 +838,41 @@ private void assertDefaultResolveFromIdentifier(final String identifier, final S private void assertApplicationResolveFromIdentifier(final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { assertNotNull(APPLICATION_MEDIA_RESOLVER); - assertResolveFromIdentifier(APPLICATION_MEDIA_RESOLVER, identifier, expectedExtensions, expectedStorageType); + assertResolveFromIdentifier(APPLICATION_MEDIA_RESOLVER, identifier, expectedExtensions, expectedStorageType); } private void assertResolveFromIdentifier(final MediaTypeResolver mediaTypeResolver, final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { final @Nullable MediaType mediaType = mediaTypeResolver.fromString(identifier); assertNotNull(mediaType); + assertEquals(identifier, mediaType.getIdentifier()); + assertArrayAnyOrderEquals(expectedExtensions, mediaType.getKnownFileExtensions()); + assertEquals(expectedStorageType, mediaType.getStorageType()); + } + + private void assertAllResolveFromAliasIdentifier(final String aliasIdentifier, final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { + assertDefaultResolveFromAliasIdentifier(aliasIdentifier, identifier, expectedExtensions, expectedStorageType); + assertApplicationResolveFromAliasIdentifier(aliasIdentifier, identifier, expectedExtensions, expectedStorageType); + } + + private void assertDefaultResolveFromAliasIdentifier(final String aliasIdentifier, final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { + assertNotNull(DEFAULT_MEDIA_RESOLVER); + assertResolveFromAliasIdentifier(DEFAULT_MEDIA_RESOLVER, aliasIdentifier, identifier, expectedExtensions, expectedStorageType); + } + + private void assertApplicationResolveFromAliasIdentifier(final String aliasIdentifier, final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { + assertNotNull(APPLICATION_MEDIA_RESOLVER); + assertResolveFromAliasIdentifier(APPLICATION_MEDIA_RESOLVER, aliasIdentifier, identifier, expectedExtensions, expectedStorageType); + } + + private void assertResolveFromAliasIdentifier(final MediaTypeResolver mediaTypeResolver, final String aliasIdentifier, final String identifier, final String[] expectedExtensions, final StorageType expectedStorageType) { + final @Nullable MediaType mediaType = mediaTypeResolver.fromString(aliasIdentifier); + assertNotNull(mediaType); + assertEquals(identifier, mediaType.getIdentifier()); + + assertTrue(mediaType instanceof MediaTypeAlias); + final MediaTypeAlias mediaTypeAlias = (MediaTypeAlias) mediaType; + assertEquals(aliasIdentifier, mediaTypeAlias.getAliasIdentifier()); + assertArrayAnyOrderEquals(expectedExtensions, mediaType.getKnownFileExtensions()); assertEquals(expectedStorageType, mediaType.getStorageType()); } diff --git a/elemental-media-type/elemental-media-type-impl/src/test/resources/xyz/elemental/mediatype/impl/media-type-aliases.xml b/elemental-media-type/elemental-media-type-impl/src/test/resources/xyz/elemental/mediatype/impl/media-type-aliases.xml new file mode 100644 index 0000000000..df81fd6be2 --- /dev/null +++ b/elemental-media-type/elemental-media-type-impl/src/test/resources/xyz/elemental/mediatype/impl/media-type-aliases.xml @@ -0,0 +1,50 @@ + + + + + + text/xml is an alias for application/xml as described in IETF RFC 7303 + + + + A made up alias for testing purposes + + + \ No newline at end of file diff --git a/elemental-parent/pom.xml b/elemental-parent/pom.xml index 6c43c96de3..c19d04b653 100644 --- a/elemental-parent/pom.xml +++ b/elemental-parent/pom.xml @@ -121,6 +121,12 @@ 3.0.2 + + com.evolvedbinary.j8fu + j8fu + 1.24.0 + + io.lacuna bifurcan diff --git a/exist-core/pom.xml b/exist-core/pom.xml index 880bda2491..c6761f5495 100644 --- a/exist-core/pom.xml +++ b/exist-core/pom.xml @@ -2261,6 +2261,7 @@ src/main/java/org/exist/xupdate/Update.java src/main/java/org/exist/xupdate/XUpdateProcessor.java src/test/java/org/exist/xupdate/XUpdateTest.java + src/main/resources/xyz/elemental/mediatype/media-type-aliases.xml src/main/resources/xyz/elemental/mediatype/media-type-mappings.xml src/main/resources/xyz/elemental/mediatype/mime.types @@ -2428,6 +2429,7 @@ The original license statement is also included below.]]> src/main/java/org/exist/mediatype/MediaTypeService.java src/main/java/org/exist/mediatype/MediaTypeUtil.java + src/main/resources/xyz/elemental/mediatype/media-type-aliases.xml src/main/resources/xyz/elemental/mediatype/media-type-mappings.xml src/main/resources/xyz/elemental/mediatype/mime.types diff --git a/exist-core/src/main/java/org/exist/backup/restore/AbstractRestoreHandler.java b/exist-core/src/main/java/org/exist/backup/restore/AbstractRestoreHandler.java index 34fe394668..80bdce6ca7 100644 --- a/exist-core/src/main/java/org/exist/backup/restore/AbstractRestoreHandler.java +++ b/exist-core/src/main/java/org/exist/backup/restore/AbstractRestoreHandler.java @@ -379,9 +379,15 @@ private DeferredPermission restoreResourceEntry(final Attributes attributes) thr listener.warn("Missing mimetype attribute in the backup __contents__.xml file for: " + commonAttributes.name + ", assuming: " + mediaType); } else { mediaType = mediaTypeResolver.fromString(mediaTypeStr.trim()); - if (xmlType && mediaType.getStorageType() != StorageType.XML) { - mediaType = MediaTypeImpl.builder(mediaTypeStr.trim(), StorageType.XML).build(); - } else if ((!xmlType) && mediaType.getStorageType() != StorageType.BINARY) { + if (mediaType != null) { + if (xmlType && mediaType.getStorageType() != StorageType.XML) { + mediaType = MediaTypeImpl.builder(mediaTypeStr.trim(), StorageType.XML).build(); + } else if ((!xmlType) && mediaType.getStorageType() != StorageType.BINARY) { + mediaType = MediaTypeImpl.builder(mediaTypeStr.trim(), StorageType.BINARY).build(); + } + } else { + // could not find a MediaType for the mediaTypeStr - this could mean we are missing a Media Type in mime.types or media-type-aliases.xml + listener.warn("Could not find MediaType for: " + mediaTypeStr + " when restoring: " + is.getSymbolicPath() + ". Document will be stored as a binary document."); mediaType = MediaTypeImpl.builder(mediaTypeStr.trim(), StorageType.BINARY).build(); } } diff --git a/exist-core/src/main/resources/xyz/elemental/mediatype/media-type-aliases.xml b/exist-core/src/main/resources/xyz/elemental/mediatype/media-type-aliases.xml new file mode 100644 index 0000000000..997a2a1fc4 --- /dev/null +++ b/exist-core/src/main/resources/xyz/elemental/mediatype/media-type-aliases.xml @@ -0,0 +1,54 @@ + + + + + + text/xml is an alias for application/xml as described in IETF RFC 7303 + + + + text/xml-external-parsed-entity is an alias for application/xml-external-parsed-entity as described in IETF RFC 7303 + + + + The correct Media Type for JavaScript has changed to text/javascript + + + \ No newline at end of file diff --git a/exist-core/src/main/resources/xyz/elemental/mediatype/mime.types b/exist-core/src/main/resources/xyz/elemental/mediatype/mime.types index 343d4c8b45..c38704e591 100644 --- a/exist-core/src/main/resources/xyz/elemental/mediatype/mime.types +++ b/exist-core/src/main/resources/xyz/elemental/mediatype/mime.types @@ -38,6 +38,7 @@ application/dita+xml dita ditamap ditaval xdita application/prs.existdb.collection-config+xml xconf application/prs.expath.package+zip xar +application/prs.expath.package+zip xar application/prs.invisible-xml.grammar ixml application/prs.invisible-xml.grammar+xml ixml.xml application/rdf+xml xmp owl rdf @@ -45,6 +46,7 @@ application/schematron+xml sch application/tei+xml odd tei teicorpus application/vnd.xmi+xml xmi application/xml fo nvdl rng stx xml xsd xsl +application/xml-external-parsed-entity ent application/xquery xq xql xqm xquery xqws xqy application/xquery+xml xqx application/zstd zst diff --git a/exist-parent/pom.xml b/exist-parent/pom.xml index 0b6616ea86..ed2468a965 100644 --- a/exist-parent/pom.xml +++ b/exist-parent/pom.xml @@ -149,11 +149,6 @@ ${saxon.version} - - com.evolvedbinary.j8fu - j8fu - 1.24.0 - com.evolvedbinary.multilock multilock