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      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    }