WCF as REST. Deeping as usual: SEO Routing, Newtonsoft.Json and more !!

Muy buenas,

Entre patines, mountaintbike, despedidas de soltero, resfriado/virus, playa y viajes de verano, saco un hueco para comentar este tema que tenía en borrador y que se estaba haciendo de rogar, es decir, como exponer servicios WCF como REST, haciendo uso de Routing para el enrutamiento basado en SEO y otras buenas prácticas al respecto.

Aunque la recomendación para la creación de nuevos servicios REST es WebApi, existirán ocasiones en la que esto no sea posible, para estos casos, he aquí las pautas a seguir a partir de nuestros servicios WCF:

1) Incluimos en los proyectos .NET la referencia a la dll “System.Web.Routing.dll” y “System.ServiceModel.Activation”.

2) Establecemos la compatibilidad ASP.NET en los servicios:

Incluimos en el Service: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

Incluimos en el web.config, en la sección “system.serviceModel”:

   1: <serviceHostingEnvironment

   2:    aspNetCompatibilityEnabled="true"

   3:    multipleSiteBindingsEnabled="true" />

3) Añadimos a nuestro proyecto un fichero “global.asax” e ncluimos en el método “Application_Start” instrucciones similares a las siguientes para cada uno de los servicios a enrutar:
   1: RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(Service1)));

   2: RouteTable.Routes.Add(new ServiceRoute("historial", new WebServiceHostFactory(), typeof(HistorialService)));

   3: RouteTable.Routes.Add(new ServiceRoute("private/buzon", new WebServiceHostFactory(), typeof(BuzonService)));

4) Modificamos los contracts asegurando la correcta “UriTemplate”:

   a) Ejemplo GET:

   1: [OperationContract]

   2: [WebGet(    

   3:     UriTemplate = "?data={value}",

   4:     ResponseFormat = WebMessageFormat.Json,

   5:     RequestFormat = WebMessageFormat.Json)]

   6: string GetData(int value);

   b) Ejemplo 2 GET:

   1: [OperationContract]

   2: [WebGet(    

   3:     UriTemplate = "{value}",

   4:     ResponseFormat = WebMessageFormat.Json,

   5:     RequestFormat = WebMessageFormat.Json)]

   6: string GetData(string value);

   c) Ejemplo PUT
   1: [OperationContract]

   2: [WebInvoke(

   3:     Method = "POST",    

   4:     UriTemplate = "",

   5:     BodyStyle = WebMessageBodyStyle.WrappedRequest,

   6:     ResponseFormat = WebMessageFormat.Json,

   7:     RequestFormat = WebMessageFormat.Json)]

   8: Stream Enviar(int IdMensaje, string Respuesta);

5) En los ficheros “.svc” de los servicios incluimos la siguiente instrucción añadiéndola a la existente generada por defecto:

   1: <%@ ServiceHost ...  Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

6) Incluimos en el web.config la siguiente configuración con objeto de poder navegar desde nuestro browser a la definición de los servicios Rest:

   1: <sectionGroup

   2:     name="system.serviceModel"

   3:     type="System.ServiceModel.Configuration.ServiceModelSectionGroup, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

   4:     <section name="standardEndpoints"

   5:         type="System.ServiceModel.Configuration.StandardEndpointsSection, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

   6: </sectionGroup>

Dentro de la sección “System.ServiceModel”:
   1: <standardEndpoints>  

   2:     <webHttpEndpoint>    

   3:         <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true">    

   4:         </standardEndpoint>  

   5:     </webHttpEndpoint>

   6: </standardEndpoints>

Para poder acceder a la información, bastará con incluir el sufijo “/help” a la url de cada servicio REST, de manera que la información obtenida sea similar a la siguiente:

image

imageimage

Nota: La ruta completa “SEO” se compone por la suma de los tres siguiente elementos:

  • Servidor: HTTP(S)://<SITE | DIRECTORIO VIRUTAL>[:PUERTO]/
  • Routing: “”
  • UriTemplate “{value}”, o, “?data={value}”, según el método del servicio a utilizar.

    De manera que la url completa sería: http://localhost/WcfService1/1

     

    7) [Opcional] Sólo en caso de publicar el servicio REST en  IIS6: Configuramos el Web Site o el Directorio Virtual del IIS como sigue:

    imageimage

    Donde la extensión será “C:WindowsMicrosoft.NETFrameworkv4.0.30319aspnet_isapi.dll” o “C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll”, asegurando que el check  “Comprobar si el archivo existe” está desmarcado.

    8) Uso de entidades JSON

    Una vez el servicio ya está adaptado a un servicio REST, ahora necesitamos revisar o crear o modificar las entidades de tipo JSON para nuestros métodos. Para ello utilizaremos la dll “Newtonsoft.Json”  que puede ser descargada desde aquí, o incluso desde NuGet: “PM> Install-Package Newtonsoft.Json”.

    Esta dll nos permitirá trasformar nuestras entidades en JSON, transformar String del tipo JSON en entidades y vicebersa, entre otras cosas, facilitando toda esta labor de conversión/transformación de una manera más fácil a como se podría hacer con la socialización por defecto a JSON de .NET, por tanto su uso es recomendado.

    Cada método debería retornar un entidad JSON con la información esperada, sin embargo también se hace necesario, en ocasiones, el retorno de códigos y descripciones de error. En este caso, la recomendación sería utilizar una entidad común de la forma siguiente, donde en cado de que Error=0, Mensaje será NULL y Data tendrá el dato esperado.

       1: [JsonObject]

       2: public class ResponseJson<T> 

       3: {

       4:     [JsonProperty]

       5:     public int Error { get; set; }

       6:     

       7:     [JsonProperty]

       8:     public string Messaje { get; set; }     

       9:     

      10:     [JsonProperty]

      11:     public T Data { get; set; }

      12:  }

    Adicionalmente, si tenemos otro tipo de requisitos que no se adapte a este formato, siempre podemos retornar un “Stream”. Podría ser un “String”, pero, en tal caso el JSon resultante no es correcto, incluyendo el carácter “”” con carácter especial “””:

       1: [JsonObject]

       2: public class ResponseJson<T> 

       3: {    

       4:     [JsonProperty]    

       5:     public int Error { get; set; }    

       6:  

       7:     [JsonProperty]    

       8:     public T Mensaje { get; set; }

       9: }

    En este caso, si Error=0, Mensaje tomará el dato esperado, mientras que si Error != 0, Mensaje tendrá un String con la descripción del error. En el siguiente snippet podemos ver un ejemplo de como quedaría nuestro método en términos generales para este último caso:

       1: public Stream Consultar(int page)

       2: {

       3:     int retCode = 0;

       4:     object retMessage = null;

       5:  

       6:     WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";

       7:  

       8:     try

       9:     {         

      10:         //

      11:         // Procesar y obtener objeto obj1

      12:         //

      13:  

      14:         retMessage = new PageResponseJson<Entidad1Json>()

      15:         {

      16:             Pagina = 1,

      17:             PaginasTotales = obj1.TotalPages,

      18:             RegistrosTotales = obj1.TotalRecords,

      19:             Listado = obj1.Listado

      20:         };

      21:  

      22:     }

      23:     catch (Exception ex)

      24:     {

      25:         ExceptionHelper.HandleException(ex);

      26:  

      27:         retCode = -1;

      28:         retMessage = ex.Message;

      29:     }

      30:  

      31:     var result = new

      32:     {

      33:         Error = retCode,

      34:         Mensaje = retMessage

      35:     };

      36:  

      37:     return new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result)));

      38: }

    Donde:

    • PageResponseJson, es un entidad Json para el retorno de listados paginados,
    • ExceptionHelper, es simplemente una clase para el tratamiento de excepciones con Entlib.

    Nota: El hecho de retornar un “Stream”, se debe a que, al devolver un objeto que puede ser de dos tipos, la serialización/deserialización falla debido a que la exposición de un servicio WCF como REST no serializa debidamente el string JSON resultante. Bien generando el string Json con caracteres especiales “” o bien, no realizándose la deserialización al tipo a retornar de manera correcta o incluso no funcionando. Otra solución, pasaría por crear el conversor adecuado, sin embargo, considero más eficiente retornar el Stream evitando así una conversión adicional.

    Con todo lo anterior en mente, nuestros servicios WCF serán servicios REST/JSON pudiendo ser consumidos 100% como tales cumpliendo además todas las especificaciones al respecto.

    Recodemos también que, Fiddler, va a permitirnos asegurar que nuestros JSON en las peticiones HTTP son totalmente correctos y se adaptan a las especificaciones requeridas.

    ¡Ahora si que dará que pensar: WCF as REST o WebAPI! ¿Tienes un departamento que controla 100% de WCF y/o WebApi, no quieres ser usada? ¿Por qué solución optarías? Si aún te quedan dudas, echa un vistazo a este link de MSDN: http://msdn.microsoft.com/en-us/library/jj823172.aspx, o incluso a este otro si tienes ganas de saber más sobre todo este tema: http://www.codeproject.com/Articles/341414/WCF-or-ASP-NET-Web-APIs-My-two-cents-on-the-subjec.

     

    Feliz verano/vacaciones a Tod@s
    Saludos @Higuera la Real
    JuanluElGuerre

  • Controlando el Clúster de HDInsight (Hadoop) programáticamente y ahorrando costes: Con C# !!!

    imageMuy buenas,

    Después de varios posts en los que hemos estado hablando de HDInsight (Hadoop de Microsoft), aun a pesar de quedarnos pendientes muchos puntos sobre los que hablar, en esta ocasión me gustaría profundizar en la gestión automática de HDInsight.

     

    La importancia de este tema radica principalmente en los siguientes puntos:

    • El coste de HDInsight, que dependerá del tiempo que tendremos activas las máquinas de Windows Azure. Recordemos que los nodos necesarios para trabajar con el clúster de HDInsight son como mínimo 2 máquinas virtuales: Una como “Head Node” A4  [L] (8 x 1.6GHz CPU, 14GB RAM, 2,040GB Storage) y otra como “Compute Node” A3 [XL] (4 x 1.6GHz CPU, 7GB RAM, 1,000GB Storage).
    • Ejecución temporal de Jobs

    Teniendo en cuenta estos puntos podemos:

    • Hacer un mayor aprovechamiento de HDInsight
    • Conseguir una reducción de los costes o, incluso,
    • Por el mismo coste, obtener una mayor velocidad de proceso, aprovisionando más máquinas durante el menor tiempo posible.

    Los pasos para conseguirlo programáticamente con C#:

    1) Crear un certificado e instalarlo en local con el fin de poder hacer el despliegue automático:

    "C:Program Files (x86)Microsoft SDKsWindowsv7.1ABinmakecert" -sky exchange -r -n "CN=WAHDInsight" -pe -a sha1 -len 2048 -ss My "WAHDInsight.cer"

     

    2) Crear un proyecto de tipo consola.

    3) Añadir al proyecto las referencias “Microsoft .NET API For Windows Azure HDInsight Cluster Management”  (versión actual: 0.8.4903.27316).

    4) Incluir el siguiente código:

       1: static void Main(string[] args)

       2: {

       3:     // Get certificate

       4:     var store = new X509Store();

       5:     store.Open(OpenFlags.ReadOnly);

       6:     var cert = store.Certificates.Cast<X509Certificate2>().First(

       7:         item => item.Thumbprint.Equals(Subscription.Thumbprint, StringComparison.InvariantCultureIgnoreCase));

       8:  

       9:  

      10:     // Create client object to connect to Azure

      11:     var client = new ClusterProvisioningClient(Subscription.SubscriptionId, cert);

      12:  

      13:  

      14:     // Specify details of the cluster to create

      15:     var clusterInfo = new HDInsightClusterCreationDetails();

      16:     clusterInfo.Name = Cluster.Name;

      17:     clusterInfo.Location = Cluster.Location;

      18:     clusterInfo.DefaultStorageAccountName = Subscription.StorageAccountName;

      19:     clusterInfo.DefaultStorageAccountKey = Subscription.StorageAccountKey;

      20:     clusterInfo.DefaultStorageContainer = Subscription.StorageContainer;

      21:     clusterInfo.UserName = Subscription.UserName;

      22:     clusterInfo.Password = Subscription.Password;

      23:     clusterInfo.ClusterSizeInNodes = Cluster.Size;

      24:             

      25:     var cluster = client.CreateCluster(clusterInfo);

      26:     Console.WriteLine("Created cluster: {0}", cluster.ConnectionUrl);

      27: }

    5) Crear un fichero de configuración “App.config” e incluir los siguientes parámetros:

       1: <appSettings>

       2:   <add key="SubscriptionId" value="###SUBSCRIPTION_ID###"/>

       3:   <add key="Thumbprint" value="‎###CERTIFICATE_THUMBPRINT###"/>

       4:  

       5:   <add key="StorageAccountName" value="###AZURE_STORAGE_NAME###.blob.core.windows.net"/>

       6:   <add key="StorageAccountKey" value="###AZURE_STORAGE_KEY###"/>

       7:   <add key="StorageContainer" value="###AZURE_STORAGE_CONTAINER###"/>

       8:  

       9:   <add key="UserName" value="admin"/>

      10:   <add key="Password" value="###PASSWORD_CLUSTER_HDINSIGHT###"/>

      11: </appSettings>

    6) Tras unos minutos, el Cluster estará creado y podremos trabajar con él tal y como hemos visto en post anteriores.

    image

    image

    7) Finalizada las tareas a realizar con el Cluster, borramos el cluster para evitar el consumo innecesario de €€€ por tener activas las Maquinas virtuales que componen el Cluster.

    Nota: Recordemos que se factura por hora de VM aunque estas no estén trabajando.

       1: // Get certificate

       2: var store = new X509Store();

       3: store.Open(OpenFlags.ReadOnly);

       4: var cert = store.Certificates.Cast<;X509Certificate2>().First(

       5:     item =>; item.Thumbprint.Equals(Subscription.Thumbprint, StringComparison.InvariantCultureIgnoreCase));

       6:  

       7:  

       8: // Create client object to connect to Azure

       9: var client = new ClusterProvisioningClient(Subscription.SubscriptionId, cert);

      10: 

      11: // Remove cluster "mycluster"

      12: client.DeleteCluster(Cluster.Name);

     

    Un ejemplo completo del código podéis encontrarlo aquí.

    Después de esto, nadie puede decir que Windows Azure no es Auto-Scalable, Guiño

     

    Continuaremos programando para HDInsight…

     

    Saludos and happy HDInsight programming  !

    Juanlu, ElGuerre

    HDInsight: Afinando y ajustando tuercas en la nueva versión. El BIGDATA de mis Twitts!

    image

    Muy buenas,

    Antes de nada gracias a todos aquellos a los que os gustan mis posts  y que esperáis que siga con ellos. Sinceramente, esto me anima a continuar y seguir al pie del cañón. En segundo lugar, perdonad por la espera, pero, hay veces que las situaciones personales/profesionales requieren una dedicación mayor de la habitual. ¡¡¡ Gracias a todos por la paciencia y los ánimos !!!

    ¡Ahora al turrón!. !Recordad que estuvimos viendo el funcionamiento de algunos componentes de Hadoop (HDInsight):

  • Hadoop
  • HiveQL
  • Hive desde Excel, Power Pivot y Power View
  • Sqoop I
  • Sqoop II
  • Mahout
  •  

    Durante esta semana he retomado el tema y he estado siguiendo algunos ejemplos para ver donde continuar sacando partido a BIGDATA. La nueva versión (0.9 en Azure) que incluye todo esto, y la inclusión de HDInsight en el portal de Azure, hay que probarla. Pues bien:

    1) En el portal de Windows Azure, ya tenemos la opción integrada:

    image

    2) La configuración del driver de ODBC y configuración con el Storage de Azure han desaparecido y ahora son trasparentes y “Out of the box”.

    3) El AddIn de Excel para Hive, también es diferente. Hemos perdido el Ribbon, y el acceso pasa a realizarse siguiendo estos pasos:

    4) Configuramos el ODBC con siempre, esto no ha cambiado. A excepción de que el dominio para HDInsight si, ahora es “.azurehdinsight.net”:

    image

    5) Desde Excel:

    imageimageimage

    Nota: Aquí podemos ver el detalle completo de la configuración.

     

    Con estos cambios presentes, ya podemos continuar trabajando.

    Como en ocasiones anteriores, volveré a partir de un ejemplo, en este caso de este, donde podemos ver como capturar información e Twitter y explotarla hasta límites impensables dentro del mundo profesional y concretamente en campañas de marketing, entre otros.

     

    Recordemos también que un fichero puede subirse a HDFS además del “Copy & Paste” como se muestra en el ejemplo, de esta otra manera:

    image

    Tras la ejecución del ejemplo, tendremos una tabla en Hive (“twitter_temp”) con todos los campos de Twitter para el usuario indicado, “juanluelguerre”, en mi caso, y con una muestra de datos equivalente al tiempo que hayamos dejado a “Curl” obteniendo información según este código:

       1: curl -d @twitter_parameters.txt -k https://stream.twitter.com/1/statuses/filter.json -u user:{password} >>twitter_stream_seq2.txt

     

    Una vez completados todos estos pasos sólo necesitamos explotar los datos en PowerView y obtener información similar a la siguiente, donde podemos ver la procedencia de algunos de mis seguidores por “Time Zone” y “User Location”:

    image

    Hasta aquí un pequeño repaso sobre hasta donde llegar con HDInsight.

    Algunas notas interesantes:

    • HDFS se apoya 100% en Azure BLOBS Storage. A este respecto aunque existen ventajas, también es cierto que pueden ocurrir penalizaciones/retardos en algunos casos, por lo que tendremos que tener esto presente en todo momento. Mas detalle aquí.
    • Ahora .NET, ya no es un wrapper de Java, hasta donde he podido averiguar. Sin embargo, si la implementación de un Map/Reduce es muy compleja, la recomendación sigue siendo hacer uso de JAVA !!
    • Cuando hablamos de HDInsight, debemos saber que existe una compañía como es Hortonworks junto con Microsoft se está encargando de conseguir lo mejor de esta plataforma (HDInsight).
    • La versión OnPremise de HDInsight es menor que la de Windows Azure, pero en breve ambas estarán a la par, es decir, la versión 1.0, donde podremos ver grandes mejoras, ¡Veremos que nos deparan las mismas!

    Gracias a todos nuevamente !!!

    Saludos @Home
    Juanlu, ElGuerre

    Nueva Build para Windows 7.8, “7.10.8862.144” !

    image

     

    Muy buenas,

    Microsoft finalmente lanza el Roll out de su nueva versión del sistema operativo Windows 7.8, unos 9 meses más tarde de lo previsto. ¡El esfuerzo y el salto es grande, merece la pena la espera, aunque cueste un poco…!

    Si alguno habéis tenido la oportunidad de disfrutar ya de el, sabréis que es espectacular la nueva experiencia. Si por el contrario aún no os ha llegado el update automático, no olvidéis que tenéis la oportunidad igualmente para hacerlo de manera manual, aquí os dejo mis pasos para la Build 7.10.8858.136. Si, para la nueva, 8862.144, no es posible.

    Tanto si lo hicisteis de manera manual, como si no, la cuestión es que aquí tenemos una dos nuevas builds, la 7.10.8860.142 y 7.10.8862.144 que se actualizarán automáticamente una tras la otra, y si, al ser automáticas, sin necesidad de perder los datos gracias al backup correspondiente, Guiño

     

    Nota: “Lo que me sorprende, es que estas dos recientes nuevas build no estén disponibles para Navifirm”.

     

    Saludos @Home
    Juanlu

    Dispositivos móviles consumiendo .NET WCF REST: Android !!!

    untitledMuy buenas,

    En esta ocasión y continuando con el mundo Java, veamos como nuestros actuales servicios WCF de .NET pueden ser expuestos como REST (Representational State Transfer) además de permanecer dando funcionalidad SOAP como hasta ahora. 

    Actualmente ya existen numerosos artículos que hablan al respecto así que nos centraremos concretamente en las adaptaciones de nuestros servicios WCF .NET y la invocación correcta desde el Emulador de Android y Eclipse.

    Cada vez más los dispositivos móviles son parte de cada minuto en nuestra vida. Toda compañía quiere acercar más su negocio a cada individuo e involucrarse en esta nueva era. REST tiene la particularidad adicional de poder ser consumido fácilmente por cualquiera de ellos de manera estándar minimizando impactos en el ancho de banda. Además, HTML 5 también está adquiriendo gran importancia y gracias a estos servicios REST, JQuery aporta la comunicación más adecuada con nuestros servicios.

    Web API, es la recomendación de Microsoft al la creación de servicios REST desde cero. No obstante, si queremos dar a nuestros clientes la oportunidad de no tener que realizar una gran inversión en la creación de nuevos servicios veamos cuales serían nuestros pasos:

    Creando Servicio WCF REST .NET

    1) Creamos nuestro servicio WCF desde Visual Studio .NET, a partir de un proyecto de tipo “WCF Service Application” e indicamos como nombre “WcfService1”:

    image

    2) Modificamos el Service Contract para incluir las siguientes instrucción como puede verse a continuación:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        [WebGet(
            UriTemplate = "GetData/{value}",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        string GetData(string value);
    
        [OperationContract]
        [WebInvoke(
            Method = "POST",
            UriTemplate = "GetDataUsingDataContract",
            BodyStyle = WebMessageBodyStyle.Bare, /* Importante. Usar "Bare" */
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        CompositeType GetDataUsingDataContract(CompositeType composite);
    }

    3) Ejecutamos el servicio a fin de comprobar que todo esta correcto. “Aunque este mensaje puede indicarnos que se trata de un error, no es así”.

    image

    Instalando el plug-in de Android en Eclipse

    4) En Eclipse, desde el menú: “Help – Install New Software…” e indicamos “https://dl-ssl.google.com/android/eclipse/” en el campo “Work with”:

    imageimage

    5) Accedemos a la carpeta “C:Users<USUARIO>android-sdks” donde encontraremos el SDK de Android instalado.

    6) Ejecutamos  “SDK Manager.exe”, marcamos “Android 4.2 (API 17)” e instalamos los 7 paquetes. (“En mi caso, como muestra la figura, se encuentran instados”):

    image

    7) Una vez instalado,  configuramos cualquiera de los emuladores de Android, por ejemplo, el de “Nexus One”:

    Ejecutamos “AVD Manager.exe” (Android Virtual Device)

    imageimage

    8 Acceso al servicio desde .NET 4.0

    8.1) Para la serialización/deserialización JSON optaremos por “Newtonsoft.Json.dll” (http://james.newtonking.com/pages/json-net.aspx).

    8.2) A continuación completamos el código de nuestras peticiones GET y POST con el siguiente código:

    Petición GET

       1: string strDataRest = String.Empty;

       2:  

       3: WebRequest request = WebRequest.Create(REST_URL_GET);

       4: using (WebResponse ws = request.GetResponse())

       5: {

       6:     using (StreamReader sr = new StreamReader(ws.GetResponseStream()))

       7:     {

       8:         string strJson = sr.ReadToEnd();

       9:         strDataRest = JsonConvert.DeserializeObject<string>(strJson);

      10:     }

      11: }

    Petición POST

       1: CompositeType newCompositeData = new CompositeType() { BoolValue = true, StringValue = "ABC" };

       2: CompositeType compositeDataRest = null;

       3:  

       4: string jsonString = JsonConvert.SerializeObject(newCompositeData);

       5: byte[] requestBytes = ASCIIEncoding.UTF8.GetBytes(jsonString);

       6:                

       7: HttpWebRequest reqPost = WebRequest.Create(REST_URL_POST) as HttpWebRequest;

       8: reqPost.Method = "POST";

       9: reqPost.Accept = "application/json";

      10: reqPost.ContentType = "application/json";

      11: reqPost.ContentLength = requestBytes.Length;

      12: reqPost.Headers.Add("json", jsonString);

      13:                 

      14:  

      15: using (Stream postStream = reqPost.GetRequestStream())

      16: {

      17:     postStream.Write(requestBytes, 0, requestBytes.Length);

      18: }

      19:  

      20: using (HttpWebResponse response = reqPost.GetResponse() as HttpWebResponse)

      21: {

      22:     if (response.StatusCode != HttpStatusCode.OK)

      23:         throw new Exception(String.Format(

      24:             "Server error (HTTP {0}: {1}).",

      25:             response.StatusCode,

      26:             response.StatusDescription));

      27:  

      28:     using (StreamReader sr = new StreamReader(response.GetResponseStream()))

      29:     {

      30:         string strJson = sr.ReadToEnd();

      31:         compositeDataRest = JsonConvert.DeserializeObject<CompositeType>(strJson);

      32:     }

      33: }

     9 Acceso al servicio desde Android

    En primer lugar crearemos un proyecto de tipo “Android Application Project” e indicaremos por ejemplo, el nombre “MyAndroidTest”:

    imageimage

    9.1) Una vez creado el proyecto arrastramos al formulario de Android una caja de texto como entrada de datos, dos botones para las peticiones GET y POST y un “TextView” para mostrar los resultados:

    image

    9.2) Editamos el fichero “activity_main.xml” y vinculamos a los eventos click los métodos que vamos a utilizar; uno para una petición GET y otro para una POST: “GetClick” y “PostClick” respectivamente:

       1: <Button

       2:      android:id="@+id/button1"

       3:      android:layout_width="wrap_content"

       4:      android:layout_height="wrap_content"

       5:      android:layout_alignRight="@+id/editText1"

       6:      android:layout_below="@+id/editText1"

       7:      android:layout_marginTop="26dp"

       8:      android:text="GET"

       9:      android:onClick="GetClick" />

      10:  

      11:  <Button

      12:      android:id="@+id/button2"

      13:      android:layout_width="wrap_content"

      14:      android:layout_height="wrap_content"

      15:      android:layout_above="@+id/textView1"

      16:      android:layout_alignRight="@+id/button1"

      17:      android:layout_marginBottom="14dp"

      18:      android:text="POST"

      19:      android:onClick="PostClick" />

    9.3) Añadimos al fichero “AndroidManifest.xml” la instrucción “<uses-permission android:name="android.permission.INTERNET" />”. De no ser así, obtendremos este error: “Request Failed: org.apache.http.conn.HttpHostConnectException:Connection to http://10.0.2.2:<PUERTO> refused”.

    9.4) Tenemos que tener en cuenta, que en lugar de utilizar URL del tipo: http://localhost o http://127.0.0.1, tendremos que utilizar en su lugar esta otra: http://10.0.2.2.

    9.5) Modificamos el método “onCreate” de nuestra clase “MainActivity.java” para inicializar el uso de los botones y la caja de texto además de incluir una política para poder trabajar con la Interfaz de Android de manera “No responsiva”. En caso contrario obtendremos el error “Request Failed: Android.Os.NetWorkOnMainTreadExeption”. Aunque no es una buena práctica, para este ejemplo es suficiente y más aun, no siendo un experto en java Risa!

       1: @TargetApi(Build.VERSION_CODES.GINGERBREAD)

       2:     @Override

       3:     protected void onCreate(Bundle savedInstanceState) {

       4:         super.onCreate(savedInstanceState);    

       5:         setContentView(R.layout.activity_main);

       6:     

       7:            if (android.os.Build.VERSION.SDK_INT > 9) {

       8:                   StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

       9:                   StrictMode.setThreadPolicy(policy);

      10:                 }

      11:  

      12:         

      13:         label = (TextView)findViewById(R.id.textView1);

      14:         text = (EditText)findViewById(R.id.editText1);

      15:         

      16:         buttonGet = (Button)findViewById(R.id.button1);

      17:         buttonPost = (Button)findViewById(R.id.button2);        

      18:     }

    9.6) Incluimos también el la clase “MainActivity.java” los dós métodos equivalentes a las peticiones GET y POST:

    Petición GET:

       1: public void GetClick(View button) {

       2:     try {        

       3:         label.setText("Pulsado GET...");

       4:         

       5:         DefaultHttpClient httpClient = new DefaultHttpClient();

       6:         HttpGet request = new HttpGet(REST_URL_GET + text.getText());

       7:  

       8:         request.setHeader("Accept", "application/json");

       9:         request.setHeader("Content-type", "application/json");

      10:  

      11:         HttpResponse response = httpClient.execute(request);

      12:  

      13:         HttpEntity responseEntity = response.getEntity();

      14:  

      15:         // Read response data into buffer

      16:         char[] buffer = new char[(int)responseEntity.getContentLength()];

      17:         InputStream stream = responseEntity.getContent();

      18:         InputStreamReader reader = new InputStreamReader(stream);

      19:         reader.read(buffer);

      20:         stream.close();

      21:  

      22:         String str = new String(buffer);

      23:              

      24:         label.setText(str);     

      25:         

      26:     } catch (Throwable t) {

      27:          Toast.makeText(this, "Request failed: " + t.toString(), Toast.LENGTH_LONG).show();            

      28:     }

      29: }

    Petición POST:

       1: public void PostClick(View button) {

       2:  

       3:    try {

       4:    

       5:         label.setText("Pulsado POST...");

       6:                       

       7:         JSONObject json = new JSONObject();

       8:         json.put("BoolValue", true);               

       9:         json.put("StringValue", text.getText());            

      10:  

      11:         HttpParams httpParams = new BasicHttpParams();

      12:         HttpConnectionParams.setConnectionTimeout(httpParams, 30000);

      13:         HttpConnectionParams.setSoTimeout(httpParams, 30000);

      14:         HttpClient client = new DefaultHttpClient(httpParams);

      15:   

      16:         HttpPost request = new HttpPost(REST_URL_POST);

      17:            

      18:         ByteArrayEntity entity = new ByteArrayEntity(json.toString().getBytes("UTF-8"));

      19:         entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));

      20:         entity.setContentType("application/json");           

      21:         request.setEntity(entity);

      22:         

      23:         HttpResponse response = client.execute(request);

      24:         HttpEntity entityReturn = response.getEntity();

      25:         InputStream instream = entityReturn.getContent();

      26:         String str = convertStreamToString(instream);                

      27:         

      28:         JSONObject myAwway = new JSONObject(str);

      29:         Boolean a = myAwway.getBoolean("BoolValue");

      30:         String b = myAwway.getString("StringValue");

      31:         

      32:         label.setText("BoolValue: " + Boolean.toString(a) + " StringValue: " + b);

      33:        } catch (Throwable t) {

      34:        Toast.makeText(this, "Request failed: " + t.toString(), Toast.LENGTH_LONG).show();

      35:        }

      36: }    

    10) Una vez que todo esta completado, nos aseguramos que los servicios .NET se encuentran levantados y ejecutamos la aplicación  Android desde Eclipse:

    image

    11) Llegado este punto, ya podremos decir a nuestros clientes que sus servicios están siendo consumidos por aplicaciones móviles. WCF puede ser expuesto como WCF REST con tan sólo unas pocas líneas de código, seguro que nuestros clientes estarán contentos.

    Acceso al servicio desde .NET 4.5

    Adicionalmente, y aunque es muy probable que muchos clientes no hayan adoptado aún .NET Framework 4.5, me ha parecido buena idea incluir un ejemplo  al respecto además de una manera mucho mas “responsiva”:

    Petición Get:

       1: HttpClient client = new HttpClient();

       2:  

       3: string strDataRest = String.Empty;

       4:  

       5: client.GetAsync(REST_URL_GET).ContinueWith((requestTask) =>

       6: {

       7:     HttpResponseMessage response = requestTask.Result;

       8:     response.EnsureSuccessStatusCode();

       9:     response.Content.ReadAsStringAsync().ContinueWith((readTask) =>

      10:         {

      11:             strDataRest = JsonConvert.DeserializeObject<string>(readTask.Result);

      12:         });

      13: });

    Petición Post:

       1: CompositeType compositeDataRest = new CompositeType();

       2: CompositeType newCompositeData = new CompositeType() { BoolValue = true, StringValue = "Test...123..." };

       3:  

       4: StringContent content = new StringContent(JsonConvert.SerializeObject(newCompositeData));                

       5: content.Headers.ContentType = new MediaTypeHeaderValue("application/json");                               

       6:  

       7: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

       8:  

       9: client.PostAsync(REST_URL_POST, content).ContinueWith((requestTask) =>

      10:     {

      11:         HttpResponseMessage response = requestTask.Result;

      12:         response.EnsureSuccessStatusCode();

      13:                        

      14:         response.Content.ReadAsStringAsync().ContinueWith((readTask) =>

      15:             {

      16:                 string strJson = readTask.Result;

      17:                 compositeDataRest = JsonConvert.DeserializeObject<CompositeType>(strJson);                                

      18:             });

      19:  

      20:         requestTask.Wait();

      21:     });

    Nota: Ni que decir tiene que nuestros servicios podrán pasar a formar parte de “Windows Azure”, permitiendo así un mayor crecimiento y una gran escalabilidad en caso de que el negocio de nuestros clientes comience a crecer imprevisiblemente.

    Espero, al igual que siempre haber estado a la altura y que sea de utilidad.

    Saludos & Happy Coding

    Juanlu, ElGuerre

    Java + Eclipse & Apache Tomcat en Windows Azure. ¡Buceando de principio a fin…!

    WindowsAzure_And_JavaMuy buenas,

    Cada día son más los clientes que me preguntan sobre cómo llevar sus aplicaciones Java a la Nube y, aunque conozco la teoría, me he querido enfrentar directamente al problema indagando en todo detalle para evitar así imprevistos de los que suelen surgir en el último momento. Además, creo que puede ayudarnos a estimar mejor y más claramente nuestro esfuerzo a la hora de comenzar una migración o un nuevo desarrollo en Windows Azure. De esta manera, el cliente, tendrá mucho más claro a que se enfrenta y cómo abordar este primer paso.

    El tema no es para nada complicado, si bien es cierto, para los que pertenecemos al mundo Microsoft, puede resultar algo más “engorroso” de a lo que estamos acostumbrados. Es más, podemos llegar a desentendernos del tema por no tratarse de .NET y dejar al cliente sin su solución, sin embargo, se sigue tratando de una solución a un problema que Windows Azure si puede resolver y con bastante facilidad. ¡No debemos perder de vista este objetivo!

    Aunque el IDE de Visual Studio .NET, proporciona grandes avances e infinidad de facilidades con respecto a cualquier otro IDE, para Java, Eclipse, está cogiendo carrera en esa misma dirección y, llega a hacer tan fácil la labor de desarrollo y despliegue en Windows Azure que no le tiene nada que envidiar.

    ¡Si aún no lo has intentado, esta es tu oportunidad y también el momento!

    Veamos los pasos a seguir partiendo totalmente desde cero:

    REQUISITOS PREVIOS Y PREPARACIÓN DEL ENTORNO DE DESARROLLO

    1) Instalamos el SDK de Java desde aquí:

    image

    2) Instalamos Eclipse desde aquí u optamos directamente por “Eclipse IDE for Java EE Developers”.

    3) Una vez instado Eclipse, instalamos el plugin para Windows Azure siguiendo los siguientes pasos e incluyendo este enlace: http://dl.msopentech.com/eclipse, en el campo Work with:”

    imageimage

    4) Una vez instalado el Plug-In chequeamos las actualizaciones para estar seguro de que tenemos la última:

    imageimage

    5) A continuación instalamos “Apache Tomcat” desde aquí: http://tomcat.apache.org/download-70.cgi.

    Importante: El instalable (.exe) para Windows no funciona.  Después de tanto probar y Googlear en distintos foros Java, a este instalable le falta algo. En el último momento durante la ejecución siempre se produce el error: “Windows cannot find ‘startup.bat’. Make sure you typed the name correctly, and then try again”.

    Así que, descargamos el (.ZIP) de 32bit o 64bits y lo descomprimimos por ejemplo en “C:Program FilesTomcat7

    6) Establecemos y actualizamos las siguientes variables de entorno para asegurar que nuestro entorno Java funciona todo correctamente.

    • Creamos estas dos:
      • JAVA_HOME=C:Program FilesJavajdk1.7.0_09
      • CATALINA_HOME=C:Program FilesTomcat 7.0
    • Actualizamos esta otra (PATH), añadiendo los valores de acuerdo a las variables anteriores:
      • PATH=<…>;%JAVA_HOME%bin;%CATALINA_HOME%bin

    DESARROLLO

    7) Desde Eclipse, creamos un proyecto “File > New > “Dinamic Web Project” con el nombre “MyJavaApp1” y pulsamos “Finish

    image

    8) Añadimos un fichero JSP de prueba, “index.jsp” (a partir de la plantilla html), dentro de la carpeta “WebContent”:

    imageimage

    9) Añadimos a este fichero, a modo de ejemplo, en el tag <body> la instrucción “<b><% out.println("My first Java application to run on Azure!"); %></b>”.

    10) Probamos que nuestra aplicación funciona correctamente, pulsando sobre el icono de “play” (image) y siguiendo los siguientes pasos:

    image imageimage

    11) Creamos el proyecto de despliegue en Windows Azure.  Aunque podemos hacerlos desde la opción de menú: “File > New > Windows Azure Deployment Project”, lo haremos más rápido haciendo “click derecho” sobre el proyecto Java “MyJavaApp1” y ”Windows Azure > Package for Windows Azure….” e introduciendo el nombre “MyJavaApp1Azure”. Seguidamente indicamos la información de nuestro JDK de Java y Servidor Tomcat y “Finish”.

    imageimageimage

    12) Una vez tenemos nuestro desarrollo listo,  aseguramos que nuestro servidor de Tomcat se ejecuta correctamente ejecutando nuestro proyecto web:

    imageimage

    Nota: Si se produce el error: “Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.”, revisaremos que los puertos 8080 y 8009, no están siendo usados. Para ello podemos ejecutar el comando “netstat -n -f -a -o” desde la línea de comandos y obtener el PID del proceso que esté usando dicho puerto y a continuación actuar en consecuencia. Para esta prueba, “maté” el proceso que lo utilizaba sin más.

    EJECUCIÓN EN EL EMULADOR LOCAL

    13) Al fin, ejecutamos Eclipse. En este punto es necesario, sin duda, tener instalado el Emulador de Windows Azure que podemos descargar desde la página de descargas de Windows Azure (SDK Only), o directamente desde aquí.

    imageimage

    DESPLIEGUE Y EJECUCIÓN EN WINDOWS AZURE

    14) Desplegamos/Publicamos el proyecto directamente en Azure:

    • En la barra de tareas de Azure seleccionamos: “Publish to Windows Azure Cloud”.
    • Download…”. Descargamos el fichero “.publishsettings” con la información de nuestra subscripción de Azure.”
    • Import…” Importamos el fichero descargado.
    • Next >”. Configuramos la venta de “Remote Desktop Settings”.
    • Publish” Esperamos a que finalice el despliegue.  Recordemos que  ANT, es la herramienta de empaquetado, configuración y preparación del entorno de despliegue, tanto en el Emulador como en el propio Azure.

    imageimage

    Nota: ¡Aunque he dejado el password del certificado por defecto, Password1, las credenciales de conexión remota las he modificado a mi antojo!.

    Tengamos en cuenta también, que la configuración anterior, puede ser modificada en cualquier momento, haciendo uso de las propiedades del proyecto de Azure (“MyJavaApp1Azure”):

    image

    Durante el despliegue puede ocurrir que no se llegue a realizar. ¿Por qué? Pues muy buena pregunta, porque al menos yo no he encontrado ningún fichero de log en el que mirar.

    image

    Si el error persiste, hacemos el despliegue manual (a partir de los ficheros “.cspkg” y “.cscfg” generados desde la opción “Build Cloud Package for Windows Azure”) desde el portal de Windows Azure, bien desde local o bien desde el storage, donde previamente habremos realizado la subida, por ejemplo con “Azure Storage Explorer”.

    image

    15) Esperamos a que finalice el proceso de subida de los ficheros al Storage de Azure y el despliegue, y tras ello, al acceder a la url “http://juanluelguerre.cloudapp.net/”, podremos ver como el Servidor Apache Tomcat ha sido instalado. Si a la misma añadimos la ruta de nuestra aplicación, “http://juanluelguerre.cloudapp.net/MyJavaApp1/index.jsp”, Voilá, tenemos nuestra aplicación Java ejecutándose en Azure !!!

    imageimage

    16) Si accedemos al portal, podremos ver nuestras instancias en ejecución. Y, además, podremos conectarnos vía terminal server con cada una de ellas.image image

    Por último, ¿Qué ocurre si el servidor Web en lugar de ser Tomcat es otro, ej.: Jetty, etc….? Aunque el automatismo no es exactamente igual, las posibilidades son las mismas. Aquí os dejo por el momento algunos enlaces relacionados con Jetty:

    Espero una vez más, haber despertado el interés y, por supuesto, que el post haya sido y sea de utilidad.

    Saludos and Happy Azure from Java.  FELIZ NAVIDAD A TOD@S
    Juanlu

    Nuevo Windows Phone 7.8 Update (“1750.0823.8858.12460”) ya está aquí !!!

    MyLumia800Muy buenas,

    Entre tanta espera y espera, y teniendo en cuenta que los rumores de la llegada del nuevo  Windows Phone 7.8 apuntaban a principios de 2013, no me esperaba, ni mucho menos, recibir anoche la noticia. Y si, es cierta, acabo de comprobarlo, ya tengo mi Nokia Lumia 800 actualizado a Windows Phone 7.8.

    Si quieres instalarlo y no esperar a que llegue la actualización de manera automática a tu dispositivo, estos son los pasos:

    1) Descargar e instalar NaviFirm Plus 1.7” en el PC.

    2) Descargar e instalar Nokia Care Suite 5.0 2012.45.5 en el PC.

    2) Desde NaviFirm, descargar el Software adecuado según el teléfono y según los pasos que pueden verse en la siguiente imagen. El primero dato a indicar es el modelo del Nokia.

     

    image

    3) En la carpeta  “C:ProgramDataNokiaPackagesProducts” crear un directorio con el nombre del modelo del teléfono,  en mi caso, “RM-801”.

    4) Copiar todo el contenido descargado directamente en la carpeta creada según el punto anterior, es decir, dentro de “RM-801”.

    5) Conectar el Teléfono al PC.

    6) Desde “Nokia Care Suite” acceder a “Multi Sofware Updater” y  comenzar con “Refurbish” . Es importante saber que esta acción eliminará cualquier dato almacenado en el dispositivo/teléfono, así que previamente será necesario hacer el backup correspondiente.

    image

    7) Esperar unos minutos y ya tendrás tu nuevo Windows Phone 7.8.

    Nota: Si tuvieras cualquier problema, con los drivers en Windows 8, o quizás cualquier otro derivado de estos simples pasos, echa un vistazo aquí.

    Saludos and happy new “WINDOWS PHONE 7.8”
    Juanlu

    Hadoop On Azure Mahout: Algoritmos de recomendación, Clasificaciones y Agrupaciones (Clustering)

    Hadoop-Mahout

    Una vez que ya conocemos los conceptos y componentes principales de Hadoop según vimos en los posts anteriores:

  • Hadoop
  • HiveQL
  • Hive desde Excel, Power Pivot y Power View
  • Sqoop I
  • Sqoop II

    Continuaremos probando más funcionalidad de y para Hadoop on Azure. En esta ocasíón Mahout.

    La verdad es que cuando la semana pasada me disponía a indagar sobre el tema de mahout, no me imaginaba que la Intenligencia Artifical estuviera tan cerca, durante la carrera pensaba que este tipo de algoritmos sólo eran teoría y estarían lejos de acercarse a una realidad, pero sinceramente, creo que me equivoqué

    ¿Que es Mahout?

    Mahout proviene del Hindi, mahaut, “montador de elefantes”, de ahí el logotipo.  La labor de un Mahout es permanecer desde joven vinculado a un elefante también joven a fin de que tanto jinete como elefante se conozcan mutuamente a lo largo de toda la vida. ¡Se podría sacar de aquí una peli, seguro,jeje…! Técnicamente hablando, Apache Mahout,es una máquina de aprendizaje cuyo objetivo consiste en construir bibliotecas escalables de aprendizaje automático.

    Los sistemas de recomendación son alguna de las aplicaciones de aprendizaje más conocida hoy día. Su objetivo es tratar de presentar al usuario items de información: películas, música, libros, noticias, páginas web, etc, sobre las que el usuario está interesado. Todo ello basado en su comportamiento previo. Estos algoritmos proporcionan una funcionalidad importante para muchos sitios de redes sociales , compras on-line, streaming, y otros sitios de Internet. Mahout ofrece un motor de recomendación out-of-the-box que es fácil de usar, tiene muchas características útiles, y es escalable en Hadoop.  Algunas de las aplicaciones más conocidas que utilizan estos algoritmos, son Pandora RadioNetflix o incluso la propia web de Amazon que ofrece/recomienda a sus clientes productos a partir de los ya comprados por otros.

    A continuación veremos las técnicas que A continuación veremos las técnicas que por ahora nos ofrece Hadoop On Azure, es decir, Clasificación y Agrupación (Clustering).

    1) Clasificación

    Partamos del propio ejemplo que nos presenta Hadoop o directamente desde aquí. Este ejemplo esta basado 100% en el ejemplo del website de Mahout el cual consiste en:

    “The 20 Newsgroups data set is a collection of approximately 20,000 newsgroup documents, partitioned (nearly) evenly across 20 different newsgroups. The 20 newsgroups collection has become a popular data set for experiments in text applications of machine learning techniques, such as text classification and text clustering. We will use Mahout Bayes Classifier to create a model that would classify a new document into one of the 20 newsgroup.”

    Para ello tal y como nos dice el tutorial y desde el Head Node al que accedemos remotamente:

    1) Descargamos del fichero de datos en la ruta ““C:appsdistmahout-0.5examplesbinwork”

    2) Descomprimimos el fichero “.tar.gz” en la capeta: “C:appsdistmahout-0.5examplesbinwork20news-bydate”

    3) Ejecutamos el “.cmd” c:appsdistmahout-0.5examplesbinbuild-20news-bayes.cmd”

    Nota: Aunque esperamos que funcione, resulta que no es así, parece que se trata de algún error de configuración en los scripts de “Mahout” para Azure on Hadoop o, incluso de alguna incompatibilidad de versiones entre los comandos Mahout y Hadoop.

    image

    Para solucionar el problema modificamos el script “build-20news-bayes.cmd” sustituyendo la llamada al comando Mahout por la llamada directa a hadoop (realmente es lo que hace internamente el comando Mahout), es decir,

    Sustituimos esto:

    call mahout.cmd org.apache.mahout.classifier.bayes.preparetwentynewsgroups ^
      -p %work_path%20news-bydate-train ^
      -o %work_path%bayes-train-input ^
      -a org.apache.mahout.vectorizer.defaultanalyzer ^
      -c utf-8

    call mahout.cmd org.apache.mahout.classifier.bayes.preparetwentynewsgroups ^
      -p %work_path%20news-bydate-test ^
      -o %work_path%bayes-test-input ^
      -a org.apache.mahout.vectorizer.defaultanalyzer ^
      -c utf-8

    por esto otro:

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^
    org.apache.mahout.driver.MahoutDriver ^

    org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^
    -p %WORK_PATH%20news-bydate-train ^
    -o %WORK_PATH%bayes-train-input ^
    -a org.apache.mahout.vectorizer.DefaultAnalyzer ^
    -c UTF-8

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^
    org.apache.mahout.driver.MahoutDriver ^

    org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^
      -p %WORK_PATH%20news-bydate-test ^
      -o %WORK_PATH%bayes-test-input ^
      -a org.apache.mahout.vectorizer.DefaultAnalyzer ^
      -c UTF-8

    De todas formas, os dejo el contenido completo del fichero “build-20news-bayes.cmd” con el problema solucionado.

    @echo off

    @rem

    @rem Licensed to the Apache Software Foundation (ASF) under one or more

    @rem contributor license agreements.  See the NOTICE file distributed with

    @rem this work for additional information regarding copyright ownership.

    @rem The ASF licenses this file to You under the Apache License, Version 2.0

    @rem (the "License"); you may not use this file except in compliance with

    @rem the License.  You may obtain a copy of the License at

    @rem

    @rem     http://www.apache.org/licenses/LICENSE-2.0

    @rem

    @rem Unless required by applicable law or agreed to in writing, software

    @rem distributed under the License is distributed on an "AS IS" BASIS,

    @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

    @rem See the License for the specific language governing permissions and

    @rem limitations under the License.

    @rem

     

    @rem

    @rem Downloads the 20newsgroups dataset, trains and tests a bayes classifier. 

    @rem

    @rem To run:  change into the mahout directory and type:

    @rem  examples/bin/build-20news.sh

     

    setlocal enabledelayedexpansion

     

    set SCRIPT_PATH=%~dp0

    set MAHOUT_BIN_PATH=%SCRIPT_PATH%....bin

    set WORK_PATH=%SCRIPT_PATH%work20news-bydate

    set HDFS_WORK_PATH=examples/bin/work/20news-bydate/

     

    if not exist %WORK_PATH% (

        mkdir %WORK_PATH%

    )

     

    if not exist %WORK_PATH%20news-bydate-train (

         goto :ErrorMessage

    )

     

    if not exist %WORK_PATH%20news-bydate-test (

         goto :ErrorMessage

    )

     

    pushd %MAHOUT_BIN_PATH%

     

    REM call mahout.cmd org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^

      REM -p %WORK_PATH%20news-bydate-train ^

      REM -o %WORK_PATH%bayes-train-input ^

      REM -a org.apache.mahout.vectorizer.DefaultAnalyzer ^

      REM -c UTF-8

     

    REM call mahout.cmd org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^

      REM -p %WORK_PATH%20news-bydate-test ^

      REM -o %WORK_PATH%bayes-test-input ^

      REM -a org.apache.mahout.vectorizer.DefaultAnalyzer ^

      REM -c UTF-8 

     

      

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^

    org.apache.mahout.driver.MahoutDriver ^

    org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^

    -p %WORK_PATH%20news-bydate-train ^

    -o %WORK_PATH%bayes-train-input ^

    -a org.apache.mahout.vectorizer.DefaultAnalyzer ^

    -c UTF-8

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^

    org.apache.mahout.driver.MahoutDriver ^

    org.apache.mahout.classifier.bayes.PrepareTwentyNewsgroups ^

      -p %WORK_PATH%20news-bydate-test ^

      -o %WORK_PATH%bayes-test-input ^

      -a org.apache.mahout.vectorizer.DefaultAnalyzer ^

      -c UTF-8 

      

    @rem mapreduce test method used on hadoop

    set TEST_METHOD="mapreduce"

     

    call hadoop.cmd dfs -rmr ^

          %HDFS_WORK_PATH%bayes-train-input 

     

    call hadoop.cmd dfs -rmr ^

          %HDFS_WORK_PATH%bayes-test-input

     

    call hadoop.cmd dfs -put ^

          %WORK_PATH%bayes-train-input ^

          %HDFS_WORK_PATH%bayes-train-input 

     

    call hadoop.cmd dfs -put ^

          %WORK_PATH%bayes-test-input ^

          %HDFS_WORK_PATH%bayes-test-input

     

          

    REM call mahout.cmd trainclassifier ^

      REM -i %HDFS_WORK_PATH%bayes-train-input ^

      REM -o %HDFS_WORK_PATH%bayes-model ^

      REM -type bayes ^

      REM -ng 1 ^

      REM -source hdfs

     

    REM call mahout.cmd testclassifier ^

      REM -m %HDFS_WORK_PATH%bayes-model ^

      REM -d %HDFS_WORK_PATH%bayes-test-input ^

      REM -type bayes ^

      REM -ng 1 ^

      REM -source hdfs ^

      REM -method %TEST_METHOD%

     

      

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^

    org.apache.mahout.driver.MahoutDriver ^

    trainclassifier ^

      -i %HDFS_WORK_PATH%bayes-train-input ^

      -o %HDFS_WORK_PATH%bayes-model ^

      -type bayes ^

      -ng 1 ^

      -source hdfs

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^

    org.apache.mahout.driver.MahoutDriver ^

    testclassifier ^

      -m %HDFS_WORK_PATH%bayes-model ^

      -d %HDFS_WORK_PATH%bayes-test-input ^

      -type bayes ^

      -ng 1 ^

      -source hdfs ^

      -method %TEST_METHOD%

     

      

    popd

     

    goto :eof

     

    :ErrorMessage

    echo Please download 20news-bydate.tar.gz from:

    echo http://people.csail.mit.edu/jrennie/20Newsgroups/20news-bydate.tar.gz

    echo and extract it under:

    echo %WORK_PATH%

    4) Solucionado el problema ahora si, el resultado esperado es la siguiente “Confusion Matrix”.  Esta misma matriz puede verse en el ejemplo de partida de Apache Mohout que indicábamos antes.

    image

    En este ejemplo, puede verse como funciona una “Confusion Matrix” y un poco de teoría.

    Adicionalmente, aquí tenemos otro ejemplo de clasificación (en este caso más concretamente, un ejemplo de algoritmo de recomendación).

    2) Agrupación(Clustering)

    Partimos nuevamente del propio ejemplo que nos presenta Hadoop o directamente desde aquí. Este ejemplo esta basado 100% en el ejemplo del website de Mahout el cual consiste en:

    “The example will demonstrate clustering of control charts which exhibits a time series. Control charts are tools used to determine whether or not a manufacturing or business process is in a state of statistical control. Such control charts are generated / simulated over equal time interval and available for use in UCI machine learning database. The data is described here .”

    Para ello y como nos dice el tutorial:

    1) Descargamos el fichero “http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data” y lo situamos en la carpeta “C:appsdistmahout-0.5examplesbinworkclustering”

    2) Ejecutamos “C:appsdistmahout-0.5examplesbinbuild-cluster-syntheticcontrol.cmd” y elegimos el tipo de “clustering”:image

    3) De la misma manera que para el ejemplo anterior, volvemos a tener problemas así que tenemos que hacer algunos ajustes:

  • Creamos un directorio “testdata” en HDFS desde la consola interactiva de Javascript ejecutando el comando ”#mkdir testdata
  • Modificamos el script (.cmd) “build-cluster-syntheticcontrol.cmd” siguiendo las misma pautas que para el ejemplo de clasificación,

    Sustituyendo esto:

    call mahout hadoop fs -rmr %HDFS_WORK_PATH%
    call mahout hadoop fs -mkdir %HDFS_WORK_PATH%
    call mahout hadoop fs -put %WORK_PATH%synthetic_control.data %HDFS_WORK_PATH%
    call mahout org.apache.mahout.clustering.syntheticcontrol.%_clustertype%.Job

    Por esto otro:

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^
       hadoop fs -rmr %HDFS_WORK_PATH%
    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^
       hadoop fs -mkdir %HDFS_WORK_PATH%
    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^
       hadoop fs -put %WORK_PATH%synthetic_control.data %HDFS_WORK_PATH%
    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^
       org.apache.mahout.clustering.syntheticcontrol.%_clustertype%.Job

     

    Nuevamente, aquí dejo el fichero “build-cluster-syntheticcontrol.cmd” con el problema ya solucionado.

    @echo off

     

    @rem

    @rem Licensed to the Apache Software Foundation (ASF) under one or more

    @rem contributor license agreements.  See the NOTICE file distributed with

    @rem this work for additional information regarding copyright ownership.

    @rem The ASF licenses this file to You under the Apache License, Version 2.0

    @rem (the "License"); you may not use this file except in compliance with

    @rem the License.  You may obtain a copy of the License at

    @rem

    @rem     http://www.apache.org/licenses/LICENSE-2.0

    @rem

    @rem Unless required by applicable law or agreed to in writing, software

    @rem distributed under the License is distributed on an "AS IS" BASIS,

    @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

    @rem See the License for the specific language governing permissions and

    @rem limitations under the License.

    @rem

     

    @rem

    @rem Downloads the Synthetic control dataset and prepares it for clustering

    @rem

    @rem To run:  change into the mahout directory and type:

    @rem  examples/bin/cluster-syntheticcontrol.sh

     

    setlocal enabledelayedexpansion

     

    set SCRIPT_PATH=%~dp0

    set MAHOUT_BIN_PATH=%SCRIPT_PATH%....bin

    set WORK_PATH=%SCRIPT_PATH%workclustering

    set HDFS_WORK_PATH=testdata

     

    if not exist %WORK_PATH% (

        mkdir %WORK_PATH%

    )

     

    if not exist %WORK_PATH%synthetic_control.data (

        echo Please downloading Synthetic control data from:

        echo http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data

        echo and put it under:

        echo %WORK_PATH%synthetic_control.data

        goto :end

    )

     

    :main

    echo "Please select a number to choose the corresponding clustering algorithm"

    echo "1. canopy clustering"

    echo "2. kmeans clustering"

    echo "3. fuzzykmeans clustering"

    echo "4. dirichlet clustering"

    echo "5. meanshift clustering"

    set /p _choice="Enter your choice:"

      

    @rem set _algorithms=canopy kmeans fuzzykmeans dirichlet meanshift

    if [%_choice%] == [1] (

        set _clustertype=canopy

    ) else (

        if [%_choice%] == [2] (

            set _clustertype=kmeans

        ) else (

            if [%_choice%] == [3] (

                set _clustertype=fuzzykmeans

            ) else (

                if [%_choice%] == [4] (

                    set _clustertype=dirichlet

                ) else (

                    if [%_choice%] == [5] (

                        set _clustertype=meanshift

                    ) else (

                        echo "Invalid choice %_choice%. Please try again"

                        goto :end

    )))))

    echo "ok. You chose %_choice% and we'll use %_clustertype% Clustering"

     

    pushd %MAHOUT_BIN_PATH%

     

    echo "DFS is healthy... "

    echo "Uploading Synthetic control data to HDFS"

     

    @REM call mahout hadoop fs -rmr %HDFS_WORK_PATH%

    @REM call mahout hadoop fs -mkdir %HDFS_WORK_PATH%

    @REM call mahout hadoop fs -put %WORK_PATH%synthetic_control.data %HDFS_WORK_PATH%

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^

    hadoop fs -rmr %HDFS_WORK_PATH%

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^

    hadoop fs -mkdir %HDFS_WORK_PATH%

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^

    hadoop fs -put %WORK_PATH%synthetic_control.data %HDFS_WORK_PATH%

     

    echo "Successfully Uploaded Synthetic control data to HDFS "

     

    @REM call mahout org.apache.mahout.clustering.syntheticcontrol.%_clustertype%.Job

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar org.apache.mahout.driver.MahoutDriver ^

    org.apache.mahout.clustering.syntheticcontrol.%_clustertype%.Job

     

    :end

    popd

    4) Una vez que hemos lanzado este script, ejecutamos los dos siguientes para la comprobación y obtención de resultados:

    Para verificar que el resultado se encuentra en HDFS, ejecutamos:

    • hadoop fs -lsr output”, o,
    • #ls output”, desde la consola interactiva de Java Script.

    Para descargar el resultado desde HDFS en un directorio local del Head Node del cluster de Hadoop:

    • hadoop fs -get output C:appsdistmahout-0.5examplesbinworkclustering
    • Veremos que la ruta “C:Appsdistmahout-0.5examplesbinworkclustering” contiene la carpeta “output” como resultado del comando anterior.

    5) A continuación, analizaremos los datos obtenidos utilizando el parámetro de Mahout “clusterdump”.

    • Creamos un nuevo script:
    @echo off

    setlocal enabledelayedexpansion

     

    set SCRIPT_PATH=%~dp0

    set MAHOUT_BIN_PATH=%SCRIPT_PATH%....bin

     

    set MAHOUT_HOME=%MAHOUT_BIN_PATH%..

     

    pushd %MAHOUT_BIN_PATH%

     

    call %HADOOP_HOME%binhadoop jar %MAHOUT_BIN_PATH%..mahout-examples-0.5-job.jar ^

       org.apache.mahout.driver.MahoutDriver ^

       clusterdump ^

       --seqFileDir output/clusters-0 ^

       --pointsDir output/clusteredPoints ^

       --output clusteranalyze.txt

     

    popd

    Nota: En vista de que el resultado esperado para este ejemplo no es satisfactorio, puesto que no obtendremos resultados, en este enlace podemos ver un análisis de los resultados de manera genérica así como el contenido del fichero de salida, “clusteranalyze.txt”.  Este fichero lo encontraremos en la carpeta  “C:appsdistmahout-0.5bin” una vez hayamos ejecutado este último script.

    Adicionalmente, de las 5 opciones que presenta este último ejemplo sólo la primera y las dos últimas funcionan, el resto producen errores y como ya hemos comentado, es posible que sea debido a las incompatibilidades para las versiones actuales entre Hadoop y Mahout. Siempre que ocurran estos problemas podemos intentar solucionarlo descargando y actualizando sus nuevas versiones: Hadoop y Mahout

    Importante: Si optamos por actualizar las versiones, tengamos en cuenta lo siguiente:

    • Es posible que las nuevas versiones no contengan los mismo ejemplos.
    • Los script (.cmd) adaptados a Windows, tendremos que generarlos manualmente a partir de los “.sh” de Linux/unix que se distribuyen con la versión original de apache. Por ejemplo, para mahout descargaremos la nueva versión, mahot-distribution-0.7, y la renombraremos por “mahout-0.7” ubicándola en “C:appsdist” al mismo nivel que “mahout-0.5”, así podremos intentar reutilizar los script (.cmd) de la versión anteriores.
    • A partir de aquí, toca probar y cruzar los dedos, por eso, no recomiendo esto a menos que los cambios a realizar fueran mínimos. Esperemos a que Microsft vaya realizando y adaptando las versiones adecuadamente.

    Aunque estos algoritmos y ejemplos están cambiando constantemente y Hadoop aun está en “Preview”, espero haber aclarado incógnitas sobre este nuevo mundo de la Inteligencia Artificial con Mahout… !

    Saludos y feliz “cacharreo” con Mahout…
    Juanlu,ElGuerre

  • Windows Azure Hadoop. Understanding, Diving & Solving issues IV. SQL To Hadoop (Sqoop) II

     

     hive_logo_mediumsqoop-logo

    En el capítulo anterior de Sqoop, veíamos como llevar a cabo una importación y exportación de datos desde SQL Azure hacia y desde Hadoop, concretamente desde y hacia su sistema de ficheros HDFS. Pues bien, en esta ocasión, veremos como conseguir esta importación directamente hacia Hive.

    1) Importación de datos desde SQL Azure hacia Hive:

    1) Creamos una vez más el alias para que Sqoop entienda la tabla, sin embargo, en este caso, no puede tener ningún “punto”, en cuyo caso Sqoop no será capaz de crear la tabla en Hive.

    CREATE SYNONYM [hivecustomer] FOR SalesLT.Customer

    2) Ejecutamos la siguiente instrucción:

    sqoop import

    --connect "jdbc:sqlserver://<SQLZAURE-SERVERNAME>.database.windows.net;database=AdventureWorksLTAZ2008R2"

    --username  jlguerrero@<SQLZAURE-SERVERNAME>

    -P

    --table hivecustomer

    --hive-import

    --hive-overwrite

    -m 1

    Donde:

    • -–hive-import, permite la importación directamente hacia Hive. En caso de que la tabla “hivecustomer” no exista esta será creada.
    • –hive-overwrite, sobreescribe la tabla “hivecustomer” si ya existe en  Hive.

    image

    3) Desde la consola de Hive podremos comprobar que se ha creado la tabla “hivecustomer” y se han cargado todo los datos: “select * from hivecustomer;

    Nota: Para más detalle sobre Sqoop, siempre podemos consultar la guía de usuario, http://sqoop.apache.org/docs/1.4.0-incubating/SqoopUserGuide.html, donde podremos profundizar todo cuanto queramos.

    En el post anterior comentaba que este nuevo capítulo íbamos a ver la exportación desde Hive hacia SQL Azure, no obstante, rectifico, puesto que en dicho sentido la exportación, a día de hoy aún no es posible. Perdonad, actualizo el post debidamente.

    Hasta aquí y tras unos cuantos posts, ya tenemos toda la información que necesitamos en Hive. Obtenida desde SQL Azure, de Azure Storage según comentamos en este post (HiveQL), y  desde HDFS, sólo nos queda comenzar a explotar la misma como si de un único repositorio se tratara. Eso sí, esto será en un nuevo capítulo, por el momento continuaremos conociendo otros componentes de Hadoop On Azure.

    Aunque este post ha sido algo más corto que de costumbre, espero que no menos interesante.

    Saludos
    Juan Luis Guerrero

    Windows Azure Hadoop. Understanding, Diving & Solving issues IV. SQL To Hadoop (Sqoop) I

    hadoop-logo sqoop-logo

    En esta ocasión veremos como Hadoop además de trabajar con datos no estructurados, también puede hacerlo con datos relacionales, para ello, Hadoop cuenta con otro componente, SQOOP, es decir, SQL To Hadoop.

    Lo que nos  va a permitir Scoop exactamente, es el traspaso de datos entre SQL Azure y Hadoop (HDFS y Hive) en ambos sentidos.

    Aunque como siempre, contamos con un tutorial de Microsoft, iremos un poco más allá, concretamente resolveremos algunos problemas encontrados tanto en el tutorial de Microsoft como en otros que podemos encontrar en internet.

    1) Como ya disponemos de este tutorial, resumamos como importar una tabla desde SQL Azure hacia Hadoop, hacia HDFS (Hadoop File System):

    1) Desde  SQL Server Management Studio, y para cada tabla (ej.: AdventureWorksLTAZ2008R2) de SQL Azure con la que vayamos a trabajar, establecemos un alias o sinónimo. Esto es necesario para que sqoop pueda identificar el nombre de la tabla:

    CREATE SYNONYM [SalesLT.Customer] FOR SalesLT.Customer

    2) Accedemos mediante terminal server al cluster de Hadoop como ya hemos comentado entre otras cosas en este post.

    3) Desde la consola de comandos de Hadoop (Hadoop Command Shell) navegamos hasta la ruta concreta “c:Appsdistsqoopbin” y ejecutamos la siguiente instrucción para realizar la importación de la tabla SalesLT.Customer al sistema de ficheros de Hadoop (HDFS) concretamente a la ruta /data/customer.

    sqoop import

    --connect "jdbc:sqlserver://<SQLAZURE-SERVERNAME>.database.windows.net;database=AdventureWorksLTAZ2008R2"

    --username jlguerrero@<SQLAZURE-SERVERNAME>

    -P

    --table SalesLT.Customer

    --target-dir /data/Customer

    -m 1

    Donde: El Parámetro “-P” va a permitirnos introducir por consola el password de conexión a SQL Azure

    Importante: La cadena de conexión no esperar recibir el usuario y password, estos se pasan a través de otros parámetros derivados eso sí, de la conexión.

    Nota: La instrucción se encuentra dividida en varias líneas pero recordemos que tendrá que encontrase todo en una misma.

    image

    4) Desde la consola interactiva de Javascript del portal de Hadoop ejeutamos:

  • “#lsr /data”  para verificar que existe una carpeta “Customer” y donde encontraremos nuestro fichero de datos “part-m-00000”.
  • “#tail /data/Customer/part-m-00000” para corroborar que se han cargado datos. Tail nos permitirá visualizar el primer KB del fichero sin necesidad de cargarlo por completo, en cuso caso, podríamos optar por #cat en su lugar.

     

    Nota: La importación desde una tabla de SQL Server (“OnPremisse”) aún no es posible puesto que las VMs del Cluster de Hadoop además de no tener habilitado los permisos de visibilidad con máquinas On-Premisse, tienen restringidos los permisos para habilitar los mismos, así que por el momento sólo podremos importar datos desde SQL Azure.

  • 2) Veamos a continuación como exportar desde Hadoop (HDFS) hacia SQL Azure:

    1) Creamos una nueva tabla “[SalesLT].[CustomerFromHadoop]” en SQL Azure

    USE [AdventureWorksLTAZ2008R2]
    GO

    CREATE TABLE [SalesLT].[CustomerFromHadoop](
    [CustomerID] [int] NOT NULL,
    [NameStyle] [varchar] (5),
    [Title] [varchar](25) NULL,
    [FirstName] [varchar](50) NOT NULL,
    [MiddleName] [varchar](50) NULL,
    [LastName] [varchar](50) NOT NULL,
    [Suffix] [varchar](25) NULL,
    [CompanyName] [varchar](150) NULL,
    [SalesPerson] [varchar](260) NULL,
    [EmailAddress] [varchar](50) NULL,
    [Phone] [varchar](50) NULL,
    [PasswordHash] [varchar](150) NOT NULL,
    [PasswordSalt] [varchar](50) NOT NULL,
    [rowguid] [varchar] (50) NOT NULL,
    [ModifiedDate] [varchar](50) NOT NULL
    CONSTRAINT [PK_CustomerHadoop_CustomerID] PRIMARY KEY CLUSTERED ([CustomerID] ASC
    ))

    GO

    2) Establecemos su alias para que pueda ser reconocida desde Sqoop.

    CREATE SYNONYM [SalesLT.CustomerFromHadoop] FOR SalesLT.CustomerFromHadoop

    3) Desde la consola de comandos de Hadoop (Hadoop Command Shell) navegamos hasta la ruta concreta “c:Appsdistsqoopbin” y ejecutamos la siguiente instrucción:

    sqoop export

    --connect "jdbc:sqlserver://<SQLAZURE-SERVERNAME>.database.windows.net;database=AdventureWorksLTAZ2008R2"

    --username jlguerrero@<SQLAZURE-SERVERNAME>

    -P

    --table SalesLT.CustomerFromHadoop

    --export-dir /data/Customer

    --input-fields-terminated-by "," 

    image

    4) Si consultamos nuestra nueva tabla de SQL Azure,  “SELECT * FROM[SalesLT].[CustomerFromHadoop]”, podremos comprobar como se ha cargado correctamente con todos sus datos.

    Nota: En este caso de exportación desde Hadoop, el proceso podría complicarse si pretendemos hacer transformaciones de campos, en tal caso, el camino será más fácil si estas transformaciones las llevamos a cabo en SQL directamente una vez realizada la exportación.

    Espero haber aclarado un poco más el camino hacia y desde Hadoop HDFS hacia SQL Azure. En el próximo capitulo de Sqoop, veremos como importar datos directamente hacia Hive.

    Saludos @Home
    Juanlu,ElGuerre