001package com.astrolabsoftware.FinkBrowser.Januser;
002
003import com.Lomikel.Utils.Pair;
004import com.Lomikel.Utils.LomikelException;
005import com.Lomikel.Januser.GremlinRecipies;
006import com.Lomikel.Januser.ModifyingGremlinClient;
007import com.astrolabsoftware.FinkBrowser.HBaser.FinkHBaseClient;
008import com.astrolabsoftware.FinkBrowser.FinkPortalClient.FPC;
009
010// Tinker Pop
011import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
012import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
013import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.otherV;
014import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.V;
015import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.fold;
016import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.has;
017import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.not;
018import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.unfold;
019import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
020import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.repeat;
021import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
022import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.count;
023import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.addV;
024import static org.apache.tinkerpop.gremlin.process.traversal.P.within;
025import org.apache.tinkerpop.gremlin.structure.Edge;
026import org.apache.tinkerpop.gremlin.structure.Vertex;
027import org.apache.tinkerpop.gremlin.structure.Direction;
028
029// HBase
030import org.apache.hadoop.hbase.client.Get;
031import org.apache.hadoop.hbase.client.Get;
032
033// org.json
034import org.json.JSONArray;
035import org.json.JSONObject;
036
037// Java
038import java.lang.Math;
039import java.util.Arrays;
040import java.util.Iterator;
041import java.util.ArrayList;
042import java.util.List;
043import java.util.Map;
044import java.util.HashMap;
045import java.util.SortedSet;
046import java.util.TreeSet;
047import java.util.Set;
048import java.util.HashSet;
049import java.util.Iterator;
050import java.util.Calendar;
051import java.util.Date;
052import java.text.SimpleDateFormat;
053
054// Log4J
055import org.apache.logging.log4j.Logger;
056import org.apache.logging.log4j.LogManager;
057
058/** <code>FinkGremlinRecipies</code> provides various recipies to handle
059  * and modify Gremlin Graphs for Fink.
060  * @opt attributes
061  * @opt operations
062  * @opt types
063  * @opt visibility
064  * @author <a href="mailto:Julius.Hrivnac@cern.ch">J.Hrivnac</a> */
065// TBD: check precodition for methods, wgich doesn't work with 'client' creation
066public class FinkGremlinRecipies extends GremlinRecipies {
067    
068  /** Create and attach to {@link GraphTraversalSource}.
069    * @param g The attached {@link GraphTraversalSource}. */
070  public FinkGremlinRecipies(GraphTraversalSource g) {
071    super(g);
072    }
073    
074  /** Create and attach to {@link ModifyingGremlinClient}.
075    * @param client The attached  {@link ModifyingGremlinClient}. */
076  public FinkGremlinRecipies(ModifyingGremlinClient client) {
077    super(client);
078    }
079    
080  /** Execute full chain of new <em>object</em> correlations analyses.
081    * @param classifiers The {@link Classifier}s to be used.
082    *                        They can contain the {@link Classifier} flavor after <em>=</em> symbol.
083    * @param filter          The HBase evaluation formula to be applied.
084    *                        Ignored if <tt>clss</tt> are specified.
085    * @param hbaseUrl        The url of HBase with alerts as <tt>ip:port:table:schema</tt>.
086    * @param nLimit          The maximal number of alerts getting from HBase or Fink Portal.
087    *                        <tt>0</tt> means no limit.
088    * @param timeLimit       How far into the past the search should search (in minutes).
089    * @param clss            An array of <em>classes</em> taken from {@link FPC},
090    *                        if contains <tt>Anomaly</tt>, get anomalies from {@link FPC},                  
091    *                        if <tt>null</tt>, analyse <em>object</em>s from HBase database.
092    * @throws LomikelException If anything fails. */
093  /*@Deprecated
094  public void processOCol(Classifier[] classifiers,
095                          String       filter,
096                          String       hbaseUrl,
097                          int          nLimit,
098                          int          timeLimit,
099                          String[]     clss) throws LomikelException {
100    fillOCol(classifiers, filter, hbaseUrl, nLimit, timeLimit, clss);
101    generateCorrelations(classifiers);
102    }*/
103        
104  /** Fill graph with <em>OCol</em>.
105    * @param classifiers The {@link Classifier}s to be used.
106    * @param filter      The HBase evaluation formula to be applied.
107    *                    Ignored if <tt>clss</tt> are specified.
108    * @param hbaseUrl    The url of HBase with alerts as <tt>ip:port:table:schema</tt>.
109    * @param nLimit      The maximal number of alerts getting from HBase or Fink Portal.
110    *                    <tt>0</tt> means no limit.
111    * @param timeLimit   How far into the past the search should search (in minutes).
112    * @param clss        An array of <em>classes</em> taken from {@link FPC},
113    *                    if contains <tt>Anomaly</tt>, get anomalies from {@link FPC},                  
114    *                    if <tt>null</tt>, analyse <em>object</em>s from HBase database.
115
116    * @throws LomikelException If anything fails. */
117  /*@Deprecated
118  public void fillOCol(Classifier[] classifiers,
119                       String       filter,
120                       String       hbaseUrl,
121                       int          nLimit,
122                       int          timeLimit,
123                       String[]     clss) throws LomikelException {
124    String clssDesc = "";
125    if (clss != null) {
126      clssDesc = "of " + Arrays.toString(clss);
127      }
128    log.info("Filling OCol " + clssDesc + " using " + Arrays.toString(classifiers) + " classifiers, nLimit = " + nLimit + ", timeLimit = " + timeLimit);
129    log.info("Importing from " + hbaseUrl + ":");
130    fhclient(hbaseUrl);
131    Set<String> oids = new HashSet<>();;
132    if (clss == null) { 
133      fhclient().setEvaluation(filter);
134      if (nLimit > 0) {
135        fhclient().setLimit(nLimit);
136        }
137      oids = fhclient().latestsT("i:objectId",
138                                 null,
139                                 timeLimit,
140                                 true);
141      fhclient().setEvaluation(null);
142      }
143    else {
144      Calendar cal;
145      Date d;
146      String sd;
147      JSONArray ja;
148      JSONObject jo;
149      for (String cls : clss) {
150        cal = Calendar.getInstance();
151        cal.add(Calendar.MINUTE, -nLimit);
152        d = cal.getTime();
153        sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(d);
154        if (cls.equals("*")) {
155          ja = FPC.anomaly(new JSONObject().put("n",             nLimit).
156                                            put("startdate",     sd).
157                                            put("columns",       "i:objectId").
158                                            put("output-format", "json"));
159          }
160        else {
161          ja = FPC.latests(new JSONObject().put("n",             nLimit).
162                                            put("class",         cls).
163                                            put("startdate",     sd).
164                                            put("columns",       "i:objectId").
165                                            put("output-format", "json"));
166          }
167        for (int i = 0; i < ja.length(); i++) {
168          jo = ja.getJSONObject(i);
169          oids.add(jo.getString("i:objectId"));
170          }
171        log.info("*** " + cls + "[" + ja.length() + "]:");
172        }
173      }
174    classifySources(classifiers, oids, hbaseUrl);
175    }*/
176    
177  /** Classify <em>object</em> .
178    * @param classifiers The {@link Classifier}s to be used.
179    * @param oids        The {@link Set} of <tt>objectId</tt>s of <em>object</em> to be added.
180    * @param hbaseUrl    The url of HBase with alerts as <tt>ip:port:table:schema</tt>.
181    * @throws LomikelException If anything fails. */
182  public void classifySources(Classifier[] classifiers,
183                              Set<String>  oids,
184                              String       hbaseUrl) throws LomikelException {
185    int size = oids.size();
186    int n = 0;
187    long dt;
188    double freq;
189    long startTime = System.currentTimeMillis();
190    // loop over all objects
191    for (String oid : oids) {
192      log.info(oid + " (" + n + " of " + size + "):");
193      for (Classifier classifier : classifiers) {
194        try {
195          classifySource(classifier, oid, hbaseUrl);
196          }
197        catch (LomikelException e) {
198          log.error("Cannot get classification for " + oid, e);
199          }
200        }
201      n++;
202      dt = (System.currentTimeMillis() - startTime) / 1000;
203      freq = (double)n / (double)dt;
204      log.info("\t\twith " + String.format("%.2f", freq) + " Hz");
205      }
206    }
207    
208   /** Classify <em>object</em>.
209    * @param classifier The {@link Classifier} to be used.
210    * @param objectId   The <tt>objectId</tt> of <em>object</em> to be added.
211    * @param hbaseUrl   The url of HBase with alerts as <tt>ip:port:table:schema</tt>.
212    * @throws LomikelException If anything fails. */
213  public void classifySource(Classifier classifier,
214                             String     objectId,
215                             String     hbaseUrl) throws LomikelException {  
216    fhclient(hbaseUrl);
217    classifySource(classifier, objectId);
218    }
219   
220  /** Classify <em>object</em>.
221    * @param classifier The {@link Classifier} to be used.
222    * @param objectId   The <tt>objectId</tt> of <em>object</em> to be added.
223    * @throws LomikelException If anything fails. */
224  public void classifySource(Classifier classifier,
225                             String     objectId) throws LomikelException {  
226    if (g().V().has("lbl", "object").has("objectId", objectId).hasNext()) {
227      Vertex v1 = g().V().has("lbl", "object").has("objectId", objectId).next();
228      List<Vertex> v2s = g().V(v1).in().
229                                   has("lbl",        "OCol").
230                                   has("survey",     classifier.survey()).
231                                   has("classifier", classifier.name()  ).
232                                   has("flavor",     classifier.flavor()).
233                                   toList();
234      Iterator<Edge> edges;
235      for (Vertex v2 : v2s) {
236        edges = g().V(v1).inE().
237                          has("lbl", "deepcontains").
238                          where(otherV().
239                          is(v2)).
240                          toStream().
241                          iterator();
242        while (edges.hasNext()) {
243          edges.next().remove(); 
244          }
245        }        
246      // will be commited in registration
247      }
248    classifier.classify(this, objectId);
249    }
250       
251  /** Register  <em>object</em> in <em>OCol</em>.
252    * @param classifier The {@link Classifier} to be used.
253    * @param cls        The type (class) of <em>OCol</em> {@link Vertex}.
254    *                   It will be created if not yet exists.
255    * @param objectId   The objectId of the new <em>Source</em> {@link Vertex}.
256    *                   It will be created if not yet exists.
257    * @param weight     The weight of the connection.
258    *                   Usualy the number of <em>Alerts</em> of this type. 
259    * @param instanceS  The <em>jd</em> of related <em>Alerts</em> as strings separated by comma.
260    *                   Potential square brackets are removed.
261    *                   May be <tt>null</tt> or empty. */
262  public void registerOCol(Classifier classifier,
263                           String     cls,
264                           String     objectId,
265                           double     weight,
266                           String     instancesS,
267                           String     weightsS) {   
268    List<String> instances = new ArrayList<>();
269    List<Double> weights   = new ArrayList<>();
270    if (instancesS != null && !instancesS.trim().equals("")) {
271      for (String instance : instancesS.replaceAll("\\[", "").replaceAll("]", "").split(",")) {
272        instances.add(instance);
273        }
274      }
275    if (weightsS != null && !weightsS.trim().equals("")) {
276      for (String weighs : weightsS.replaceAll("\\[", "").replaceAll("]", "").split(",")) {
277        weights.add(Double.valueOf(weight));
278        }
279      }
280    registerOCol(classifier, cls, objectId, weight, instances, weights);
281    }
282    
283  /** Register <em>object</em> in <em>OCol</em>. Replace possible existing registration.
284    * @param classifier The {@link Classifier} to be used.
285    * @param cls        The type (class) of <em>OCol</em> {@link Vertex}.
286    *                   It will be created if not yet exists.
287    * @param objectId   The objectId of the new <em>Source</em> {@link Vertex}.
288    *                   It will be created if not yet exists.
289    * @param weight     The total weight of the connection.
290    *                   Usualy the number of <em>Alerts</em> of this type. 
291    * @param instances  The <em>jd</em> of related <em>Alerts</em>.
292    * @param weights    The weights of related <em>Alerts</em>. */
293  public void registerOCol(Classifier   classifier,
294                           String       cls,
295                           String       objectId,
296                           double       weight,
297                           List<String> instances,
298                           List<Double> weights) { 
299    Map<String, String> attributes = new HashMap<>();
300    attributes.put("weight",    "" + weight);
301    attributes.put("instances", instances.toString().replaceFirst("\\[", "").replaceAll("]", ""));
302    attributes.put("weights",   weights.toString().replaceFirst("\\[", "").replaceAll("]", ""));
303    registerOCol(classifier, cls, objectId, attributes, true);
304    }
305    
306  /** Register <em>object</em> in <em>OCol</em>.
307    * @param classifier The {@link Classifier} to be used.
308    * @param cls        The type (class) of <em>OCol</em> {@link Vertex}.
309    *                   It will be created if not yet exists.
310    * @param objectId   The objectId of the new <em>Source</em> {@link Vertex}.
311    *                   It will be created if not yet exists.
312    * @param attributes The additional {@link Edge} attributes.    
313    * @param replace    Whether to replace existing resistration. */
314  public void registerOCol(Classifier          classifier,
315                           String              cls,
316                           String              objectId,
317                           Map<String, String> attributes,
318                           boolean             replace) {   
319    //log.info("\tregistering " + objectId + " as " + classifier + " / " + cls + " with attributes " + attributes + ", replace = " + replace);
320    log.info("\tregistering " + objectId + " as " + classifier + " / " + cls + " with weight = " + attributes.get("weight") + ", replace = " + replace);
321    Vertex ocol = g().V().has("lbl",        "OCol"             ).
322                          has("survey",     classifier.survey()).
323                          has("classifier", classifier.name()  ).
324                          has("flavor",     classifier.flavor()).
325                          has("cls",        cls                ).
326                          fold().
327                          coalesce(unfold(), 
328                                  addV("OCol").
329                                  property("lbl",        "OCol").
330                                  property("survey",     classifier.survey()).
331                                  property("classifier", classifier.name()  ).
332                                  property("flavor",     classifier.flavor()).
333                                  property("cls",        cls                )).
334                         next();
335    Vertex s = g().V().has("lbl",      "object").
336                       has("objectId", objectId).
337                       fold().
338                       coalesce(unfold(), 
339                                addV("object").
340                                property("lbl",      "object").
341                                property("objectId", objectId)).
342                       property("importDate", _now).
343                       next();
344    if (replace) {
345      addEdge(g().V(ocol).next(),
346              g().V(s).next(),
347              "deepcontains",
348              attributes.keySet().toArray(new String[0]),
349              attributes.values().toArray(new String[0]),
350              true);
351      }
352    else {
353      Edge e = ocol.addEdge("deepcontains", s);
354      e.property("lbl", "deepcontains");
355      for (Map.Entry<String, String> attribute : attributes.entrySet()) {
356        e.property(attribute.getKey(), attribute.getValue());
357        }
358      }
359    commit();
360    }
361   
362  /** Clean tree under <em>OCol</em>.
363    * Drop alerts. Alerts are dropped even if they have other
364    * {@link Edge}s.
365    * @param classifier The {@link Classifier} to be used.
366    *                   All its <em>flavors</em> are handled.
367    * @param cls        The type (class) of <em>OCol</em>.
368    * @throws LomikelException If anything goes wrong. */
369  public void cleanOCol(Classifier classifier,
370                        String     cls) throws LomikelException {
371    log.info("Cleaning " + cls + " OCol");
372    g().V().has("lbl",        "OCol").
373            has("survey",     classifier.survey()).
374            has("classifier", classifier.name()  ).
375            has("flavor",     classifier.flavor()).
376            has("cls",        cls                ).
377            out().
378            out().
379            drop().
380            iterate();
381    commit();
382    }
383        
384  /** Generate <em>overlaps</em> Edges between <em>OCol</em>.
385    * Possibly between two {@link Classifier}s.
386    * @param classifier The {@link Classifier}s to be used. */
387  public void generateCorrelations(Classifier... classifiers) {
388    log.info("Generating correlations for OCol of " + Arrays.asList(classifiers));
389    // Clean all correlations 
390    g().E().has("lbl", "overlaps").
391            drop().
392            iterate();
393    List<String> surveysL = new ArrayList<>();
394    List<String> namesL   = new ArrayList<>();
395    List<String> flavorsL = new ArrayList<>();
396    for (Classifier classifier : classifiers) {
397      surveysL.add(classifier.survey());
398      namesL.add(  classifier.name()  );
399      flavorsL.add(classifier.flavor());
400      // Remove wrong OCol
401      g().V().has("lbl",        "OCol"             ).
402              has("classifier", classifier.name()  ).
403              has("flavor",     classifier.flavor()).
404              not(has("cls")).
405              drop().
406              iterate();
407      }
408    String[] surveys = surveysL.toArray(String[]::new);
409    String[] names   = namesL.toArray(  String[]::new);
410    String[] flavors = flavorsL.toArray(String[]::new);
411    commit();
412    // Accumulate correlations and sizes
413    Map<OCol, Double>             weights0 = new HashMap<>(); // cls -> weight (for one objext)
414    Map<Pair<OCol, OCol>, Double> corrS    = new HashMap<>(); // [cls1, cls2] -> weight (for all object between OCol-OCol)
415    Map<OCol, Double>             sizeS    = new HashMap<>(); // cls -> total (for all objects of OCol)
416    SortedSet<OCol>               types0   = new TreeSet<>(); // [cls] (for one object)
417    SortedSet<OCol>               types    = new TreeSet<>(); // [cls] (for all objects)
418    Vertex object;
419    Iterator<Edge> deepcontainsIt;
420    Edge deepcontains;
421    Edge overlaps;
422    double weight;
423    double weight1;
424    double weight2;
425    double cor;
426    Vertex ocol;
427    Vertex ocol1;
428    Vertex ocol2;
429    OCol cls;
430    Pair<OCol, OCol> rel;
431    // Loop over objets and accumulated weights to each object
432    GraphTraversal<Vertex, Vertex> objectT = g().V().has("lbl", "object");
433    while (objectT.hasNext()) {
434      weights0.clear();
435      types0.clear();
436      object = objectT.next();
437      deepcontainsIt = object.edges(Direction.IN);
438      // Get all weights to this object
439      while (deepcontainsIt.hasNext()) {
440        deepcontains = deepcontainsIt.next();
441        weight = Double.parseDouble(deepcontains.property("weight").value().toString());
442        ocol1 = deepcontains.outVertex();
443        cls = new OCol(ocol1);
444        types0.add(cls);
445        types.add(cls);
446        weights0.put(cls, weight);
447        }
448      // Double loop over accumulated weights and fill weights between OCols
449      for (OCol cls1 : types0) {
450        weight1 = weights0.get(cls1);
451        for (OCol cls2 : types0) {
452          weight2 = weights0.get(cls2);
453          rel = Pair.of(cls1, cls2);
454          // OCol-OCol
455          if (!corrS.containsKey(rel)) {
456            corrS.put(rel, 0.0);
457            }
458          cor = corrS.get(rel);
459          //corrS.put(rel, cor + 1.0);
460          corrS.put(rel, cor + Math.sqrt(weight1 * weight2));
461          }
462        }
463      }
464    // Fill total sizes
465    double sizeS0;
466    for (OCol cls1 : types) {
467      sizeS0 = 0.0;
468      for (OCol cls2 : types) {
469        if (corrS.containsKey(Pair.of(cls1, cls2))) {
470          sizeS0 += corrS.get(Pair.of(cls1, cls2));
471          }
472        }
473      sizeS.put(cls1, sizeS0);
474      }
475    // Create overlaps
476    int ns = 0;
477    // Loop over OCol-OCol and create overlaps Edge OCol-OCol if non empty 
478    OCol cls1;
479    OCol cls2;
480    for (Map.Entry<Pair<OCol, OCol>, Double> entry : corrS.entrySet()) {
481      rel = entry.getKey();
482      cls1 = rel.first();
483      cls2 = rel.second();
484      weight = entry.getValue();
485      ocol1 = g().V().has("lbl",        "OCol"          ).
486                      has("survey",     cls1.survey()    ).
487                      has("classifier", cls1.classifier()).
488                      has("flavor",     cls1.flavor()    ).
489                      has("cls",        cls1.cls()       ).
490                      next();
491      ocol2 = g().V().has("lbl",        "OCol"          ).
492                      has("survey",     cls2.survey()    ).
493                      has("classifier", cls2.classifier()).
494                      has("flavor",     cls2.flavor()    ).
495                      has("cls",        cls2.cls()       ).
496                      next();
497      overlaps = ocol1.addEdge("overlaps", ocol2);
498      overlaps.property("lbl",          "overlaps");
499      overlaps.property("intersection", weight);
500      overlaps.property("sizeIn",       sizeS.get(cls1));
501      overlaps.property("sizeOut",      sizeS.get(cls2));
502      ns++;
503      }
504    commit();
505    log.info("" + ns + " object-object correlations generated");
506    }
507    
508  /** Create a new {@link FinkHBaseClient}. Singleton when url unchanged.
509    * @param hbaseUrl The HBase url as <tt>ip:port:table[:schema]</tt>.
510    * @return         The corresponding {@link FinkHBaseClient}, created and initialised if needed.
511    * @throws LomikelException If cannot be created. */
512  public FinkHBaseClient fhclient(String hbaseUrl) throws LomikelException {
513    if (hbaseUrl == null || hbaseUrl.equals(_fhclientUrl)) {
514      return _fhclient;
515      }
516    _fhclientUrl = hbaseUrl;
517    String[] url = hbaseUrl.split(":");
518    String ip     = url[0];
519    String port   = url[1];
520    String table  = url[2];
521    String schema = "";
522    if (url.length >= 4) {
523      schema = url[3];
524      }
525    _fhclient = new FinkHBaseClient(ip, port);
526    _fhclient.connect(table, schema);
527    return _fhclient;
528    }
529    
530  /** Get existing {@link FinkHBaseClient}.
531    * @return The corresponding {@link FinkHBaseClient}.
532    * @throws LomikelException If not yet created. */
533  public FinkHBaseClient fhclient() throws LomikelException {
534    if (_fhclient == null) {
535      throw new LomikelException("FinkHBaseClient not initialised");
536      }
537    return _fhclient;
538    }
539    
540  /** Give HBase url.
541    * @return The HBase url as <tt>ip:port:table[:schema]</tt>. */
542  public String hbaseUrl() {
543    return _fhclientUrl;
544    }
545    
546  private FinkHBaseClient _fhclient;
547  
548  private String _fhclientUrl;
549   
550  private String _now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()).toString();
551 
552  private static String FINK_OBJECTS_WS = "https://api.ztf.fink-portal.org/api/v1/objects";
553  private static String FINK_LATESTS_WS = "https://api.ztf.fink-portal.org/api/v1/latests";
554  private static String FINK_ANOMALY_WS = "https://api.ztf.fink-portal.org/api/v1/anomaly";
555
556  /** Logging . */
557  private static Logger log = LogManager.getLogger(FinkGremlinRecipies.class);
558
559  }