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 */
018
019package org.apache.hadoop.io;
020
021import java.io.IOException;
022import java.nio.charset.StandardCharsets;
023import java.nio.charset.UnsupportedCharsetException;
024import java.util.ArrayList;
025
026import org.apache.commons.codec.binary.Base64;
027import org.apache.hadoop.classification.InterfaceAudience;
028import org.apache.hadoop.classification.InterfaceStability;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.io.serializer.Deserializer;
031import org.apache.hadoop.io.serializer.Serialization;
032import org.apache.hadoop.io.serializer.SerializationFactory;
033import org.apache.hadoop.io.serializer.Serializer;
034import org.apache.hadoop.util.GenericsUtil;
035
036/**
037 * DefaultStringifier is the default implementation of the {@link Stringifier}
038 * interface which stringifies the objects using base64 encoding of the
039 * serialized version of the objects. The {@link Serializer} and
040 * {@link Deserializer} are obtained from the {@link SerializationFactory}.
041 * <br>
042 * DefaultStringifier offers convenience methods to store/load objects to/from
043 * the configuration.
044 * 
045 * @param <T> the class of the objects to stringify
046 */
047@InterfaceAudience.Public
048@InterfaceStability.Stable
049public class DefaultStringifier<T> implements Stringifier<T> {
050
051  private static final String SEPARATOR = ",";
052
053  private Serializer<T> serializer;
054
055  private Deserializer<T> deserializer;
056
057  private DataInputBuffer inBuf;
058
059  private DataOutputBuffer outBuf;
060
061  public DefaultStringifier(Configuration conf, Class<T> c) {
062
063    SerializationFactory factory = new SerializationFactory(conf);
064    this.serializer = factory.getSerializer(c);
065    this.deserializer = factory.getDeserializer(c);
066    this.inBuf = new DataInputBuffer();
067    this.outBuf = new DataOutputBuffer();
068    try {
069      serializer.open(outBuf);
070      deserializer.open(inBuf);
071    } catch (IOException ex) {
072      throw new RuntimeException(ex);
073    }
074  }
075
076  @Override
077  public T fromString(String str) throws IOException {
078    try {
079      byte[] bytes = Base64.decodeBase64(str.getBytes("UTF-8"));
080      inBuf.reset(bytes, bytes.length);
081      T restored = deserializer.deserialize(null);
082      return restored;
083    } catch (UnsupportedCharsetException ex) {
084      throw new IOException(ex.toString());
085    }
086  }
087
088  @Override
089  public String toString(T obj) throws IOException {
090    outBuf.reset();
091    serializer.serialize(obj);
092    byte[] buf = new byte[outBuf.getLength()];
093    System.arraycopy(outBuf.getData(), 0, buf, 0, buf.length);
094    return new String(Base64.encodeBase64(buf), StandardCharsets.UTF_8);
095  }
096
097  @Override
098  public void close() throws IOException {
099    inBuf.close();
100    outBuf.close();
101    deserializer.close();
102    serializer.close();
103  }
104
105  /**
106   * Stores the item in the configuration with the given keyName.
107   * 
108   * @param <K>  the class of the item
109   * @param conf the configuration to store
110   * @param item the object to be stored
111   * @param keyName the name of the key to use
112   * @throws IOException : forwards Exceptions from the underlying 
113   * {@link Serialization} classes. 
114   */
115  public static <K> void store(Configuration conf, K item, String keyName)
116  throws IOException {
117
118    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
119        GenericsUtil.getClass(item));
120    conf.set(keyName, stringifier.toString(item));
121    stringifier.close();
122  }
123
124  /**
125   * Restores the object from the configuration.
126   * 
127   * @param <K> the class of the item
128   * @param conf the configuration to use
129   * @param keyName the name of the key to use
130   * @param itemClass the class of the item
131   * @return restored object
132   * @throws IOException : forwards Exceptions from the underlying 
133   * {@link Serialization} classes.
134   */
135  public static <K> K load(Configuration conf, String keyName,
136      Class<K> itemClass) throws IOException {
137    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
138        itemClass);
139    try {
140      String itemStr = conf.get(keyName);
141      return stringifier.fromString(itemStr);
142    } finally {
143      stringifier.close();
144    }
145  }
146
147  /**
148   * Stores the array of items in the configuration with the given keyName.
149   * 
150   * @param <K> the class of the item
151   * @param conf the configuration to use 
152   * @param items the objects to be stored
153   * @param keyName the name of the key to use
154   * @throws IndexOutOfBoundsException if the items array is empty
155   * @throws IOException : forwards Exceptions from the underlying 
156   * {@link Serialization} classes.         
157   */
158  public static <K> void storeArray(Configuration conf, K[] items,
159      String keyName) throws IOException {
160
161    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf, 
162        GenericsUtil.getClass(items[0]));
163    try {
164      StringBuilder builder = new StringBuilder();
165      for (K item : items) {
166        builder.append(stringifier.toString(item)).append(SEPARATOR);
167      }
168      conf.set(keyName, builder.toString());
169    }
170    finally {
171      stringifier.close();
172    }
173  }
174
175  /**
176   * Restores the array of objects from the configuration.
177   * 
178   * @param <K> the class of the item
179   * @param conf the configuration to use
180   * @param keyName the name of the key to use
181   * @param itemClass the class of the item
182   * @return restored object
183   * @throws IOException : forwards Exceptions from the underlying 
184   * {@link Serialization} classes.
185   */
186  public static <K> K[] loadArray(Configuration conf, String keyName,
187      Class<K> itemClass) throws IOException {
188    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
189        itemClass);
190    try {
191      String itemStr = conf.get(keyName);
192      ArrayList<K> list = new ArrayList<K>();
193      String[] parts = itemStr.split(SEPARATOR);
194
195      for (String part : parts) {
196        if (!part.isEmpty())
197          list.add(stringifier.fromString(part));
198      }
199
200      return GenericsUtil.toArray(itemClass, list);
201    }
202    finally {
203      stringifier.close();
204    }
205  }
206
207}