001/* 002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 */ 020package org.nuxeo.ecm.core.convert.service; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.regex.Pattern; 030import java.util.stream.Collectors; 031 032import org.apache.logging.log4j.LogManager; 033import org.apache.logging.log4j.Logger; 034import org.nuxeo.ecm.core.convert.extension.ConverterDescriptor; 035 036/** 037 * Helper class to manage chains of converters. 038 * 039 * @author tiry 040 */ 041public class MimeTypeTranslationHelper { 042 043 private static final Logger log = LogManager.getLogger(MimeTypeTranslationHelper.class); 044 045 /** 046 * @since 10.3 047 */ 048 public static final String ANY_MIME_TYPE = "*"; 049 050 /** 051 * @since 10.3 052 */ 053 public static final Pattern MIME_TYPE_PATTERN = Pattern.compile("(.*?);(.*)", Pattern.DOTALL); 054 055 protected final Map<String, List<ConvertOption>> srcMappings = new HashMap<>(); 056 057 protected final Map<String, List<ConvertOption>> dstMappings = new HashMap<>(); 058 059 public void addConverter(ConverterDescriptor desc) { 060 List<String> sMts = desc.getSourceMimeTypes(); 061 String dMt = desc.getDestinationMimeType(); 062 063 List<ConvertOption> dco = dstMappings.computeIfAbsent(dMt, key -> new ArrayList<>()); 064 for (String sMT : sMts) { 065 List<ConvertOption> sco = srcMappings.computeIfAbsent(sMT, key -> new ArrayList<>()); 066 sco.add(new ConvertOption(desc.getConverterName(), dMt)); 067 dco.add(new ConvertOption(desc.getConverterName(), sMT)); 068 } 069 log.debug("Added converter {} to {}", desc::getSourceMimeTypes, desc::getDestinationMimeType); 070 } 071 072 /** 073 * Returns the last registered converter name for the given {@code sourceMimeType} and {@code destinationMimeType}. 074 * <p> 075 * Follows the algorithm of {@link #getConverterNames(String, String)}. 076 * 077 * @see #getConverterNames(String, String) 078 * @see #getConverterName(String, String, boolean) 079 */ 080 public String getConverterName(String sourceMimeType, String destinationMimeType) { 081 return getConverterName(sourceMimeType, destinationMimeType, true); 082 } 083 084 /** 085 * Returns the last registered converter name for the given {@code sourceMimeType} and {@code destinationMimeType}. 086 * <p> 087 * Follows the algorithm of {@link #getConverterNames(String, String, boolean)}. 088 * 089 * @since 11.1 090 * @see #getConverterNames(String, String, boolean) 091 */ 092 public String getConverterName(String sourceMimeType, String destinationMimeType, boolean allowWildcard) { 093 List<String> converterNames = getConverterNames(sourceMimeType, destinationMimeType, allowWildcard); 094 return converterNames.isEmpty() ? null : converterNames.get(converterNames.size() - 1); 095 } 096 097 /** 098 * Returns {@code true} if the given {@code mimeTypes} has a compatible mime type with {@code mimeType}, 099 * {@code false} otherwise. 100 * <p> 101 * The {@code mimeTypes} list has a compatible mime type if: 102 * <ul> 103 * <li>it contains "*"</li> 104 * <li>it contains exactly {@code mimeType}</li> 105 * <li>it contains a mime type with the same primary type as {@code mimeType} and a wildcard sub type</li> 106 * </ul> 107 * 108 * @since 10.3 109 */ 110 public boolean hasCompatibleMimeType(List<String> mimeTypes, String mimeType) { 111 String mt = parseMimeType(mimeType); 112 Set<String> expectedMimeTypes = new HashSet<>(); 113 expectedMimeTypes.add(ANY_MIME_TYPE); 114 if (mt != null) { 115 expectedMimeTypes.add(mt); 116 expectedMimeTypes.add(computeMimeTypeWithWildcardSubType(mt)); 117 } 118 return mimeTypes.stream().anyMatch(expectedMimeTypes::contains); 119 } 120 121 /** 122 * Returns the list of converter names handling the given {@code sourceMimeType} and {@code destinationMimeType}. 123 * 124 * @see #getConverterNames(String, String, boolean) 125 */ 126 public List<String> getConverterNames(String sourceMimeType, String destinationMimeType) { 127 return getConverterNames(sourceMimeType, destinationMimeType, true); 128 } 129 130 /** 131 * Returns the list of converter names handling the given {@code sourceMimeType} and {@code destinationMimeType}. 132 * <p> 133 * Finds the converter names based on the following algorithm: 134 * <ul> 135 * <li>Find the converters exactly matching the given {@code sourceMimeType}</li> 136 * <li>If no converter found, find the converters matching a wildcard subtype based on the {@code sourceMimeType}, 137 * such has "image/*"</li> 138 * <li>If no converter found and {@code allowWildcard} is {@code true}, find the converters matching a wildcard 139 * source mime type "*"</li> 140 * <li>Then, filter only the converters matching the given {@code destinationMimeType}</li> 141 * </ul> 142 * 143 * @param allowWildcard {@code true} to allow returning converters with '*' as source mime type. 144 * @since 11.1 145 */ 146 public List<String> getConverterNames(String sourceMimeType, String destinationMimeType, boolean allowWildcard) { 147 // remove content type parameters if any 148 String srcMimeType = parseMimeType(sourceMimeType); 149 150 List<ConvertOption> cos = srcMappings.getOrDefault(srcMimeType, Collections.emptyList()); 151 if (cos.isEmpty()) { 152 // use a mime type with a wildcard sub type 153 cos = srcMappings.getOrDefault(computeMimeTypeWithWildcardSubType(srcMimeType), Collections.emptyList()); 154 } 155 156 if (cos.isEmpty() && allowWildcard) { 157 // use a wildcard mime type 158 cos = srcMappings.getOrDefault(ANY_MIME_TYPE, Collections.emptyList()); 159 } 160 161 return cos.stream() 162 .filter(co -> destinationMimeType == null || destinationMimeType.equals(co.mimeType)) 163 .map(co -> co.converter) 164 .collect(Collectors.toList()); 165 } 166 167 /** 168 * Parses the given {@code mimeType} and returns only the primary type and optionally the sub type if any. 169 * <p> 170 * Some input/output samples: 171 * <ul> 172 * <li>"image/jpeg" => "image/jpeg"</li> 173 * <li>"image/*" => "image/*"</li> 174 * <li>"image/png; param1=foo; param2=bar" => "image/png"</li> 175 * </ul> 176 * 177 * @since 10.3 178 */ 179 protected String parseMimeType(String mimeType) { 180 if (mimeType == null) { 181 return null; 182 } 183 184 return MIME_TYPE_PATTERN.matcher(mimeType).replaceAll("$1").trim(); 185 } 186 187 /** 188 * Returns a new mime type with the primary type of the given {@code mimeType} and a wildcard sub type. 189 * <p> 190 * Some input/output samples: 191 * <ul> 192 * <li>"image/jpeg" => "image/*"</li> 193 * <li>"video/*" => "video/*"</li> 194 * <li>"application/pdf" => "application/*"</li> 195 * </ul> 196 * 197 * @since 10.3 198 */ 199 protected String computeMimeTypeWithWildcardSubType(String mimeType) { 200 return mimeType != null ? mimeType.replaceAll("(.*)/(.*)", "$1/" + ANY_MIME_TYPE) : null; 201 } 202 203 /** 204 * @deprecated since 10.3. Not used. 205 */ 206 @Deprecated 207 public List<String> getDestinationMimeTypes(String sourceMimeType) { 208 List<String> dst = new ArrayList<>(); 209 210 List<ConvertOption> sco = srcMappings.get(sourceMimeType); 211 212 if (sco != null) { 213 for (ConvertOption co : sco) { 214 dst.add(co.getMimeType()); 215 } 216 } 217 return dst; 218 } 219 220 /** 221 * @deprecated since 10.3. Not used. 222 */ 223 @Deprecated 224 public List<String> getSourceMimeTypes(String destinationMimeType) { 225 List<String> src = new ArrayList<>(); 226 227 List<ConvertOption> dco = dstMappings.get(destinationMimeType); 228 229 if (dco != null) { 230 for (ConvertOption co : dco) { 231 src.add(co.getMimeType()); 232 } 233 } 234 return src; 235 } 236 237 public void clear() { 238 dstMappings.clear(); 239 srcMappings.clear(); 240 log.debug("clear"); 241 } 242 243}