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    
019    package org.apache.hadoop.io;
020    
021    import java.io.IOException;
022    import java.nio.charset.UnsupportedCharsetException;
023    import java.util.ArrayList;
024    
025    import org.apache.commons.codec.binary.Base64;
026    import org.apache.hadoop.classification.InterfaceAudience;
027    import org.apache.hadoop.classification.InterfaceStability;
028    import org.apache.hadoop.conf.Configuration;
029    import org.apache.hadoop.io.serializer.Deserializer;
030    import org.apache.hadoop.io.serializer.Serialization;
031    import org.apache.hadoop.io.serializer.SerializationFactory;
032    import org.apache.hadoop.io.serializer.Serializer;
033    import 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
048    public 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    }