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}