ensai.RMQtestKmeans.java Source code

Java tutorial

Introduction

Here is the source code for ensai.RMQtestKmeans.java

Source

package ensai;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.flink.api.common.functions.FilterFunction;

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

//TODO virer import
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.RichFilterFunction;
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.api.java.tuple.Tuple4;
import org.apache.flink.api.java.tuple.Tuple5;
import org.apache.flink.api.java.tuple.Tuple6;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.metrics.Counter;
import org.apache.flink.metrics.Meter;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.RichWindowFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
import org.apache.flink.streaming.connectors.rabbitmq.RMQSink;
import org.apache.flink.streaming.connectors.rabbitmq.RMQSource;
import org.apache.flink.streaming.connectors.rabbitmq.common.RMQConnectionConfig;
import org.apache.flink.streaming.util.serialization.SimpleStringSchema;
import org.apache.flink.util.Collector;

import localKmeans.Cluster;
import localKmeans.KMeans;
import localKmeans.Point;

/**
 * This class represent a solution from ENSAI to the DEBS 2017 challenge.
 * @see http://www.debs2017.org/call-for-grand-challenge-solutions/ 
 * The github solution can be accessed at
 * @see https://github.com/kent930/PFE
 * 
 * <p>
 * To start the main class, you need to :
 *                              - start Rabbot MQ : invoke-rc.d rabbitmq-server start
 *                              - start flink 1.2.0 (from flink directory): ./bin/start-local.sh
 *                              - build the solution jar (from solution projet directory) : mvn clean install -Pbuild-jar
 *                              - start the main class (from flink directory) : bin/flink run -c ensai.RMQtestKmeans /home/minifranger/ensai_debs/PFE/target/debs-1.0-SNAPSHOT.jar 
 *                                - start the flink jobmanager output (from flink directory) : tail -f log/flink-*-jobmanager-*.out
 *                                - start the Send.java class (from Eclipse)
 */

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////// MAIN CLASS ////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public class RMQtestKmeans {

    /**
     * Le chemin du fichier metadata qui sert de propreties.
     * @see Metadata
     */
    static String cheminConfiguration = "/home/minifranger/ensai_debs/PFE/ressources/metadata";

    //TODO Rajouter javadoc
    public static int currentObsGroup;
    public static Map<Integer, Integer> mapObsSensors = new HashMap<Integer, Integer>();
    public static String currentTimestamp;
    public static String currentMachineType;
    public static int currentMachine;

    /**
     * Le numro de l'anomalie dans la sortie RDF. 
     * @see SortieTransformation
     */
    public static int numeroAnomalie = 0;

    /**
     * Le numro du timestamp dans la sortie RDF. 
     * @see SortieTransformation
     */
    public static int numeroTimestamp = 0;

    /**
     * La taille de la fentre sur laquelle appliquer l'algorithme KMean.
     * @see KMean
     */
    public static int countWindowSize = 10;

    /**
     * Le nombre de transitions utiliss pour le calcul d'une probabilit de transition.
     * @see Markov
     */
    public static int N = 5;

    /**
     * Le nombre maximal d'itration de l'algorithme KMean.
     * @see KMean
     */
    public static int maxIterationKMean = 50;

    //TODO Rajouter javadoc, changer nom variable et valeur de la variable
    //private final static String QUEUE_NAME2 = "voila";

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////// MAIN METHOD ///////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public static void main(String[] args) throws Exception {

        //TODO javadoc : expliquer ce bloc metadata et pourquoi !file.exists
        Metadata metadata = new Metadata();

        File file = new File(cheminConfiguration);

        if (!file.exists()) {
            metadata.readData();
        }

        /**
         * Cration de la map qui contient la nombre de clusters pour le KMean et le seuil de dtection d'anomalies pour chaque capteurs.
         * @see Metadata 
         */
        Map<Integer, Tuple2<Integer, Double>> mapSensors_ClustersSeuils = new HashMap<Integer, Tuple2<Integer, Double>>();
        mapSensors_ClustersSeuils = metadata.load();

        /**
         * Cration de l'environnement de streaming de Flink
         * @see https://ci.apache.org/projects/flink/flink-docs-release-1.2/dev/api_concepts.html
         */
        final StreamExecutionEnvironment environnementStream = StreamExecutionEnvironment.getExecutionEnvironment();

        //TODO javadoc, expliquer demarche, expliquer localhost, port, guest, guest
        final RMQConnectionConfig connectionConfig = new RMQConnectionConfig.Builder().setHost("localhost")
                .setPort(5672).setUserName("guest").setPassword("guest").setVirtualHost("/").build();

        //TODO Changer coucou dans tout le systme, et dans la javadoc en dessous, expliquer  true et simple schema
        /**
         * Cration du datastream grce  la source RabbitMQ
         * connectionConfig : Configuration de la connection de RabbitMQ
         * coucou : le nom de la queue RabbitMQ  consommer
         * true : 
         * @see StreamExecutionEnvironment.addSource()
         */
        final DataStream<String> streamFromRabbitMQ = environnementStream
                .addSource(new RMQSource<String>(connectionConfig, "coucou", true, // use correlation ids; can be false if only at-least-once is required
                        new SimpleStringSchema())); // deserialization schema to turn messages into Java objects
        /**
         * Application de la map LineSplitter
         * @see LineSplitter
         */
        DataStream<Tuple3<String, String, String>> streamAfterSplit = streamFromRabbitMQ
                .flatMap(new LineSplitter1()).flatMap(new LineSplitter2()).flatMap(new LineSplitter3())
                .flatMap(new LineSplitter4()).flatMap(new LineSplitter5());

        /**
         * Application de la flatMap InputAnalyze
         * @see InputAnalyze
         */
        DataStream<Tuple4<Integer, Integer, Float, String>> streamAfterAnalyze = streamAfterSplit
                .flatMap(new InputAnalyze(mapSensors_ClustersSeuils));

        //streamAfterAnalyze.print();
        /**
         * Application du keyBy pour sparer le stream selon la valeur du couple (field0, field1) de chaque tuple du stream
         * Mise en place d'une window glissante de taille countWindowSize. L'apparition de chaque nouveau input trigger la fonction apply.
         * @see https://ci.apache.org/projects/flink/flink-docs-release-1.2/dev/api_concepts.html
         * 
         * Application de la fonction KMean
         * @see KMean
         */
        DataStream<ArrayList<Tuple5<Integer, Integer, Float, String, Integer>>> streamAfterKMean = streamAfterAnalyze
                .keyBy(0, 1).countWindow(countWindowSize, 1).apply(new KMean(mapSensors_ClustersSeuils));

        /**
         * Application de la map Markov
         * @see Markov
         */
        DataStream<Tuple5<Integer, Integer, Float, String, Double>> streamAfterMarkov = streamAfterKMean
                .flatMap(new Markov(mapSensors_ClustersSeuils));

        /**
         * Application du filter FilterAnomalies
         * @see FilterAnomalies
         */
        DataStream<Tuple5<Integer, Integer, Float, String, Double>> streamAfterFilterAnomalies = streamAfterMarkov
                .filter(new FilterAnomalies(mapSensors_ClustersSeuils));

        //TODO 
        streamAfterFilterAnomalies.print();

        /**
         * Application de la flatMap SortieTransformation
         * @see SortieTransformation
         */
        DataStream<String> streamAfterSortieTransformation = streamAfterFilterAnomalies
                .flatMap(new SortieTransformation());

        //TODO enlever ce print
        //streamAfterSortieTransformation.print();

        /**
         * Cration de la sortie du stream dans RabbitMQ
         * connectionConfig : Configuration de la connection de RabbitMQ
         * voila : le nom de la queue RabbitMQ  laquelle envoyer des messages
         * true : 
         * @see StreamExecutionEnvironment.addSink()
         */
        streamAfterSortieTransformation.addSink(new RMQSink<String>(connectionConfig, // config for the RabbitMQ connection
                "voila", // name of the RabbitMQ queue to send messages to
                new SimpleStringSchema()));

        /**
         * Excution de l'environnement de streaming
         * @see https://ci.apache.org/projects/flink/flink-docs-release-1.2/dev/api_concepts.html
         */
        environnementStream.execute("DEBS 2017 ENSAI SOLUTION");
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////// USER FUNCTIONS ////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * @author ENSAI
     * Traite les RDF en entre pour ressortir des tuples contenant l'information pertinente.
     * @return Tuple3<String, String, String>
     *                                  Tuple qui contient les trois lements du RDF : sujet, prdicat, objet
     */
    public static final class LineSplitter1 implements FlatMapFunction<String, String> {

        @Override
        public void flatMap(String value, Collector<String> out) throws Exception {

            // normalize and split the line
            String[] lineOrLines = value.split("\n");

            for (int i = 0; i < lineOrLines.length; i++) {
                out.collect(lineOrLines[i]);
            }
        }
    }

    public static final class LineSplitter2 implements FlatMapFunction<String, Tuple3<String, String, String>> {

        @Override
        public void flatMap(String value, Collector<Tuple3<String, String, String>> out) throws Exception {

            String[] tokens = StringUtils.split(value.toLowerCase(), " ");
            out.collect(new Tuple3<String, String, String>(tokens[0], tokens[1], tokens[2]));

        }
    }

    public static final class LineSplitter3
            implements FlatMapFunction<Tuple3<String, String, String>, Tuple3<String, String, String>> {

        @Override
        public void flatMap(Tuple3<String, String, String> value, Collector<Tuple3<String, String, String>> out)
                throws Exception {

            value.f0 = StringUtils.split(value.f0, "#")[1];

            out.collect(new Tuple3<String, String, String>(value.f0, value.f1, value.f2));

        }
    }

    public static final class LineSplitter4
            implements FlatMapFunction<Tuple3<String, String, String>, Tuple3<String, String, String>> {

        @Override
        public void flatMap(Tuple3<String, String, String> value, Collector<Tuple3<String, String, String>> out)
                throws Exception {

            value.f1 = StringUtils.split(value.f1, "#")[1];

            out.collect(new Tuple3<String, String, String>(value.f0, value.f1, value.f2));

        }
    }

    public static final class LineSplitter5
            implements FlatMapFunction<Tuple3<String, String, String>, Tuple3<String, String, String>> {

        @Override
        public void flatMap(Tuple3<String, String, String> value, Collector<Tuple3<String, String, String>> out)
                throws Exception {

            String[] tab = value.f2.split("\\^\\^");

            if (tab.length == 1) {
                value.f2 = StringUtils.split(tab[0], "#")[1];
            } else {
                value.f2 = tab[0];
            }
            out.collect(new Tuple3<String, String, String>(value.f0, value.f1, value.f2));

        }
    }

    /**
     * @author ENSAI
     * Filtre les tuples en entre pour ressortir que les tuples dont leur probabilit de transition est infrieur  leur seuil metadata.
     * @return Tuple5<Integer, Integer, Float, String, Double>
     *                                              Tuple qui prsente une anomalie
     */
    public static class FilterAnomalies
            extends RichFilterFunction<Tuple5<Integer, Integer, Float, String, Double>> {

        private Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils;

        public FilterAnomalies(Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils) {
            this.mapClustersSeuils = mapClustersSeuils;
        }

        @Override
        public boolean filter(Tuple5<Integer, Integer, Float, String, Double> input) throws Exception {
            //return (input.f4 < this.mapClustersSeuils.get(input.f1).f1);
            return true;
        }
    }

    /**
     * @author ENSAI
     * Applique l'algorithme KMean sur les tuples contenues dans la countWindow. L'id du cluster est ajout au tuple par l'intermdiaire d'un nouveau field
     * @return ArrayList<Tuple5<Integer, Integer, Float, String, Integer>
     *                                                       ArrayList de tuples qui constituent les clusters gnrs par KMean
     */
    public static class KMean extends
            RichWindowFunction<Tuple4<Integer, Integer, Float, String>, ArrayList<Tuple5<Integer, Integer, Float, String, Integer>>, Tuple, GlobalWindow> {

        private Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils;

        public KMean(Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils) {
            this.mapClustersSeuils = mapClustersSeuils;
        }

        private Counter counterTempsKMean;

        /**
         * Cration de la mtrique CounterKMean utilise pour mesurer le temps d'excution de la fonction.
         */
        @Override
        public void open(Configuration config) {
            this.counterTempsKMean = getRuntimeContext().getMetricGroup().counter("CounterKMean");
        }

        @Override
        public void apply(Tuple key, GlobalWindow window, Iterable<Tuple4<Integer, Integer, Float, String>> input,
                Collector<ArrayList<Tuple5<Integer, Integer, Float, String, Integer>>> output) throws Exception {

            this.counterTempsKMean.dec(System.currentTimeMillis());

            int machine = 0;
            int capteur = 0;

            KMeans kmean = new KMeans();
            List<Point> listePoints = new ArrayList<Point>();
            List<Cluster> listeCluster = new ArrayList<Cluster>();
            List<Float> listCentroidCluster = new ArrayList<Float>();
            ArrayList<Tuple5<Integer, Integer, Float, String, Integer>> resultatKMean = new ArrayList<Tuple5<Integer, Integer, Float, String, Integer>>();
            int nbCluster = 0;

            /**
             * Transformation des tuples en objets necessaire pour appliquer l'algorithme KMean
             */
            for (Tuple4<Integer, Integer, Float, String> tuple : input) {
                if (nbCluster < mapClustersSeuils.get(tuple.f1).f0 && !listCentroidCluster.contains(tuple.f2)) {
                    listCentroidCluster.add(tuple.f2);
                    machine = tuple.f0;
                    capteur = tuple.f1;
                    Cluster cluster = new Cluster(nbCluster);
                    cluster.setCentroid(new Point(tuple.f2));
                    listeCluster.add(cluster);
                    nbCluster++;
                }
                listePoints.add(new Point(tuple.f2, tuple.f3));

            }
            kmean.setPoints(listePoints);
            kmean.setNUM_POINTS(listePoints.size());

            kmean.setClusters(listeCluster);
            kmean.setNUM_CLUSTERS(listeCluster.size());

            kmean.calculate(maxIterationKMean);

            for (Point point : kmean.getPoints()) {
                resultatKMean.add(new Tuple5<Integer, Integer, Float, String, Integer>(machine, capteur,
                        (float) point.getX(), point.getTimestamp(), point.getCluster()));
            }

            this.counterTempsKMean.inc(System.currentTimeMillis());

            output.collect(resultatKMean);
        }
    }

    /**
     * @author ENSAI
     * Calcule les probabilits de transitions d'un cluster  un autre d'aprs le modle des chaines de Markov
     * @return Tuple5<Integer, Integer, Float, String, Double>
     *                                                             Tuple d'interet
     */
    public static final class Markov extends
            RichFlatMapFunction<ArrayList<Tuple5<Integer, Integer, Float, String, Integer>>, Tuple5<Integer, Integer, Float, String, Double>> {

        private Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils;

        public Markov(Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils) {
            this.mapClustersSeuils = mapClustersSeuils;
        }

        private Counter counter;

        /**
         * Cration de la mtrique CounterMarkov utilise pour mesurer le temps d'excution de la fonction.
         */
        @Override
        public void open(Configuration config) {
            this.counter = getRuntimeContext().getMetricGroup().counter("CounterMarkov");
        }

        @Override
        public void flatMap(ArrayList<Tuple5<Integer, Integer, Float, String, Integer>> input,
                Collector<Tuple5<Integer, Integer, Float, String, Double>> out) throws Exception {

            this.counter.dec(System.currentTimeMillis());

            /** Number of clusters */
            int K = this.mapClustersSeuils.get(input.get(0).f1).f0;
            /** Size of the window */
            int windowSize = input.size();
            if (windowSize == 1) {
                /** On sort le premier tuple avec comme proba : 1. */
                out.collect(new Tuple5(input.get(0).f0, input.get(0).f1, input.get(0).f2, input.get(0).f3,
                        Double.valueOf(1)));
            } else {
                //System.out.println(windowSize);
                int trueN = Math.min(input.size() - 1, N);
                Tuple5<Integer, Integer, Float, String, Double> output;

                HashMap<Tuple2<Integer, Integer>, Integer> countTransitions = new HashMap<Tuple2<Integer, Integer>, Integer>();
                double[] countFrom = new double[K];

                Iterator<Tuple5<Integer, Integer, Float, String, Integer>> tupleIter = input.iterator();
                Tuple5<Integer, Integer, Float, String, Integer> tuple = tupleIter.next();

                Integer stateFrom = tuple.f4;

                while (tupleIter.hasNext()) {
                    countFrom[tuple.f4]++;
                    tuple = tupleIter.next();

                    Tuple2<Integer, Integer> transitionKey = new Tuple2<Integer, Integer>(stateFrom, tuple.f4);
                    if (!countTransitions.containsKey(transitionKey)) {
                        countTransitions.put(transitionKey, 1);
                    } else {
                        countTransitions.put(transitionKey, countTransitions.get(transitionKey) + 1);
                    }

                    stateFrom = tuple.f4;
                }

                ArrayList<Integer> lastN = new ArrayList<Integer>(trueN + 1);
                for (int j = 1; j < trueN + 2; j++) {
                    lastN.add(input.get(windowSize - j).f4);
                }

                Double probability = Double.valueOf(1);
                for (int position = 0; position < trueN; position++) {
                    Tuple2<Integer, Integer> couple = new Tuple2<Integer, Integer>(lastN.get(position + 1),
                            lastN.get(position));
                    probability = probability * countTransitions.get(couple) / countFrom[couple.f0];
                    output = new Tuple5(tuple.f0, tuple.f1, tuple.f2, tuple.f3, probability);
                    out.collect(output);
                }
            }

            this.counter.inc(System.currentTimeMillis());

        }

    }

    /**
     * @author ENSAI
     * Analyse des triplets RDF pour renvoyer les tuples pertinents 
     * @return Tuple4<Integer, Integer, Float, String>
     *                                        tuple qui contient : numro de la machine, numro du capteur, valeur de l'observation et le timestamp
     */
    public static final class InputAnalyze
            extends RichFlatMapFunction<Tuple3<String, String, String>, Tuple4<Integer, Integer, Float, String>> {

        private Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils;

        private Counter counter;

        /**
         * Cration de la mtrique CounterInputAnalyze utilise pour mesurer le temps d'excution de la fonction.
         */
        @Override
        public void open(Configuration config) {
            this.counter = getRuntimeContext().getMetricGroup().counter("CounterInputAnalyze");
        }

        public InputAnalyze(Map<Integer, Tuple2<Integer, Double>> mapClustersSeuils) {
            this.mapClustersSeuils = mapClustersSeuils;
        }

        @Override
        public void flatMap(Tuple3<String, String, String> input,
                Collector<Tuple4<Integer, Integer, Float, String>> out) throws Exception {

            this.counter.dec(System.currentTimeMillis());

            switch (input.f1) {

            case "machine>":
                RMQtestKmeans.currentMachine = Integer.parseInt(input.f2.split("\\_|>")[1]);
                //System.out.println("Current machine number :  " + RMQtestKmeans.currentMachine);
                break;

            case "type>":
                if (input.f0.split("\\_")[0].equals("ObservationGroup")) {
                    RMQtestKmeans.currentMachineType = input.f2.split(">")[0];
                    //System.out.println("Current machine type : " + RMQtestKmeans.currentMachineType);
                }
                break;

            case "observationresulttime>":
                RMQtestKmeans.currentObsGroup = Integer.parseInt(input.f0.split("\\_|>")[1]);
                //System.out.println("Current Observation group : " + RMQtestKmeans.currentObsGroup);
                break;

            case "observedproperty>":

                // System.out.println("Numro de capteur : " +
                // Integer.parseInt(value.f2.split("\\_|>")[1]));
                // System.out.println("Numro d'observation : " +
                // Integer.parseInt(value.f0.split("\\_|>")[1]));

                input.f2 = StringUtils.split(input.f2, "_")[1];
                int sensorNb = Integer.parseInt(input.f2.split(">")[0]);
                //System.out.println("Current capteur : " + value.f2);

                int obsNb = Integer.parseInt(input.f0.split("\\_|>")[1]);

                // System.out.println(listSensors.contains(sensorNb));
                // System.out.println(listSensors);
                // System.out.println(sensorNb);

                //TODO Grer le cas du sensor 104 
                if (mapClustersSeuils.containsKey(sensorNb) && sensorNb != 104) {
                    RMQtestKmeans.mapObsSensors.put(obsNb, sensorNb);
                    // System.out.println("Numro d'observation enregist");
                } else {
                    // System.out.println("Ce capteur ne nous interesse pas
                    // !!");
                }

                break;

            case "valueliteral>":

                String[] tab = input.f0.split("\\_|>");

                if (tab[0].split("\\_")[0].equals("timestamp")) {
                    RMQtestKmeans.currentTimestamp = input.f2;
                    //System.out.println("Current timestamp : " + RMQtestKmeans.currentTimestamp);
                }

                else if (tab[0].equals("value")
                        && RMQtestKmeans.mapObsSensors.containsKey(Integer.parseInt(tab[1]))) {
                    // System.out.println(" ================= > " + "Capteur : "
                    // +
                    // StreamRabbitMQ.mapObsSensors.get(Integer.parseInt(tab[1]))
                    // + " | Valeur : "
                    // + Float.parseFloat(value.f2.split("\"")[1]));

                    out.collect(new Tuple4<Integer, Integer, Float, String>(RMQtestKmeans.currentMachine,
                            RMQtestKmeans.mapObsSensors.get(Integer.parseInt(tab[1])),
                            Float.parseFloat(input.f2.split("\"")[1]), RMQtestKmeans.currentTimestamp));

                    RMQtestKmeans.mapObsSensors.remove(Integer.parseInt(tab[1]));

                }
                break;
            }

            this.counter.inc(System.currentTimeMillis());
        }
    }

    /**
     * @author ENSAI
     * Transformation des tuples d'entre en sortie RDF
     * @return String
     *                Sortie RDF des tuples concerns
     */
    public static final class SortieTransformation
            extends RichFlatMapFunction<Tuple5<Integer, Integer, Float, String, Double>, String> {

        private Counter counter;

        /**
         * Cration de la mtrique CounterSortieTransformation utilise pour mesurer le temps d'excution de la fonction.
         */
        @Override
        public void open(Configuration config) {
            this.counter = getRuntimeContext().getMetricGroup().counter("CounterSortieTransformation");
        }

        @Override
        public void flatMap(Tuple5<Integer, Integer, Float, String, Double> avant, Collector<String> apres)
                throws Exception {

            this.counter.dec(System.currentTimeMillis());

            String numMachine = avant.f0.toString();
            String numSensor = avant.f1.toString();
            String valeur = avant.f2.toString();
            String timestamp = avant.f3.toString();
            String probTrans = avant.f4.toString();

            numeroAnomalie++;
            numeroTimestamp++;
            String numAno = Integer.toString(numeroAnomalie);
            String numTsp = Integer.toString(numeroTimestamp);

            String l1 = "<http://PFEensai/outputs/debs2017#Anomaly_" + numAno
                    + "> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.agtinternational.com/ontologies/DEBSAnalyticResults#Anomaly> .";
            String l2 = "<http://PFEensai/outputs/debs2017#Anomaly_" + numAno
                    + "> <http://www.agtinternational.com/ontologies/DEBSAnalyticResults#hasProbabilityOfObservedAbnormalSequence> \""
                    + probTrans + "\"^^<http://www.w3.org/2001/XMLSchema#float> .";
            String l3 = "<http://PFEensai/outputs/debs2017#Anomaly_" + numAno
                    + "> <http://www.agtinternational.com/ontologies/DEBSAnalyticResults#hasTimestamp> <http://project-hobbit.eu/resources/debs2017#Timestamp_"
                    + numTsp + "> .";
            String l4 = "<http://PFEensai/outputs/debs2017#Anomaly_" + numAno
                    + "> <http://www.agtinternational.com/ontologies/DEBSAnalyticResults#inAbnormalDimension> <http://www.agtinternational.com/ontologies/WeidmullerMetadata#_"
                    + numSensor + "> .";
            String l5 = "<http://PFEensai/outputs/debs2017#Anomaly_" + numAno
                    + "> <http://www.agtinternational.com/ontologies/I4.0#machine> <http://www.agtinternational.com/ontologies/WeidmullerMetadata#MoldingMachine_"
                    + numMachine + "> .";
            String l6 = "<http://PFEensai/outputs/debs2017#Timestamp_" + numTsp
                    + "> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.agtinternational.com/ontologies/IoTCore#Timestamp> .";
            String l7 = "<http://PFEensai/outputs/debs2017#Timestamp_" + numTsp
                    + "> <http://www.agtinternational.com/ontologies/IoTCore#valueLiteral> \"" + timestamp
                    + "\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .";

            this.counter.inc(System.currentTimeMillis());

            apres.collect(l1);
            apres.collect(l2);
            apres.collect(l3);
            apres.collect(l4);
            apres.collect(l5);
            apres.collect(l6);
            apres.collect(l7);
        }
    }

}