001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.compress;
019
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.util.Arrays;
024import java.util.zip.GZIPOutputStream;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.util.JVM;
027import org.apache.hadoop.io.compress.CompressionOutputStream;
028import org.apache.hadoop.io.compress.CompressorStream;
029import org.apache.hadoop.io.compress.GzipCodec;
030import org.apache.hadoop.io.compress.zlib.ZlibFactory;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Fixes an inefficiency in Hadoop's Gzip codec, allowing to reuse compression streams.
037 */
038@InterfaceAudience.Private
039public class ReusableStreamGzipCodec extends GzipCodec implements ByteBuffDecompressionCodec {
040
041  private static final Logger LOG = LoggerFactory.getLogger(Compression.class);
042
043  /**
044   * A bridge that wraps around a DeflaterOutputStream to make it a CompressionOutputStream.
045   */
046  protected static class ReusableGzipOutputStream extends CompressorStream {
047
048    private static final int GZIP_HEADER_LENGTH = 10;
049
050    /**
051     * Fixed ten-byte gzip header. See {@link GZIPOutputStream}'s source for details.
052     */
053    private static final byte[] GZIP_HEADER;
054
055    static {
056      // Capture the fixed ten-byte header hard-coded in GZIPOutputStream.
057      ByteArrayOutputStream baos = new ByteArrayOutputStream();
058      byte[] header = null;
059      GZIPOutputStream gzipStream = null;
060      try {
061        gzipStream = new GZIPOutputStream(baos);
062        gzipStream.finish();
063        header = Arrays.copyOfRange(baos.toByteArray(), 0, GZIP_HEADER_LENGTH);
064      } catch (IOException e) {
065        throw new RuntimeException("Could not create gzip stream", e);
066      } finally {
067        if (gzipStream != null) {
068          try {
069            gzipStream.close();
070          } catch (IOException e) {
071            LOG.error(e.toString(), e);
072          }
073        }
074      }
075      GZIP_HEADER = header;
076    }
077
078    private static class ResetableGZIPOutputStream extends GZIPOutputStream {
079
080      private static final int TRAILER_SIZE = 8;
081      private static final boolean HAS_BROKEN_FINISH = JVM.isGZIPOutputStreamFinishBroken();
082
083      public ResetableGZIPOutputStream(OutputStream out) throws IOException {
084        super(out);
085      }
086
087      public void resetState() throws IOException {
088        def.reset();
089        crc.reset();
090        out.write(GZIP_HEADER);
091      }
092
093      /**
094       * Override because certain implementation calls def.end() which causes problem when resetting
095       * the stream for reuse.
096       */
097      @Override
098      public void finish() throws IOException {
099        if (HAS_BROKEN_FINISH) {
100          if (!def.finished()) {
101            def.finish();
102            while (!def.finished()) {
103              int i = def.deflate(this.buf, 0, this.buf.length);
104              if (def.finished() && (i <= this.buf.length - TRAILER_SIZE)) {
105                writeTrailer(this.buf, i);
106                i += TRAILER_SIZE;
107                out.write(this.buf, 0, i);
108
109                return;
110              }
111              if (i > 0) {
112                out.write(this.buf, 0, i);
113              }
114            }
115
116            byte[] arrayOfByte = new byte[TRAILER_SIZE];
117            writeTrailer(arrayOfByte, 0);
118            out.write(arrayOfByte);
119          }
120        } else {
121          super.finish();
122        }
123      }
124
125      /** re-implement because the relative method in jdk is invisible */
126      private void writeTrailer(byte[] paramArrayOfByte, int paramInt) throws IOException {
127        writeInt((int) this.crc.getValue(), paramArrayOfByte, paramInt);
128        writeInt(this.def.getTotalIn(), paramArrayOfByte, paramInt + 4);
129      }
130
131      /** re-implement because the relative method in jdk is invisible */
132      private void writeInt(int paramInt1, byte[] paramArrayOfByte, int paramInt2)
133        throws IOException {
134        writeShort(paramInt1 & 0xFFFF, paramArrayOfByte, paramInt2);
135        writeShort(paramInt1 >> 16 & 0xFFFF, paramArrayOfByte, paramInt2 + 2);
136      }
137
138      /** re-implement because the relative method in jdk is invisible */
139      private void writeShort(int paramInt1, byte[] paramArrayOfByte, int paramInt2)
140        throws IOException {
141        paramArrayOfByte[paramInt2] = (byte) (paramInt1 & 0xFF);
142        paramArrayOfByte[(paramInt2 + 1)] = (byte) (paramInt1 >> 8 & 0xFF);
143      }
144    }
145
146    public ReusableGzipOutputStream(OutputStream out) throws IOException {
147      super(new ResetableGZIPOutputStream(out));
148    }
149
150    @Override
151    public void close() throws IOException {
152      out.close();
153    }
154
155    @Override
156    public void flush() throws IOException {
157      out.flush();
158    }
159
160    @Override
161    public void write(int b) throws IOException {
162      out.write(b);
163    }
164
165    @Override
166    public void write(byte[] data, int offset, int length) throws IOException {
167      out.write(data, offset, length);
168    }
169
170    @Override
171    public void finish() throws IOException {
172      ((GZIPOutputStream) out).finish();
173    }
174
175    @Override
176    public void resetState() throws IOException {
177      ((ResetableGZIPOutputStream) out).resetState();
178    }
179  }
180
181  @Override
182  public CompressionOutputStream createOutputStream(OutputStream out) throws IOException {
183    if (ZlibFactory.isNativeZlibLoaded(getConf())) {
184      return super.createOutputStream(out);
185    }
186    return new ReusableGzipOutputStream(out);
187  }
188
189  @Override
190  public ByteBuffDecompressor createByteBuffDecompressor() {
191    return new GzipByteBuffDecompressor(ZlibFactory.isNativeZlibLoaded(getConf()));
192  }
193
194  @Override
195  public Class<? extends ByteBuffDecompressor> getByteBuffDecompressorType() {
196    return GzipByteBuffDecompressor.class;
197  }
198
199  @Override
200  public Compression.HFileDecompressionContext
201    getDecompressionContextFromConfiguration(Configuration conf) {
202    return GzipHFileDecompressionContext.fromConfiguration(conf);
203  }
204
205}