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