001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.encoders; 014 015import java.util.*; 016import java.util.concurrent.*; 017 018import org.apache.juneau.collections.*; 019import org.apache.juneau.http.header.*; 020 021/** 022 * Represents the group of {@link Encoder encoders} keyed by codings. 023 * 024 * <h5 class='topic'>Description</h5> 025 * 026 * Maintains a set of encoders and the codings that they can handle. 027 * 028 * <p> 029 * The {@link #getEncoderMatch(String)} and {@link #getEncoder(String)} methods are then used to find appropriate 030 * encoders for specific <c>Accept-Encoding</c> and <c>Content-Encoding</c> header values. 031 * 032 * <h5 class='topic'>Match ordering</h5> 033 * 034 * Encoders are matched against <c>Accept-Encoding</c> strings in the order they exist in this group. 035 * 036 * <p> 037 * Adding new entries will cause the entries to be prepended to the group. 038 * This allows for previous encoders to be overridden through subsequent calls. 039 * 040 * <p> 041 * For example, calling <code>groupBuilder.append(E1.<jk>class</jk>,E2.<jk>class</jk>).append(E3.<jk>class</jk>, 042 * E4.<jk>class</jk>)</code> will result in the order <c>E3, E4, E1, E2</c>. 043 * 044 * <h5 class='section'>Example:</h5> 045 * <p class='bcode w800'> 046 * <jc>// Create an encoder group with support for gzip compression.</jc> 047 * EncoderGroup g = EncoderGroup.<jsm>create</jsm>().append(GzipEncoder.<jk>class</jk>).build(); 048 * 049 * <jc>// Should return "gzip"</jc> 050 * String matchedCoding = g.findMatch(<js>"compress;q=1.0, gzip;q=0.8, identity;q=0.5, *;q=0"</js>); 051 * 052 * <jc>// Get the encoder</jc> 053 * IEncoder encoder = g.getEncoder(matchedCoding); 054 * </p> 055 */ 056public final class EncoderGroup { 057 058 /** 059 * A default encoder group consisting of identity and G-Zip encoding. 060 */ 061 public static final EncoderGroup DEFAULT = create().append(IdentityEncoder.class, GzipEncoder.class).build(); 062 063 // Maps Accept-Encoding headers to matching encoders. 064 private final ConcurrentHashMap<String,EncoderMatch> cache = new ConcurrentHashMap<>(); 065 066 private final List<String> encodings; 067 private final Encoder[] encodingsEncoders; 068 private final List<Encoder> encoders; 069 070 /** 071 * Instantiates a new clean-slate {@link EncoderGroupBuilder} object. 072 * 073 * <p> 074 * This is equivalent to simply calling <code><jk>new</jk> EncoderGroupBuilder()</code>. 075 * 076 * @return A new {@link EncoderGroupBuilder} object. 077 */ 078 public static EncoderGroupBuilder create() { 079 return new EncoderGroupBuilder(); 080 } 081 082 /** 083 * Returns a builder that's a copy of the settings on this encoder group. 084 * 085 * @return A new {@link EncoderGroupBuilder} initialized to this group. 086 */ 087 public EncoderGroupBuilder builder() { 088 return new EncoderGroupBuilder(this); 089 } 090 091 /** 092 * Constructor 093 * 094 * @param encoders The encoders to add to this group. 095 */ 096 public EncoderGroup(Encoder[] encoders) { 097 this.encoders = AList.unmodifiable(encoders); 098 099 AList<String> lc = AList.of(); 100 AList<Encoder> l = AList.of(); 101 for (Encoder e : encoders) { 102 for (String c: e.getCodings()) { 103 lc.add(c); 104 l.add(e); 105 } 106 } 107 108 this.encodings = lc.unmodifiable(); 109 this.encodingsEncoders = l.asArrayOf(Encoder.class); 110 } 111 112 /** 113 * Returns the coding string for the matching encoder that can handle the specified <c>Accept-Encoding</c> 114 * or <c>Content-Encoding</c> header value. 115 * 116 * <p> 117 * Returns <jk>null</jk> if no encoders can handle it. 118 * 119 * <p> 120 * This method is fully compliant with the RFC2616/14.3 and 14.11 specifications. 121 * 122 * @param acceptEncoding The <c>Accept-Encoding</c> or <c>Content-Encoding</c> value. 123 * @return The coding value (e.g. <js>"gzip"</js>). 124 */ 125 public EncoderMatch getEncoderMatch(String acceptEncoding) { 126 EncoderMatch em = cache.get(acceptEncoding); 127 if (em != null) 128 return em; 129 130 AcceptEncoding ae = AcceptEncoding.of(acceptEncoding); 131 int match = ae.match(encodings); 132 133 if (match >= 0) { 134 em = new EncoderMatch(encodings.get(match), encodingsEncoders[match]); 135 cache.putIfAbsent(acceptEncoding, em); 136 } 137 138 return cache.get(acceptEncoding); 139 } 140 141 /** 142 * Returns the encoder registered with the specified coding (e.g. <js>"gzip"</js>). 143 * 144 * @param encoding The coding string. 145 * @return The encoder, or <jk>null</jk> if encoder isn't registered with that coding. 146 */ 147 public Encoder getEncoder(String encoding) { 148 EncoderMatch em = getEncoderMatch(encoding); 149 return (em == null ? null : em.getEncoder()); 150 } 151 152 /** 153 * Returns the set of codings supported by all encoders in this group. 154 * 155 * @return An unmodifiable list of codings supported by all encoders in this group. Never <jk>null</jk>. 156 */ 157 public List<String> getSupportedEncodings() { 158 return encodings; 159 } 160 161 /** 162 * Returns the encoders in this group. 163 * 164 * @return An unmodifiable list of encoders in this group. 165 */ 166 public List<Encoder> getEncoders() { 167 return encoders; 168 } 169}