View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  
22  package org.dbunit.dataset.datatype;
23  
24  import java.io.BufferedInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileNotFoundException;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.net.MalformedURLException;
32  import java.net.URL;
33  import java.nio.charset.Charset;
34  import java.sql.Blob;
35  import java.sql.PreparedStatement;
36  import java.sql.ResultSet;
37  import java.sql.SQLException;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  import org.dbunit.dataset.ITable;
42  import org.dbunit.util.Base64;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * @author Manuel Laflamme
48   * @author Last changed by: $Author$
49   * @version $Revision$ $Date$
50   * @since 1.0 (Mar 20, 2002)
51   */
52  public class BytesDataType extends AbstractDataType
53  {
54      private static final Logger logger =
55              LoggerFactory.getLogger(BytesDataType.class);
56  
57      private static final int MAX_URI_LENGTH = 256;
58      private static final Pattern inputPattern =
59              Pattern.compile("^\\[(.*?)](.*)");
60  
61      public BytesDataType(final String name, final int sqlType)
62      {
63          super(name, sqlType, byte[].class, false);
64      }
65  
66      private byte[] toByteArray(InputStream in, final int length)
67              throws IOException
68      {
69          if (logger.isDebugEnabled())
70          {
71              logger.debug("toByteArray(in={}, length={}) - start", in, length);
72          }
73  
74          final ByteArrayOutputStream out = new ByteArrayOutputStream(length);
75          in = new BufferedInputStream(in);
76          int i = in.read();
77          while (i != -1)
78          {
79              out.write(i);
80              i = in.read();
81          }
82          return out.toByteArray();
83      }
84  
85      public byte[] loadFile(final String filename) throws IOException
86      {
87          // Not an URL, try as file name
88          final File file = new File(filename);
89          return toByteArray(new FileInputStream(file), (int) file.length());
90      }
91  
92      public byte[] loadURL(final String urlAsString) throws IOException
93      {
94          // Not an URL, try as file name
95          final URL url = new URL(urlAsString);
96          return toByteArray(url.openStream(), 0);
97      }
98  
99      ////////////////////////////////////////////////////////////////////////////
100     // DataType class
101 
102     /**
103      * Casts the given value into a byte[] using different strategies. Note that
104      * this might sometimes result in undesired behavior when character data
105      * (Strings) are used.
106      *
107      * @see org.dbunit.dataset.datatype.DataType#typeCast(java.lang.Object)
108      */
109     @Override
110     public Object typeCast(final Object value) throws TypeCastException
111     {
112         logger.debug("typeCast(value={}) - start", value);
113 
114         if (value == null || value == ITable.NO_VALUE)
115         {
116             return null;
117         }
118 
119         if (value instanceof byte[])
120         {
121             return value;
122         }
123 
124         if (value instanceof String)
125         {
126             String stringValue = (String) value;
127 
128             // If the string starts with <text [encoding id]>, it means that the
129             // user
130             // intentionally wants to transform the text into a blob.
131             //
132             // Example of a valid string: "<text UTF-8>This is a valid string
133             // with the accent 'é'"
134             if (isExtendedSyntax(stringValue))
135             {
136                 final Matcher matcher = inputPattern.matcher(stringValue);
137                 if (matcher.matches())
138                 {
139                     final String commandLine = matcher.group(1).toUpperCase();
140                     stringValue = matcher.group(2);
141 
142                     final String[] split = commandLine.split(" ");
143                     final String command = split[0];
144 
145                     if ("TEXT".equals(command))
146                     {
147                         String encoding = "UTF-8"; // Default
148 
149                         if (split.length > 1)
150                         {
151                             encoding = split[1];
152                         }
153                         logger.debug(
154                                 "Data explicitly states that given string is text encoded {}",
155                                 encoding);
156                         try
157                         {
158                             final Charset charset = Charset.forName(encoding);
159                             return stringValue.getBytes(charset);
160                         } catch (final IllegalArgumentException e)
161                         {
162                             return "Error:  [text " + encoding
163                                     + "] has an invalid encoding id.";
164                         }
165                     } else if ("BASE64".equals(command))
166                     {
167                         logger.debug(
168                                 "Data explicitly states that given string is base46");
169                         return Base64.decode(stringValue);
170                     } else if ("FILE".equals(command))
171                     {
172                         try
173                         {
174                             logger.debug(
175                                     "Data explicitly states that given string is a file name");
176                             return loadFile(stringValue);
177                         } catch (final IOException e)
178                         {
179                             final String errMsg =
180                                     "Could not load file following instruction >>"
181                                             + value + "<<";
182                             logger.error(errMsg);
183                             return ("Error:  " + errMsg).getBytes();
184                         }
185                     } else if ("URL".equals(command))
186                     {
187                         try
188                         {
189                             logger.debug(
190                                     "Data explicitly states that given string is a URL");
191                             return loadURL(stringValue);
192                         } catch (final IOException e)
193                         {
194                             final String errMsg =
195                                     "Could not load URL following instruction >>"
196                                             + value + "<<";
197                             logger.error(errMsg);
198                             return ("Error:  " + errMsg).getBytes();
199                         }
200                     }
201                 }
202             }
203 
204             // Assume not an uri if length greater than max uri length
205             if (stringValue.length() == 0
206                     || stringValue.length() > MAX_URI_LENGTH)
207             {
208                 logger.debug(
209                         "Assuming given string to be Base64 and not a URI");
210                 return Base64.decode((String) value);
211             }
212 
213             try
214             {
215                 logger.debug("Assuming given string to be a URI");
216                 try
217                 {
218                     // Try value as URL
219                     return loadURL(stringValue);
220                 } catch (final MalformedURLException e1)
221                 {
222                     logger.debug(
223                             "Given string is not a valid URI - trying to resolve it as file...");
224                     try
225                     {
226                         // Not an URL, try as file name
227                         return loadFile(stringValue);
228                     } catch (final FileNotFoundException e2)
229                     {
230                         logger.debug(
231                                 "Assuming given string to be Base64 and not a URI or File");
232                         // Not a file name either
233                         final byte[] decodedBytes = Base64.decode(stringValue);
234                         if (decodedBytes == null && stringValue.length() > 0)
235                         {
236                             // Ok, here the user has not specified the "[text
237                             // ...]" tag, but
238                             // it looks that its text that should be stored in
239                             // the blob. So
240                             // we make a last attempt at doing so.
241                             logger.debug(
242                                     "Assuming given string to be content of the blob, encoded with UTF-8.");
243                             return stringValue.getBytes();
244                         } else
245                         {
246                             return decodedBytes;
247                         }
248                     }
249                 }
250             } catch (final IOException e)
251             {
252                 throw new TypeCastException(value, this, e);
253             }
254         }
255 
256         if (value instanceof Blob)
257         {
258             try
259             {
260                 final Blob blobValue = (Blob) value;
261                 if (blobValue.length() == 0)
262                 {
263                     return null;
264                 }
265                 return blobValue.getBytes(1, (int) blobValue.length());
266             } catch (final SQLException e)
267             {
268                 throw new TypeCastException(value, this, e);
269             }
270         }
271 
272         if (value instanceof URL)
273         {
274             try
275             {
276                 return toByteArray(((URL) value).openStream(), 0);
277             } catch (final IOException e)
278             {
279                 throw new TypeCastException(value, this, e);
280             }
281         }
282 
283         if (value instanceof File)
284         {
285             try
286             {
287                 final File file = (File) value;
288                 return toByteArray(new FileInputStream(file),
289                         (int) file.length());
290             } catch (final IOException e)
291             {
292                 throw new TypeCastException(value, this, e);
293             }
294         }
295 
296         throw new TypeCastException(value, this);
297     }
298 
299     @Override
300     protected int compareNonNulls(final Object value1, final Object value2)
301             throws TypeCastException
302     {
303         logger.debug("compareNonNulls(value1={}, value2={}) - start", value1,
304                 value2);
305 
306         try
307         {
308             final byte[] value1cast = (byte[]) typeCast(value1);
309             final byte[] value2cast = (byte[]) typeCast(value2);
310 
311             return compare(value1cast, value2cast);
312         } catch (final ClassCastException e)
313         {
314             throw new TypeCastException(e);
315         }
316     }
317 
318     public int compare(final byte[] v1, final byte[] v2)
319             throws TypeCastException
320     {
321         if (logger.isDebugEnabled())
322         {
323             logger.debug("compare(v1={}, v2={}) - start", v1, v2);
324         }
325 
326         final int len1 = v1.length;
327         final int len2 = v2.length;
328         int n = Math.min(len1, len2);
329         int i = 0;
330         int j = 0;
331 
332         if (i == j)
333         {
334             int k = i;
335             final int lim = n + i;
336             while (k < lim)
337             {
338                 final byte c1 = v1[k];
339                 final byte c2 = v2[k];
340                 if (c1 != c2)
341                 {
342                     return c1 - c2;
343                 }
344                 k++;
345             }
346         } else
347         {
348             while (n-- != 0)
349             {
350                 final byte c1 = v1[i++];
351                 final byte c2 = v2[j++];
352                 if (c1 != c2)
353                 {
354                     return c1 - c2;
355                 }
356             }
357         }
358         return len1 - len2;
359     }
360 
361     @Override
362     public Object getSqlValue(final int column, final ResultSet resultSet)
363             throws SQLException, TypeCastException
364     {
365         logger.debug("getSqlValue(column={}, resultSet={}) - start", column,
366                 resultSet);
367         final byte[] rawValue = resultSet.getBytes(column);
368         final byte[] value = resultSet.wasNull() ? null : rawValue;
369         logger.debug("getSqlValue: column={}, value={}", column, value);
370         return value;
371     }
372 
373     @Override
374     public void setSqlValue(final Object value, final int column,
375             final PreparedStatement statement)
376             throws SQLException, TypeCastException
377     {
378         logger.debug("setSqlValue(value={}, column={}, statement={}) - start",
379                 value, column, statement);
380 
381         super.setSqlValue(value, column, statement);
382     }
383 }