Commits

dizzyd committed 7bba90c

Stupid packaging of thrift + cassandra stubs for benchmarking

Comments (0)

Files changed (29)

+#!/usr/local/bin/thrift --java --php --py
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Interface definition for Cassandra Service
+#
+
+namespace java org.apache.cassandra.service
+namespace cpp org.apache.cassandra
+namespace csharp Apache.Cassandra
+namespace py cassandra
+namespace php cassandra
+namespace perl Cassandra
+
+# Thrift.rb has a bug where top-level modules that include modules 
+# with the same name are not properly referenced, so we can't do
+# Cassandra::Cassandra::Client.
+namespace rb CassandraThrift
+
+
+#
+# constants
+#
+
+# for clients checking that server and it have same thrift definitions.
+# no promises are made other than "if both are equal, you're good."
+# in particular, don't try to parse numeric information out and assume
+# that a "greater" version is a superset of a "smaller" one.
+const string VERSION = "0.5.1"
+
+
+#
+# data structures
+#
+
+/** Basic unit of data within a ColumnFamily.
+ * @param name. A column name can act both as structure (a label) or as data (like value). Regardless, the name of the column
+ *        is used as a key to its value.
+ * @param value. Some data
+ * @param timestamp. Used to record when data was sent to be written.
+ */
+struct Column {
+   1: required binary name,
+   2: required binary value,
+   3: required i64 timestamp,
+}
+
+/** A named list of columns.
+ * @param name. see Column.name.
+ * @param columns. A collection of standard Columns.  The columns within a super column are defined in an adhoc manner.
+ *                 Columns within a super column do not have to have matching structures (similarly named child columns).
+ */
+struct SuperColumn {
+   1: required binary name,
+   2: required list<Column> columns,
+}
+
+/**
+    Methods for fetching rows/records from Cassandra will return either a single instance of ColumnOrSuperColumn or a list
+    of ColumnOrSuperColumns (get_slice()). If you're looking up a SuperColumn (or list of SuperColumns) then the resulting
+    instances of ColumnOrSuperColumn will have the requested SuperColumn in the attribute super_column. For queries resulting
+    in Columns, those values will be in the attribute column. This change was made between 0.3 and 0.4 to standardize on
+    single query methods that may return either a SuperColumn or Column.
+
+    @param column. The Column returned by get() or get_slice().
+    @param super_column. The SuperColumn returned by get() or get_slice().
+ */
+struct ColumnOrSuperColumn {
+    1: optional Column column,
+    2: optional SuperColumn super_column,
+}
+
+
+#
+# Exceptions
+# (note that internal server errors will raise a TApplicationException, courtesy of Thrift)
+#
+
+/** A specific column was requested that does not exist. */
+exception NotFoundException {
+}
+
+/** Invalid request could mean keyspace or column family does not exist, required parameters are missing, or a parameter is malformed. 
+    why contains an associated error message.
+*/
+exception InvalidRequestException {
+    1: required string why
+}
+
+/** Not all the replicas required could be created and/or read. */
+exception UnavailableException {
+}
+
+/** RPC timeout was exceeded.  either a node failed mid-operation, or load was too high, or the requested op was too large. */
+exception TimedOutException {
+}
+
+
+#
+# service api
+#
+/** The ConsistencyLevel is an enum that controls both read and write behavior based on <ReplicationFactor> in your
+ * storage-conf.xml. The different consistency levels have different meanings, depending on if you're doing a write or read
+ * operation. Note that if W + R > ReplicationFactor, where W is the number of nodes to block for on write, and R
+ * the number to block for on reads, you will have strongly consistent behavior; that is, readers will always see the most
+ * recent write. Of these, the most interesting is to do QUORUM reads and writes, which gives you consistency while still
+ * allowing availability in the face of node failures up to half of <ReplicationFactor>. Of course if latency is more
+ * important than consistency then you can use lower values for either or both.
+ *
+ * Write:
+ *      ZERO    Ensure nothing. A write happens asynchronously in background
+ *      ONE     Ensure that the write has been written to at least 1 node's commit log and memory table before responding to the client.
+ *      QUORUM  Ensure that the write has been written to <ReplicationFactor> / 2 + 1 nodes before responding to the client.
+ *      ALL     Ensure that the write is written to <code>&lt;ReplicationFactor&gt;</code> nodes before responding to the client.
+ *
+ * Read:
+ *      ZERO    Not supported, because it doesn't make sense.
+ *      ONE     Will return the record returned by the first node to respond. A consistency check is always done in a
+ *              background thread to fix any consistency issues when ConsistencyLevel.ONE is used. This means subsequent
+ *              calls will have correct data even if the initial read gets an older value. (This is called 'read repair'.)
+ *      QUORUM  Will query all storage nodes and return the record with the most recent timestamp once it has at least a
+ *              majority of replicas reported. Again, the remaining replicas will be checked in the background.
+ *      ALL     Not yet supported, but we plan to eventually.
+*/
+enum ConsistencyLevel {
+    ZERO = 0,
+    ONE = 1,
+    QUORUM = 2,
+    DCQUORUM = 3,
+    DCQUORUMSYNC = 4,
+    ALL = 5,
+}
+
+/**
+    ColumnParent is used when selecting groups of columns from the same ColumnFamily. In directory structure terms, imagine
+    ColumnParent as ColumnPath + '/../'.
+
+    See also <a href="cassandra.html#Struct_ColumnPath">ColumnPath</a>
+ */
+struct ColumnParent {
+    3: required string column_family,
+    4: optional binary super_column,
+}
+
+/** The ColumnPath is the path to a single column in Cassandra. It might make sense to think of ColumnPath and
+ * ColumnParent in terms of a directory structure.
+ *
+ * ColumnPath is used to looking up a single column.
+ *
+ * @param column_family. The name of the CF of the column being looked up.
+ * @param super_column. The super column name.
+ * @param column. The column name.
+ */
+struct ColumnPath {
+    3: required string column_family,
+    4: optional binary super_column,
+    5: optional binary column,
+}
+
+/**
+    A slice range is a structure that stores basic range, ordering and limit information for a query that will return
+    multiple columns. It could be thought of as Cassandra's version of LIMIT and ORDER BY
+
+    @param start. The column name to start the slice with. This attribute is not required, though there is no default value,
+                  and can be safely set to '', i.e., an empty byte array, to start with the first column name. Otherwise, it
+                  must a valid value under the rules of the Comparator defined for the given ColumnFamily.
+    @param finish. The column name to stop the slice at. This attribute is not required, though there is no default value,
+                   and can be safely set to an empty byte array to not stop until 'count' results are seen. Otherwise, it
+                   must also be a value value to the ColumnFamily Comparator.
+    @param reversed. Whether the results should be ordered in reversed order. Similar to ORDER BY blah DESC in SQL.
+    @param count. How many keys to return. Similar to LIMIT 100 in SQL. May be arbitrarily large, but Thrift will
+                  materialize the whole result into memory before returning it to the client, so be aware that you may
+                  be better served by iterating through slices by passing the last value of one call in as the 'start'
+                  of the next instead of increasing 'count' arbitrarily large.
+ */
+struct SliceRange {
+    1: required binary start,
+    2: required binary finish,
+    3: required bool reversed=0,
+    4: required i32 count=100,
+}
+
+/**
+    A SlicePredicate is similar to a mathematic predicate (see http://en.wikipedia.org/wiki/Predicate_(mathematical_logic)),
+    which is described as "a property that the elements of a set have in common."
+
+    SlicePredicate's in Cassandra are described with either a list of column_names or a SliceRange.  If column_names is
+    specified, slice_range is ignored.
+
+    @param column_name. A list of column names to retrieve. This can be used similar to Memcached's "multi-get" feature
+                        to fetch N known column names. For instance, if you know you wish to fetch columns 'Joe', 'Jack',
+                        and 'Jim' you can pass those column names as a list to fetch all three at once.
+    @param slice_range. A SliceRange describing how to range, order, and/or limit the slice.
+ */
+struct SlicePredicate {
+    1: optional list<binary> column_names,
+    2: optional SliceRange   slice_range,
+}
+
+/**
+    A KeySlice is key followed by the data it maps to. A collection of KeySlice is returned by the get_range_slice operation.
+
+    @param key. a row key
+    @param columns. List of data represented by the key. Typically, the list is pared down to only the columns specified by
+                    a SlicePredicate.
+ */
+struct KeySlice {
+    1: required string key,
+    2: required list<ColumnOrSuperColumn> columns,
+}
+
+service Cassandra {
+  # retrieval methods
+
+  /**
+    Get the Column or SuperColumn at the given column_path. If no value is present, NotFoundException is thrown. (This is
+    the only method that can throw an exception under non-failure conditions.)
+   */
+  ColumnOrSuperColumn get(1:required string keyspace,
+                          2:required string key,
+                          3:required ColumnPath column_path,
+                          4:required ConsistencyLevel consistency_level=1)
+                      throws (1:InvalidRequestException ire, 2:NotFoundException nfe, 3:UnavailableException ue, 4:TimedOutException te),
+
+  /**
+    Get the group of columns contained by column_parent (either a ColumnFamily name or a ColumnFamily/SuperColumn name
+    pair) specified by the given SlicePredicate. If no matching values are found, an empty list is returned.
+   */
+  list<ColumnOrSuperColumn> get_slice(1:required string keyspace, 
+                                      2:required string key, 
+                                      3:required ColumnParent column_parent, 
+                                      4:required SlicePredicate predicate, 
+                                      5:required ConsistencyLevel consistency_level=1)
+                            throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+    Perform a get for column_path in parallel on the given list<string> keys. The return value maps keys to the
+    ColumnOrSuperColumn found. If no value corresponding to a key is present, the key will still be in the map, but both
+    the column and super_column references of the ColumnOrSuperColumn object it maps to will be null.  
+  */
+  map<string,ColumnOrSuperColumn> multiget(1:required string keyspace, 
+                                           2:required list<string> keys, 
+                                           3:required ColumnPath column_path, 
+                                           4:required ConsistencyLevel consistency_level=1)
+                                  throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+    Performs a get_slice for column_parent and predicate for the given keys in parallel.
+  */
+  map<string,list<ColumnOrSuperColumn>> multiget_slice(1:required string keyspace, 
+                                                       2:required list<string> keys, 
+                                                       3:required ColumnParent column_parent, 
+                                                       4:required SlicePredicate predicate, 
+                                                       5:required ConsistencyLevel consistency_level=1)
+                                        throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+    returns the number of columns for a particular <code>key</code> and <code>ColumnFamily</code> or <code>SuperColumn</code>.
+  */
+  i32 get_count(1:required string keyspace, 
+                2:required string key, 
+                3:required ColumnParent column_parent, 
+                4:required ConsistencyLevel consistency_level=1)
+      throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /** @deprecated; use get_range_slice instead */
+  list<string> get_key_range(1:required string keyspace, 
+                             2:required string column_family, 
+                             3:required string start="", 
+                             4:required string finish="", 
+                             5:required i32 count=100,
+                             6:required ConsistencyLevel consistency_level=1)
+               throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+   returns a subset of columns for a range of keys.
+  */
+  list<KeySlice> get_range_slice(1:required string keyspace, 
+                                 2:required ColumnParent column_parent, 
+                                 3:required SlicePredicate predicate,
+                                 4:required string start_key="", 
+                                 5:required string finish_key="", 
+                                 6:required i32 row_count=100, 
+                                 7:required ConsistencyLevel consistency_level=1)
+                 throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  # modification methods
+
+  /**
+    Insert a Column consisting of (column_path.column, value, timestamp) at the given column_path.column_family and optional
+    column_path.super_column. Note that column_path.column is here required, since a SuperColumn cannot directly contain binary
+    values -- it can only contain sub-Columns. 
+   */
+  void insert(1:required string keyspace, 
+              2:required string key, 
+              3:required ColumnPath column_path, 
+              4:required binary value, 
+              5:required i64 timestamp, 
+              6:required ConsistencyLevel consistency_level=0)
+       throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+    Insert Columns or SuperColumns across different Column Families for the same row key. batch_mutation is a
+    map<string, list<ColumnOrSuperColumn>> -- a map which pairs column family names with the relevant ColumnOrSuperColumn
+    objects to insert.
+   */
+  void batch_insert(1:required string keyspace, 
+                    2:required string key, 
+                    3:required map<string, list<ColumnOrSuperColumn>> cfmap, 
+                    4:required ConsistencyLevel consistency_level=0)
+       throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+  /**
+    Remove data from the row specified by key at the granularity specified by column_path, and the given timestamp. Note
+    that all the values in column_path besides column_path.column_family are truly optional: you can remove the entire
+    row by just specifying the ColumnFamily, or you can remove a SuperColumn or a single Column by specifying those levels too.
+   */
+  void remove(1:required string keyspace,
+              2:required string key, 
+              3:required ColumnPath column_path,
+              4:required i64 timestamp,
+              5:ConsistencyLevel consistency_level=0)
+       throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+
+  // Meta-APIs -- APIs to get information about the node or cluster,
+  // rather than user data.  The nodeprobe program provides usage examples.
+
+  /** get property whose value is of type string. */
+  string get_string_property(1:required string property),
+
+  /** get property whose value is list of strings. */
+  list<string> get_string_list_property(1:required string property),
+
+  /** describe specified keyspace */
+  map<string, map<string, string>> describe_keyspace(1:required string keyspace)
+                                   throws (1:NotFoundException nfe),
+}
+
+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+%%% -*- mode:erlang -*-
+{application, thrift,
+ [
+  % A quick description of the application.
+  {description, "Thrift bindings"},
+
+  % The version of the applicaton
+  {vsn, "0.1"},
+
+  % All modules used by the application.
+  {modules, [
+             test_handler,
+             test_service,
+             thrift_base64_transport,
+             thrift_binary_protocol,
+             thrift_buffered_transport,
+             thrift_client,
+             thrift_disk_log_transport,
+             thrift_file_transport,
+             thrift_framed_transport,
+             thrift_http_transport,
+             thrift_memory_buffer,
+             thrift_processor,
+             thrift_protocol,
+             thrift_server,
+             thrift_service,
+             thrift_socket_server,
+             thrift_socket_transport,
+             thrift_transport,
+             cassandra_thrift,
+             cassandra_types
+   ]},
+
+  % All of the registered names the application uses. This can be ignored.
+  {registered, []},
+
+  % Applications that are to be started prior to this one. This can be ignored
+  % leave it alone unless you understand it well and let the .rel files in
+  % your release handle this.
+  {applications,
+   [
+    kernel,
+    stdlib
+   ]},
+
+  % OTP application loader will load, but not start, included apps. Again
+  % this can be ignored as well.  To load but not start an application it
+  % is easier to include it in the .rel file followed by the atom 'none'
+  {included_applications, []},
+
+  % configuration parameters similar to those in the config file specified
+  % on the command line. can be fetched with gas:get_env
+  {env, [
+         % If an error/crash occurs during processing of a function,
+         % should the TApplicationException serialized back to the client
+         % include the erlang backtrace?
+         {exceptions_include_traces, true}
+  ]},
+
+  % The Module and Args used to start this application.
+  {mod, {thrift_app, []}}
+ ]
+}.

ebin/thrift.appup

+{"0.1",[],[]}.

include/cassandra_constants.hrl

+%%
+%% Autogenerated by Thrift
+%%
+%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+%%
+
+
+-include("cassandra_types.hrl").
+
+-define(cassandra_VERSION, "0.5.1").
+

include/cassandra_thrift.hrl

+-ifndef(_cassandra_included).
+-define(_cassandra_included, yeah).
+-include("cassandra_types.hrl").
+
+-endif.

include/cassandra_types.hrl

+-ifndef(_cassandra_types_included).
+-define(_cassandra_types_included, yeah).
+
+-define(cassandra_ZERO, 0).
+-define(cassandra_ONE, 1).
+-define(cassandra_QUORUM, 2).
+-define(cassandra_DCQUORUM, 3).
+-define(cassandra_DCQUORUMSYNC, 4).
+-define(cassandra_ALL, 5).
+
+-record(column, {name, value, timestamp}).
+
+-record(superColumn, {name, columns}).
+
+-record(columnOrSuperColumn, {column, super_column}).
+
+-record(notFoundException, {}).
+
+-record(invalidRequestException, {why}).
+
+-record(unavailableException, {}).
+
+-record(timedOutException, {}).
+
+-record(columnParent, {column_family, super_column}).
+
+-record(columnPath, {column_family, super_column, column}).
+
+-record(sliceRange, {start, finish, reversed, count}).
+
+-record(slicePredicate, {column_names, slice_range}).
+
+-record(keySlice, {key, columns}).
+
+-endif.

include/thrift_constants.hrl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+%% TType
+-define(tType_STOP, 0).
+-define(tType_VOID, 1).
+-define(tType_BOOL, 2).
+-define(tType_BYTE, 3).
+-define(tType_DOUBLE, 4).
+-define(tType_I16, 6).
+-define(tType_I32, 8).
+-define(tType_I64, 10).
+-define(tType_STRING, 11).
+-define(tType_STRUCT, 12).
+-define(tType_MAP, 13).
+-define(tType_SET, 14).
+-define(tType_LIST, 15).
+
+% TMessageType
+-define(tMessageType_CALL, 1).
+-define(tMessageType_REPLY, 2).
+-define(tMessageType_EXCEPTION, 3).
+-define(tMessageType_ONEWAY, 4).
+
+% TApplicationException
+-define(TApplicationException_Structure,
+        {struct, [{1, string},
+                  {2, i32}]}).
+
+-record('TApplicationException', {message, type}).
+
+-define(TApplicationException_UNKNOWN, 0).
+-define(TApplicationException_UNKNOWN_METHOD, 1).
+-define(TApplicationException_INVALID_MESSAGE_TYPE, 2).
+-define(TApplicationException_WRONG_METHOD_NAME, 3).
+-define(TApplicationException_BAD_SEQUENCE_ID, 4).
+-define(TApplicationException_MISSING_RESULT, 5).
+

include/thrift_protocol.hrl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-ifndef(THRIFT_PROTOCOL_INCLUDED).
+-define(THRIFT_PROTOCOL_INCLUDED, yea).
+
+-record(protocol_message_begin, {name, type, seqid}).
+-record(protocol_struct_begin, {name}).
+-record(protocol_field_begin, {name, type, id}).
+-record(protocol_map_begin, {ktype, vtype, size}).
+-record(protocol_list_begin, {etype, size}).
+-record(protocol_set_begin, {etype, size}).
+
+
+-endif.
Binary file added.

src/cassandra_thrift.erl

+%%
+%% Autogenerated by Thrift
+%%
+%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+%%
+
+-module(cassandra_thrift).
+-behaviour(thrift_service).
+
+
+-include("cassandra_thrift.hrl").
+
+-export([struct_info/1, function_info/2]).
+
+struct_info('i am a dummy struct') -> undefined.
+%%% interface
+% get(This, Keyspace, Key, Column_path, Consistency_level)
+function_info('get', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {struct, {'cassandra_types', 'columnPath'}}},
+  {4, i32}]}
+;
+function_info('get', reply_type) ->
+  {struct, {'cassandra_types', 'columnOrSuperColumn'}};
+function_info('get', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'notFoundException'}}},
+  {3, {struct, {'cassandra_types', 'unavailableException'}}},
+  {4, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% get_slice(This, Keyspace, Key, Column_parent, Predicate, Consistency_level)
+function_info('get_slice', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {struct, {'cassandra_types', 'columnParent'}}},
+  {4, {struct, {'cassandra_types', 'slicePredicate'}}},
+  {5, i32}]}
+;
+function_info('get_slice', reply_type) ->
+  {list, {struct, {'cassandra_types', 'columnOrSuperColumn'}}};
+function_info('get_slice', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% multiget(This, Keyspace, Keys, Column_path, Consistency_level)
+function_info('multiget', params_type) ->
+  {struct, [{1, string},
+  {2, {list, string}},
+  {3, {struct, {'cassandra_types', 'columnPath'}}},
+  {4, i32}]}
+;
+function_info('multiget', reply_type) ->
+  {map, string, {struct, {'cassandra_types', 'columnOrSuperColumn'}}};
+function_info('multiget', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% multiget_slice(This, Keyspace, Keys, Column_parent, Predicate, Consistency_level)
+function_info('multiget_slice', params_type) ->
+  {struct, [{1, string},
+  {2, {list, string}},
+  {3, {struct, {'cassandra_types', 'columnParent'}}},
+  {4, {struct, {'cassandra_types', 'slicePredicate'}}},
+  {5, i32}]}
+;
+function_info('multiget_slice', reply_type) ->
+  {map, string, {list, {struct, {'cassandra_types', 'columnOrSuperColumn'}}}};
+function_info('multiget_slice', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% get_count(This, Keyspace, Key, Column_parent, Consistency_level)
+function_info('get_count', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {struct, {'cassandra_types', 'columnParent'}}},
+  {4, i32}]}
+;
+function_info('get_count', reply_type) ->
+  i32;
+function_info('get_count', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% get_key_range(This, Keyspace, Column_family, Start, Finish, Count, Consistency_level)
+function_info('get_key_range', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, string},
+  {4, string},
+  {5, i32},
+  {6, i32}]}
+;
+function_info('get_key_range', reply_type) ->
+  {list, string};
+function_info('get_key_range', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% get_range_slice(This, Keyspace, Column_parent, Predicate, Start_key, Finish_key, Row_count, Consistency_level)
+function_info('get_range_slice', params_type) ->
+  {struct, [{1, string},
+  {2, {struct, {'cassandra_types', 'columnParent'}}},
+  {3, {struct, {'cassandra_types', 'slicePredicate'}}},
+  {4, string},
+  {5, string},
+  {6, i32},
+  {7, i32}]}
+;
+function_info('get_range_slice', reply_type) ->
+  {list, {struct, {'cassandra_types', 'keySlice'}}};
+function_info('get_range_slice', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% insert(This, Keyspace, Key, Column_path, Value, Timestamp, Consistency_level)
+function_info('insert', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {struct, {'cassandra_types', 'columnPath'}}},
+  {4, string},
+  {5, i64},
+  {6, i32}]}
+;
+function_info('insert', reply_type) ->
+  {struct, []};
+function_info('insert', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% batch_insert(This, Keyspace, Key, Cfmap, Consistency_level)
+function_info('batch_insert', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {map, string, {list, {struct, {'cassandra_types', 'columnOrSuperColumn'}}}}},
+  {4, i32}]}
+;
+function_info('batch_insert', reply_type) ->
+  {struct, []};
+function_info('batch_insert', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% remove(This, Keyspace, Key, Column_path, Timestamp, Consistency_level)
+function_info('remove', params_type) ->
+  {struct, [{1, string},
+  {2, string},
+  {3, {struct, {'cassandra_types', 'columnPath'}}},
+  {4, i64},
+  {5, i32}]}
+;
+function_info('remove', reply_type) ->
+  {struct, []};
+function_info('remove', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'invalidRequestException'}}},
+  {2, {struct, {'cassandra_types', 'unavailableException'}}},
+  {3, {struct, {'cassandra_types', 'timedOutException'}}}]}
+;
+% get_string_property(This, Property)
+function_info('get_string_property', params_type) ->
+  {struct, [{1, string}]}
+;
+function_info('get_string_property', reply_type) ->
+  string;
+function_info('get_string_property', exceptions) ->
+  {struct, []}
+;
+% get_string_list_property(This, Property)
+function_info('get_string_list_property', params_type) ->
+  {struct, [{1, string}]}
+;
+function_info('get_string_list_property', reply_type) ->
+  {list, string};
+function_info('get_string_list_property', exceptions) ->
+  {struct, []}
+;
+% describe_keyspace(This, Keyspace)
+function_info('describe_keyspace', params_type) ->
+  {struct, [{1, string}]}
+;
+function_info('describe_keyspace', reply_type) ->
+  {map, string, {map, string, string}};
+function_info('describe_keyspace', exceptions) ->
+  {struct, [{1, {struct, {'cassandra_types', 'notFoundException'}}}]}
+;
+function_info(xxx, dummy) -> dummy.
+

src/cassandra_types.erl

+%%
+%% Autogenerated by Thrift
+%%
+%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+%%
+
+-module(cassandra_types).
+
+-include("cassandra_types.hrl").
+
+-export([struct_info/1]).
+%% struct column
+
+% -record(column, {name, value, timestamp}).
+
+struct_info('column') ->
+  {struct, [{1, string},
+  {2, string},
+  {3, i64}]}
+;
+
+%% struct superColumn
+
+% -record(superColumn, {name, columns}).
+
+struct_info('superColumn') ->
+  {struct, [{1, string},
+  {2, {list, {struct, {'cassandra_types', 'column'}}}}]}
+;
+
+%% struct columnOrSuperColumn
+
+% -record(columnOrSuperColumn, {column, super_column}).
+
+struct_info('columnOrSuperColumn') ->
+  {struct, [{1, {struct, {'cassandra_types', 'column'}}},
+  {2, {struct, {'cassandra_types', 'superColumn'}}}]}
+;
+
+%% struct notFoundException
+
+% -record(notFoundException, {}).
+
+struct_info('notFoundException') ->
+  {struct, []}
+;
+
+%% struct invalidRequestException
+
+% -record(invalidRequestException, {why}).
+
+struct_info('invalidRequestException') ->
+  {struct, [{1, string}]}
+;
+
+%% struct unavailableException
+
+% -record(unavailableException, {}).
+
+struct_info('unavailableException') ->
+  {struct, []}
+;
+
+%% struct timedOutException
+
+% -record(timedOutException, {}).
+
+struct_info('timedOutException') ->
+  {struct, []}
+;
+
+%% struct columnParent
+
+% -record(columnParent, {column_family, super_column}).
+
+struct_info('columnParent') ->
+  {struct, [{3, string},
+  {4, string}]}
+;
+
+%% struct columnPath
+
+% -record(columnPath, {column_family, super_column, column}).
+
+struct_info('columnPath') ->
+  {struct, [{3, string},
+  {4, string},
+  {5, string}]}
+;
+
+%% struct sliceRange
+
+% -record(sliceRange, {start, finish, reversed, count}).
+
+struct_info('sliceRange') ->
+  {struct, [{1, string},
+  {2, string},
+  {3, bool},
+  {4, i32}]}
+;
+
+%% struct slicePredicate
+
+% -record(slicePredicate, {column_names, slice_range}).
+
+struct_info('slicePredicate') ->
+  {struct, [{1, {list, string}},
+  {2, {struct, {'cassandra_types', 'sliceRange'}}}]}
+;
+
+%% struct keySlice
+
+% -record(keySlice, {key, columns}).
+
+struct_info('keySlice') ->
+  {struct, [{1, string},
+  {2, {list, {struct, {'cassandra_types', 'columnOrSuperColumn'}}}}]}
+;
+
+struct_info('i am a dummy struct') -> undefined.

src/test_handler.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(test_handler).
+
+-export([handle_function/2]).
+
+handle_function(add, Params = {A, B}) ->
+    io:format("Got params: ~p~n", [Params]),
+    {reply, A + B}.

src/test_service.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(test_service).
+%
+% Test service definition
+
+-export([function_info/2]).
+
+function_info(add, params_type) ->
+    {struct, [{1, i32},
+              {2, i32}]};
+function_info(add, reply_type) -> i32.

src/thrift_base64_transport.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(thrift_base64_transport).
+
+-behaviour(thrift_transport).
+
+%% API
+-export([new/1, new_transport_factory/1]).
+
+%% thrift_transport callbacks
+-export([write/2, read/2, flush/1, close/1]).
+
+%% State
+-record(b64_transport, {wrapped}).
+
+new(Wrapped) ->
+    State = #b64_transport{wrapped = Wrapped},
+    thrift_transport:new(?MODULE, State).
+
+
+write(#b64_transport{wrapped = Wrapped}, Data) ->
+    thrift_transport:write(Wrapped, base64:encode(iolist_to_binary(Data))).
+
+
+%% base64 doesn't support reading quite yet since it would involve
+%% nasty buffering and such
+read(#b64_transport{wrapped = Wrapped}, Data) ->
+    {error, no_reads_allowed}.
+
+
+flush(#b64_transport{wrapped = Wrapped}) ->
+    thrift_transport:write(Wrapped, <<"\n">>),
+    thrift_transport:flush(Wrapped).
+
+
+close(Me = #b64_transport{wrapped = Wrapped}) ->
+    flush(Me),
+    thrift_transport:close(Wrapped).
+
+
+%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+new_transport_factory(WrapFactory) ->
+    F = fun() ->
+                {ok, Wrapped} = WrapFactory(),
+                new(Wrapped)
+        end,
+    {ok, F}.

src/thrift_binary_protocol.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(thrift_binary_protocol).
+
+-behavior(thrift_protocol).
+
+-include("thrift_constants.hrl").
+-include("thrift_protocol.hrl").
+
+-export([new/1, new/2,
+         read/2,
+         write/2,
+         flush_transport/1,
+         close_transport/1,
+
+         new_protocol_factory/2
+        ]).
+
+-record(binary_protocol, {transport,
+                          strict_read=true,
+                          strict_write=true
+                         }).
+
+-define(VERSION_MASK, 16#FFFF0000).
+-define(VERSION_1, 16#80010000).
+-define(TYPE_MASK, 16#000000ff).
+
+new(Transport) ->
+    new(Transport, _Options = []).
+
+new(Transport, Options) ->
+    State  = #binary_protocol{transport = Transport},
+    State1 = parse_options(Options, State),
+    thrift_protocol:new(?MODULE, State1).
+
+parse_options([], State) ->
+    State;
+parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) ->
+    parse_options(Rest, State#binary_protocol{strict_read=Bool});
+parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) ->
+    parse_options(Rest, State#binary_protocol{strict_write=Bool}).
+
+
+flush_transport(#binary_protocol{transport = Transport}) ->
+    thrift_transport:flush(Transport).
+
+close_transport(#binary_protocol{transport = Transport}) ->
+    thrift_transport:close(Transport).
+
+%%%
+%%% instance methods
+%%%
+
+write(This, #protocol_message_begin{
+        name = Name,
+        type = Type,
+        seqid = Seqid}) ->
+    case This#binary_protocol.strict_write of
+        true ->
+            write(This, {i32, ?VERSION_1 bor Type}),
+            write(This, {string, Name}),
+            write(This, {i32, Seqid});
+        false ->
+            write(This, {string, Name}),
+            write(This, {byte, Type}),
+            write(This, {i32, Seqid})
+    end,
+    ok;
+
+write(This, message_end) -> ok;
+
+write(This, #protocol_field_begin{
+       name = _Name,
+       type = Type,
+       id = Id}) ->
+    write(This, {byte, Type}),
+    write(This, {i16, Id}),
+    ok;
+
+write(This, field_stop) ->
+    write(This, {byte, ?tType_STOP}),
+    ok;
+
+write(This, field_end) -> ok;
+
+write(This, #protocol_map_begin{
+       ktype = Ktype,
+       vtype = Vtype,
+       size = Size}) ->
+    write(This, {byte, Ktype}),
+    write(This, {byte, Vtype}),
+    write(This, {i32, Size}),
+    ok;
+
+write(This, map_end) -> ok;
+
+write(This, #protocol_list_begin{
+        etype = Etype,
+        size = Size}) ->
+    write(This, {byte, Etype}),
+    write(This, {i32, Size}),
+    ok;
+
+write(This, list_end) -> ok;
+
+write(This, #protocol_set_begin{
+        etype = Etype,
+        size = Size}) ->
+    write(This, {byte, Etype}),
+    write(This, {i32, Size}),
+    ok;
+
+write(This, set_end) -> ok;
+
+write(This, #protocol_struct_begin{}) -> ok;
+write(This, struct_end) -> ok;
+
+write(This, {bool, true})  -> write(This, {byte, 1});
+write(This, {bool, false}) -> write(This, {byte, 0});
+
+write(This, {byte, Byte}) ->
+    write(This, <<Byte:8/big-signed>>);
+
+write(This, {i16, I16}) ->
+    write(This, <<I16:16/big-signed>>);
+
+write(This, {i32, I32}) ->
+    write(This, <<I32:32/big-signed>>);
+
+write(This, {i64, I64}) ->
+    write(This, <<I64:64/big-signed>>);
+
+write(This, {double, Double}) ->
+    write(This, <<Double:64/big-signed-float>>);
+
+write(This, {string, Str}) when is_list(Str) ->
+    write(This, {i32, length(Str)}),
+    write(This, list_to_binary(Str));
+
+write(This, {string, Bin}) when is_binary(Bin) ->
+    write(This, {i32, size(Bin)}),
+    write(This, Bin);
+
+%% Data :: iolist()
+write(This, Data) ->
+    thrift_transport:write(This#binary_protocol.transport, Data).
+
+%%
+
+read(This, message_begin) ->
+    case read(This, ui32) of
+        {ok, Sz} when Sz band ?VERSION_MASK =:= ?VERSION_1 ->
+            %% we're at version 1
+            {ok, Name}  = read(This, string),
+            Type        = Sz band ?TYPE_MASK,
+            {ok, SeqId} = read(This, i32),
+            #protocol_message_begin{name  = binary_to_list(Name),
+                                    type  = Type,
+                                    seqid = SeqId};
+
+        {ok, Sz} when Sz < 0 ->
+            %% there's a version number but it's unexpected
+            {error, {bad_binary_protocol_version, Sz}};
+
+        {ok, Sz} when This#binary_protocol.strict_read =:= true ->
+            %% strict_read is true and there's no version header; that's an error
+            {error, no_binary_protocol_version};
+
+        {ok, Sz} when This#binary_protocol.strict_read =:= false ->
+            %% strict_read is false, so just read the old way
+            {ok, Name}  = read(This, Sz),
+            {ok, Type}  = read(This, byte),
+            {ok, SeqId} = read(This, i32),
+            #protocol_message_begin{name  = binary_to_list(Name),
+                                    type  = Type,
+                                    seqid = SeqId};
+
+        Err = {error, closed} -> Err;
+        Err = {error, timeout}-> Err;
+        Err = {error, ebadf}  -> Err
+    end;
+
+read(This, message_end) -> ok;
+
+read(This, struct_begin) -> ok;
+read(This, struct_end) -> ok;
+
+read(This, field_begin) ->
+    case read(This, byte) of
+        {ok, Type = ?tType_STOP} ->
+            #protocol_field_begin{type = Type};
+        {ok, Type} ->
+            {ok, Id} = read(This, i16),
+            #protocol_field_begin{type = Type,
+                                  id = Id}
+    end;
+
+read(This, field_end) -> ok;
+
+read(This, map_begin) ->
+    {ok, Ktype} = read(This, byte),
+    {ok, Vtype} = read(This, byte),
+    {ok, Size}  = read(This, i32),
+    #protocol_map_begin{ktype = Ktype,
+                        vtype = Vtype,
+                        size = Size};
+read(This, map_end) -> ok;
+
+read(This, list_begin) ->
+    {ok, Etype} = read(This, byte),
+    {ok, Size}  = read(This, i32),
+    #protocol_list_begin{etype = Etype,
+                         size = Size};
+read(This, list_end) -> ok;
+
+read(This, set_begin) ->
+    {ok, Etype} = read(This, byte),
+    {ok, Size}  = read(This, i32),
+    #protocol_set_begin{etype = Etype,
+                        size = Size};
+read(This, set_end) -> ok;
+
+read(This, field_stop) ->
+    {ok, ?tType_STOP} =  read(This, byte),
+    ok;
+
+%%
+
+read(This, bool) ->
+    case read(This, byte) of
+        {ok, Byte} -> {ok, Byte /= 0};
+        Else -> Else
+    end;
+
+read(This, byte) ->
+    case read(This, 1) of
+        {ok, <<Val:8/integer-signed-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+read(This, i16) ->
+    case read(This, 2) of
+        {ok, <<Val:16/integer-signed-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+read(This, i32) ->
+    case read(This, 4) of
+        {ok, <<Val:32/integer-signed-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+%% unsigned ints aren't used by thrift itself, but it's used for the parsing
+%% of the packet version header. Without this special function BEAM works fine
+%% but hipe thinks it received a bad version header.
+read(This, ui32) ->
+    case read(This, 4) of
+        {ok, <<Val:32/integer-unsigned-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+read(This, i64) ->
+    case read(This, 8) of
+        {ok, <<Val:64/integer-signed-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+read(This, double) ->
+    case read(This, 8) of
+        {ok, <<Val:64/float-signed-big, _/binary>>} -> {ok, Val};
+        Else -> Else
+    end;
+
+% returns a binary directly, call binary_to_list if necessary
+read(This, string) ->
+    {ok, Sz}  = read(This, i32),
+    {ok, Bin} = read(This, Sz);
+
+read(This, 0) -> {ok, <<>>};
+read(This, Len) when is_integer(Len), Len >= 0 ->
+    thrift_transport:read(This#binary_protocol.transport, Len).
+
+
+%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(tbp_opts, {strict_read = true,
+                   strict_write = true}).
+
+parse_factory_options([], Opts) ->
+    Opts;
+parse_factory_options([{strict_read, Bool} | Rest], Opts) when is_boolean(Bool) ->
+    parse_factory_options(Rest, Opts#tbp_opts{strict_read=Bool});
+parse_factory_options([{strict_write, Bool} | Rest], Opts) when is_boolean(Bool) ->
+    parse_factory_options(Rest, Opts#tbp_opts{strict_write=Bool}).
+
+
+%% returns a (fun() -> thrift_protocol())
+new_protocol_factory(TransportFactory, Options) ->
+    ParsedOpts = parse_factory_options(Options, #tbp_opts{}),
+    F = fun() ->
+                {ok, Transport} = TransportFactory(),
+                thrift_binary_protocol:new(
+                  Transport,
+                  [{strict_read,  ParsedOpts#tbp_opts.strict_read},
+                   {strict_write, ParsedOpts#tbp_opts.strict_write}])
+        end,
+    {ok, F}.
+

src/thrift_buffered_transport.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(thrift_buffered_transport).
+
+-behaviour(gen_server).
+-behaviour(thrift_transport).
+
+%% API
+-export([new/1, new_transport_factory/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+%% thrift_transport callbacks
+-export([write/2, read/2, flush/1, close/1]).
+
+-record(buffered_transport, {wrapped, % a thrift_transport
+                             write_buffer % iolist()
+                            }).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+new(WrappedTransport) ->
+    case gen_server:start_link(?MODULE, [WrappedTransport], []) of
+        {ok, Pid} ->
+            thrift_transport:new(?MODULE, Pid);
+        Else ->
+            Else
+    end.
+
+
+
+%%--------------------------------------------------------------------
+%% Function: write(Transport, Data) -> ok
+%%
+%% Data = iolist()
+%%
+%% Description: Writes data into the buffer
+%%--------------------------------------------------------------------
+write(Transport, Data) ->
+    gen_server:call(Transport, {write, Data}).
+
+%%--------------------------------------------------------------------
+%% Function: flush(Transport) -> ok
+%%
+%% Description: Flushes the buffer through to the wrapped transport
+%%--------------------------------------------------------------------
+flush(Transport) ->
+    gen_server:call(Transport, flush).
+
+%%--------------------------------------------------------------------
+%% Function: close(Transport) -> ok
+%%
+%% Description: Closes the transport and the wrapped transport
+%%--------------------------------------------------------------------
+close(Transport) ->
+    gen_server:cast(Transport, close).
+
+%%--------------------------------------------------------------------
+%% Function: Read(Transport, Len) -> {ok, Data}
+%%
+%% Data = binary()
+%%
+%% Description: Reads data through from the wrapped transoprt
+%%--------------------------------------------------------------------
+read(Transport, Len) when is_integer(Len) ->
+    gen_server:call(Transport, {read, Len}, _Timeout=10000).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%%                         {ok, State, Timeout} |
+%%                         ignore               |
+%%                         {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Wrapped]) ->
+    {ok, #buffered_transport{wrapped = Wrapped,
+                             write_buffer = []}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({write, Data}, _From, State = #buffered_transport{write_buffer = WBuf}) ->
+    {reply, ok, State#buffered_transport{write_buffer = [WBuf, Data]}};
+
+handle_call({read, Len}, _From, State = #buffered_transport{wrapped = Wrapped}) ->
+    Response = thrift_transport:read(Wrapped, Len),
+    {reply, Response, State};
+
+handle_call(flush, _From, State = #buffered_transport{write_buffer = WBuf,
+                                                      wrapped = Wrapped}) ->
+    Response = thrift_transport:write(Wrapped, WBuf),
+    thrift_transport:flush(Wrapped),
+    {reply, Response, State#buffered_transport{write_buffer = []}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast(close, State = #buffered_transport{write_buffer = WBuf,
+                                               wrapped = Wrapped}) ->
+    thrift_transport:write(Wrapped, WBuf),
+    %% Wrapped is closed by terminate/2
+    %%  error_logger:info_msg("thrift_buffered_transport ~p: closing", [self()]),
+    {stop, normal, State};
+handle_cast(Msg, State=#buffered_transport{}) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, State = #buffered_transport{wrapped=Wrapped}) ->
+    thrift_transport:close(Wrapped),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+new_transport_factory(WrapFactory) ->
+    F = fun() ->
+                {ok, Wrapped} = WrapFactory(),
+                new(Wrapped)
+        end,
+    {ok, F}.

src/thrift_client.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(thrift_client).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/2, start_link/3, start_link/4,
+         start/3, start/4,
+         call/3, send_call/3, close/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+
+-include("thrift_constants.hrl").
+-include("thrift_protocol.hrl").
+
+-record(state, {service, protocol, seqid}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server as a linked process.
+%%--------------------------------------------------------------------
+start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
+    start_link(Host, Port, Service, []).
+
+start_link(Host, Port, Service, Options) ->
+    start(Host, Port, Service, [{monitor, link} | Options]).
+
+start_link(ProtocolFactory, Service) ->
+    start(ProtocolFactory, Service, [{monitor, link}]).
+
+%%
+%% Splits client options into protocol options and transport options
+%%
+%% split_options([Options...]) -> {ProtocolOptions, TransportOptions}
+%%
+split_options(Options) ->
+    split_options(Options, [], [], []).
+
+split_options([], ClientIn, ProtoIn, TransIn) ->
+    {ClientIn, ProtoIn, TransIn};
+
+split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
+  when OptKey =:= monitor ->
+    split_options(Rest, [Opt | ClientIn], ProtoIn, TransIn);
+
+split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
+  when OptKey =:= strict_read;
+       OptKey =:= strict_write ->
+    split_options(Rest, ClientIn, [Opt | ProtoIn], TransIn);
+
+split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
+  when OptKey =:= framed;
+       OptKey =:= connect_timeout;
+       OptKey =:= sockopts ->
+    split_options(Rest, ClientIn, ProtoIn, [Opt | TransIn]).
+
+
+%%--------------------------------------------------------------------
+%% Function: start() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server as an unlinked process.
+%%--------------------------------------------------------------------
+
+%% Backwards-compatible starter for the common-case of socket transports
+start(Host, Port, Service, Options)
+  when is_integer(Port), is_atom(Service), is_list(Options) ->
+    {ClientOpts, ProtoOpts, TransOpts} = split_options(Options),
+
+    {ok, TransportFactory} =
+        thrift_socket_transport:new_transport_factory(Host, Port, TransOpts),
+
+    {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory(
+                              TransportFactory, ProtoOpts),
+
+    start(ProtocolFactory, Service, ClientOpts).
+
+
+%% ProtocolFactory :: fun() -> thrift_protocol()
+start(ProtocolFactory, Service, ClientOpts)
+  when is_function(ProtocolFactory), is_atom(Service) ->
+    {Starter, Opts} =
+        case lists:keysearch(monitor, 1, ClientOpts) of
+            {value, {monitor, link}} ->
+                {start_link, []};
+            {value, {monitor, tether}} ->
+                {start, [{tether, self()}]};
+            _ ->
+                {start, []}
+        end,
+
+    Connect =
+        case lists:keysearch(connect, 1, ClientOpts) of
+            {value, {connect, Choice}} ->
+                Choice;
+            _ ->
+                %% By default, connect at creation-time.
+                true
+        end,
+
+
+    Started = gen_server:Starter(?MODULE, [Service, Opts], []),
+
+    if
+        Connect ->
+            case Started of
+                {ok, Pid} ->
+                    case gen_server:call(Pid, {connect, ProtocolFactory}) of
+                        ok ->
+                            {ok, Pid};
+                        Error ->
+                            Error
+                    end;
+                Else ->
+                    Else
+            end;
+        true ->
+            Started
+    end.
+
+call(Client, Function, Args)
+  when is_pid(Client), is_atom(Function), is_list(Args) ->
+    case gen_server:call(Client, {call, Function, Args}) of
+        R = {ok, _} -> R;
+        R = {error, _} -> R;
+        {exception, Exception} -> throw(Exception)
+    end.
+
+cast(Client, Function, Args)
+  when is_pid(Client), is_atom(Function), is_list(Args) ->
+    gen_server:cast(Client, {call, Function, Args}).
+
+%% Sends a function call but does not read the result. This is useful
+%% if you're trying to log non-oneway function calls to write-only
+%% transports like thrift_disk_log_transport.
+send_call(Client, Function, Args)
+  when is_pid(Client), is_atom(Function), is_list(Args) ->
+    gen_server:call(Client, {send_call, Function, Args}).
+
+close(Client) when is_pid(Client) ->
+    gen_server:cast(Client, close).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%%                         {ok, State, Timeout} |
+%%                         ignore               |
+%%                         {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Service, Opts]) ->
+    case lists:keysearch(tether, 1, Opts) of
+        {value, {tether, Pid}} ->
+            erlang:monitor(process, Pid);
+        _Else ->
+            ok
+    end,
+    {ok, #state{service = Service}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({connect, ProtocolFactory}, _From,
+            State = #state{service = Service}) ->
+    case ProtocolFactory() of
+        {ok, Protocol} ->
+            {reply, ok, State#state{protocol = Protocol,
+                                    seqid = 0}};
+        Error ->
+            {stop, normal, Error, State}
+    end;
+
+handle_call({call, Function, Args}, _From, State = #state{service = Service}) ->
+    Result = catch_function_exceptions(
+               fun() ->
+                       ok = send_function_call(State, Function, Args),
+                       receive_function_result(State, Function)
+               end,
+               Service),
+    {reply, Result, State};
+
+
+handle_call({send_call, Function, Args}, _From, State = #state{service = Service}) ->
+    Result = catch_function_exceptions(
+               fun() ->
+                       send_function_call(State, Function, Args)
+               end,
+               Service),
+    {reply, Result, State}.
+
+
+%% Helper function that catches exceptions thrown by sending or receiving
+%% a function and returns the correct response for call or send_only above.
+catch_function_exceptions(Fun, Service) ->
+    try
+        Fun()
+    catch
+        throw:{return, Return} ->
+            Return;
+          error:function_clause ->
+            ST = erlang:get_stacktrace(),
+            case hd(ST) of
+                {Service, function_info, [Function, _]} ->
+                    {error, {no_function, Function}};
+                _ -> throw({error, {function_clause, ST}})
+            end
+    end.
+
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({call, Function, Args}, State = #state{service = Service,
+                                                   protocol = Protocol,
+                                                   seqid = SeqId}) ->
+    _Result =
+        try
+            ok = send_function_call(State, Function, Args),
+            receive_function_result(State, Function)
+        catch
+            Class:Reason ->
+                error_logger:error_msg("error ignored in handle_cast({cast,...},...): ~p:~p~n", [Class, Reason])
+        end,
+
+    {noreply, State};
+
+handle_cast(close, State=#state{protocol = Protocol}) ->
+%%     error_logger:info_msg("thrift_client ~p received close", [self()]),
+    {stop,normal,State};
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info({'DOWN', MonitorRef, process, Pid, _Info}, State)
+  when is_reference(MonitorRef), is_pid(Pid) ->
+    %% We don't actually verify the correctness of the DOWN message.
+    {stop, parent_died, State};
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(Reason, State = #state{protocol=undefined}) ->
+    ok;
+terminate(Reason, State = #state{protocol=Protocol}) ->
+    thrift_protocol:close_transport(Protocol),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+send_function_call(#state{protocol = Proto,
+                          service  = Service,
+                          seqid    = SeqId},
+                   Function,
+                   Args) ->
+    Params = Service:function_info(Function, params_type),
+    {struct, PList} = Params,
+    if
+        length(PList) =/= length(Args) ->
+            throw({return, {error, {bad_args, Function, Args}}});
+        true -> ok
+    end,
+
+    Begin = #protocol_message_begin{name = atom_to_list(Function),
+                                    type = ?tMessageType_CALL,
+                                    seqid = SeqId},
+    ok = thrift_protocol:write(Proto, Begin),
+    ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
+    ok = thrift_protocol:write(Proto, message_end),
+    thrift_protocol:flush_transport(Proto),
+    ok.
+
+receive_function_result(State = #state{protocol = Proto,
+                                       service = Service},
+                        Function) ->
+    ResultType = Service:function_info(Function, reply_type),
+    read_result(State, Function, ResultType).
+
+read_result(_State,
+            _Function,
+            oneway_void) ->
+    {ok, ok};
+
+read_result(State = #state{protocol = Proto,
+                           seqid    = SeqId},
+            Function,
+            ReplyType) ->
+    case thrift_protocol:read(Proto, message_begin) of
+        #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
+            {error, {bad_seq_id, SeqId}};
+
+        #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
+            handle_application_exception(State);
+
+        #protocol_message_begin{type = ?tMessageType_REPLY} ->
+            handle_reply(State, Function, ReplyType)
+    end.
+
+handle_reply(State = #state{protocol = Proto,
+                            service = Service},
+             Function,
+             ReplyType) ->
+    {struct, ExceptionFields} = Service:function_info(Function, exceptions),
+    ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
+    {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
+    ReplyList = tuple_to_list(Reply),
+    true = length(ReplyList) == length(ExceptionFields) + 1,
+    ExceptionVals = tl(ReplyList),
+    Thrown = [X || X <- ExceptionVals,
+                   X =/= undefined],
+    Result =
+        case Thrown of
+            [] when ReplyType == {struct, []} ->
+                {ok, ok};
+            [] ->
+                {ok, hd(ReplyList)};
+            [Exception] ->
+                {exception, Exception}
+        end,
+    ok = thrift_protocol:read(Proto, message_end),
+    Result.
+
+handle_application_exception(State = #state{protocol = Proto}) ->
+    {ok, Exception} = thrift_protocol:read(Proto,
+                                           ?TApplicationException_Structure),
+    ok = thrift_protocol:read(Proto, message_end),
+    XRecord = list_to_tuple(
+                ['TApplicationException' | tuple_to_list(Exception)]),
+    error_logger:error_msg("X: ~p~n", [XRecord]),
+    true = is_record(XRecord, 'TApplicationException'),
+    {exception, XRecord}.

src/thrift_disk_log_transport.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+%%% Todo: this might be better off as a gen_server type of transport
+%%%       that handles stuff like group commit, similar to TFileTransport
+%%%       in cpp land
+-module(thrift_disk_log_transport).
+
+-behaviour(thrift_transport).
+
+%% API
+-export([new/2, new_transport_factory/2, new_transport_factory/3]).
+
+%% thrift_transport callbacks
+-export([read/2, write/2, force_flush/1, flush/1, close/1]).
+
+%% state
+-record(dl_transport, {log,
+                       close_on_close = false,
+                       sync_every = infinity,
+                       sync_tref}).
+
+
+%% Create a transport attached to an already open log.
+%% If you'd like this transport to close the disk_log using disk_log:lclose()
+%% when the transport is closed, pass a {close_on_close, true} tuple in the
+%% Opts list.
+new(LogName, Opts) when is_atom(LogName), is_list(Opts) ->
+    State = parse_opts(Opts, #dl_transport{log = LogName}),
+
+    State2 =
+        case State#dl_transport.sync_every of
+            N when is_integer(N), N > 0 ->
+                {ok, TRef} = timer:apply_interval(N, ?MODULE, force_flush, State),
+                State#dl_transport{sync_tref = TRef};
+            _ -> State
+        end,
+
+    thrift_transport:new(?MODULE, State2).
+
+
+parse_opts([], State) ->
+    State;
+parse_opts([{close_on_close, Bool} | Rest], State) when is_boolean(Bool) ->
+    State#dl_transport{close_on_close = Bool};
+parse_opts([{sync_every, Int} | Rest], State) when is_integer(Int), Int > 0 ->
+    State#dl_transport{sync_every = Int}.
+
+
+%%%% TRANSPORT IMPLENTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% disk_log_transport is write-only
+read(_State, Len) ->
+    {error, no_read_from_disk_log}.
+
+write(#dl_transport{log = Log}, Data) ->
+    disk_log:balog(Log, erlang:iolist_to_binary(Data)).
+
+force_flush(#dl_transport{log = Log}) ->
+    error_logger:info_msg("~p syncing~n", [?MODULE]),
+    disk_log:sync(Log).
+
+flush(#dl_transport{log = Log, sync_every = SE}) ->
+    case SE of
+        undefined -> % no time-based sync
+            disk_log:sync(Log);
+        _Else ->     % sync will happen automagically
+            ok
+    end.
+
+
+%% On close, close the underlying log if we're configured to do so.
+close(#dl_transport{close_on_close = false}) ->
+    ok;
+close(#dl_transport{log = Log}) ->
+    disk_log:lclose(Log).
+
+
+%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+new_transport_factory(Name, ExtraLogOpts) ->
+    new_transport_factory(Name, ExtraLogOpts, [{close_on_close, true},
+                                               {sync_every, 500}]).
+
+new_transport_factory(Name, ExtraLogOpts, TransportOpts) ->
+    F = fun() -> factory_impl(Name, ExtraLogOpts, TransportOpts) end,
+    {ok, F}.
+
+factory_impl(Name, ExtraLogOpts, TransportOpts) ->
+    LogOpts = [{name, Name},
+               {format, external},
+               {type, wrap} |
+               ExtraLogOpts],
+    Log =
+        case disk_log:open(LogOpts) of
+            {ok, Log} ->
+                Log;
+            {repaired, Log, Info1, Info2} ->
+                error_logger:info_msg("Disk log ~p repaired: ~p, ~p~n", [Log, Info1, Info2]),
+                Log
+        end,
+    new(Log, TransportOpts).

src/thrift_file_transport.erl

+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an