text:
<?xml version="1.0" encoding="UTF-8"?>

<sect1 id="zend.gdata.photos">
    <title>Utilisation des albums Web Picasa</title>

    <para>
        Les albums Web Picasa représentent un service Google permettant de maintenir à jour
        des albums photos, tout en pouvant récupérer des photos de l'album d'un membre. L'API
        propose des services pour ajouter, mettre à jour ou supprimer des photos d'un album, de même
        que gérer des mots-clés ou des commentaires sur des images(photos).
    </para>

    <para>
        L'accès public à un album, en lecture donc, n'est pas sujet à demande
        d'authentification. En revanche, toute autre manipulation telle que la mise à jour ou la
        suppression, nécessitera que vous vous authentifiez.
    </para>

    <para>
        Pour plus d'informations sur l'API, voyez <ulink
        url="http://code.google.com/apis/picasaweb/overview.html">l'API Picasa Web
        Albums</ulink>.
    </para>

    <note>
        <title>Authentification</title>

        <para>
            L'API propose les deux modes d'authentification, AuthSub (recommandé) et
            ClientAuth. Pour toute opération d'écriture vers le service, une authentification sera
            demandée, la lecture est elle, libre, au regard de l'API.
        </para>
    </note>

    <sect2 id="zend.gdata.photos.connecting">
        <title>Se connecter au service</title>

        <para>
            L'API Picasa, comme tous les autres services Web Google Gdata, est basée sur le
            protocole Atom Publishing Protocol (APP), et le <acronym>XML</acronym>. Le trafic entre le client et le
            serveur se fait sur <acronym>HTTP</acronym>, et autorise des connexions authentifiées, ou non.
        </para>

        <para>
            Avant tout, il faut donc se connecter. Ceci se fait en deux étapes : créer un
            client <acronym>HTTP</acronym>, et insérer un <classname>Zend_Gdata_Photos</classname> dans
            celui-ci.
        </para>

        <sect3 id="zend.gdata.photos.connecting.authentication">
            <title>Authentification</title>

            <para>
                L'API propose un accès à la fois aux données publiques, et aux données
                privées. Les données publiques ne requièrent pas d'authentification, mais ne sont
                accessibles qu'en lecture seule. L'écriture et l'accès aux données privées
                requièrent une authentification, qui peut s'effectuer de trois manières différentes
                :
            </para>

            <itemizedlist>
                <listitem>
                    <para>
                        <firstterm>ClientAuth</firstterm> permet une authentification directe
                        en donnant un couple login/password. Les utilisateurs devront donc
                        renseigner ces 2 paramètres sur votre site directement.
                    </para>
                </listitem>

                <listitem>
                    <para>
                        <firstterm>AuthSub</firstterm> permet l'authentification en passant
                        par un serveur proxy de Google. Les risques liés à la sécurité sont donc
                        moindre avec cette méthode.
                    </para>
                </listitem>
            </itemizedlist>

            <para>
                La librairie <classname>Zend_Gdata</classname> permet ces 2 types
                d'authentification. Le reste de ce chapitre supposera que vous soyez habitué à
                l'authentification avec les service Web Google GData. Si ce n'est pas le cas, nous
                vous conseillons de consulter <link
                linkend="zend.gdata.introduction.authentication">la section authentification</link>
                de ce manuel, ou encore <ulink url="http://code.google.com/apis/gdata/auth.html">le
                guide d'authentification Google GData webservices <acronym>API</acronym></ulink>.
            </para>
        </sect3>

        <sect3 id="zend.gdata.photos.connecting.service">
            <title>Créer une instance du service</title>

            <para>
                Pour interagir avec les serveurs, la classe
                <classname>Zend_Gdata_Photos</classname> sera nécessaire. Elle abstrait toute la
                logique de communication avec le Protocol Atom Publishing vers les serveurs de
                Google.
            </para>

            <para>
                Une fois que vous avez choisi une méthode d'authentification, vous devez créer
                une instance de <classname>Zend_Gdata_Photos</classname>. Le constructeur prend en
                paramètre une instance de <classname>Zend_Http_Client</classname>. Cette classe est
                l'interface AuthSub ou ClientAuth authentification. Si vous ne passez pas cette
                instance en argument, alors une instance de <classname>Zend_Http_Client</classname>
                sera crée automatiquement, mais en mode non authentifié.
            </para>

            <para>
                Voici un exemple qui démontre comment créer une classe vers le service avec le
                procédé d'authentification ClientAuth :
            </para>

            <programlisting language="php"><![CDATA[
// Paramètres pour ClientAuth authentification
$service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$user = "sample.user@gmail.com";
$pass = "pa$$w0rd";

// Création d'une client HTTP authentifié
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);

// Création de l'instance du service
$service = new Zend_Gdata_Photos($client);
]]></programlisting>

            <para>Au sujet du procédé AuthSub, voici la démarche :</para>

            <programlisting language="php"><![CDATA[
session_start();

/**
 * Retourne l'URL complet de la page actuelle,
 * en fonction des variables d'environnement
 *
 * Env variables utilisées:
 * $_SERVER['HTTPS'] = (on|off|)
 * $_SERVER['HTTP_HOST'] = value of the Host: header
 * $_SERVER['SERVER_PORT'] = port number (only used if not http/80,https/443)
 * $_SERVER['REQUEST_URI'] = the URI after the method of the HTTP request
 *
 * @return string Current URL
 */
function getCurrentUrl()
{
    global $_SERVER;

    /**
     * Filtre php_self pour éviter des problèmes de sécurité
     */
    $php_request_uri = htmlentities(substr($_SERVER['REQUEST_URI'], 0,
    strcspn($_SERVER['REQUEST_URI'], "\n\r")), ENT_QUOTES);

    if (isset($_SERVER['HTTPS'])
     && strtolower($_SERVER['HTTPS']) == 'on') {
        $protocol = 'https://';
    } else {
        $protocol = 'http://';
    }
    $host = $_SERVER['HTTP_HOST'];
    if ($_SERVER['SERVER_PORT'] != '' &&
        (($protocol == 'http://' && $_SERVER['SERVER_PORT'] != '80') ||
        ($protocol == 'https://' && $_SERVER['SERVER_PORT'] != '443'))) {
            $port = ':' . $_SERVER['SERVER_PORT'];
    } else {
        $port = '';
    }
    return $protocol . $host . $port . $php_request_uri;
}

/**
 * Retourne l'URL AuthSub que l'utilisateur doit visiter
 * pour authentifier ses requêtes
 *
 * Utilise getCurrentUrl() pour récupérer le prochain URL
 * vers lequel l'utilisateur sera redirigé après
 * s'être authentifié.
 *
 * @return string AuthSub URL
 */
function getAuthSubUrl()
{
    $next = getCurrentUrl();
    $scope = 'http://picasaweb.google.com/data';
    $secure = false;
    $session = true;
    return Zend_Gdata_AuthSub::getAuthSubTokenUri($next,
                                                  $scope,
                                                  $secure,
                                                  $session);
}

/**
 * Retourne un objet servant de client HTTP avec les bons en-têtes,
 * permettant de communiquer avec les services Google, et utilisant
 * l'authentification AuthSub.
 *
 * Utilise $_SESSION['sessionToken'] pour stocker le jeton de session
 * AuthSub après l'avoir obtenu. $_GET['token'] récupère ce jeton
 * après la redirection d'authentification
 *
 * @return Zend_Http_Client
 */
function getAuthSubHttpClient()
{
    global $_SESSION, $_GET;
    if (!isset($_SESSION['sessionToken']) && isset($_GET['token'])) {
        $_SESSION['sessionToken'] =
            Zend_Gdata_AuthSub::getAuthSubSessionToken($_GET['token']);
    }
    $client =
        Zend_Gdata_AuthSub::getHttpClient($_SESSION['sessionToken']);
    return $client;
}

/**
 * Créer une instance du service, redirigeant l'utilisateur
 * vers le serveur AuthSub si nécéssaire.
 */
$service = new Zend_Gdata_Photos(getAuthSubHttpClient());
]]></programlisting>

            <para>Enfin, un client non authentifié peut aussi être crée :</para>

            <programlisting language="php"><![CDATA[
// Création d'une instance du service en mode non authentifié
$service = new Zend_Gdata_Photos();
]]></programlisting>
        </sect3>
    </sect2>

    <sect2 id="zend.gdata.photos.queries">
        <title>Comprendre et construire des requêtes</title>

        <para>
            Pour créer des requêtes vers le service Web, vous devrez utiliser une de ces
            classes :
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <firstterm>User</firstterm> Cette classe requêtera tout ce qui concerne un
                    utilisateur du service. Sans spécifier d'utilisateur, "default" sera
                    utilisé.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Album</firstterm> Cette classe va servir de base pour toutes
                    les requêtes concernant les albums Picasa.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Photo</firstterm> Cette classe va servir de base pour toutes
                    les requêtes concernant les photos Picasa.
                </para>
            </listitem>
        </itemizedlist>

        <para>Une UserQuery peut être construite comme suit :</para>

        <programlisting language="php"><![CDATA[
$service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_UserQuery();
$query->setUser("sample.user");
]]></programlisting>

        <para>
            Pour chaque requête, des paramètres de limitations de la recherche peuvent être
            passés grâce aux méthodes get(Paramètre) and set(Paramètre) :
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <firstterm>Projection</firstterm> spécifie le format de retour des données
                    dans le flux. Peut être "api" ou "base". En temps normal, "api" est conseillé,
                    c'est la valeur par défaut d'ailleurs.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Type</firstterm> détermine le type des éléments retournés,
                    "feed"(défaut) ou "entry".
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Access</firstterm> détermine la visibilité des éléments
                    retournés, "all"(défaut), "public", ou "private". Les éléments non publics ne
                    seront retournés que si le client est authentifié.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Tag</firstterm> fournit un filtre par mots-clés sur les
                    éléments retournés.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Kind</firstterm> détermine un filtre sur la sorte (le type)
                    d'éléments retournés.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>ImgMax</firstterm> spécifie un filtre par dimension maximale
                    sur les éléments retournés.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>Thumbsize</firstterm> spécifie un filtre par dimension maximale
                    des miniatures retournées.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>User</firstterm> spécifie l'utilisateur dont les éléments sont
                    recherchés. Par défaut, "default".
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>AlbumId</firstterm> spécifie l'identifiant de l'album
                    recherché. Ceci ne s'applique qu'aux requêtes album et photo. Dans le cas d'une
                    recherche de photo, ceci indique l'album dans lequel effectuer la requête de
                    recherche. Ce paramètre annule et remplace AlbumName, si spécifié.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>AlbumName</firstterm> spécifie le nom de l'album recherché.
                    Ceci ne s'applique qu'aux requêtes album et photo. Dans le cas d'une recherche
                    de photo, ceci indique l'album dans lequel effectuer la requête de recherche. Ce
                    paramètre annule et remplace AlbumId, si spécifié.
                </para>
            </listitem>

            <listitem>
                <para>
                    <firstterm>PhotoId</firstterm> spécifie l'identifiant de la photo
                    recherchée. Ceci ne s'applique qu'aux requêtes photo.
                </para>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.gdata.photos.retrieval">
        <title>Récupérer des flux et des éléments</title>

        <para>
            Le service propose des méthodes de récupération de flux, ou d'éléments simples,
            concernant les utilisateurs, albums, ou photos.
        </para>

        <sect3 id="zend.gdata.photos.user_retrieval">
            <title>Récupérer un utilisateur</title>

            <para>
                Le service propose de récupérer un utilisateur, et toutes les infos de son
                flux, comme ses photos, ses albums.... Si le client est authentifié et demande des
                informations sur son propre compte, alors les éléments marqués comme
                "hidden" seront aussi retournés.
            </para>

            <para>
                Le flux de l'utilisateur est accessible en passant son nom à la méthode
                getUserFeed :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

try {
    $userFeed = $service->getUserFeed("sample.user");
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>

            <para>Ou alors, le flux peut être requêté directement :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_UserQuery();
$query->setUser("sample.user");

try {
    $userFeed = $service->getUserFeed(null, $query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>

            <para>
                Construire une requête donne aussi accès aux éléments d'un utilisateur
                :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_UserQuery();
$query->setUser("sample.user");
$query->setType("entry");

try {
    $userEntry = $service->getUserEntry($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.album_retrieval">
            <title>Récupérer un album</title>

            <para>Le service donne accès aux flux d'albums et à leurs contenus.</para>

            <para>
                Le flux d'albums est disponible en construisant un objet de requête et en le
                passant à getAlbumFeed :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_AlbumQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");

try {
    $albumFeed = $service->getAlbumFeed($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>

            <para>
                L'objet de requête accepte aussi un nom d'album avec
                setAlbumName. Attention, ceci annule un identifiant d'album
                éventuellement précédemment spécifié.
            </para>

            <para>Construire une requête donne aussi accès au requêtage d'un album :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_AlbumQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");
$query->setType("entry");

try {
    $albumEntry = $service->getAlbumEntry($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.photo_retrieval">
            <title>Récupérer une Photo</title>

            <para>
                Le service permet la récupération de flux de photos, et des commentaires et/ou
                mots-clés associés
            </para>

            <para>
                Le flux de photos est accessible en construisant un objet de requête et en le
                passant à la méthode getPhotoFeed :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_PhotoQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");
$query->setPhotoId("100");

try {
    $photoFeed = $service->getPhotoFeed($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>

            <para>Construire une requête donne aussi accès au requêtage d'une photo :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_PhotoQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");
$query->setPhotoId("100");
$query->setType("entry");

try {
    $photoEntry = $service->getPhotoEntry($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.comment_retrieval">
            <title>Récupérer des commentaires</title>

            <para>
                Vous pouvez récupérer des commentaires depuis des éléments divers de flux. En
                spécifiant à votre requête un paramètre de "comment", celle-ci retournera les
                mots-clés associés à la ressource demandée (user, album ou photo)
            </para>

            <para>
                Voici comment effectuer des actions sur les commentaires récupérés d'une photo
                :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_PhotoQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");
$query->setPhotoId("100");
$query->setKind("comment");

try {
    $photoFeed = $service->getPhotoFeed($query);

    foreach ($photoFeed as $entry) {
        if ($entry instanceof Zend_Gdata_Photos_CommentEntry) {
            // Faites quelque chose avec le commentaire
        }
    }
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.tag_retrieval">
            <title>Récupérer des mots-clés</title>

            <para>
                Vous pouvez récupérer des mots-clés depuis des éléments divers de flux. En
                spécifiant à votre requête un paramètre de "tag", celle-ci retournera les mots-clés
                associés à la ressource demandée.
            </para>

            <para>
                Voici comment effectuer des actions sur les mots-clés récupérés d'une photo
                :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$query = new Zend_Gdata_Photos_PhotoQuery();
$query->setUser("sample.user");
$query->setAlbumId("1");
$query->setPhotoId("100");
$query->setKind("tag");

try {
    $photoFeed = $service->getPhotoFeed($query);

    foreach ($photoFeed as $entry) {
        if ($entry instanceof Zend_Gdata_Photos_TagEntry) {
            // Faites quelque chose avec le mot-clé
        }
    }
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getMessage();
}
]]></programlisting>
        </sect3>
    </sect2>

    <sect2 id="zend.gdata.photos.creation">
        <title>Créer des ressources</title>

        <para>
            Des opérations de création sont possible, qu'il s'agisse d'albums, photos,
            commentaires, ou mots-clés.
        </para>

        <sect3 id="zend.gdata.photos.album_creation">
            <title>Créer un album</title>

            <para>Il est possible de créer un album, pour les clients authentifiés :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$entry = new Zend_Gdata_Photos_AlbumEntry();
$entry->setTitle($service->newTitle("test album"));

$service->insertAlbumEntry($entry);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.photo_creation">
            <title>Créer une photo</title>

            <para>
                Créer une photo est possible pour les clients authentifiés, procédez comme
                suit :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

// $photo est le nom d'un fichier issu d'un formulaire d'uplaod

$fd = $service->newMediaFileSource($photo["tmp_name"]);
$fd->setContentType($photo["type"]);

$entry = new Zend_Gdata_Photos_PhotoEntry();
$entry->setMediaSource($fd);
$entry->setTitle($service->newTitle($photo["name"]));

$albumQuery = new Zend_Gdata_Photos_AlbumQuery;
$albumQuery->setUser("sample.user");
$albumQuery->setAlbumId("1");

$albumEntry = $service->getAlbumEntry($albumQuery);

$service->insertPhotoEntry($entry, $albumEntry);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.comment_creation">
            <title>Créer un commentaire pour une photo</title>

            <para>
                Il est possible de créer un commentaire pour une photo, voici un exemple
                :
            </para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$entry = new Zend_Gdata_Photos_CommentEntry();
$entry->setTitle($service->newTitle("comment"));
$entry->setContent($service->newContent("comment"));

$photoQuery = new Zend_Gdata_Photos_PhotoQuery;
$photoQuery->setUser("sample.user");
$photoQuery->setAlbumId("1");
$photoQuery->setPhotoId("100");
$photoQuery->setType('entry');

$photoEntry = $service->getPhotoEntry($photoQuery);

$service->insertCommentEntry($entry, $photoEntry);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.tag_creation">
            <title>Créer un mot-clé pour une photo</title>

            <para>Il est possible de créer un mot-clé pour une photo, voici un exemple :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$entry = new Zend_Gdata_Photos_TagEntry();
$entry->setTitle($service->newTitle("tag"));

$photoQuery = new Zend_Gdata_Photos_PhotoQuery;
$photoQuery->setUser("sample.user");
$photoQuery->setAlbumId("1");
$photoQuery->setPhotoId("100");
$photoQuery->setType('entry');

$photoEntry = $service->getPhotoEntry($photoQuery);

$service->insertTagEntry($entry, $photoEntry);
]]></programlisting>
        </sect3>
    </sect2>

    <sect2 id="zend.gdata.photos.deletion">
        <title>Supprimer des éléments</title>

        <para>Il est possible de supprimer albums, photos, commentaires, et mots-clés.</para>

        <sect3 id="zend.gdata.photos.album_deletion">
            <title>Supprimer un album</title>

            <para>Supprimer un album est possible si le client est authentifié :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$albumQuery = new Zend_Gdata_Photos_AlbumQuery;
$albumQuery->setUser("sample.user");
$albumQuery->setAlbumId("1");
$albumQuery->setType('entry');

$entry = $service->getAlbumEntry($albumQuery);

$service->deleteAlbumEntry($entry, true);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.photo_deletion">
            <title>Supprimer une photo</title>

            <para>Supprimer une photo est possible si le client est authentifié :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$photoQuery = new Zend_Gdata_Photos_PhotoQuery;
$photoQuery->setUser("sample.user");
$photoQuery->setAlbumId("1");
$photoQuery->setPhotoId("100");
$photoQuery->setType('entry');

$entry = $service->getPhotoEntry($photoQuery);

$service->deletePhotoEntry($entry, true);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.comment_deletion">
            <title>Supprimer un commentaire</title>

            <para>Supprimer un commentaire est possible si le client est authentifié :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$photoQuery = new Zend_Gdata_Photos_PhotoQuery;
$photoQuery->setUser("sample.user");
$photoQuery->setAlbumId("1");
$photoQuery->setPhotoId("100");
$photoQuery->setType('entry');

$path = $photoQuery->getQueryUrl() . '/commentid/' . "1000";

$entry = $service->getCommentEntry($path);

$service->deleteCommentEntry($entry, true);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.tag_deletion">
            <title>Supprimer un mot-clé</title>

            <para>Supprimer un mot-clé est possible, si le client est authentifié :</para>

            <programlisting language="php"><![CDATA[
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
$service = new Zend_Gdata_Photos($client);

$photoQuery = new Zend_Gdata_Photos_PhotoQuery;
$photoQuery->setUser("sample.user");
$photoQuery->setAlbumId("1");
$photoQuery->setPhotoId("100");
$photoQuery->setKind("tag");
$query = $photoQuery->getQueryUrl();

$photoFeed = $service->getPhotoFeed($query);

foreach ($photoFeed as $entry) {
    if ($entry instanceof Zend_Gdata_Photos_TagEntry) {
        if ($entry->getContent() == $tagContent) {
            $tagEntry = $entry;
        }
    }
}

$service->deleteTagEntry($tagEntry, true);
]]></programlisting>
        </sect3>

        <sect3 id="zend.gdata.photos.optimistic_concurrenty">
            <title>Gestion des accès concurrents</title>

            <para>
                Les flux GData, dont ceux de Picasa Web Albums, implémentent un système
                d'accès concurrent qui empêche les changements avec écrasements par inadvertance. Si
                vous demandez l'effacement d'une ressource qui a été modifiée depuis votre dernière
                requête, alors une exception sera levée, sauf si vous demandez le contraire
                explicitement (dans un tel cas, la procédure d'effacement sera réessayée sur
                l'élément mis à jour).
            </para>

            <para>
                Voici un exemple de gestion des versions et accès concurrent sur un effacement
                avec deleteAlbumEntry:
            </para>

            <programlisting language="php"><![CDATA[
    // $album est l'albumEntry à effacer
try {
    $this->delete($album);
} catch (Zend_Gdata_App_HttpException $e) {
    if ($e->getMessage()->getStatus() === 409) {
        $entry =
            new Zend_Gdata_Photos_AlbumEntry($e->getMessage()
                                               ->getBody());
        $this->delete($entry->getLink('edit')->href);
    } else {
        throw $e;
    }
}
]]></programlisting>
        </sect3>
    </sect2>
</sect1>


text:
package org.jbpm.process.audit.jms;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.XAConnectionFactory;
import javax.naming.InitialContext;
import javax.persistence.EntityManagerFactory;
import javax.transaction.UserTransaction;

import org.assertj.core.api.Assertions;
import org.hornetq.jms.server.embedded.EmbeddedJMS;
import org.jboss.narayana.jta.jms.ConnectionFactoryProxy;
import org.jboss.narayana.jta.jms.TransactionHelperImpl;
import org.jbpm.process.audit.AbstractAuditLogger;
import org.jbpm.process.audit.AuditLogService;
import org.jbpm.process.audit.AuditLoggerFactory;
import org.jbpm.process.audit.AuditLoggerFactory.Type;
import org.jbpm.process.audit.JPAAuditLogService;
import org.jbpm.process.audit.NodeInstanceLog;
import org.jbpm.process.audit.ProcessInstanceLog;
import org.jbpm.process.audit.VariableInstanceLog;
import org.jbpm.process.instance.impl.demo.SystemOutWorkItemHandler;
import org.jbpm.test.util.AbstractBaseTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.kie.api.KieBase;
import org.kie.api.runtime.Environment;
import org.kie.api.runtime.EnvironmentName;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.jbpm.process.audit.AbstractAuditLogServiceTest.createKieSession;
import static org.jbpm.process.audit.AbstractAuditLogServiceTest.createKnowledgeBase;
import static org.jbpm.test.persistence.util.PersistenceUtil.JBPM_PERSISTENCE_UNIT_NAME;
import static org.jbpm.test.persistence.util.PersistenceUtil.cleanUp;
import static org.jbpm.test.persistence.util.PersistenceUtil.createEnvironment;
import static org.jbpm.test.persistence.util.PersistenceUtil.setupWithPoolingDataSource;
import static org.junit.Assert.assertTrue;

public class AsyncAuditLogProducerTest extends AbstractBaseTest {

    private static final Logger logger = LoggerFactory.getLogger(AsyncAuditLogProducerTest.class);

    private HashMap<String, Object> context;
    private ConnectionFactory factory;
    private Queue queue;

    private EmbeddedJMS jmsServer;

    @Before
    public void setup() throws Exception {
        startHornetQServer();
        context = setupWithPoolingDataSource(JBPM_PERSISTENCE_UNIT_NAME);
    }

    @After
    public void tearDown() throws Exception {
        cleanUp(context);
        stopHornetQServer();
    }

    @Test
    public void testAsyncAuditProducer() throws Exception {
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", false);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        // start process instance
        long processInstanceId = session.startProcess("com.sample.ruleflow").getId();
        // setup listener

        MessageReceiver receiver = new MessageReceiver();
        List<Message> messages = receiver.receive(queue);
        Assertions.assertThat(messages).isNotNull();
        Assertions.assertThat(messages.size()).isEqualTo(11);

    }

    @Test
    public void testAsyncAuditProducerTransactional() throws Exception {
        UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction");
        ut.begin();
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", true);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        // start process instance
        long processInstanceId = session.startProcess("com.sample.ruleflow").getId();

        ut.commit();

        MessageReceiver receiver = new MessageReceiver();
        List<Message> messages = receiver.receive(queue);
        Assertions.assertThat(messages).isNotNull();
        Assertions.assertThat(messages.size()).isEqualTo(11);

    }

    @Test
    public void testAsyncAuditProducerTransactionalWithRollback() throws Exception {
        UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction");
        ut.begin();
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", true);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        // start process instance
        long processInstanceId = session.startProcess("com.sample.ruleflow").getId();

        ut.rollback();

        MessageReceiver receiver = new MessageReceiver();
        List<Message> messages = receiver.receive(queue);
        Assertions.assertThat(messages).isNotNull();
        Assertions.assertThat(messages.size()).isEqualTo(0);
    }

    @Test
    public void testAsyncAuditProducerNonTransactionalWithRollback() throws Exception {
        UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction");
        ut.begin();
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", false);
        jmsProps.put("jbpm.audit.jms.connection.factory", jmsServer.lookup("ConnectionFactory"));
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        // start process instance
        long processInstanceId = session.startProcess("com.sample.ruleflow").getId();

        ut.rollback();

        MessageReceiver receiver = new MessageReceiver();
        List<Message> messages = receiver.receive(queue);
        Assertions.assertThat(messages).isNotNull();
        Assertions.assertThat(messages.size()).isEqualTo(11);
    }

    @Test
    public void testAsyncAuditLoggerComplete() throws Exception {
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", false);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        // start process instance
        ProcessInstance processInstance = session.startProcess("com.sample.ruleflow");

        MessageReceiver receiver = new MessageReceiver();
        receiver.receiveAndProcess(queue, ((EntityManagerFactory)env.get(EnvironmentName.ENTITY_MANAGER_FACTORY)), 2000, 11);

        // validate if everything is stored in db
        AuditLogService logService = new JPAAuditLogService(env);
        List<ProcessInstanceLog> processInstances = logService.findProcessInstances("com.sample.ruleflow");
        Assertions.assertThat(processInstances.size()).isEqualTo(1);
        List<NodeInstanceLog> nodeInstances = logService.findNodeInstances(processInstance.getId());
        Assertions.assertThat(nodeInstances.size()).isEqualTo(6);
        for (NodeInstanceLog nodeInstance: nodeInstances) {
            Assertions.assertThat(processInstance.getId()).isEqualTo(nodeInstance.getProcessInstanceId().longValue());
            Assertions.assertThat(nodeInstance.getProcessId()).isEqualTo("com.sample.ruleflow");
            Assertions.assertThat(nodeInstance.getDate()).isNotNull();
        }
        //reordering needed for mysql, mariadb, based on nodeInstanceId and id -log_date is probably the same-
        nodeInstances.sort(Comparator.comparing(NodeInstanceLog::getNodeInstanceId).thenComparing(NodeInstanceLog::getId));
        for (int i = 1; i < 4; i = i + 2) {
            assertTrue(nodeInstances.get(i).getConnection().equals(nodeInstances.get(i + 1).getNodeId()));
        }
        for (int i = 2; i < 6; i = i + 2) {
            assertTrue(nodeInstances.get(i).getConnection().equals(nodeInstances.get(i - 1).getNodeId()));
        }
        logService.clear();
        processInstances = logService.findProcessInstances("com.sample.ruleflow");
        logService.dispose();
        Assertions.assertThat(processInstances).isEmpty();
    }

    @Test
    public void testAsyncAuditLoggerCompleteDirectCreation() throws Exception {
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        AbstractAuditLogger logger = AuditLoggerFactory.newJMSInstance(true, factory, queue);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();
        session.addEventListener(logger);

        // start process instance
        ProcessInstance processInstance = session.startProcess("com.sample.ruleflow");

        MessageReceiver receiver = new MessageReceiver();
        receiver.receiveAndProcess(queue, ((EntityManagerFactory)env.get(EnvironmentName.ENTITY_MANAGER_FACTORY)), 6000, 11);

        // validate if everything is stored in db
        AuditLogService logService = new JPAAuditLogService(env);
        List<ProcessInstanceLog> processInstances = logService.findProcessInstances("com.sample.ruleflow");
        Assertions.assertThat(processInstances.size()).isEqualTo(1);
        List<NodeInstanceLog> nodeInstances = logService.findNodeInstances(processInstance.getId());
        Assertions.assertThat(nodeInstances.size()).isEqualTo(6);
        for (NodeInstanceLog nodeInstance: nodeInstances) {
            Assertions.assertThat(nodeInstance.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
            Assertions.assertThat(nodeInstance.getProcessId()).isEqualTo("com.sample.ruleflow");
            Assertions.assertThat(nodeInstance.getDate()).isNotNull();
        }
        logService.clear();
        processInstances = logService.findProcessInstances("com.sample.ruleflow");
        logService.dispose();
        Assertions.assertThat(processInstances).isEmpty();
    }

    @Test
    public void testAsyncAuditLoggerCompleteWithVariables() throws Exception {
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", false);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("s", "test value");

        // start process instance
        ProcessInstance processInstance = session.startProcess("com.sample.ruleflow3", params);

        MessageReceiver receiver = new MessageReceiver();
        receiver.receiveAndProcess(queue, ((EntityManagerFactory)env.get(EnvironmentName.ENTITY_MANAGER_FACTORY)), 3000, 13);

        // validate if everything is stored in db
        AuditLogService logService = new JPAAuditLogService(env);
        List<ProcessInstanceLog> processInstances = logService.findProcessInstances("com.sample.ruleflow3");
        Assertions.assertThat(processInstances.size()).isEqualTo(1);
        List<NodeInstanceLog> nodeInstances = logService.findNodeInstances(processInstance.getId());
        Assertions.assertThat(nodeInstances.size()).isEqualTo(6);
        for (NodeInstanceLog nodeInstance: nodeInstances) {
            Assertions.assertThat(nodeInstance.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
            Assertions.assertThat(nodeInstance.getProcessId()).isEqualTo("com.sample.ruleflow3");
            Assertions.assertThat(nodeInstance.getDate()).isNotNull();
        }
        //verify variables
        List<VariableInstanceLog> variables = logService.findVariableInstances(processInstance.getId());
        Assertions.assertThat(variables).isNotNull();
        Assertions.assertThat(variables).hasSize(2);

        VariableInstanceLog var = variables.get(0);
        // initial value from rule flow definition
        Assertions.assertThat(var.getValue()).isEqualTo("InitialValue");
        Assertions.assertThat(var.getOldValue()).isIn("", " ", null);
        Assertions.assertThat(var.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
        Assertions.assertThat(var.getProcessId()).isEqualTo(processInstance.getProcessId());
        Assertions.assertThat(var.getVariableId()).isEqualTo("s");
        Assertions.assertThat(var.getVariableInstanceId()).isEqualTo("s");

        // value given at process start
        var = variables.get(1);
        // initial value from rule flow definition
        Assertions.assertThat(var.getValue()).isEqualTo("test value");
        Assertions.assertThat(var.getOldValue()).isEqualTo("InitialValue");
        Assertions.assertThat(var.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
        Assertions.assertThat(var.getProcessId()).isEqualTo(processInstance.getProcessId());
        Assertions.assertThat(var.getVariableId()).isEqualTo("s");
        Assertions.assertThat(var.getVariableInstanceId()).isEqualTo("s");

        logService.clear();
        processInstances = logService.findProcessInstances("com.sample.ruleflow3");
        logService.dispose();
        Assertions.assertThat(processInstances).isNullOrEmpty();
    }

    @Test
    public void testAsyncAuditLoggerCompleteWithVariablesCustomIndexer() throws Exception {
        Environment env = createEnvironment(context);
        // load the process
        KieBase kbase = createKnowledgeBase();
        // create a new session
        KieSession session = createSession(kbase, env);

        Map<String, Object> jmsProps = new HashMap<String, Object>();
        jmsProps.put("jbpm.audit.jms.transacted", false);
        jmsProps.put("jbpm.audit.jms.connection.factory", factory);
        jmsProps.put("jbpm.audit.jms.queue", queue);
        AbstractAuditLogger logger = AuditLoggerFactory.newInstance(Type.JMS, session, jmsProps);
        Assertions.assertThat(logger).isNotNull();
        Assertions.assertThat((logger instanceof AsyncAuditLogProducer)).isTrue();

        List<String> names = new LinkedList<String>();
        names.add("john");
        names.add("mary");
        names.add("peter");

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("list", names);

        // start process instance
        ProcessInstance processInstance = session.startProcess("com.sample.ruleflow3", params);

        MessageReceiver receiver = new MessageReceiver();
        receiver.receiveAndProcess(queue, ((EntityManagerFactory)env.get(EnvironmentName.ENTITY_MANAGER_FACTORY)), 6000, 28);

        // validate if everything is stored in db
        AuditLogService logService = new JPAAuditLogService(env);
        List<ProcessInstanceLog> processInstances = logService.findProcessInstances("com.sample.ruleflow3");
        Assertions.assertThat(processInstances.size()).isEqualTo(1);
        List<NodeInstanceLog> nodeInstances = logService.findNodeInstances(processInstance.getId());
        Assertions.assertThat(nodeInstances.size()).isEqualTo(12);
        for (NodeInstanceLog nodeInstance: nodeInstances) {
            Assertions.assertThat(nodeInstance.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
            Assertions.assertThat(nodeInstance.getProcessId()).isEqualTo("com.sample.ruleflow3");
            Assertions.assertThat(nodeInstance.getDate()).isNotNull();
        }
        //verify variables
        List<VariableInstanceLog> variables = logService.findVariableInstances(processInstance.getId());
        Assertions.assertThat(variables).isNotNull();
        Assertions.assertThat(variables.size()).isEqualTo(8);

        List<VariableInstanceLog> listVariables = new ArrayList<VariableInstanceLog>();
        // collect only those that are related to list process variable
        for (VariableInstanceLog v : variables) {
            if (v.getVariableInstanceId().equals("list")) {
                listVariables.add(v);
            }
        }

        Assertions.assertThat(listVariables.size()).isEqualTo(3);

        List<String> variableValues = new ArrayList<String>();
        List<String> variableIds = new ArrayList<String>();
        for (VariableInstanceLog var : listVariables) {
            variableValues.add(var.getValue());
            variableIds.add(var.getVariableId());
            Assertions.assertThat(var.getOldValue()).isIn("", " ", null);
            Assertions.assertThat(var.getProcessInstanceId().longValue()).isEqualTo(processInstance.getId());
            Assertions.assertThat(var.getProcessId()).isEqualTo(processInstance.getProcessId());
            Assertions.assertThat(var.getVariableInstanceId()).isEqualTo("list");
        }

        Assertions.assertThat(variableValues).contains("john", "mary", "peter");
        Assertions.assertThat(variableIds).contains("list[0]", "list[1]", "list[2]");

        logService.clear();
        processInstances = logService.findProcessInstances("com.sample.ruleflow3");
        logService.dispose();
        Assertions.assertThat(processInstances).isNullOrEmpty();
    }

    public KieSession createSession(KieBase kbase, Environment env) {

        KieSession session = createKieSession(kbase, env);
        session.getWorkItemManager().registerWorkItemHandler("Human Task", new SystemOutWorkItemHandler());
        return session;
    }

    private void startHornetQServer() throws Exception {
        jmsServer = new EmbeddedJMS();
        jmsServer.start();
        logger.debug("Started Embedded JMS Server");

        XAConnectionFactory connectionFactory = (XAConnectionFactory) jmsServer.lookup("ConnectionFactory");

        new InitialContext().rebind("java:comp/UserTransaction", com.arjuna.ats.jta.UserTransaction.userTransaction());
        new InitialContext().rebind("java:comp/TransactionManager", com.arjuna.ats.jta.TransactionManager.transactionManager());
        new InitialContext().rebind("java:comp/TransactionSynchronizationRegistry", new com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple());
        factory = new ConnectionFactoryProxy(connectionFactory, new TransactionHelperImpl(com.arjuna.ats.jta.TransactionManager.transactionManager()));

        queue = (Queue) jmsServer.lookup("/queue/exampleQueue");
    }

    private void stopHornetQServer() throws Exception {
        jmsServer.stop();
        jmsServer = null;
    }

    private class MessageReceiver {

        void receiveAndProcess(Queue queue, EntityManagerFactory entityManagerFactory, long waitTime, int countDown) throws Exception {

            Connection qconnetion = factory.createConnection();
            Session qsession = qconnetion.createSession(true, QueueSession.AUTO_ACKNOWLEDGE);
            MessageConsumer consumer = qsession.createConsumer(queue);
            qconnetion.start();

            CountDownLatch latch = new CountDownLatch(countDown);
            AsyncAuditLogReceiver rec = new AsyncAuditLogReceiver(entityManagerFactory) {

                @Override
                public void onMessage(Message message) {
                    try {
                        // need to use transaction so entity manager will persist logs
                        UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction");
                        ut.begin();
                        super.onMessage(message);
                        ut.commit();
                        latch.countDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            };
            consumer.setMessageListener(rec);
            Assertions.assertThat(latch.await(waitTime, TimeUnit.MILLISECONDS)).isTrue();

            consumer.close();
            qsession.close();
            qconnetion.close();

        }

        public List<Message> receive(Queue queue) throws Exception {
            List<Message> messages = new ArrayList<Message>();

            Connection qconnetion = factory.createConnection();
            Session qsession = qconnetion.createSession(true, QueueSession.AUTO_ACKNOWLEDGE);
            MessageConsumer consumer = qsession.createConsumer(queue);
            qconnetion.start();

            Message m = null;

            while ((m = consumer.receiveNoWait()) != null) {
                messages.add(m);
            }
            consumer.close();
            qsession.close();
            qconnetion.close();

            return messages;
        }
    }
}

redpajama_set_name: RedPajamaGithub


text:
module FreeboxApi

  module Configuration

    module Dhcp

      def self.getConfig(session)
        session.http_call('get', '/dhcp/config/')
      end

      def self.updateConfig(session, value)
        session.http_call('put', '/dhcp/config/', value)
      end

      def self.static_leases(session)
        session.http_call('get', '/dhcp/static_lease/')
      end

      def self.dynamic_leases(session)
        session.http_call('get', '/dhcp/dynamic_lease/')
      end

      class StaticLease

        def initialize(session, id)
          @session = session
          @id = id
	end

        def self.show(session, id)
          session.http_call('get', "/dhcp/static_lease/#{id}")
        end

        def show
          self.class.show(@session, @id)
  	end

        def self.update(session, id, value = {})
          session.http_call('put', "/dhcp/static_lease/#{id}", value)
        end

        def update(value = {})
          self.class.update(@session, @id, value)
        end

        def self.delete(session, id)
          session.http_call('delete', "/dhcp/static_lease/#{id}")
        end

        def delete
          self.class.delete(@session, @id)
        end

        def self.create(session, value)
          session.http_call('post', '/dhcp/static_lease/', value)
        end

        def create(value)
          self.class.create(@session, value)
        end

      end

    end

  end

end


text:
package me.vilsol.nmswrapper.wraps.unparsed;

import me.vilsol.nmswrapper.*;
import me.vilsol.nmswrapper.reflections.*;
import me.vilsol.nmswrapper.wraps.*;

@ReflectiveClass(name = "ItemArmor")
public class NMSItemArmor extends NMSItem {

    public NMSItemArmor(Object nmsObject){
        super(nmsObject);
    }

    public NMSItemArmor(NMSEnumArmorMaterial enumArmorMaterial, int i, int i1){
        super("ItemArmor", new Object[]{NMSEnumArmorMaterial.class, int.class, int.class}, new Object[]{enumArmorMaterial, i, i1});
    }

    /**
     * TODO Find correct name
     * @see net.minecraft.server.v1_9_R1.ItemArmor#a(net.minecraft.server.v1_9_R1.ItemStack, net.minecraft.server.v1_9_R1.World, net.minecraft.server.v1_9_R1.EntityHuman)
     */
    @ReflectiveMethod(name = "a", types = {NMSItemStack.class, NMSWorld.class, NMSEntityHuman.class})
    public NMSItemStack a(NMSItemStack itemStack, NMSWorld world, NMSEntityHuman entityHuman){
        return new NMSItemStack(NMSWrapper.getInstance().exec(nmsObject, itemStack, world, entityHuman));
    }

    /**
     * TODO Find correct name
     * @see net.minecraft.server.v1_9_R1.ItemArmor#b(net.minecraft.server.v1_9_R1.ItemStack, int)
     */
    @ReflectiveMethod(name = "b", types = {NMSItemStack.class, int.class})
    public void b(NMSItemStack itemStack, int i){
        NMSWrapper.getInstance().exec(nmsObject, itemStack, i);
    }

    /**
     * TODO Find correct name
     * @see net.minecraft.server.v1_9_R1.ItemArmor#c(net.minecraft.server.v1_9_R1.ItemStack)
     */
    @ReflectiveMethod(name = "c", types = {NMSItemStack.class})
    public void c(NMSItemStack itemStack){
        NMSWrapper.getInstance().exec(nmsObject, itemStack);
    }

    /**
     * TODO Find correct name
     * @see net.minecraft.server.v1_9_R1.ItemArmor#d_(net.minecraft.server.v1_9_R1.ItemStack)
     */
    @ReflectiveMethod(name = "d_", types = {NMSItemStack.class})
    public boolean d_(NMSItemStack itemStack){
        return (boolean) NMSWrapper.getInstance().exec(nmsObject, itemStack);
    }

    /**
     * TODO Find correct name
     * @see net.minecraft.server.v1_9_R1.ItemArmor#x_()
     */
    @ReflectiveMethod(name = "x_", types = {})
    public NMSEnumArmorMaterial x_(){
        return new NMSEnumArmorMaterial(NMSWrapper.getInstance().exec(nmsObject));
    }

}


text:
package org.assertj.core.api.recursive.comparison;

import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.assertj.core.api.recursive.comparison.ComparisonDifference.rootComparisonDifference;
import static org.assertj.core.api.recursive.comparison.DualValue.DEFAULT_ORDERED_COLLECTION_TYPES;
import static org.assertj.core.internal.Objects.getDeclaredFieldsIncludingInherited;
import static org.assertj.core.util.IterableUtil.sizeOf;
import static org.assertj.core.util.IterableUtil.toCollection;
import static org.assertj.core.util.Lists.list;
import static org.assertj.core.util.Sets.newHashSet;
import static org.assertj.core.util.Strings.join;
import static org.assertj.core.util.introspection.PropertyOrFieldSupport.COMPARISON;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import org.assertj.core.internal.DeepDifference;

/**
 * Based on {@link DeepDifference} but takes a {@link RecursiveComparisonConfiguration}, {@link DeepDifference}
 * being itself based on the deep equals implementation of https://github.com/jdereg/java-util
 *
 * @author John DeRegnaucourt (john@cedarsoftware.com)
 * @author Pascal Schumacher
 */
public class RecursiveComparisonDifferenceCalculator {

  private static final String DIFFERENT_ACTUAL_AND_EXPECTED_FIELD_TYPES = "expected field is %s but actual field is not (%s)";
  private static final String ACTUAL_NOT_ORDERED_COLLECTION = "expected field is an ordered collection but actual field is not (%s), ordered collections are: "
                                                              + describeOrderedCollectionTypes();

  private static final String VALUE_FIELD_NAME = "value";
  private static final String STRICT_TYPE_ERROR = "the fields are considered different since the comparison enforces strict type check and %s is not a subtype of %s";
  private static final String DIFFERENT_SIZE_ERROR = "actual and expected values are %s of different size, actual size=%s when expected size=%s";
  private static final String MISSING_FIELDS = "%s can't be compared to %s as %s does not declare all %s fields, it lacks these: %s";
  private static final Map<Class<?>, Boolean> customEquals = new ConcurrentHashMap<>();
  private static final Map<Class<?>, Boolean> customHash = new ConcurrentHashMap<>();

  private static class ComparisonState {
    Set<DualValue> visitedDualValues;
    List<ComparisonDifference> differences = new ArrayList<>();
    Deque<DualValue> dualValuesToCompare;
    RecursiveComparisonConfiguration recursiveComparisonConfiguration;

    public ComparisonState(Set<DualValue> visited, RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
      this.visitedDualValues = visited;
      this.dualValuesToCompare = new DualValueDeque(recursiveComparisonConfiguration);
      this.recursiveComparisonConfiguration = recursiveComparisonConfiguration;
    }

    void addDifference(DualValue dualValue) {
      differences.add(new ComparisonDifference(dualValue.getPath(), dualValue.actual, dualValue.expected));
    }

    void addDifference(DualValue dualValue, String description, Object... args) {
      differences.add(new ComparisonDifference(dualValue.getPath(), dualValue.actual, dualValue.expected,
                                               format(description, args)));
    }

    public List<ComparisonDifference> getDifferences() {
      Collections.sort(differences);
      return differences;
    }

    public boolean hasDualValuesToCompare() {
      return !dualValuesToCompare.isEmpty();
    }

    public DualValue pickDualValueToCompare() {
      final DualValue dualValue = dualValuesToCompare.removeFirst();
      visitedDualValues.add(dualValue);
      return dualValue;
    }

    private void registerForComparison(DualValue dualValue) {
      if (!visitedDualValues.contains(dualValue)) dualValuesToCompare.addFirst(dualValue);
    }

    private void initDualValuesToCompare(Object actual, Object expected, List<String> parentPath) {
      boolean isRootObject = parentPath.isEmpty();
      List<String> currentPath = new ArrayList<>(parentPath);
      DualValue dualValue = new DualValue(currentPath, actual, expected);
      if (actual != null && expected != null && !isContainerType(actual) && !isContainerType(expected)
          && (isRootObject || !hasCustomComparator(dualValue, recursiveComparisonConfiguration))) {
        // disregard the equals method and start comparing fields
        Set<String> actualFieldsNameSet = getNonIgnoredFieldNames(actual.getClass(), parentPath,
                                                                  recursiveComparisonConfiguration);
        if (!actualFieldsNameSet.isEmpty()) {
          Set<String> expectedFieldsNameSet = getFieldsNames(expected.getClass());
          if (!expectedFieldsNameSet.containsAll(actualFieldsNameSet)) {
            dualValuesToCompare.addFirst(dualValue);
          } else {
            for (String fieldName : actualFieldsNameSet) {
              List<String> fieldPath = new ArrayList<>(currentPath);
              fieldPath.add(fieldName);
              DualValue fieldDualKey = new DualValue(fieldPath,
                                                     COMPARISON.getSimpleValue(fieldName, actual),
                                                     COMPARISON.getSimpleValue(fieldName, expected));
              dualValuesToCompare.addFirst(fieldDualKey);
            }
          }
        } else {
          dualValuesToCompare.addFirst(dualValue);
        }
      } else {
        dualValuesToCompare.addFirst(dualValue);
      }
      // need to remove already visited fields pair to avoid infinite recursion in case
      // parent -> set{child} with child having a reference back to parent
      // it occurs to unordered collection where we compare all possible combination of the collection elements recursively
      dualValuesToCompare.removeAll(visitedDualValues);
    }

  }

  /**
   * Compare two objects for differences by doing a 'deep' comparison. This will traverse the
   * Object graph and perform either a field-by-field comparison on each
   * object (if not .equals() method has been overridden from Object), or it
   * will call the customized .equals() method if it exists.
   * <p>
   *
   * This method handles cycles correctly, for example A->B->C->A.
   * Suppose a and a' are two separate instances of the A with the same values
   * for all fields on A, B, and C. Then a.deepEquals(a') will return an empty list. It
   * uses cycle detection storing visited objects in a Set to prevent endless
   * loops.
   *
   * @param actual Object one to compare
   * @param expected Object two to compare
   * @param recursiveComparisonConfiguration the recursive comparison configuration
   * @return the list of differences found or an empty list if objects are equivalent.
   *         Equivalent means that all field values of both subgraphs are the same,
   *         either at the field level or via the respectively encountered overridden
   *         .equals() methods during traversal.
   */
  public List<ComparisonDifference> determineDifferences(Object actual, Object expected,
                                                         RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
    if (recursiveComparisonConfiguration.isInStrictTypeCheckingMode() && expectedTypeIsNotSubtypeOfActualType(actual, expected)) {
      return list(expectedAndActualTypeDifference(actual, expected));
    }
    List<String> rootPath = list();
    final Set<DualValue> visited = new HashSet<>();
    return determineDifferences(actual, expected, rootPath, visited, recursiveComparisonConfiguration);
  }

  // TODO keep track of ignored fields in an RecursiveComparisonExecution class ?

  private static List<ComparisonDifference> determineDifferences(Object actual, Object expected, List<String> parentPath,
                                                                 Set<DualValue> visited,
                                                                 RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
    ComparisonState comparisonState = new ComparisonState(visited, recursiveComparisonConfiguration);
    comparisonState.initDualValuesToCompare(actual, expected, parentPath);

    while (comparisonState.hasDualValuesToCompare()) {
      final DualValue dualValue = comparisonState.pickDualValueToCompare();

      final List<String> currentPath = dualValue.getPath();
      final Object actualFieldValue = dualValue.actual;
      final Object expectedFieldValue = dualValue.expected;

      if (actualFieldValue == expectedFieldValue) continue;

      // Custom comparators take precedence over all other types of comparison
      if (hasCustomComparator(dualValue, recursiveComparisonConfiguration)) {
        if (!propertyOrFieldValuesAreEqual(dualValue, recursiveComparisonConfiguration)) comparisonState.addDifference(dualValue);
        // since we used a custom comparator we don't need to inspect the nested fields any further
        continue;
      }

      if (actualFieldValue == null || expectedFieldValue == null) {
        // one of the value is null while the other is not as we already know that actualFieldValue != expectedFieldValue
        comparisonState.addDifference(dualValue);
        continue;
      }

      // TODO move hasFieldTypesDifference check into each compareXXX

      if (dualValue.isExpectedFieldAnArray()) {
        compareArrays(dualValue, comparisonState);
        continue;
      }

      // we compare ordered collections specifically as to be matching, each pair of elements at a given index must match.
      // concretely we compare: (col1[0] vs col2[0]), (col1[1] vs col2[1])...(col1[n] vs col2[n])
      if (dualValue.isExpectedFieldAnOrderedCollection() && !recursiveComparisonConfiguration.shouldIgnoreCollectionOrder(dualValue)) {
        compareOrderedCollections(dualValue, comparisonState);
        continue;
      }

      if (dualValue.isExpectedFieldAnIterable()) {
        compareIterables(dualValue, comparisonState);
        continue;
      }

      if (dualValue.isExpectedFieldAnOptional()) {
        compareOptional(dualValue, comparisonState);
        continue;
      }

      // Compare two SortedMaps taking advantage of the fact that these Maps can be compared in O(N) time due to their ordering
      if (dualValue.isExpectedFieldASortedMap()) {
        compareSortedMap(dualValue, comparisonState);
        continue;
      }

      // Compare two Unordered Maps. This is a slightly more expensive comparison because order cannot be assumed, therefore a
      // temporary Map must be created, however the comparison still runs in O(N) time.
      if (dualValue.isExpectedFieldAMap()) {
        compareUnorderedMap(dualValue, comparisonState);
        continue;
      }

      if (!recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(dualValue)
          && hasCustomEquals(actualFieldValue.getClass())) {
        if (!actualFieldValue.equals(expectedFieldValue)) {
          comparisonState.addDifference(dualValue);
        }
        continue;
      }

      Class<?> expectedFieldClass = expectedFieldValue.getClass();
      if (recursiveComparisonConfiguration.isInStrictTypeCheckingMode() && expectedTypeIsNotSubtypeOfActualType(dualValue)) {
        comparisonState.addDifference(dualValue, STRICT_TYPE_ERROR, expectedFieldClass.getName(),
                                      actualFieldValue.getClass().getName());
        continue;
      }

      Set<String> actualFieldsNames = getNonIgnoredFieldNames(actualFieldValue.getClass(), currentPath,
                                                              recursiveComparisonConfiguration);
      Set<String> expectedFieldsNames = getFieldsNames(expectedFieldClass);
      if (!expectedFieldsNames.containsAll(actualFieldsNames)) {
        Set<String> actualFieldsNamesNotInExpected = newHashSet(actualFieldsNames);
        actualFieldsNamesNotInExpected.removeAll(expectedFieldsNames);
        String missingFields = actualFieldsNamesNotInExpected.toString();
        String expectedClassName = expectedFieldClass.getName();
        String actualClassName = actualFieldValue.getClass().getName();
        String missingFieldsDescription = format(MISSING_FIELDS, actualClassName, expectedClassName,
                                                 expectedFieldClass.getSimpleName(),
                                                 actualFieldValue.getClass().getSimpleName(), missingFields);
        comparisonState.addDifference(dualValue, missingFieldsDescription);
      } else {
        for (String fieldName : actualFieldsNames) {
          List<String> path = new ArrayList<>(currentPath);
          path.add(fieldName);
          DualValue newDualValue = new DualValue(path,
                                                 COMPARISON.getSimpleValue(fieldName, actualFieldValue),
                                                 COMPARISON.getSimpleValue(fieldName, expectedFieldValue));
          comparisonState.registerForComparison(newDualValue);
        }
      }
    }
    return comparisonState.getDifferences();
  }

  private static boolean hasCustomComparator(DualValue dualValue,
                                             RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
    String fieldName = dualValue.getConcatenatedPath();
    if (recursiveComparisonConfiguration.hasComparatorForField(fieldName)) return true;
    // we know that dualValue.actual != dualValue.expected at this point, so one the value is not null
    Class<?> valueType = dualValue.actual != null ? dualValue.actual.getClass() : dualValue.expected.getClass();
    return recursiveComparisonConfiguration.hasComparatorForType(valueType);
  }

  private static Set<String> getNonIgnoredFieldNames(Class<?> actualClass, List<String> parentPath,
                                                     RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
    String parentConcatenatedPath = join(parentPath).with(".");
    // need to ignore fields according to the configuration
    return getFieldsNames(actualClass).stream()
                                      .filter(recursiveComparisonConfiguration.shouldKeepField(parentConcatenatedPath))
                                      .collect(toSet());
  }

  private static Set<String> getFieldsNames(Class<?> clazz) {
    Collection<Field> fields = getDeclaredFieldsIncludingInherited(clazz);
    Set<String> fieldNames = new LinkedHashSet<>();
    for (Field field : fields) {
      fieldNames.add(field.getName());
    }
    return fieldNames;
  }

  private static boolean isContainerType(Object o) {
    return o instanceof Collection || o instanceof Map;
  }

  private static void compareArrays(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldAnArray()) {
      // at the moment we only allow comparing arrays with arrays but we might allow comparing to collections later on
      // but only if we are not in strict type mode.
      comparisonState.addDifference(dualValue, differentTypeErrorMessage(dualValue, "an array"));

      return;
    }
    // both values in dualValue are arrays
    int actualArrayLength = Array.getLength(dualValue.actual);
    int expectedArrayLength = Array.getLength(dualValue.expected);
    if (actualArrayLength != expectedArrayLength) {
      comparisonState.addDifference(dualValue, DIFFERENT_SIZE_ERROR, "arrays", actualArrayLength, expectedArrayLength);
      // no need to inspect elements, arrays are not equal as they don't have the same size
      return;
    }
    // register each pair of actual/expected elements for recursive comparison
    List<String> arrayFieldPath = dualValue.getPath();
    for (int i = 0; i < actualArrayLength; i++) {
      Object actualElement = Array.get(dualValue.actual, i);
      Object expectedElement = Array.get(dualValue.expected, i);
      // TODO add [i] to the path ?
      comparisonState.registerForComparison(new DualValue(arrayFieldPath, actualElement, expectedElement));
    }
  }

  /*
   * Deeply compare two Collections that must be same length and in same order.
   */
  private static void compareOrderedCollections(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldAnOrderedCollection()) {
      // at the moment if expected is an ordered collection then actual should also be one
      comparisonState.addDifference(dualValue, ACTUAL_NOT_ORDERED_COLLECTION, dualValue.actual.getClass().getCanonicalName());
      return;
    }

    Collection<?> actualCollection = (Collection<?>) dualValue.actual;
    Collection<?> expectedCollection = (Collection<?>) dualValue.expected;
    if (actualCollection.size() != expectedCollection.size()) {
      comparisonState.addDifference(dualValue, DIFFERENT_SIZE_ERROR,
                                    "collections", actualCollection.size(), expectedCollection.size());
      // no need to inspect elements, arrays are not equal as they don't have the same size
      return;
    }
    // register pair of elements with same index for later comparison as we compare elements in order
    Iterator<?> expectedIterator = expectedCollection.iterator();
    List<String> path = dualValue.getPath();
    actualCollection.stream()
                    .map(element -> new DualValue(path, element, expectedIterator.next()))
                    .forEach(comparisonState::registerForComparison);
  }

  private static String differentTypeErrorMessage(DualValue dualValue, String actualTypeDescription) {
    return format(DIFFERENT_ACTUAL_AND_EXPECTED_FIELD_TYPES,
                  actualTypeDescription, dualValue.actual.getClass().getCanonicalName());
  }

  private static void compareIterables(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldAnIterable()) {
      // at the moment we only compare iterable with iterables (but we might allow arrays too)
      comparisonState.addDifference(dualValue, differentTypeErrorMessage(dualValue, "an iterable"));
      return;
    }
    Iterable<?> actual = (Iterable<?>) dualValue.actual;
    Iterable<?> expected = (Iterable<?>) dualValue.expected;
    int actualSize = sizeOf(actual);
    int expectedSize = sizeOf(expected);
    if (actualSize != expectedSize) {
      comparisonState.addDifference(dualValue, DIFFERENT_SIZE_ERROR, "collections", actualSize, expectedSize);
      // no need to inspect elements, iterables are not equal as they don't have the same size
      return;
      // TODO instead we could register the diff between expected and actual that is:
      // - unexpected actual elements (the ones not matching any expected)
      // - expected elements not found in actual.
    }
    List<String> path = dualValue.getPath();
    // copy expected as we will remove elements found in actual
    Collection<?> expectedCopy = new LinkedList<>(toCollection(expected));
    for (Object actualElement : actual) {
      // compare recursively actualElement to all remaining expected elements
      Iterator<?> expectedIterator = expectedCopy.iterator();
      while (expectedIterator.hasNext()) {
        Object expectedElement = expectedIterator.next();
        // we need to get the currently visited dual values otherwise a cycle would cause an infinite recursion.
        List<ComparisonDifference> differences = determineDifferences(actualElement, expectedElement, path,
                                                                      comparisonState.visitedDualValues,
                                                                      comparisonState.recursiveComparisonConfiguration);
        if (differences.isEmpty()) {
          // we found an element in expected matching actualElement, we must remove it as if actual matches expected
          // it means for each actual element there is one and only matching expected element.
          expectedIterator.remove();
          // jump to next actual element check
          break;
        }
      }
    }

    // expectedCopy not empty = there was at least one actual element not matching any expected elements.
    if (!expectedCopy.isEmpty()) comparisonState.addDifference(dualValue);
    // TODO instead we could register the diff between expected and actual that is:
    // - unexpected actual elements (the ones not matching any expected)
    // - expected elements not found in actual.
  }

  private static <K, V> void compareSortedMap(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldASortedMap()) {
      // at the moment we only compare iterable with iterables (but we might allow arrays too)
      comparisonState.addDifference(dualValue, differentTypeErrorMessage(dualValue, "a sorted map"));
      return;
    }

    Map<?, ?> actualMap = (Map<?, ?>) dualValue.actual;
    @SuppressWarnings("unchecked")
    Map<K, V> expectedMap = (Map<K, V>) dualValue.expected;
    if (actualMap.size() != expectedMap.size()) {
      comparisonState.addDifference(dualValue, DIFFERENT_SIZE_ERROR, "sorted maps", actualMap.size(), expectedMap.size());
      // no need to inspect entries, maps are not equal as they don't have the same size
      return;
      // TODO instead we could register the diff between expected and actual that is:
      // - unexpected actual entries (the ones not matching any expected)
      // - expected entries not found in actual.
    }
    List<String> path = dualValue.getPath();
    Iterator<Map.Entry<K, V>> expectedMapEntries = expectedMap.entrySet().iterator();
    for (Map.Entry<?, ?> actualEntry : actualMap.entrySet()) {
      Map.Entry<?, ?> expectedEntry = expectedMapEntries.next();
      // Must split the Key and Value so that Map.Entry's equals() method is not used.
      comparisonState.registerForComparison(new DualValue(path, actualEntry.getKey(), expectedEntry.getKey()));
      comparisonState.registerForComparison(new DualValue(path, actualEntry.getValue(), expectedEntry.getValue()));
    }
  }

  private static void compareUnorderedMap(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldAMap()) {
      comparisonState.addDifference(dualValue, differentTypeErrorMessage(dualValue, "a map"));
      return;
    }

    Map<?, ?> actualMap = (Map<?, ?>) dualValue.actual;
    Map<?, ?> expectedMap = (Map<?, ?>) dualValue.expected;
    if (actualMap.size() != expectedMap.size()) {
      comparisonState.addDifference(dualValue, DIFFERENT_SIZE_ERROR, "maps", actualMap.size(), expectedMap.size());
      // no need to inspect entries, maps are not equal as they don't have the same size
      return;
      // TODO instead we could register the diff between expected and actual that is:
      // - unexpected actual entries (the ones not matching any expected)
      // - expected entries not found in actual.
    }

    Map<Integer, Map.Entry<?, ?>> fastLookup = expectedMap.entrySet().stream()
                                                          .collect(toMap(entry -> deepHashCode(entry.getKey()), entry -> entry));
    List<String> path = dualValue.getPath();
    for (Map.Entry<?, ?> actualEntry : actualMap.entrySet()) {
      int deepHashCode = deepHashCode(actualEntry.getKey());
      if (!fastLookup.containsKey(deepHashCode)) {
        // TODO add description of the entry in actual not found in expected.
        comparisonState.addDifference(dualValue);
        return;
      }
      Map.Entry<?, ?> expectedEntry = fastLookup.get(deepHashCode);
      // Must split the Key and Value so that Map.Entry's equals() method is not used.
      comparisonState.registerForComparison(new DualValue(path, actualEntry.getKey(), expectedEntry.getKey()));
      comparisonState.registerForComparison(new DualValue(path, actualEntry.getValue(), expectedEntry.getValue()));
    }
  }

  private static void compareOptional(DualValue dualValue, ComparisonState comparisonState) {
    if (!dualValue.isActualFieldAnOptional()) {
      // at the moment we only allow comparing arrays with arrays but we might allow comparing to collections later on
      // but only if we are not in strict type mode.
      comparisonState.addDifference(dualValue, differentTypeErrorMessage(dualValue, "an Optional"));
      return;
    }
    Optional<?> actual = (Optional<?>) dualValue.actual;
    Optional<?> expected = (Optional<?>) dualValue.expected;
    if (actual.isPresent() != expected.isPresent()) {
      comparisonState.addDifference(dualValue);
      return;
    }
    // either both are empty or present
    if (!actual.isPresent()) return; // both optional are empty => end of the comparison
    // both are present, we have to compare their values recursively
    Object value1 = actual.get();
    Object value2 = expected.get();
    // we add VALUE_FIELD_NAME to the path since we register Optional.value fields.
    List<String> path = new ArrayList<>(dualValue.getPath());
    path.add(VALUE_FIELD_NAME);
    comparisonState.registerForComparison(new DualValue(path, value1, value2));
  }

  /**
   * Determine if the passed in class has a non-Object.equals() method. This
   * method caches its results in static ConcurrentHashMap to benefit
   * execution performance.
   *
   * @param c Class to check.
   * @return true, if the passed in Class has a .equals() method somewhere
   *         between itself and just below Object in it's inheritance.
   */
  static boolean hasCustomEquals(Class<?> c) {
    if (customEquals.containsKey(c)) {
      return customEquals.get(c);
    }

    Class<?> origClass = c;
    while (!Object.class.equals(c)) {
      try {
        c.getDeclaredMethod("equals", Object.class);
        customEquals.put(origClass, true);
        return true;
      } catch (Exception ignored) {}
      c = c.getSuperclass();
    }
    customEquals.put(origClass, false);
    return false;
  }

  /**
   * Get a deterministic hashCode (int) value for an Object, regardless of
   * when it was created or where it was loaded into memory. The problem with
   * java.lang.Object.hashCode() is that it essentially relies on memory
   * location of an object (what identity it was assigned), whereas this
   * method will produce the same hashCode for any object graph, regardless of
   * how many times it is created.<br>
   * <br>
   *
   * This method will handle cycles correctly (A->B->C->A). In this
   * case, Starting with object A, B, or C would yield the same hashCode. If
   * an object encountered (root, subobject, etc.) has a hashCode() method on
   * it (that is not Object.hashCode()), that hashCode() method will be called
   * and it will stop traversal on that branch.
   *
   * @param obj Object who hashCode is desired.
   * @return the 'deep' hashCode value for the passed in object.
   */
  static int deepHashCode(Object obj) {
    Set<Object> visited = new HashSet<>();
    LinkedList<Object> stack = new LinkedList<>();
    stack.addFirst(obj);
    int hash = 0;

    while (!stack.isEmpty()) {
      obj = stack.removeFirst();
      if (obj == null || visited.contains(obj)) {
        continue;
      }

      visited.add(obj);

      if (obj.getClass().isArray()) {
        int len = Array.getLength(obj);
        for (int i = 0; i < len; i++) {
          stack.addFirst(Array.get(obj, i));
        }
        continue;
      }

      if (obj instanceof Collection) {
        stack.addAll(0, (Collection<?>) obj);
        continue;
      }

      if (obj instanceof Map) {
        stack.addAll(0, ((Map<?, ?>) obj).keySet());
        stack.addAll(0, ((Map<?, ?>) obj).values());
        continue;
      }

      if (obj instanceof Double || obj instanceof Float) {
        // just take the integral value for hashcode
        // equality tests things more comprehensively
        stack.add(Math.round(((Number) obj).doubleValue()));
        continue;
      }

      if (hasCustomHashCode(obj.getClass())) {
        // A real hashCode() method exists, call it.
        hash += obj.hashCode();
        continue;
      }

      Collection<Field> fields = getDeclaredFieldsIncludingInherited(obj.getClass());
      for (Field field : fields) {
        stack.addFirst(COMPARISON.getSimpleValue(field.getName(), obj));
      }
    }
    return hash;
  }

  /**
   * Determine if the passed in class has a non-Object.hashCode() method. This
   * method caches its results in static ConcurrentHashMap to benefit
   * execution performance.
   *
   * @param c Class to check.
   * @return true, if the passed in Class has a .hashCode() method somewhere
   *         between itself and just below Object in it's inheritance.
   */
  static boolean hasCustomHashCode(Class<?> c) {
    Class<?> origClass = c;
    if (customHash.containsKey(c)) {
      return customHash.get(c);
    }

    while (!Object.class.equals(c)) {
      try {
        c.getDeclaredMethod("hashCode");
        customHash.put(origClass, true);
        return true;
      } catch (Exception ignored) {}
      c = c.getSuperclass();
    }
    customHash.put(origClass, false);
    return false;
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private static boolean propertyOrFieldValuesAreEqual(DualValue dualValue,
                                                       RecursiveComparisonConfiguration recursiveComparisonConfiguration) {
    final String fieldName = dualValue.getConcatenatedPath();
    final Object actualFieldValue = dualValue.actual;
    final Object expectedFieldValue = dualValue.expected;
    // no need to look into comparators if objects are the same
    if (actualFieldValue == expectedFieldValue) return true;
    // check field comparators as they take precedence over type comparators
    Comparator fieldComparator = recursiveComparisonConfiguration.getComparatorForField(fieldName);
    if (fieldComparator != null) return fieldComparator.compare(actualFieldValue, expectedFieldValue) == 0;
    // check if a type comparators exist for the field type
    Class fieldType = actualFieldValue != null ? actualFieldValue.getClass() : expectedFieldValue.getClass();
    Comparator typeComparator = recursiveComparisonConfiguration.getComparatorForType(fieldType);
    if (typeComparator != null) return typeComparator.compare(actualFieldValue, expectedFieldValue) == 0;
    // default comparison using equals
    return org.assertj.core.util.Objects.areEqual(actualFieldValue, expectedFieldValue);
  }

  private static ComparisonDifference expectedAndActualTypeDifference(Object actual, Object expected) {
    String additionalInformation = format("actual and expected are considered different since the comparison enforces strict type check and expected type %s is not a subtype of actual type %s",
                                          expected.getClass().getName(), actual.getClass().getName());
    return rootComparisonDifference(actual, expected, additionalInformation);
  }

  // TODO should be checking actual!
  private static boolean expectedTypeIsNotSubtypeOfActualType(DualValue dualField) {
    return expectedTypeIsNotSubtypeOfActualType(dualField.actual, dualField.expected);
  }

  private static boolean expectedTypeIsNotSubtypeOfActualType(Object actual, Object expected) {
    return !actual.getClass().isAssignableFrom(expected.getClass());
  }

  private static String describeOrderedCollectionTypes() {
    return Stream.of(DEFAULT_ORDERED_COLLECTION_TYPES)
                 .map(Class::getName)
                 .collect(joining(", ", "[", "]"));
  }
}


text:
> Django generator with live-reload and front-end support

## Installation

First, install [Yeoman](http://yeoman.io) and generator-django-reload using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).

```bash
npm install -g yo
npm install -g generator-django-reload
```

Then generate your new project:

```bash
yo django-reload
```

## Gulp task

This generator uses gulp to manage the development tasks. The following task are available

### patch
Generate a new git tag for patch release

### feature
Generate a new git tag for minor release

### release
Generate a new git tag for major release

### styles-dev
Compiles the SASS files with sourceMap support to style.css. The css file is autoprefixed for the last 2 release of the major browsers and IE>8. Also concat normalize.css and external css installed via bower at the top of the css stack.

### styles
This does the same that styles-dev with the exception of no sourceMap.

### minify-css
This minify the css files with css optimizer.

### eslint-dev
This runs the eslinter and print the errors in the console without exiting.

### eslint
This runs the eslinter and exist if there is an error.

### uglify
Minify the js files with uglify.

### watch
This task watches the sass and js files and runs styles-dev and eslint-dev on modifications.

### clean
This task clean the assets dir.

### shell
This task runs the django shell

### migrate
This task runs the django migrations

### collectstatic
This task runs the django collectstatic

### makemigrations
This task runs the django makemigrations

### runserver
This task runs the django runserver with livereload support

### build
This task runs clean, styles, minify-css, eslint, uglify, migrate and collecstatic task to build the assets pipeline

### default
This task builds the development enviroment, runs the django server with livereload support and the watches the sass and js files.

## Tree structure
```
.
├── assets
├── bower.json
├── gulpfile.js
├── home
│   ├── apps.py
│   ├── __init__.py
│   ├── templates
│   │   └── home
│   │       └── index.html
│   └── views.py
├── manage.py
├── minihtml
│   ├── apps.py
│   ├── __init__.py
│   └── middleware.py
├── package.json
├── requirements
│   ├── base.txt
│   ├── development.txt
│   └── production.txt
├── requirements.txt
├── static
│   ├── css
│   ├── img
│   │   └── logo.png
│   ├── js
│   │   └── menu.js
│   └── scss
│       ├── base
│       │   ├── _base.scss
│       │   ├── _clearfix.scss
│       │   └── _reset.scss
│       ├── components
│       │   ├── _components.scss
│       │   ├── _main-menu.scss
│       │   └── _mask.scss
│       ├── helpers
│       │   ├── _helpers.scss
│       │   ├── _media.scss
│       │   └── _variables.scss
│       ├── layout
│       │   └── _layout.scss
│       ├── pages
│       │   └── _pages.scss
│       └── style.scss
├── templates
│   ├── 400.html
│   ├── 403.html
│   ├── 404.html
│   ├── 500.html
│   ├── base.html
│   ├── footer.html
│   └── header.html
└── project
    ├── __init__.py
    ├── settings
    │   ├── base.py
    │   ├── development.py
    │   ├── __init__.py
    │   └── production.py
    ├── urls.py
    └── wsgi.py
```

In the the static files are server from the assets dir. The home app servers the home page (index.html) and can be used to server all the static pages like about, contact, etc. Minihtml app minifies the html output, this is done to use percentage width in divs and reduce the page load. Requirements are distributed in development, test and production enviroments, by default production requirements are used.
Manage.py uses development settings by default and wsgi.py uses production settings. To use other setting just set DJANGO_SETTINGS_MODULE env to the required settings file.

## License

MIT © [Gildardo Adrian Maravilla Jacome]()

[npm-image]: https://badge.fury.io/js/generator-django-reload.svg
[npm-url]: https://npmjs.org/package/generator-django-reload
[travis-image]: https://travis-ci.org/gilmrjc/generator-django-reload.svg?branch=master
[travis-url]: https://travis-ci.org/gilmrjc/generator-django-reload
[daviddm-image]: https://david-dm.org/gilmrjc/generator-django-reload.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/gilmrjc/generator-django-reload


text:
require 'gcloud'

module IseshimaStore
  class Connection
    class << self
      attr_accessor :config
    end

    def self.configure(&block)
      self.config ||= OpenStruct.new
      yield(config)
    end

    def self.datastore
      Gcloud.datastore(config.project_id)
    end

    def self.current
      @current ||= Gcloud.datastore(config.project_id)
    end

    def self.clear_connection!
      @current = nil
    end
  end
end


text:
package io.mangoo.build;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.StartedProcess;
import org.zeroturnaround.exec.listener.ProcessListener;

/**
 * This is a refactored version of
 * RunClassInSeparateJvmMachine.java from the Ninja Web Framework
 *
 * Original source code can be found here:
 * https://github.com/ninjaframework/ninja/blob/develop/ninja-maven-plugin/src/main/java/ninja/build/DelayedRestartTrigger.java
 *
 * @author svenkubiak
 *
 */
public class Runner {
    private static final Logger LOG = LoggerFactory.getLogger(Runner.class);
    private OutputStream outputStream;
    private StartedProcess startedProcess;
    private final AtomicBoolean restarting;
    private final String mainClass;
    private final String classpath;
    private final File mavenBaseDir;

    public Runner(String mainClass, String classpath, File mavenBaseDir) {
        this.outputStream = System.out; //NOSONAR
        this.mainClass = mainClass;
        this.classpath = classpath;
        this.mavenBaseDir = mavenBaseDir;
        this.restarting = new AtomicBoolean(false);
    }

    public OutputStream getOutput() {
        return outputStream;
    }

    public void setOutput(OutputStream output) {
        this.outputStream = output;
    }

    public StartedProcess getActiveProcess() {
        return startedProcess;
    }

    public void setActiveProcess(StartedProcess activeProcess) {
        this.startedProcess = activeProcess;
    }

    public void restart() {
        synchronized (this) {
            restarting.set(true);
            try {
                if (this.startedProcess != null) {
                    this.startedProcess.getProcess().destroy();
                    this.startedProcess.getFuture().get();
                }
                this.startedProcess = startProcess();
            } catch (ExecutionException | InterruptedException | IOException e) {
                LOG.error("Something fishy happenend. Unable to cleanly restart!", e);
                LOG.error("You'll probably need to restart maven?");
            } finally {
                restarting.set(false);
            }
        }
    }

    private StartedProcess startProcess() throws IOException {
        ProcessExecutor processExecutor = buildProcessExecutor();
        return processExecutor.start();
    }

    @SuppressWarnings("all")
    private ProcessExecutor buildProcessExecutor() {
        List<String> commandLine = new ArrayList<>();
        String javaHome = System.getProperty("java.home");
        String javaBin = javaHome + File.separator + "bin" + File.separator + "java";

        commandLine.add(javaBin);
        commandLine.add("-Dapplication.mode=dev");
        commandLine.add("-cp");
        commandLine.add(classpath);
        commandLine.add(mainClass);

        return new ProcessExecutor(commandLine)
            .directory(mavenBaseDir)
            .destroyOnExit()
            .addListener(new ProcessListener() {
                @Override
                public void afterStop(Process process) {
                    if (!restarting.get()) {
                        LOG.error("JVM process terminated (next file change will attempt to restart it)");
                    }
                }
            })
            .redirectErrorStream(true)
            .redirectOutput(this.outputStream);
    }
}