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 * Follow the algorithm of {@link #getConverterNames(String, String)}. 076 * 077 * @see #getConverterNames(String, String) 078 */ 079 public String getConverterName(String sourceMimeType, String destinationMimeType) { 080 List<String> converterNames = getConverterNames(sourceMimeType, destinationMimeType); 081 return converterNames.isEmpty() ? null : converterNames.get(converterNames.size() - 1); 082 } 083 084 /** 085 * Returns {@code true} if the given {@code mimeTypes} has a compatible mime type with {@code mimeType}, 086 * {@code false} otherwise. 087 * <p> 088 * The {@code mimeTypes} list has a compatible mime type if: 089 * <ul> 090 * <li>it contains "*"</li> 091 * <li>it contains exactly {@code mimeType}</li> 092 * <li>it contains a mime type with the same primary type as {@code mimeType} and a wildcard sub type</li> 093 * </ul> 094 * 095 * @since 10.3 096 */ 097 public boolean hasCompatibleMimeType(List<String> mimeTypes, String mimeType) { 098 String mt = parseMimeType(mimeType); 099 Set<String> expectedMimeTypes = new HashSet<>(); 100 expectedMimeTypes.add(ANY_MIME_TYPE); 101 if (mt != null) { 102 expectedMimeTypes.add(mt); 103 expectedMimeTypes.add(computeMimeTypeWithWildcardSubType(mt)); 104 } 105 return mimeTypes.stream().anyMatch(expectedMimeTypes::contains); 106 } 107 108 /** 109 * Returns the list of converter names handling the given {@code sourceMimeType} and {@code destinationMimeType}. 110 * <p> 111 * Find the converter names based on the following algorithm: 112 * <ul> 113 * <li>Find the converters exactly matching the given {@code sourceMimeType}</li> 114 * <li>If no converter found, find the converters matching a wildcard subtype based on the {@code sourceMimeType}, 115 * such has "image/*"</li> 116 * <li>If no converter found, find the converters matching a wildcard source mime type "*"</li> 117 * <li>Then, filter only the converting matching the given {@code destinationMimeType}</li> 118 * </ul> 119 */ 120 public List<String> getConverterNames(String sourceMimeType, String destinationMimeType) { 121 // remove content type parameters if any 122 String srcMimeType = parseMimeType(sourceMimeType); 123 124 List<ConvertOption> cos = srcMappings.getOrDefault(srcMimeType, Collections.emptyList()); 125 if (cos.isEmpty()) { 126 // use a mime type with a wildcard sub type 127 cos = srcMappings.getOrDefault(computeMimeTypeWithWildcardSubType(srcMimeType), Collections.emptyList()); 128 } 129 130 if (cos.isEmpty()) { 131 // use a wildcard mime type 132 cos = srcMappings.getOrDefault(ANY_MIME_TYPE, Collections.emptyList()); 133 } 134 135 return cos.stream() 136 .filter(co -> destinationMimeType == null || co.mimeType.equals(destinationMimeType)) 137 .map(co -> co.converter) 138 .collect(Collectors.toList()); 139 } 140 141 /** 142 * Parses the given {@code mimeType} and returns only the primary type and optionally the sub type if any. 143 * <p> 144 * Some input/output samples: 145 * <ul> 146 * <li>"image/jpeg" => "image/jpeg"</li> 147 * <li>"image/*" => "image/*"</li> 148 * <li>"image/png; param1=foo; param2=bar" => "image/png"</li> 149 * </ul> 150 * 151 * @since 10.3 152 */ 153 protected String parseMimeType(String mimeType) { 154 if (mimeType == null) { 155 return null; 156 } 157 158 return MIME_TYPE_PATTERN.matcher(mimeType).replaceAll("$1").trim(); 159 } 160 161 /** 162 * Returns a new mime type with the primary type of the given {@code mimeType} and a wildcard sub type. 163 * <p> 164 * Some input/output samples: 165 * <ul> 166 * <li>"image/jpeg" => "image/*"</li> 167 * <li>"video/*" => "video/*"</li> 168 * <li>"application/pdf" => "application/*"</li> 169 * </ul> 170 * 171 * @since 10.3 172 */ 173 protected String computeMimeTypeWithWildcardSubType(String mimeType) { 174 return mimeType != null ? mimeType.replaceAll("(.*)/(.*)", "$1/" + ANY_MIME_TYPE) : null; 175 } 176 177 /** 178 * @deprecated since 10.3. Not used. 179 */ 180 @Deprecated 181 public List<String> getDestinationMimeTypes(String sourceMimeType) { 182 List<String> dst = new ArrayList<>(); 183 184 List<ConvertOption> sco = srcMappings.get(sourceMimeType); 185 186 if (sco != null) { 187 for (ConvertOption co : sco) { 188 dst.add(co.getMimeType()); 189 } 190 } 191 return dst; 192 } 193 194 /** 195 * @deprecated since 10.3. Not used. 196 */ 197 @Deprecated 198 public List<String> getSourceMimeTypes(String destinationMimeType) { 199 List<String> src = new ArrayList<>(); 200 201 List<ConvertOption> dco = dstMappings.get(destinationMimeType); 202 203 if (dco != null) { 204 for (ConvertOption co : dco) { 205 src.add(co.getMimeType()); 206 } 207 } 208 return src; 209 } 210 211 public void clear() { 212 dstMappings.clear(); 213 srcMappings.clear(); 214 log.debug("clear"); 215 } 216 217}