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  @Override
076  public T fromString(String str) throws IOException {
077    try {
078      byte[] bytes = Base64.decodeBase64(str.getBytes("UTF-8"));
079      inBuf.reset(bytes, bytes.length);
080      T restored = deserializer.deserialize(null);
081      return restored;
082    } catch (UnsupportedCharsetException ex) {
083      throw new IOException(ex.toString());
084    }
085  }
086
087  @Override
088  public String toString(T obj) throws IOException {
089    outBuf.reset();
090    serializer.serialize(obj);
091    byte[] buf = new byte[outBuf.getLength()];
092    System.arraycopy(outBuf.getData(), 0, buf, 0, buf.length);
093    return new String(Base64.encodeBase64(buf));
094  }
095
096  @Override
097  public void close() throws IOException {
098    inBuf.close();
099    outBuf.close();
100    deserializer.close();
101    serializer.close();
102  }
103
104  /**
105   * Stores the item in the configuration with the given keyName.
106   * 
107   * @param <K>  the class of the item
108   * @param conf the configuration to store
109   * @param item the object to be stored
110   * @param keyName the name of the key to use
111   * @throws IOException : forwards Exceptions from the underlying 
112   * {@link Serialization} classes. 
113   */
114  public static <K> void store(Configuration conf, K item, String keyName)
115  throws IOException {
116
117    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
118        GenericsUtil.getClass(item));
119    conf.set(keyName, stringifier.toString(item));
120    stringifier.close();
121  }
122
123  /**
124   * Restores the object from the configuration.
125   * 
126   * @param <K> the class of the item
127   * @param conf the configuration to use
128   * @param keyName the name of the key to use
129   * @param itemClass the class of the item
130   * @return restored object
131   * @throws IOException : forwards Exceptions from the underlying 
132   * {@link Serialization} classes.
133   */
134  public static <K> K load(Configuration conf, String keyName,
135      Class<K> itemClass) throws IOException {
136    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
137        itemClass);
138    try {
139      String itemStr = conf.get(keyName);
140      return stringifier.fromString(itemStr);
141    } finally {
142      stringifier.close();
143    }
144  }
145
146  /**
147   * Stores the array of items in the configuration with the given keyName.
148   * 
149   * @param <K> the class of the item
150   * @param conf the configuration to use 
151   * @param items the objects to be stored
152   * @param keyName the name of the key to use
153   * @throws IndexOutOfBoundsException if the items array is empty
154   * @throws IOException : forwards Exceptions from the underlying 
155   * {@link Serialization} classes.         
156   */
157  public static <K> void storeArray(Configuration conf, K[] items,
158      String keyName) throws IOException {
159
160    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf, 
161        GenericsUtil.getClass(items[0]));
162    try {
163      StringBuilder builder = new StringBuilder();
164      for (K item : items) {
165        builder.append(stringifier.toString(item)).append(SEPARATOR);
166      }
167      conf.set(keyName, builder.toString());
168    }
169    finally {
170      stringifier.close();
171    }
172  }
173
174  /**
175   * Restores the array of objects from the configuration.
176   * 
177   * @param <K> the class of the item
178   * @param conf the configuration to use
179   * @param keyName the name of the key to use
180   * @param itemClass the class of the item
181   * @return restored object
182   * @throws IOException : forwards Exceptions from the underlying 
183   * {@link Serialization} classes.
184   */
185  public static <K> K[] loadArray(Configuration conf, String keyName,
186      Class<K> itemClass) throws IOException {
187    DefaultStringifier<K> stringifier = new DefaultStringifier<K>(conf,
188        itemClass);
189    try {
190      String itemStr = conf.get(keyName);
191      ArrayList<K> list = new ArrayList<K>();
192      String[] parts = itemStr.split(SEPARATOR);
193
194      for (String part : parts) {
195        if (!part.isEmpty())
196          list.add(stringifier.fromString(part));
197      }
198
199      return GenericsUtil.toArray(itemClass, list);
200    }
201    finally {
202      stringifier.close();
203    }
204  }
205
206}