View Javadoc

1   /*
2    * Copyright 2010-2011 Ning, Inc.
3    *
4    * Ning licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * License.  You may obtain a copy of the License at:
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.ning.metrics.goodwill.access;
18  
19  import com.google.common.collect.ImmutableMap;
20  import com.ning.metrics.serialization.schema.SchemaField;
21  import com.ning.metrics.serialization.schema.SchemaFieldType;
22  import org.codehaus.jackson.JsonGenerationException;
23  import org.codehaus.jackson.annotate.JsonCreator;
24  import org.codehaus.jackson.annotate.JsonProperty;
25  import org.codehaus.jackson.annotate.JsonValue;
26  import org.codehaus.jackson.map.ObjectMapper;
27  
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  
31  /**
32   * Describe a SchemaField in Goodwill.
33   * This is basically the union of a SchemaField and extra metadata for the SQL sink.
34   *
35   * @see com.ning.metrics.serialization.schema.SchemaField
36   */
37  public class GoodwillSchemaField
38  {
39      private static final ObjectMapper mapper = new ObjectMapper();
40  
41      public static final String JSON_THRIFT_FIELD_NAME = "name";
42      public static final String JSON_THRIFT_FIELD_TYPE = "type";
43      public static final String JSON_THRIFT_FIELD_ID = "position";
44      public static final String JSON_THRIFT_FIELD_DESCRIPTION = "description";
45  
46      /*
47       * When storing the thrifts in a database/datawarehouse, use these fields to describe
48       * the associated sql type and length.
49       * Given the large range of data types (Netezza, Oracle, MySQL, ...), no enforcement
50       * is performed (plain Strings).
51       */
52      public static final String JSON_THRIFT_FIELD_SQL_KEY = "sql";
53      public static final String JSON_THRIFT_FIELD_SQL_TYPE = "type";
54      public static final String JSON_THRIFT_FIELD_SQL_LENGTH = "length";
55      public static final String JSON_THRIFT_FIELD_SQL_SCALE = "scale";
56      public static final String JSON_THRIFT_FIELD_SQL_PRECISION = "precision";
57  
58      private final SchemaField schemaField;
59      private final String description;
60      private Sql sql;
61  
62      /**
63       * Jackson constructor
64       * <p/>
65       * {
66       * "name": "myField",
67       * "type": "string",
68       * "position": 1,
69       * "description": "string",
70       * "sql": {
71       * "type": "nvarchar",
72       * "length": 255,
73       * "scale": null,
74       * "precision": null
75       * }
76       *
77       * @param name        Schema field name
78       * @param type        Schema field type. This is not necessarily a Thrift type. @see SchemaFieldType
79       * @param id          field position
80       * @param description Short description of the field
81       * @param sql         SQL object (used by the sink)
82       */
83      @JsonCreator
84      @SuppressWarnings("unused")
85      public GoodwillSchemaField(
86          @JsonProperty(JSON_THRIFT_FIELD_NAME) final String name,
87          @JsonProperty(JSON_THRIFT_FIELD_TYPE) final String type,
88          @JsonProperty(JSON_THRIFT_FIELD_ID) final short id,
89          @JsonProperty(JSON_THRIFT_FIELD_DESCRIPTION) final String description,
90          @JsonProperty(JSON_THRIFT_FIELD_SQL_KEY) final Sql sql
91      )
92      {
93          this(name, type, id, description, sql == null ? null : sql.getType(), sql == null ? null : sql.getLength(), sql == null ? null : sql.getScale(), sql == null ? null : sql.getPrecision());
94      }
95  
96      /**
97       * Manual constructor, typically used by Goodwill stores.
98       *
99       * @param name         Schema field name
100      * @param type         Schema field type. This is not necessarily a Thrift type. @see SchemaFieldType
101      * @param id           field position
102      * @param description  Short description of the field
103      * @param sqlType      SQL type (varchar, int, ...)
104      * @param sqlLength    SQL type length
105      * @param sqlScale     SQL type scale
106      * @param sqlPrecision SQL type precision
107      */
108     public GoodwillSchemaField(
109         final String name,
110         final String type,
111         final short id,
112         final String description,
113         final String sqlType,
114         final Integer sqlLength,
115         final Integer sqlScale,
116         final Integer sqlPrecision
117     )
118     {
119         if (name == null) {
120             throw new IllegalArgumentException("GoodwillSchemaField name can't be null");
121         }
122 
123         if ((sqlType == null || sqlType.equals("string")) && (sqlScale != null || sqlPrecision != null)) {
124             throw new IllegalArgumentException("Strings cannot have a scale or precision");
125         }
126 
127         this.schemaField = SchemaFieldType.createSchemaField(name, type, id);
128 
129         // Optional fields
130         sql = new Sql(sqlType, sqlLength, sqlScale, sqlPrecision);
131         this.description = description;
132     }
133 
134     public GoodwillSchemaField(final SchemaField field)
135     {
136         this(field.getName(), field.getType().name(), field.getId(), null, null);
137     }
138 
139     public static GoodwillSchemaField decode(final String thriftItemJson) throws IOException
140     {
141         return mapper.readValue(thriftItemJson, GoodwillSchemaField.class);
142     }
143 
144     @JsonValue
145     @SuppressWarnings({"unchecked", "unused"})
146     public ImmutableMap toMap()
147     {
148         return new ImmutableMap.Builder()
149             .put(JSON_THRIFT_FIELD_NAME, getName())
150             .put(JSON_THRIFT_FIELD_TYPE, getType())
151             .put(JSON_THRIFT_FIELD_ID, getId())
152             .put(JSON_THRIFT_FIELD_DESCRIPTION, getDescription() == null ? "" : getDescription())
153             .put(JSON_THRIFT_FIELD_SQL_KEY, getSql() == null ? "" : getSql())
154             .build();
155     }
156 
157     /**
158      * Extra information for the SQL Sink
159      */
160     public static class Sql
161     {
162         private final String type;
163         private final Integer length;
164         private final Integer scale;
165         private final Integer precision;
166 
167         @JsonCreator
168         public Sql(
169             @JsonProperty(JSON_THRIFT_FIELD_SQL_TYPE) final String type,
170             @JsonProperty(JSON_THRIFT_FIELD_SQL_LENGTH) final Integer length,
171             @JsonProperty(JSON_THRIFT_FIELD_SQL_SCALE) final Integer scale,
172             @JsonProperty(JSON_THRIFT_FIELD_SQL_PRECISION) final Integer precision)
173         {
174             this.type = type;
175             this.length = length;
176             this.scale = scale;
177             this.precision = precision;
178         }
179 
180 
181         @JsonValue
182         @SuppressWarnings({"unchecked", "unused"})
183         public ImmutableMap toMap()
184         {
185             return new ImmutableMap.Builder()
186                 .put(JSON_THRIFT_FIELD_SQL_TYPE, getType() == null ? "" : getType())
187                 .put(JSON_THRIFT_FIELD_SQL_LENGTH, getLength() == null ? "" : getLength())
188                 .put(JSON_THRIFT_FIELD_SQL_SCALE, getScale() == null ? "" : getScale())
189                 .put(JSON_THRIFT_FIELD_SQL_PRECISION, getPrecision() == null ? "" : getPrecision())
190                 .build();
191         }
192 
193         public String getType()
194         {
195             return type;
196         }
197 
198         public Integer getLength()
199         {
200             return length;
201         }
202 
203         public Integer getScale()
204         {
205             return scale;
206         }
207 
208         public Integer getPrecision()
209         {
210             return precision;
211         }
212     }
213 
214     public String getName()
215     {
216         return schemaField.getName();
217     }
218 
219     public SchemaFieldType getType()
220     {
221         return schemaField.getType();
222     }
223 
224     public short getId()
225     {
226         return schemaField.getId();
227     }
228 
229     public Sql getSql()
230     {
231         return sql;
232     }
233 
234     public String getDescription()
235     {
236         return description;
237     }
238 
239     @Override
240     public String toString()
241     {
242         try {
243             return toJSON().toString();
244         }
245         catch (JsonGenerationException e) {
246             return "GoodwillSchemaField{" +
247                 JSON_THRIFT_FIELD_NAME + "='" + getName() + '\'' +
248                 ", " + JSON_THRIFT_FIELD_TYPE + "='" + getType() + '\'' +
249                 ", " + JSON_THRIFT_FIELD_ID + "=" + getId() +
250                 ", " + JSON_THRIFT_FIELD_SQL_TYPE + "='" + sql.type + '\'' +
251                 ", " + JSON_THRIFT_FIELD_SQL_LENGTH + "=" + sql.length +
252                 ", " + JSON_THRIFT_FIELD_SQL_SCALE + "=" + sql.scale +
253                 ", " + JSON_THRIFT_FIELD_SQL_PRECISION + "=" + sql.precision +
254                 ", " + JSON_THRIFT_FIELD_DESCRIPTION + "=" + description +
255                 '}';
256         }
257         catch (IOException e) {
258             return "GoodwillSchemaField{" +
259                 JSON_THRIFT_FIELD_NAME + "='" + getName() + '\'' +
260                 ", " + JSON_THRIFT_FIELD_TYPE + "='" + getType() + '\'' +
261                 ", " + JSON_THRIFT_FIELD_ID + "=" + getId() +
262                 ", " + JSON_THRIFT_FIELD_SQL_TYPE + "='" + sql.type + '\'' +
263                 ", " + JSON_THRIFT_FIELD_SQL_LENGTH + "=" + sql.length +
264                 ", " + JSON_THRIFT_FIELD_SQL_SCALE + "=" + sql.scale +
265                 ", " + JSON_THRIFT_FIELD_SQL_PRECISION + "=" + sql.precision +
266                 ", " + JSON_THRIFT_FIELD_DESCRIPTION + "=" + description +
267                 '}';
268         }
269     }
270 
271     /**
272      * Create a JSON representation of the GoodwillSchemaField. It will always contain
273      * the name, type and position. Description and SQL attributes are however
274      * optional.
275      *
276      * @return JSONObject containing all fields
277      * @throws IOException             if a serialization exception occurs
278      * @throws JsonGenerationException if a serialization exception occurs
279      */
280     public ByteArrayOutputStream toJSON() throws IOException
281     {
282         final ByteArrayOutputStream out = new ByteArrayOutputStream();
283         mapper.writeValue(out, this);
284         return out;
285     }
286 
287     /**
288      * Pretty print the SQL type.
289      * TODO: add layer of abstraction, too Netezza specific
290      *
291      * @return a human readable representation of the SQL type
292      */
293     @SuppressWarnings("unused")
294     public String getFullSQLType()
295     {
296         String fullSQLType = null;
297 
298         if (sql.type == null) {
299             return null;
300         }
301         else if (sql.type.equals("decimal") || sql.type.equals("numeric")) {
302             if (sql.precision != null) {
303                 if (sql.scale != null) {
304                     fullSQLType = sql.type + "(" + sql.precision + ", " + sql.scale + ")";
305                 }
306                 else {
307                     fullSQLType = sql.type + "(" + sql.precision + ")";
308                 }
309             }
310         }
311         else {
312             if (sql.type.equals("nvarchar") || sql.type.equals("varchar")) {
313                 if (sql.length != null) {
314                     fullSQLType = sql.type + "(" + sql.length + ")";
315                 }
316             }
317         }
318 
319         if (fullSQLType == null) {
320             fullSQLType = sql.type;
321         }
322 
323         return fullSQLType;
324     }
325 }