THRIFT-976 c_glib tutorial

Patch: Simon South
diff --git a/tutorial/c_glib/Makefile.am b/tutorial/c_glib/Makefile.am
index 33d6c93..24075dd 100755
--- a/tutorial/c_glib/Makefile.am
+++ b/tutorial/c_glib/Makefile.am
@@ -43,8 +43,16 @@
 
 
 noinst_PROGRAMS = \
+	tutorial_server \
 	tutorial_client
 
+tutorial_server_SOURCES = \
+	c_glib_server.c
+
+tutorial_server_LDADD = \
+	libtutorialgencglib.la \
+	$(top_builddir)/lib/c_glib/libthrift_c_glib.la
+
 tutorial_client_SOURCES = \
 	c_glib_client.c
 
@@ -59,8 +67,12 @@
 clean-local:
 	$(RM) -r gen-c_glib
 
+tutorialserver: all
+	./tutorial_server
+
 tutorialclient: all
 	./tutorial_client
 
 EXTRA_DIST = \
+	c_glib_server.c \
 	c_glib_client.c
diff --git a/tutorial/c_glib/c_glib_client.c b/tutorial/c_glib/c_glib_client.c
index d3f2a34..3cea0c2 100644
--- a/tutorial/c_glib/c_glib_client.c
+++ b/tutorial/c_glib/c_glib_client.c
@@ -26,12 +26,12 @@
 
 #include "gen-c_glib/calculator.h"
 
-int main (void) {
+int main (void)
+{
   ThriftSocket *socket;
   ThriftTransport *transport;
   ThriftProtocol *protocol;
-  CalculatorClient *client;
-  CalculatorIf *iface;
+  CalculatorIf *client;
 
   GError *error = NULL;
   InvalidOperation *invalid_operation = NULL;
@@ -43,16 +43,20 @@
 
   int exit_status = 0;
 
-  socket = g_object_new (THRIFT_TYPE_SOCKET,
-                         "hostname", "localhost",
-                         "port", 9090,
-                         NULL);
+#if (!GLIB_CHECK_VERSION (2, 36, 0))
+  g_type_init ();
+#endif
+
+  socket    = g_object_new (THRIFT_TYPE_SOCKET,
+                            "hostname",  "localhost",
+                            "port",      9090,
+                            NULL);
   transport = g_object_new (THRIFT_TYPE_BUFFERED_TRANSPORT,
                             "transport", socket,
                             NULL);
-  protocol = g_object_new (THRIFT_TYPE_BINARY_PROTOCOL,
-                           "transport", transport,
-                           NULL);
+  protocol  = g_object_new (THRIFT_TYPE_BINARY_PROTOCOL,
+                            "transport", transport,
+                            NULL);
 
   thrift_transport_open (transport, &error);
 
@@ -63,10 +67,9 @@
      service through an instance of CalculatorClient, which implements
      CalculatorIf. */
   client = g_object_new (TYPE_CALCULATOR_CLIENT,
-                         "input_protocol", protocol,
+                         "input_protocol",  protocol,
                          "output_protocol", protocol,
                          NULL);
-  iface = CALCULATOR_IF (client);
 
   /* Each of the client methods requires at least two parameters: A
      pointer to the client-interface implementation (the client
@@ -76,13 +79,13 @@
      On success, client methods return TRUE. A return value of FALSE
      indicates an error occured and the error parameter has been
      set. */
-  if (!error && calculator_client_ping (iface, &error)) {
+  if (!error && calculator_if_ping (client, &error)) {
     puts ("ping()");
   }
 
   /* Service methods that return a value do so by passing the result
      back via an output parameter (here, "sum"). */
-  if (!error && calculator_client_add (iface, &sum, 1, 1, &error)) {
+  if (!error && calculator_if_add (client, &sum, 1, 1, &error)) {
     printf ("1+1=%d\n", sum);
   }
 
@@ -99,12 +102,12 @@
 
     /* Exceptions are passed back from service methods in a manner
        similar to return values. */
-    if (calculator_client_calculate (iface,
-                                     NULL,
-                                     1,
-                                     work,
-                                     &invalid_operation,
-                                     &error)) {
+    if (calculator_if_calculate (client,
+                                 NULL,
+                                 1,
+                                 work,
+                                 &invalid_operation,
+                                 &error)) {
       puts ("Whoa? We can divide by zero!");
     }
     else {
@@ -123,8 +126,7 @@
         invalid_operation = NULL;
       }
 
-      g_error_free (error);
-      error = NULL;
+      g_clear_error (&error);
     }
   }
 
@@ -136,12 +138,12 @@
                   "op",   OPERATION_SUBTRACT,
                   NULL);
 
-    if (calculator_client_calculate (iface,
-                                     &diff,
-                                     1,
-                                     work,
-                                     &invalid_operation,
-                                     &error)) {
+    if (calculator_if_calculate (client,
+                                 &diff,
+                                 1,
+                                 work,
+                                 &invalid_operation,
+                                 &error)) {
       printf ("15-10=%d\n", diff);
     }
   }
@@ -156,10 +158,9 @@
 
     /* As defined in the Thrift file, the Calculator service extends
        the SharedService service. Correspondingly, in the generated
-       code CalculatorClient inherits from SharedServiceClient, and
-       the parent service's methods are accessible through a simple
-       cast. */
-     if (shared_service_client_get_struct (SHARED_SERVICE_IF (iface),
+       code CalculatorIf inherits from SharedServiceIf, and the parent
+       service's methods are accessible through a simple cast. */
+    if (shared_service_client_get_struct (SHARED_SERVICE_IF (client),
                                           &shared_struct,
                                           1,
                                           &error)) {
@@ -173,8 +174,7 @@
 
   if (error) {
     printf ("ERROR: %s\n", error->message);
-    g_error_free (error);
-    error = NULL;
+    g_clear_error (&error);
 
     exit_status = 1;
   }
diff --git a/tutorial/c_glib/c_glib_server.c b/tutorial/c_glib/c_glib_server.c
new file mode 100644
index 0000000..27f9f95
--- /dev/null
+++ b/tutorial/c_glib/c_glib_server.c
@@ -0,0 +1,527 @@
+/*
+ * 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.
+ */
+
+#include <glib-object.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <thrift/c_glib/thrift.h>
+#include <thrift/c_glib/protocol/thrift_binary_protocol_factory.h>
+#include <thrift/c_glib/protocol/thrift_protocol_factory.h>
+#include <thrift/c_glib/server/thrift_server.h>
+#include <thrift/c_glib/server/thrift_simple_server.h>
+#include <thrift/c_glib/transport/thrift_buffered_transport_factory.h>
+#include <thrift/c_glib/transport/thrift_server_socket.h>
+#include <thrift/c_glib/transport/thrift_server_transport.h>
+
+#include "gen-c_glib/calculator.h"
+
+G_BEGIN_DECLS
+
+/* In the C (GLib) implementation of Thrift, the actual work done by a
+   server---that is, the code that runs when a client invokes a
+   service method---is defined in a separate "handler" class that
+   implements the service interface. Here we define the
+   TutorialCalculatorHandler class, which implements the CalculatorIf
+   interface and provides the behavior expected by tutorial clients.
+   (Typically this code would be placed in its own module but for
+   clarity this tutorial is presented entirely in a single file.)
+
+   For each service the Thrift compiler generates an abstract base
+   class from which handler implementations should inherit. In our
+   case TutorialCalculatorHandler inherits from CalculatorHandler,
+   defined in gen-c_glib/calculator.h.
+
+   If you're new to GObject, try not to be intimidated by the quantity
+   of code here---much of it is boilerplate and can mostly be
+   copied-and-pasted from existing work. For more information refer to
+   the GObject Reference Manual, available online at
+   https://developer.gnome.org/gobject/. */
+
+#define TYPE_TUTORIAL_CALCULATOR_HANDLER \
+  (tutorial_calculator_handler_get_type ())
+
+#define TUTORIAL_CALCULATOR_HANDLER(obj)                                \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                                   \
+                               TYPE_TUTORIAL_CALCULATOR_HANDLER,        \
+                               TutorialCalculatorHandler))
+#define TUTORIAL_CALCULATOR_HANDLER_CLASS(c)                    \
+  (G_TYPE_CHECK_CLASS_CAST ((c),                                \
+                            TYPE_TUTORIAL_CALCULATOR_HANDLER,   \
+                            TutorialCalculatorHandlerClass))
+#define IS_TUTORIAL_CALCULATOR_HANDLER(obj)                             \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                                   \
+                               TYPE_TUTORIAL_CALCULATOR_HANDLER))
+#define IS_TUTORIAL_CALCULATOR_HANDLER_CLASS(c)                 \
+  (G_TYPE_CHECK_CLASS_TYPE ((c),                                \
+                            TYPE_TUTORIAL_CALCULATOR_HANDLER))
+#define TUTORIAL_CALCULATOR_HANDLER_GET_CLASS(obj)              \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj),                            \
+                              TYPE_TUTORIAL_CALCULATOR_HANDLER, \
+                              TutorialCalculatorHandlerClass))
+
+struct _TutorialCalculatorHandler {
+  CalculatorHandler parent_instance;
+
+  /* private */
+  GHashTable *log;
+};
+typedef struct _TutorialCalculatorHandler TutorialCalculatorHandler;
+
+struct _TutorialCalculatorHandlerClass {
+  CalculatorHandlerClass parent_class;
+};
+typedef struct _TutorialCalculatorHandlerClass TutorialCalculatorHandlerClass;
+
+GType tutorial_calculator_handler_get_type (void);
+
+G_END_DECLS
+
+/* ---------------------------------------------------------------- */
+
+/* The implementation of TutorialCalculatorHandler follows. */
+
+G_DEFINE_TYPE (TutorialCalculatorHandler,
+               tutorial_calculator_handler,
+               TYPE_CALCULATOR_HANDLER);
+
+/* Each of a handler's methods accepts at least two parameters: A
+   pointer to the service-interface implementation (the handler object
+   itself) and a handle to a GError structure to receive information
+   about any error that occurs.
+
+   On success, a handler method returns TRUE. A return value of FALSE
+   indicates an error occured and the error parameter has been
+   set. (Methods should not return FALSE without first setting the
+   error parameter.) */
+static gboolean
+tutorial_calculator_handler_ping (CalculatorIf  *iface,
+                                  GError       **error)
+{
+  THRIFT_UNUSED_VAR (iface);
+  THRIFT_UNUSED_VAR (error);
+
+  puts ("ping()");
+
+  return TRUE;
+}
+
+/* Service-method parameters are passed through as parameters to the
+   handler method.
+
+   If the service method returns a value an output parameter, _return,
+   is additionally passed to the handler method. This parameter should
+   be set appropriately before the method returns, whenever it
+   succeeds.
+
+   The return value from this method happens to be of a base type,
+   i32, but note if a method returns a complex type such as a map or
+   list *_return will point to a pre-allocated data structure that
+   does not need to be re-allocated and should not be destroyed. */
+static gboolean
+tutorial_calculator_handler_add (CalculatorIf  *iface,
+                                 gint32        *_return,
+                                 const gint32   num1,
+                                 const gint32   num2,
+                                 GError       **error)
+{
+  THRIFT_UNUSED_VAR (iface);
+  THRIFT_UNUSED_VAR (error);
+
+  printf ("add(%d,%d)\n", num1, num2);
+  *_return = num1 + num2;
+
+  return TRUE;
+}
+
+/* Any handler method can return a ThriftApplicationException to the
+   client by setting its error parameter appropriately and returning
+   FALSE. See the ThriftApplicationExceptionError enumeration defined
+   in thrift_application_exception.h for a list of recognized
+   exception types (GError codes).
+
+   If a service method can also throw a custom exception (that is, one
+   defined in the .thrift file) an additional output parameter will be
+   provided (here, "ouch") to hold an instance of the exception, when
+   necessary. Note there will be a separate parameter added for each
+   type of exception the method can throw.
+
+   Unlike return values, exception objects are never pre-created; this
+   is always the responsibility of the handler method. */
+static gboolean
+tutorial_calculator_handler_calculate (CalculatorIf      *iface,
+                                       gint32            *_return,
+                                       const gint32       logid,
+                                       const Work        *w,
+                                       InvalidOperation **ouch,
+                                       GError           **error)
+{
+  THRIFT_UNUSED_VAR (error);
+
+  TutorialCalculatorHandler *self;
+
+  gint *log_key;
+  gchar log_value[12];
+  SharedStruct *log_struct;
+
+  gint num1;
+  gint num2;
+  Operation op;
+  gboolean result = TRUE;
+
+  g_return_val_if_fail (IS_TUTORIAL_CALCULATOR_HANDLER (iface),
+                        FALSE);
+  self = TUTORIAL_CALCULATOR_HANDLER (iface);
+
+  /* Remember: Exception objects are never pre-created */
+  g_assert (*ouch == NULL);
+
+  /* Fetch the contents of our Work parameter.
+
+     Note that integer properties of thirty-two bits or fewer in width
+     are _always_ of type gint, regardless of the range of values they
+     hold. A common error is trying to retrieve, say, a structure
+     member defined in the .thrift file as type i16 into a variable of
+     type gint16, which will clobber variables adjacent on the
+     stack. Remember: If you're retrieving an integer property the
+     receiving variable must be of either type gint or gint64, as
+     appropriate. */
+  g_object_get ((Work *)w,
+                "num1", &num1,
+                "num2", &num2,
+                "op",   &op,
+                NULL);
+
+  printf ("calculate(%d,{%d,%d,%d})\n", logid, op, num1, num2);
+
+  switch (op) {
+  case OPERATION_ADD:
+    *_return = num1 + num2;
+    break;
+
+  case OPERATION_SUBTRACT:
+    *_return = num1 - num2;
+    break;
+
+  case OPERATION_MULTIPLY:
+    *_return = num1 * num2;
+    break;
+
+  case OPERATION_DIVIDE:
+    if (num2 == 0) {
+      /* For each custom exception type a subclass of ThriftStruct is
+         generated by the Thrift compiler. Throw an exception by
+         setting the corresponding output parameter to a new instance
+         of its type and returning FALSE. */
+      *ouch = g_object_new (TYPE_INVALID_OPERATION,
+                            "what", op,
+                            "why",  g_strdup ("Cannot divide by 0"),
+                            NULL);
+      result = FALSE;
+
+      /* Note the call to g_strdup above: All the memory used by a
+         ThriftStruct's properties belongs to the object itself and
+         will be freed on destruction. Removing this call to g_strdup
+         will lead to a segmentation fault as the object tries to
+         release memory allocated statically to the program. */
+    }
+    else {
+      *_return = num1 / num2;
+    }
+    break;
+
+  default:
+    *ouch = g_object_new (TYPE_INVALID_OPERATION,
+                          "what", op,
+                          "why",  g_strdup ("Invalid Operation"),
+                          NULL);
+    result = FALSE;
+  }
+
+  /* On success, log a record of the result to our hash table */
+  if (result) {
+    log_key = g_malloc (sizeof *log_key);
+    *log_key = logid;
+
+    snprintf (log_value, sizeof log_value, "%d", *_return);
+
+    log_struct = g_object_new (TYPE_SHARED_STRUCT,
+                               "key",   *log_key,
+                               "value",  g_strdup (log_value),
+                               NULL);
+    g_hash_table_replace (self->log, log_key, log_struct);
+  }
+
+  return result;
+}
+
+/* A one-way method has the same signature as an equivalent, regular
+   method that returns no value. */
+static gboolean
+tutorial_calculator_handler_zip (CalculatorIf  *iface,
+                                 GError       **error)
+{
+  THRIFT_UNUSED_VAR (iface);
+  THRIFT_UNUSED_VAR (error);
+
+  puts ("zip()");
+
+  return TRUE;
+}
+
+/* As specified in the .thrift file (tutorial.thrift), the Calculator
+   service extends the SharedService service. Correspondingly, in the
+   generated code the Calculator interface, CalculatorIf, extends the
+   SharedService interface, SharedServiceIf, and subclasses of
+   CalculatorHandler should implement its methods as well.
+
+   Here we provide an implementation for the getStruct method from the
+   parent service. */
+static gboolean
+tutorial_calculator_handler_get_struct (SharedServiceIf  *iface,
+                                        SharedStruct    **_return,
+                                        const gint32      key32,
+                                        GError          **error)
+{
+  THRIFT_UNUSED_VAR (error);
+
+  gint key = (gint)key32;
+  TutorialCalculatorHandler *self;
+  SharedStruct *log_struct;
+  gint log_key;
+  gchar *log_value;
+
+  g_return_val_if_fail (IS_TUTORIAL_CALCULATOR_HANDLER (iface),
+                        FALSE);
+  self = TUTORIAL_CALCULATOR_HANDLER (iface);
+
+  /* Remember: Complex return types are always pre-created and need
+     only be populated */
+  g_assert (*_return != NULL);
+
+  printf ("getStruct(%d)\n", key);
+
+  /* If the key exists in our log, return the corresponding logged
+     data (or an empty SharedStruct structure if it does not).
+
+     Incidentally, note we _must_ here copy the values from the hash
+     table into the return structure. All memory used by the return
+     structure belongs to the structure itself and will be freed once
+     a response is sent to the client. If we merely freed *_return and
+     set it to point to our hash-table entry, that would mean memory
+     would be released (effectively, data erased) out of the hash
+     table! */
+  log_struct = g_hash_table_lookup (self->log, &key);
+  if (log_struct != NULL) {
+    g_object_get (log_struct,
+                  "key",   &log_key,
+                  "value", &log_value,
+                  NULL);
+    g_object_set (*_return,
+                  "key",   log_key,
+                  "value", g_strdup (log_value),
+                  NULL);
+  }
+
+  return TRUE;
+}
+
+/* TutorialCalculatorHandler's instance finalizer (destructor) */
+static void
+tutorial_calculator_handler_finalize (GObject *object)
+{
+  TutorialCalculatorHandler *self =
+    TUTORIAL_CALCULATOR_HANDLER (object);
+
+  /* Free our calculation-log hash table */
+  g_hash_table_unref (self->log);
+  self->log = NULL;
+
+  /* Chain up to the parent class */
+  G_OBJECT_CLASS (tutorial_calculator_handler_parent_class)->
+    finalize (object);
+}
+
+/* TutorialCalculatorHandler's instance initializer (constructor) */
+static void
+tutorial_calculator_handler_init (TutorialCalculatorHandler *self)
+{
+  /* Create our calculation-log hash table */
+  self->log = g_hash_table_new_full (g_int_hash,
+                                     g_int_equal,
+                                     g_free,
+                                     g_object_unref);
+}
+
+/* TutorialCalculatorHandler's class initializer */
+static void
+tutorial_calculator_handler_class_init (TutorialCalculatorHandlerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  SharedServiceHandlerClass *shared_service_handler_class =
+    SHARED_SERVICE_HANDLER_CLASS (klass);
+  CalculatorHandlerClass *calculator_handler_class =
+    CALCULATOR_HANDLER_CLASS (klass);
+
+  /* Register our destructor */
+  gobject_class->finalize = tutorial_calculator_handler_finalize;
+
+  /* Register our implementations of CalculatorHandler's methods */
+  calculator_handler_class->ping =
+    tutorial_calculator_handler_ping;
+  calculator_handler_class->add =
+    tutorial_calculator_handler_add;
+  calculator_handler_class->calculate =
+    tutorial_calculator_handler_calculate;
+  calculator_handler_class->zip =
+    tutorial_calculator_handler_zip;
+
+  /* Register our implementation of SharedServiceHandler's method */
+  shared_service_handler_class->get_struct =
+    tutorial_calculator_handler_get_struct;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* That ends the implementation of TutorialCalculatorHandler.
+   Everything below is fairly generic code that sets up a minimal
+   Thrift server for tutorial clients. */
+
+
+/* Our server object, declared globally so it is accessible within the
+   SIGINT signal handler */
+ThriftServer *server = NULL;
+
+/* A flag that indicates whether the server was interrupted with
+   SIGINT (i.e. Ctrl-C) so we can tell whether its termination was
+   abnormal */
+gboolean sigint_received = FALSE;
+
+/* Handle SIGINT ("Ctrl-C") signals by gracefully stopping the
+   server */
+static void
+sigint_handler (int signal_number)
+{
+  THRIFT_UNUSED_VAR (signal_number);
+
+  /* Take note we were called */
+  sigint_received = TRUE;
+
+  /* Shut down the server gracefully */
+  if (server != NULL)
+    thrift_server_stop (server);
+}
+
+int main (void)
+{
+  TutorialCalculatorHandler *handler;
+  CalculatorProcessor *processor;
+
+  ThriftServerTransport *server_transport;
+  ThriftTransportFactory *transport_factory;
+  ThriftProtocolFactory *protocol_factory;
+
+  struct sigaction sigint_action;
+
+  GError *error;
+  int exit_status = 0;
+
+#if (!GLIB_CHECK_VERSION (2, 36, 0))
+  g_type_init ();
+#endif
+
+  /* Create an instance of our handler, which provides the service's
+     methods' implementation */
+  handler =
+    g_object_new (TYPE_TUTORIAL_CALCULATOR_HANDLER,
+                  NULL);
+
+  /* Create an instance of the service's processor, automatically
+     generated by the Thrift compiler, which parses incoming messages
+     and dispatches them to the appropriate method in the handler */
+  processor =
+    g_object_new (TYPE_CALCULATOR_PROCESSOR,
+                  "handler", handler,
+                  NULL);
+
+  /* Create our server socket, which binds to the specified port and
+     listens for client connections */
+  server_transport =
+    g_object_new (THRIFT_TYPE_SERVER_SOCKET,
+                  "port", 9090,
+                  NULL);
+
+  /* Create our transport factory, used by the server to wrap "raw"
+     incoming connections from the client (in this case with a
+     ThriftBufferedTransport to improve performance) */
+  transport_factory =
+    g_object_new (THRIFT_TYPE_BUFFERED_TRANSPORT_FACTORY,
+                  NULL);
+
+  /* Create our protocol factory, which determines which wire protocol
+     the server will use (in this case, Thrift's binary protocol) */
+  protocol_factory =
+    g_object_new (THRIFT_TYPE_BINARY_PROTOCOL_FACTORY,
+                  NULL);
+
+  /* Create the server itself */
+  server =
+    g_object_new (THRIFT_TYPE_SIMPLE_SERVER,
+                  "processor",                processor,
+                  "server_transport",         server_transport,
+                  "input_transport_factory",  transport_factory,
+                  "output_transport_factory", transport_factory,
+                  "input_protocol_factory",   protocol_factory,
+                  "output_protocol_factory",  protocol_factory,
+                  NULL);
+
+  /* Install our SIGINT handler, which handles Ctrl-C being pressed by
+     stopping the server gracefully (not strictly necessary, but a
+     nice touch) */
+  memset (&sigint_action, 0, sizeof (sigint_action));
+  sigint_action.sa_handler = sigint_handler;
+  sigint_action.sa_flags = SA_RESETHAND;
+  sigaction (SIGINT, &sigint_action, NULL);
+
+  /* Start the server, which will run until its stop method is invoked
+     (from within the SIGINT handler, in this case) */
+  puts ("Starting the server...");
+  thrift_server_serve (server, &error);
+
+  /* If the server stopped for any reason other than having been
+     interrupted by the user, report the error */
+  if (!sigint_received) {
+    g_message ("thrift_server_serve: %s",
+               error != NULL ? error->message : "(null)");
+    g_clear_error (&error);
+  }
+
+  puts ("done.");
+
+  g_object_unref (server);
+  g_object_unref (transport_factory);
+  g_object_unref (protocol_factory);
+  g_object_unref (server_transport);
+
+  g_object_unref (processor);
+  g_object_unref (handler);
+
+  return exit_status;
+}