Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

4031
LINES

< > BotCompany Repo | #1032712 // GazelleBEA [LIVE]

JavaX fragment (include) [tags: use-pretranspiled]

1  
need latest JavaXHyperlinker.
2  
3  
// GazelleBEA - Base class of all Gazelle web servers
4  
5  
// About BEA objects with code: There is some confusion about the actions on them.
6  
// There are:
7  
// - compileAndLoadObject
8  
// - activateDynamicObject
9  
// - reactivateDynamicObject
10  
// - deactivateObject
11  
//
12  
// It's not completely clear what each of these actions exactly does
13  
// with respect to the at least 3 internal states of an object:
14  
// - no custom code, or none compiled
15  
// - custom code compiled (or imported from another object)
16  
// - custom code loaded   (actually migrated object to custom class)
17  
// - object activated     (activate listeners etc defined by the
18  
//                         object in _activateImpl())
19  
//
20  
// Right now things seem to work, but we could really use some test cases related to this.
21  
22  
// Ways to put code into a BEA object:
23  
//
24  
// Way 1. Put the JavaX code directly in meta_code (string field).
25  
//        You can use an exports {} block to make things on the global level (e.g. static classes to export)
26  
//        You can use a special "extends" declaration (extends BBla from 123;) in meta_code to subclass a class defined in another object.
27  
//
28  
// Way 2. Point meta_prototype to another object with a custom class.
29  
//        This will use the same class for this object.
30  
//
31  
// Way 3. Set meta_useClass to something like "123.BBla" to use a class
32  
//        called BBla exported by object 123 for this object.
33  
34  
// Note about GazelleBEA:
35  
// If you ever move this class into loadableUtils, make sure to copy the definition of method db() to the subclass
36  
// and override uploadsBaseDir()
37  
38  
mainPackage gazelle
39  
mainClassName main
40  
41  
set flag needToKnowMainLib.
42  
43  
!include once #1030833 // BEACalculations
44  
45  
//set flag defaultDefaultClassFinder_debug.
46  
47  
set flag OurSyncCollections.
48  
set flag CleanImports.
49  
set flag DynModule.
50  
set flag NoNanoHTTPD.
51  
set flag AllPublic. // for dynamic BEA objects
52  
rewrite BEA with BEAObject.
53  
54  
abstract static class GazelleBEA > DynGazelleRocks {
55  
  switchable bool mirrorBEAObjects; // don't do it for now
56  
  switchable bool enableAutoRuns = true;
57  
  switchable bool enableNewBEAObjectNotis = true;
58  
  switchable bool printAllConceptChanges;
59  
  switchable bool useShadowLogger = false;
60  
  switchable bool autoActivateDynamicObjects = true;
61  
  switchable bool reloadWhenMainLibUpdates;
62  
  switchable S baseSystemVersion = "1"; // increase this when all dynamic objects need a recompile
63  
  switchable int backupFrequency = 5; // make new backup file every x minutes
64  
  switchable S gazelleServerGlobalID = aGlobalID();
65  
  
66  
  // proofOfStart
67  
  transient bool allObjectsActivated;
68  
  transient Throwable startError;
69  
  
70  
  switchable S activateOnlyTheseIDs;
71  
  switchable S dontActivateTheseIDs;
72  
73  
  transient ReliableSingleThread_Multi<BEA> rstAutoRuns = dm_rstMulti(this, 1000, lambda1 performAutoRuns);
74  
  transient Q notificationQ;
75  
  transient ReliableSingleThread_Multi<BEA> rstDistributeNewObject = dm_rstMulti(this, 1000, lambda1 distributeNewObject_impl);
76  
  transient Set<BEA> newObjectsDistributed = weakSet();
77  
78  
  transient ReliableSingleThread_Multi<BEA> rstUpdateBEAMirrors = dm_rstMulti(this, 100, c -> c.updateMirrorPost());
79  
  
80  
  transient ReliableSingleThread_Multi<BEA> rstAutoActivateDynamicObjects = dm_rstMulti(this, 100, lambda1 autoActivateDynamicObject);
81  
82  
  transient BEACalculations calculations = new(this);
83  
  transient BEACalculations calc = calculations;
84  
  transient int newFieldsToShow = 3;
85  
  transient bool allowAnonymousInputUpload = true; // TODO
86  
  switchable int maxInputLength = 50000;
87  
  
88  
  switchable S mainDomainWithProtocol = "https://bea.gazelle.rocks";
89  
  
90  
  switchable S grabUsersFromDomain = "https://gazelle.rocks";
91  
  
92  
  switchable bool verboseQStartsAndStops;
93  
94  
  transient ConceptClassesDependentValue<Int> inputsWithoutRewrites;
95  
  transient ConceptClassesDependentValue<Int> inputsWithoutMatches;
96  
  transient ConceptClassesDependentValue<Int> syntacticPatternsWithoutRewrites;
97  
  
98  
  transient Set<File> deadClassPaths = syncLinkedHashSet();
99  
  
100  
  transient new x30_pkg.x30_util.BetterThreadLocal<BEA> beaThreadOwner;
101  
  
102  
  switchable long beaHomePageID;
103  
  
104  
  bool verboseClassLoading;
105  
  
106  
  transient bool saveAllQsAsInput = true;
107  
  transient bool moveNavItemsToMisc = false;
108  
  transient bool additionalCommandsForObjects = true;
109  
  
110  
  transient bool exportUsersInVM, exportUsersGlobally;
111  
  
112  
  transient bool sourceIsPublic = true;
113  
  
114  
  // create an anonymous user for each cookie unless user is logged in
115  
  transient bool makeAnonymousUsers = true;
116  
  
117  
  transient bool showInputAndPatternNavItems = true;
118  
  
119  
  transient IConceptCounter beaModifiedIndex;
120  
  
121  
  transient bool showInputCounts;
122  
  
123  
  transient ConceptFieldIndexCI_certainValues<BEA> serviceIndex;
124  
  
125  
  transient bool runRefChecker = true;
126  
127  
  // add more fields for GazelleBEA here
128  
  
129  
  !include #1030883 // DB quickImport mix-in
130  
  
131  
  void init :: after {
132  
    //set quickDBReloadEnabled;
133  
    botName = heading = adminName = "Gazelle";
134  
    set enableVars;
135  
    unset showTalkToBotLink;
136  
    unset phoneNumberSpecialInputField;
137  
    unset showMetaBotOnEveryPage;
138  
    unset showDeliveredDomains;
139  
    set showCRUDToEveryone;
140  
  }
141  
142  
  
143  
  Class lookForClass(ClassLoader cl, S name) {
144  
    pcall {
145  
      // first thing - filter
146  
      if (!name.startsWith(DynClassName_mainPrefixForVM())) null;
147  
    
148  
      if (verboseClassLoading) print("Looking for class " + name);
149  
      DynClassName dcn = DynClassName_parse(name);
150  
      if (dcn == null) {
151  
        vmBus_send lookingForClass_noDCNParse(name);
152  
        null;
153  
      }
154  
      S subPath = vmClassNameToSubPath(dcn.fullClassNameForVM());
155  
      File byteCode = dcn.byteCodeFile();
156  
      vmBus_send lookingForClass_haveByteCodeFile(byteCode);
157  
      
158  
      // jar already added? done.
159  
      if (classLoaderContainsByteCodePath(cl, byteCode)) {
160  
        vmBus_send lookingForClass_haveByteCodePath(byteCode);
161  
        null;
162  
      }
163  
      
164  
      // class file not found? bad.
165  
      bool exists = fileExistsInInDirOrZip(byteCode, subPath);
166  
      printVars(+exists, +subPath, +byteCode);
167  
      if (!exists) {
168  
        vmBus_send lookingForClass_subPathNotInByteCode(byteCode, subPath);
169  
        fail("Class not found: " + subPath + " in " + byteCode);
170  
      }
171  
        
172  
      // register byte code path
173  
      addByteCodePathToClassLoader(cl, byteCode);
174  
      assertTrue("Byte code added", classLoaderContainsByteCodePath(cl, byteCode));
175  
      vmBus_send lookingForClass_byteCodePathAdded(byteCode);
176  
      
177  
      ret (Class) call(cl, "super_findClass", name);
178  
      
179  
      // null; // just let the class loader do its thing
180  
      
181  
      // recursive call
182  
      //ctex { ret cl.loadClass(name); }
183  
    }
184  
    null;
185  
  }
186  
  
187  
  // MUST be called outside of loadableUtils
188  
  void db {
189  
    main db();
190  
  }
191  
192  
  void startDB {
193  
    ClassLoader cl = dm_moduleClassLoader();
194  
    setOpt(cl, verbose := verboseClassLoading);
195  
    setFieldToIF1Proxy(cl, findClass_extension := (IF1<S, Class>) name -> {
196  
      vmBus_send lookingForClass(cl, name);
197  
      Class c = lookForClass(cl, name);
198  
      vmBus_send lookingForClass_result(cl, name, c);
199  
      ret c;
200  
    });
201  
    
202  
    S mainPrefix = dotToDollar(DynClassName_mainPrefix());
203  
    O classFinder = _defaultClassFinder();
204  
    //Map<S, Class> classesCache = syncMap();
205  
    db_mainConcepts().classFinder = func(S name) -> Class enter {
206  
      //ret syncGetOrCreate(classesCache, name, () -> {
207  
        print("Deserializing class " + name);
208  
        try {
209  
          S name2 = dropPrefix(mcName() + "$", name);
210  
          //printVars(+mainPrefix, +name2);
211  
          if (name2.startsWith(mainPrefix)) {
212  
            S vmName = DynClassName_parse(name2).fullClassNameForVM();
213  
            if (verboseClassLoading) print("Loading dynamic class " + vmName);
214  
            Class c = classLoader_loadClass(module(), vmName);
215  
            //print(+c);
216  
            ret c;
217  
          }
218  
          Class c = cast callF(classFinder, name);
219  
          print(+c);
220  
          ret c;
221  
        } on fail e {
222  
          printStackTrace(e);
223  
        }
224  
      //});
225  
    };
226  
    
227  
    db();
228  
    Concepts concepts = db_mainConcepts();
229  
    concepts.useBackRefsForSearches = true;
230  
    concepts.newBackupEveryXMinutes = backupFrequency;
231  
    
232  
    fixConceptIDs();
233  
    
234  
    /*concepts.makeStructureData = () ->
235  
      concepts.finishStructureData(new structure_Data {
236  
        void writeObject(O o, S shortName, MapSO fv) {
237  
          if (o instanceof Concept && conceptID(o/Concept) == 136925)
238  
            print("writeProblemObject " + shortName + " " + fv);
239  
          super.writeObject(o, shortName, fv);
240  
        }
241  
      });*/
242  
      
243  
    inputsWithoutRewrites = ConceptClassesDependentValue(litset(BEA), () -> countPred(beaList("Input"), c -> empty(beaBackRefs(c, "Rewrite"))));
244  
    inputsWithoutMatches = ConceptClassesDependentValue(litset(BEA), () -> countPred(beaList("Input"), c -> empty(beaBackRefs(c, "Match"))));
245  
    syntacticPatternsWithoutRewrites = ConceptClassesDependentValue(litset(BEA), () -> countPred(beaList("Syntactic Pattern"), c -> empty(beaBackRefs(c, "Rewrite"))));
246  
    
247  
    if (useShadowLogger) {
248  
      ConceptsShadowLogger shadowLogger = new(db_mainConcepts());
249  
      shadowLogger.install();
250  
      //shadowLogger.writer = printWriter(deflaterOutputStream_syncFlush_append(programFile("shadow.log.deflated")));
251  
      shadowLogger.writer = filePrintWriter_append(programFile("shadow.log"));
252  
      dm_doEvery(10.0, r { shadowLogger.flush(); });
253  
      ownResource(shadowLogger);
254  
    }
255  
    
256  
    if (printAllConceptChanges) printAllConceptChanges();
257  
  }
258  
  
259  
  MapSO emergencyFlags() {
260  
    ret (Map) parseEqualsProperties(
261  
      joinNemptiesWithEmptyLines(
262  
        loadTextFile(programFile("gazelle-emergency-options.txt")),
263  
        loadTextFile(javaxDataDir("gazelle-emergency-options.txt")),
264  
      ));
265  
  }
266  
  
267  
  void start {
268  
    try {
269  
      start2();
270  
    } catch print e {
271  
      startError = e;
272  
    }
273  
  }
274  
  
275  
  void start2 {
276  
    print("Module ID: " + dm_moduleID());
277  
    set !useBotNameAsModuleName;
278  
    setOptAllDyn_pcall(module(), pnl(+emergencyFlags()));
279  
    change();
280  
    S name = actualMCDollar() + "BEAObject";
281  
    print(+name);
282  
    assertEquals(BEA, callF(defaultDefaultClassFinder(), name));
283  
    assertEquals(ConceptWithGlobalID, callF(defaultDefaultClassFinder(), mcDollar() + "ConceptWithGlobalID"));
284  
    //seedDBFrom(#1030602);
285  
    set botDisabled;
286  
    set storeBaseClassesInStructure;
287  
    super.start();
288  
    assertSameVerbose("miscMap me", db_mainConcepts().miscMapGet(DynNewBot2), this);
289  
    assertSameVerbose("beaMod()", main beaMod(), this);
290  
    print("main concepts: " + db_mainConcepts() + ", count: " + db_mainConcepts().countConcepts());
291  
    print(renderConceptClassesWithCount(db_mainConcepts()));
292  
    print("Inputs: " + n2(beaCount("Input")));
293  
    //set showOnlySelectedObject;
294  
    
295  
    // reload module when lib changes
296  
      dm_onSnippetTranspiled(mainLibID, r {
297  
        if (reloadWhenMainLibUpdates) {
298  
          setField(reloadWhenMainLibUpdates := false);
299  
          dm_reloadModule();
300  
        }
301  
      });
302  
303  
    if (!enabled) ret;
304  
    
305  
    if (exportUsersInVM) {
306  
       dm_vmBus_answerToMessage lookupGazelleUser(func(S name, S pw, S salt) {
307  
        if (!eq(salt, passwordSalt())) null;
308  
        User user = conceptWhereCI User(+name);
309  
        if (user == null) null;
310  
        if (!eq(getVar(user.passwordMD5), hashPW(pw))) null;
311  
        ret user;
312  
      });
313  
    }
314  
315  
    // mirror all objects to be sure
316  
    rstUpdateBEAMirrors.addAll(list(BEA));
317  
    
318  
    newObjectsDistributed.addAll(list(BEA));
319  
    
320  
    /*onIndividualConceptChange_notOnAllChanged(BEA,
321  
      p -> { calculations.makeSyntacticPattern(p); });*/
322  
      
323  
    onIndividualConceptChange_notOnAllChanged(BEA,
324  
      o -> {
325  
        if (enableAutoRuns) rstAutoRuns.add(o);
326  
        if (enableNewBEAObjectNotis && newObjectsDistributed.add(o))
327  
          rstDistributeNewObject.add(o);
328  
      });
329  
      
330  
    notificationQ = dm_startQ();
331  
    
332  
    // fix refs occasionally
333  
    
334  
    if (runRefChecker)
335  
      dm_doEvery(60*60.0, r-enter {
336  
        print(ConceptsRefChecker(db_mainConcepts()).runAndFixAll());
337  
      });
338  
    
339  
    // call _activate on all activatable objects (initial activations)
340  
    
341  
    setField(activateOnlyTheseIDs := nemptyLinesLL(
342  
      loadTextFile(programDir("gazelle-activate-only")),
343  
      loadTextFile(javaxDataDir("gazelle-activate-only"))));
344  
    Set<Long> ids = activateOnlyTheseIDs();
345  
    
346  
    Set<Long> antiIDs = dontActivateTheseIDs();
347  
    print("Object IDs to activate: " + (ids == null ? "ALL" : ids));
348  
    if (nempty(antiIDs)) print("Object IDs not to activate: " + (ids == null ? "ALL" : ids));
349  
    for (BEA bea)
350  
      if ((ids == null || contains(ids, bea.id)) && !contains(antiIDs, bea.id)) {
351  
        //print("Activating: " + bea);
352  
        callActivate(bea);
353  
      }
354  
      
355  
    cleanReifiedWebSockets();
356  
      
357  
    set allObjectsActivated; // proof of start
358  
    print("All objects activated");
359  
  } // end of start2()
360  
  
361  
  void makeIndices :: after {    
362  
    indexConceptClass(BSettings);
363  
    beaModifiedIndex = (IConceptCounter) indexConceptFieldDesc_v2(BEA, "_modified");
364  
    indexConceptFieldCIWithTopTen(BEA, "type");
365  
    indexConceptFieldIC(BEA, "text");
366  
    indexConceptFieldIC(BEA, "purpose");
367  
    
368  
    serviceIndex = ConceptFieldIndexCI_certainValues<BEA>(BEA, "meta_isService") {
369  
      bool isApplicableValue(O o) { ret o != null; }
370  
    };
371  
  }
372  
  
373  
  bool isMaster(Req req) {
374  
    ret req?.masterAuthed;
375  
  }
376  
  
377  
  @Override
378  
  L<Class> crudClasses(DynGazelleRocks.Req req) {
379  
    var l = super.crudClasses(req);
380  
    l.add(BEA);
381  
    if (masterAuthed(req))
382  
      l.add(AuthedDialogID);
383  
    ret l;
384  
  }
385  
  
386  
  Set<Class> hiddenCrudClasses() {
387  
    Set<Class> set = super.hiddenCrudClasses();
388  
    set.remove(UploadedSound);
389  
    addAll(set, Conversation, ExecutedStep, InputHandled);
390  
    ret set;
391  
  }
392  
  
393  
  S authFormHeading() {
394  
    ret h3(adminName);
395  
  }
396  
  
397  
  swappable S authFormMoreContent() {
398  
    S html = p(html_loggedIn(false));
399  
    
400  
    new HTMLFramer1 framer;
401  
    if (anonymousUserWarning(currentReq(), framer))
402  
      html += lines(framer.contents);
403  
    
404  
    ret html + super.authFormMoreContent();
405  
  }
406  
  
407  
  HTMLFramer1 newFramerInstance() {
408  
    ret new BEAHTMLFramer;
409  
  }
410  
411  
  @Override
412  
  void makeFramer(DynGazelleRocks.Req req) {
413  
    super.makeFramer(req);
414  
    
415  
    if (!allObjectsActivated)
416  
      req.framer.add(p("WARNING: Start failure"));
417  
      
418  
    if (startError != null)
419  
      req.framer.add(pre_htmlEncode("WARNING: Start error\n\n" + renderStackTrace(startError)));
420  
421  
    req.framer.addInHead(hjs_selectize());
422  
    req.framer.addInHead(hjs_selectizeClickable());
423  
    req.framer.addInHead(hjs_autoExpandingTextAreas());
424  
  }
425  
426  
  @Override
427  
  <A extends Concept> HCRUD_Concepts<A> crudData(Class<A> c, DynGazelleRocks.Req _req) {
428  
    Req req = cast _req;
429  
    HCRUD_Concepts<A> cc = super.crudData(c, req);
430  
    
431  
    if (c == UploadedSound || c == UploadedFile || c == UploadedImage) {
432  
      cc.onCreate.add(o -> cset(o, createdBy := currentUser());
433  
      cc.emptyConcept = () -> {
434  
        Concept ccc = cc.emptyConcept_base();
435  
        cset(ccc, isPublic := true);
436  
        ret (A) ccc;
437  
      };
438  
    }
439  
    
440  
    if (c == BEA) {
441  
      cc.lsMagic = true;
442  
      cc.humanizeFieldNames = false;
443  
      cc.convertConceptValuesToRefs = true;
444  
      cc.itemName = () -> "BEA Object";
445  
      
446  
      cc.addRenderer("meta_code", new HCRUD_Data.AceEditor(80, 20));
447  
      
448  
      cc.titleForObjectID = id -> beaHTML(beaGet(toLong(id)));
449  
      
450  
      cc.isEditableValue = o ->
451  
        cc.isEditableValue_base(o)
452  
          && !eq(shortClassName(o), "MultiSet");
453  
      
454  
      cc.conceptClassForComboBoxSearch = (info, query) -> {
455  
        if (endsWith(info, "_nativeValue"))
456  
          ret Concept;
457  
        ret cc.conceptClassForComboBoxSearch_base(info, query);
458  
      };
459  
      
460  
      cc.comboBoxSearchBaseItems = (info, query) -> {
461  
        new Matches m;
462  
        printVars comboBoxSearchBaseItems(+info);
463  
        if (jMatchStart("type=<quoted>", info, m)) {
464  
          var l = beaList($1);
465  
          print("Got " + nItems(l));
466  
          ret cc.comboBoxItems(l);
467  
        }
468  
        ret cc.comboBoxSearchBaseItems_base(info, query);
469  
      };
470  
      
471  
      // prevent non-master users from forging/changing createdBy
472  
      cc.massageItemMapForUpdate = (o, map) -> {
473  
        cc.massageItemMapForUpdate_base(o, map);
474  
        
475  
        if (!isMaster(req)) {
476  
          O createdByOld = cget createdBy(o);
477  
          if (createdByOld != null || map.get("createdBy") != user(req))
478  
            map.remove("createdBy");
479  
        }
480  
      };
481  
      
482  
      // probably already done by massageItemMapForUpdate
483  
      cc.onCreate.add(o ->
484  
        cset(o, createdBy := currentUser()));
485  
      
486  
      cc.getObjectForDuplication = id -> {
487  
        BEA o = beaGet(toLong(id));
488  
        MapSO item = cc.getObjectForDuplication_base(id);
489  
        item.put(createdBy := req.auth.user!); // set default creator to current user
490  
        item.put(meta_createdFrom := o);
491  
        
492  
        // call enhancer object
493  
        item = or((MapSO) pcallOpt(cget cleanObjectForDuplication(websiteEnhancersObject()), "cleanObjectForDuplication", o, item), item);
494  
        
495  
        ret item;
496  
      };
497  
      
498  
      cc.emptyObject = () -> {
499  
        MapSO item = cc.emptyObject_base();
500  
        item.put(type := "");
501  
        ret item;
502  
      };
503  
      
504  
      Set<S> deletableRefs = litciset("Match");
505  
      
506  
      cc.objectCanBeDeleted = id -> {
507  
        BEA o = cast cc.conceptForID(id);
508  
        ret userCanEditObject(user(req), o)
509  
          && all(findBackRefs(BEA, o), x -> contains(deletableRefs, x.type()));
510  
      };
511  
512  
      cc.actuallyDeleteConcept = o -> {
513  
        deleteConcepts(filter(findBackRefs(BEA, o),
514  
          o2 -> contains(deletableRefs, o2.type())));
515  
        cdelete(o);
516  
      };
517  
    }
518  
519  
    ret cc;
520  
  }
521  
  
522  
  bool newLinkTargetBlank_base(Class c) {
523  
    ret super.newLinkTargetBlank_base(c) || c == BEA;
524  
  }
525  
526  
  <A extends Concept> HCRUD makeCRUDBase(Class<A> c, DynGazelleRocks.Req req, HTMLFramer1 framer) {
527  
    HCRUD crud = super.makeCRUD(c, req, framer);
528  
    HCRUD_Concepts<A> data = cast crud.data;
529  
    crud.showOnlySelected = true;
530  
    crud.showSearchField = true;
531  
    if (data.customFilter == null)
532  
      crud.descending = true; // show latest objects first by default except when searching
533  
    crud.cleanItemIDs = true;
534  
    
535  
    if (c == BEA)
536  
      customizeBEACrud(crud, req, framer);
537  
      
538  
    ret crud;
539  
  }
540  
541  
  @Override
542  
  <A extends Concept> HCRUD makeCRUD(Class<A> c, DynGazelleRocks.Req req, HTMLFramer1 framer) {
543  
    HCRUD crud = makeCRUDBase(c, req, framer);
544  
    if (c == BEA)
545  
      optimizeBEACrud(crud);
546  
    ret crud;
547  
  }
548  
    
549  
  void customizeBEACrud(HCRUD crud, DynGazelleRocks.Req req, HTMLFramer1 framer) {
550  
    HCRUD_Concepts<BEA> data = cast crud.data;
551  
    
552  
    crud.allowFieldRenaming = true;
553  
    
554  
    var renderValue_old = crud.renderValue;
555  
    crud.renderValue = (field, value) -> {
556  
      S html = crud.renderValue_fallback(renderValue_old, field, value);
557  
      ret addShowMoreButton(html);
558  
    };
559  
    
560  
    crud.haveSelectizeClickable = true;
561  
    
562  
    crud.cellColumnToolTips = true;
563  
    crud.unshownFields = litset("mirrorPost", "globalID");
564  
    crud.uneditableFields = litset("log", "meta_dependsOnCode");
565  
    crud.showTextFieldsAsAutoExpandingTextAreas = true;
566  
    crud.duplicateInNewTab = true;
567  
    
568  
    crud.customizeACEEditor = ace -> {
569  
      ace.onKeyDown = "function(event) { " + jquery_submitFormOnCtrlEnter() + " }";
570  
    };
571  
    
572  
    HCRUD_Concepts cc = cast crud.data;
573  
    
574  
    S typeFilter = req.get("type");
575  
    if (nempty(typeFilter))
576  
      cc.addCIFilter(type := typeFilter);
577  
    
578  
    crud.renderCmds = map -> {
579  
      BEA o = getConcept BEA(crud.itemIDAsLong(map));
580  
      if (o == null) ret "";
581  
      
582  
      new LS cmds;
583  
      
584  
      // ask the object itself for commands
585  
      
586  
      addAll(cmds, allToString(optCast Cl(pcall(o, "cmds"))));
587  
      
588  
      // special commands for BEA types or objects with certain fields
589  
      
590  
      if (fileNotEmpty(javaSourceFileForObject(o)))
591  
        cmds.add(targetBlank(baseLink + "/javaSource?id=" + o.id, "Show Java source"));
592  
      
593  
      for (S field : ll("text", "what", "type")) {
594  
        S text = getStringOpt(o, field);
595  
        if (nempty(text))
596  
          cmds.add(
597  
            targetBlank_noFollow(addParamsToURL(baseLink + "/query",
598  
              q := text, algorithm := "process input"),
599  
              "Use field " + quote(field) + " as query"));
600  
      }
601  
602  
      if (o.typeIs("Input")) {
603  
        cmds.add(ahref(addParamsToURL(crudLink(BEA),
604  
          cmd := "new",
605  
          title := "Add Pattern For Input",
606  
          f_type := "Pattern",
607  
          f_text := getStringOpt text(o),
608  
          f_shouldMatch := o.id, metaInfo_shouldMatch := "concept",
609  
        ), "Add pattern"));
610  
611  
        cmds.add(ahref(appendParamsToURL(baseLink + "/query", q := o.text(), algorithm := "Apply all text functions"),
612  
         "Apply all text functions"));
613  
      }
614  
615  
      if (o.typeIs("Pattern"))
616  
        cmds.add(ahref(appendParamsToURL(baseLink + "/query", q := o.id, algorithm := "Run pattern"),
617  
         "Try-run pattern against all inputs"));
618  
      
619  
      if (o.typeIsOneOf("Pattern", "Syntactic Pattern")) {
620  
        cmds.add(ahref(appendParamsToURL(baseLink + "/reactAllInputsWithPattern", patternID := o.id),
621  
         "React pattern with all inputs"));
622  
         
623  
        cmds.add(ahref(appendParamsToURL(baseLink + "/convertSyntacticToSemanticMatchesForWholePattern", patternID := o.id),
624  
          "Convert all syntactic matches to semantic matches"));
625  
      }
626  
627  
      if (o.typeIs("Match")) {
628  
        cmds.add(ahref(appendParamsToURL(baseLink + "/query", q := o.id, algorithm := "Find rewrites for match"),
629  
         "Find rewrites"));
630  
        if (beaTypeIs(beaGet pattern(o), "Syntactic Pattern"))
631  
          cmds.add(ahref(appendParamsToURL(baseLink + "/convertSyntacticToSemanticMatches", matchID := o.id),
632  
          "Convert to semantic matches"));
633  
      }
634  
635  
      cmds.add(
636  
        targetBlank(addParamsToURL(crudLink(BEA),
637  
          cmd := "new",
638  
          newField1_name := "inReferenceTo",
639  
          newField1_type := "BEAObject",
640  
          newField1_conceptValue := o.id),
641  
          "Reference this"));
642  
      
643  
      cmds.add(
644  
        ahref_noFollow(addParamsToURL(baseLink + "/markUseful",
645  
          redirect := beaShortURL(o),
646  
          objectID := o.id),
647  
          "Mark useful"));
648  
      
649  
      cmds.add(
650  
        ahref_noFollow(addParamsToURL(baseLink + "/markBad",
651  
          redirect := beaShortURL(o),
652  
          objectID := o.id),
653  
          "Mark bad"));
654  
      
655  
      cmds.add(addCommentHTML(o));
656  
        
657  
      if (o.typeIsOneOf("Script", "Step in script") || eqic(beforeVerticalBar(o.type()), "Instruction"))
658  
        cmds.add(
659  
          ahref(addParamsToURL(baseLink + "/runInstruction",
660  
            instruction := o.id),
661  
            "Run"));
662  
            
663  
      if (o.typeIs("Function Result")) {
664  
        if (eqic_gen(cget resultType(o), "string"))
665  
          cmds.add(
666  
            ahref(addParamsToURL(baseLink + "/convertResultToInput",
667  
              result := o.id),
668  
              "Convert to input"));
669  
      }
670  
      
671  
      if (o.typeIs("Auto Run"))
672  
        cmds.add(
673  
          ahref(addParamsToURL(baseLink + "/performAutoRunOnAllObjects", autoRun := o.id), "Run on all objects"));
674  
675  
      if (o.getClass() != BEA)
676  
        cmds.add(
677  
          ahref(addParamsToURL(baseLink + "/migrateToBase", id := o.id), "Migrate to BEAObject"));
678  
          
679  
      cmds.add(
680  
        ahref(addParamsToURL(baseLink + "/performAutoRuns",
681  
          onObject := o.id),
682  
          "Perform auto runs on this object"));
683  
          
684  
      framer.addInHead(hjs_copyToClipboard());
685  
      
686  
      cmds.add(ahref_onClick(formatFunctionCall copyToClipboard(jsQuote(o.globalIDStr())), "Copy global ID [" + o.globalIDStr() + "]"));
687  
      
688  
      LS items = llNempties(
689  
        crud.renderCmds_base(map),
690  
        !o.typeIsOneOf("Input", "Pattern", "Syntactic Pattern")
691  
          ? null : addRewriteHTML(o),
692  
        !canAutoMigrate(o) ? null 
693  
          : ahref(addParamsToURL(baseLink + "/autoMigrate", id := o.id), "Auto-migrate to " + shortClassName(defaultCustomClass(o))));
694  
695  
      if (isObjectWithCode(o) && isMasterAuthed(req)) {
696  
        S link = baseLink + "/activateDynamicObject?id=" + o.id;
697  
        if (!codeHashUpToDate(o))
698  
          if (cget meta_codeHash(o) == null)
699  
            items.add(ahref(link, "Compile new code", title := "Object has not been compiled yet"));
700  
          else
701  
            items.add(ahref(link, "Compile changed code", title := "Code has changed afer last compilation"));
702  
        else if (compileErrorInObject(o))
703  
          items.add("Code error. " + ahref(link, "Compile again", title := "Object has code errors - maybe recompiling fixes them?"));
704  
        else if (o.customCodeLoaded())
705  
          items.add(ahref(link, "Compile again", title := "Custom code is loaded. Click if you want to recompile anyway"));
706  
        else
707  
          items.add(ahref(link, "Compile &amp; load"));
708  
      }
709  
      
710  
      if (additionalCommandsForObjects) {
711  
        if (o.canRenderHTML())
712  
          items.add(targetBlank(beaMod().baseLink + "/beaHTML/" + o.id, "Show HTML"));
713  
          
714  
        if (nempty(getStringOpt meta_code(o)))
715  
          items.add(targetBlank(addParamsToURL(beaMod().baseLink + "/get-field", obj := o.id, field := "meta_code", enhance := "JavaX"),
716  
          "Show code"));
717  
      
718  
        S codeState = getStringOpt meta_codeState(o);
719  
        if (swic(codeState, "error"))
720  
          items.add(targetBlank(addParamsToURL(beaMod().baseLink + "/beaHTML/249697", obj := o.id, field := "meta_codeState"),
721  
          "Show compile error"));
722  
          
723  
        bool master = beaMod().isMasterAuthed();
724  
      
725  
        if (hasMethod(o, "_activate")) {
726  
          bool active = isTrue(pcallOpt(o, "_active"));
727  
          bool shouldActivate = isTrue(getOpt(o, "meta_shouldActivate"));
728  
          if (active != shouldActivate)
729  
            items.add("Warning: Object is semi-activated");
730  
          else if (active)
731  
            items.add("Object is active");
732  
          else
733  
            items.add("Object is inactive");
734  
            
735  
          if (master) {
736  
            if (!active || !shouldActivate)
737  
              items.add(ahref(addParamsToURL(beaMod().baseLink + "/activateObject", id := o.id), "ACTIVATE"));
738  
            if (active || shouldActivate)
739  
              items.add(ahref(addParamsToURL(beaMod().baseLink + "/deactivateObject", id := o.id), "DEACTIVATE"));
740  
          }
741  
        }
742  
        
743  
        if (master) {
744  
          new LS methodCalls;
745  
          for (Method method : methodsSortedByNameIC(allLiveMethodsBelowClass(o, BEA))) {
746  
            SS params = litorderedmap(id := o.id, name := method.getName());
747  
            bool incomplete = false, problem = false;
748  
            var types = method.getParameterTypes();
749  
            for (int i = 1; i <= l(types); i++) {
750  
              var argType = types[i-1];
751  
              set incomplete;
752  
              if (eq(argType, S))
753  
                params.put("arg" + i, "putArgHere");
754  
              else if (isSubclassOf(argType, BEA))
755  
                params.put("conceptArg" + i, "putArgHere");
756  
              else {
757  
                params.put("arg" + i, "missingConverterProblem");
758  
                set problem;
759  
              }
760  
            }
761  
            S url = addParamsToURL("/callAnyMethod", params);
762  
            S html = method.getName();
763  
            if (problem) html += appendBracketed("probably can't call, need converter");
764  
            else if (incomplete) html += appendBracketed("fill in parameters first");
765  
            methodCalls.add(targetBlank(url, html));
766  
          }
767  
        
768  
          if (nempty(methodCalls)) items.add(hPopDownButtonWithText("Call a method", methodCalls));
769  
        }
770  
771  
      }
772  
773  
      // add more commands here
774  
      
775  
      pcall { addAll(items, o.directCmds()); }
776  
      
777  
      pcall {
778  
        addAll(items, (LS) callOpt(getWebsiteEnhancer("commandsForObject"), 'commandsFor, o));
779  
      }
780  
      
781  
      items.add(hPopDownButton(cmds));
782  
      ret joinNemptiesWithVBar(items);
783  
    };
784  
    
785  
    cc.massageItemMapForList = (item, map) -> {
786  
      BEA o = cast item;
787  
      
788  
      if (o.typeIs("Input")) {
789  
        Cl<BEA> matches = objectsWhereNotIC(objectsWhereCI(findBackRefs(o, BEA), type := "match"),
790  
          label := "bad");
791  
        map/Map.put("Best Matches", HTML(hparagraphs(
792  
          lmap matchDescHTML(takeFirst(3, matches)))));
793  
      }
794  
      S idField = crud.idField();
795  
      O id = map/Map.get(idField);
796  
      if (id instanceof S)
797  
        map/Map.put(idField, HTML(ahref(beaShortURL(o), id)));
798  
      if (o.typeIs("Function Result")) {
799  
        O value = o~.result;
800  
        map/Map.put("result", HTML(javaValueToHTML(value)));
801  
      }
802  
      
803  
      if (o.typeIs("Match")) {
804  
        SS mapping = o.mapping();
805  
        if (mapping != null)
806  
        map/Map.put("mapping", HTML(
807  
          joinWithBR(map(mapping, (k, v) ->
808  
            htmlEncode2(k) + "=" + calculations.bestInputHTML(v)))));
809  
      }
810  
    
811  
      if (o.getClass() != BEA)
812  
        map/Map.put("Java Class", dropPrefix(actualMCDollar(), className(o)));
813  
        
814  
      if (eq(req.get("showSimpleObjectScore"), "1"))
815  
        map/Map.put("Simple object score" := calculations.simpleObjectScore(o));
816  
        
817  
      o.massageItemMapForList(map/Map);
818  
    }; // end of massageItemMapForList
819  
    
820  
    crud.massageFormMatrix = (map, matrix) -> {
821  
      for (int i = 1; i <= newFieldsToShow; i++) {
822  
        S nf = "newField" + i;
823  
        
824  
        S key = "\*nf*/_conceptValue";
825  
        BEA refValue = beaGet(req.get(key));
826  
        S refSelector =
827  
          crud.renderInput(key,
828  
            cc.makeConceptsComboBox(key, BEA), refValue)
829  
          + hjs([[$("[name=]] + key + [[]").hide()]]);
830  
            
831  
        S nativeSelector =
832  
          crud.renderInput("\*nf*/_nativeValue",
833  
            cc.makeConceptsComboBox("\*nf*/_nativeValue", Concept), null)
834  
          + hjs([[$("[name=]] + nf + [[_nativeValue]").hide()]]);
835  
            
836  
        LS types = ll("String", "BEAObject", "Bool", "Native");
837  
            
838  
        S toggleVis = [[
839  
          $("[name=]] + nf + [[_value]").toggle(value == "String" || value == "Bool");
840  
              $("#]] + nf + [[_refBox").toggle(value == "BEAObject");
841  
              $("#]] + nf + [[_nativeSel").toggle(value == "Native");
842  
        ]];
843  
844  
        S type = req.get("\*nf*/_type");
845  
        S typeSelector = hselect_list(types, type, name := "\*nf*/_type",
846  
            onchange := "var value = this.value;" + toggleVis);
847  
        if (nempty(type))
848  
          typeSelector += hscript("(function(value) {" + toggleVis + "})(" + jsQuote(type) + ")");
849  
        
850  
        matrix.add(ll("Add field:<br>" + htextfield("\*nf*/_name", req.get("\*nf*/_name"), title := "New field name"),
851  
          htmlTable2_noHtmlEncode(ll(ll(
852  
            // string input
853  
            //htextfield("\*nf*/_value"),
854  
            crud.renderTextField("\*nf*/_value", "", 40)
855  
            /*htextarea("",
856  
              name := nf + "_value", 
857  
              class := "auto-expand",
858  
              style := "width: 300px",
859  
            )*/ +
860  
            span(refSelector, id := "\*nf*/_refBox", style := "display: none"),
861  
            span(nativeSelector, id := "\*nf*/_nativeSel", style := "display: none"),
862  
          "Type", typeSelector
863  
          )),
864  
          noHeader := true,
865  
          tableParams := litobjectarray(style := "width: 100%"))));
866  
        }
867  
      };
868  
    
869  
    crud.preprocessUpdateParams = params -> {
870  
      params = cloneMap(params);
871  
872  
      // drop empty strings
873  
      //removeFromMapWhereValue(params, v -> eq(v, ""));
874  
      params = mapValues(params, v -> eq(v, "") ? null : v);
875  
      
876  
      for (int i = 1; i <= max(newFieldsToShow, 10); i++) {
877  
        S nf = "newField" + i;
878  
        S name = trim(params.get("\*nf*/_name")),
879  
          type = params.get("\*nf*/_type"),
880  
          refValue = params.get("\*nf*/_conceptValue"),
881  
          nativeValue = params.get("\*nf*/_nativeValue"),
882  
          value = params.get("\*nf*/_value");
883  
884  
        if (eqic(type, "BEAObject")) {
885  
          value = refValue;
886  
          params.put("metaInfo_" + name, "concept");
887  
        } else if (eqic(type, "Native")) {
888  
          value = nativeValue;
889  
          params.put("metaInfo_" + name, "concept");
890  
        } else if (eqic(type, "Bool"))
891  
          params.put("metaInfo_" + name, "bool");
892  
        
893  
        if (eq(value, "")) value = null;
894  
          
895  
        if (nempty(name) /*&& neqOneOf(value, null, "")*/)
896  
          params.put(crud.fieldPrefix + name, value);
897  
      }
898  
      
899  
      ret params;
900  
    };
901  
902  
    // regular users can only edit their own objects
903  
    if (!isMasterAuthed(req))
904  
      cc.objectCanBeEdited = id -> userCanEditObject(user(req), (BEA) cc.conceptForID(id));
905  
  } // end of customizeBEACrud
906  
  
907  
  // optimize listing all BEA objects
908  
  void optimizeBEACrud(HCRUD crud) {
909  
    HCRUD_Concepts<BEA> data = cast crud.data;
910  
    
911  
    if (empty(data.filters) && empty(data.ciFilters)) {
912  
      crud.sortByField = "_modified";
913  
      crud.descending = true;
914  
      data.listConcepts_firstStep = () -> (L) cloneList(beaModifiedIndex.allConcepts());
915  
      data.defaultSortField = () -> pair("_modified", true);
916  
      data.defaultSort = lambda1 id;
917  
    }
918  
  } // end of optimizeBEACrud
919  
920  
  bool userCanEditObject(User user, BEA o) {
921  
    ret user != null && (user.isMaster || cget createdBy(o) == user);
922  
  }
923  
  
924  
  @Override
925  
  O servePossiblyUserlessBotFunction(DynGazelleRocks.Req req, S function, Map data, User user) {
926  
    if (eq(function, "beaExport")) {
927  
      Cl<Long> ids = concatLists(
928  
        tok_integersAsLongs(urldecode(req.subURI)),
929  
        tok_integersAsLongs(req.get("ids")));
930  
        
931  
      var objects = map beaGet(ids);
932  
      ret serveJSON_breakAtLevels(2, result := map(objects, obj ->
933  
        litorderedmap(id := obj.id,
934  
          gid := str(obj.globalID()),
935  
          struct := obj.structureString())));
936  
    }
937  
    
938  
    ret super.servePossiblyUserlessBotFunction(req, function, data, user);
939  
  }
940  
  
941  
  @Override
942  
  O serveBotFunction(DynGazelleRocks.Req req, S function, Map data, User user) {
943  
    if (eq(function, "beaList")) {
944  
      long changedAfter = toLong(data.get("changedAfter"));
945  
      double pollFor = min(bot_maxPollSeconds, toLong(data.get("pollFor"))); // how long to poll (seconds)
946  
      long startTime = sysNow();
947  
948  
      // We're super-anal about catching all changes. This will probably never trigger
949  
      if (changedAfter > 0 && changedAfter == now()) sleep(1);
950  
      
951  
      Cl<BEA> objects;
952  
      while true {
953  
        objects = changedAfter == 0 ? list(BEA)
954  
          : conceptsWithFieldGreaterThan_sorted(BEA, _modified := changedAfter);
955  
956  
        // return when there are results, no polling or poll expired
957  
        if (nempty(objects) || pollFor == 0 || elapsedSeconds_sysNow(startTime) >= pollFor)
958  
          ret serveJSON_breakAtLevels(2, result := map(objects, obj ->
959  
            litorderedmap(gid := str(obj.globalID()), struct := obj.structureString())
960  
          ));
961  
962  
        // sleep and try again
963  
        sleep(bot_pollInterval);
964  
      }
965  
    }
966  
    
967  
    ret super.serveBotFunction(req, function, data, user);
968  
  }
969  
970  
  @Override
971  
  O serveOtherPage2(DynGazelleRocks.Req req) { ret serveOtherPage2((Req) req); }
972  
  O serveOtherPage2(Req req) null {
973  
    //printVars_str serveOtherPage2(uri := req.uri);
974  
    try object super.serveOtherPage2(req);
975  
    
976  
    S uri = dropTrailingSlashIfNemptyAfterwards(req.uri);
977  
    new Matches m;
978  
    
979  
    if (swic_notSame(uri, "/beaCRUD/", m))
980  
      ret renderBEAObjectTable(req, urldecode(m.rest()));
981  
    
982  
    if (eq(uri, "/inputs"))
983  
      ret renderBEAObjectTable(req, "input");
984  
      
985  
    if (eq(uri, "/syntacticMatchesTable"))
986  
      ret hrefresh(baseLink + "/matchesTable?syntacticOnly=1");
987  
      
988  
    if (eq(uri, "/matchesTable")) {
989  
      bool syntacticOnly = eq("1", req.get("syntacticOnly"));
990  
991  
      new L<BEA> list;
992  
      for (BEA match : beaList("Match")) {
993  
        BEA input = cgetOpt BEA(match, "input");
994  
        BEA pat = cgetOpt BEA(match, "pattern");
995  
        if (syntacticOnly && !calculations.isSyntacticPattern(pat)) continue;
996  
        continue if calculations.patternAlwaysMatches(pat);
997  
998  
        SS mapping = match.mapping();
999  
        if (input == null || pat == null || mapping == null) continue;
1000  
        list.add(match);
1001  
      }
1002  
      list = sortedByCalculatedFieldDesc(list,
1003  
        match -> conceptID(cgetBEA input(match)));
1004  
      
1005  
      new HTMLPaginator paginator;
1006  
      paginator.processParams(req.params());
1007  
      paginator.baseLink = addParamsToURL(baseLink,
1008  
        filterKeys(req.params(), p -> eq(p, "syntacticOnly")));
1009  
      paginator.max = l(list);
1010  
      req.framer.add(divUnlessEmpty(paginator.renderNav()));
1011  
      list = subList(list, paginator.visibleRange());
1012  
      
1013  
      new L<Map> data;
1014  
      for (BEA match : list) {
1015  
        BEA input = cgetOpt BEA(match, "input");
1016  
        BEA pat = cgetOpt BEA(match, "pattern");
1017  
        SS mapping = match.mapping();
1018  
        S patText = calculations.patternTextWithAngleBracketVars(pat);
1019  
        LS tok = javaTokWithAngleBrackets(patText);
1020  
        new BitSet bs;
1021  
        tok = replaceAngleBracketVars(tok, mapping, bs);
1022  
        for (IntRange r, bool b : unpair bitSetStreaksAndNonStreaks(bs, l(tok))) {
1023  
          S t = joinSubList(tok, r);
1024  
          replaceTokens(tok, r, b
1025  
            ? formatSubInput(input, t, "obj" + match.id)
1026  
            : htmlEncode2(t));
1027  
        }
1028  
        S boldenedInput = join(tok);
1029  
        
1030  
        data.add(litorderedmap(
1031  
          "Input" := aname("obj" + match.id, joinWithBR(
1032  
            ahref(beaURL(input), htmlEncode2_str(input~.text())),
1033  
            boldenedInput)),
1034  
          "Pattern" := ahref(beaURL(pat), htmlEncode2(pat.text())), // beaHTML(pat),
1035  
          //"Sub-Inputs" := htmlEncode2_str(mapping),
1036  
          "DB object" := beaHTML_justID(match),
1037  
          "Feedback" := joinNemptiesWithSpace(calculations.feedbackHTML(match),
1038  
            addCommentHTML(match, defaultComment := "good match", redirectAfterSave := req.uriWithParams(),
1039  
            text := "+")),
1040  
        ));
1041  
      }
1042  
        
1043  
      req.framer.title = syntacticOnly ? "Syntactic matches table" : "Matches table";
1044  
      req.framer.add(p());
1045  
      req.framer.add(htmlTable2_noHtmlEncode(data));
1046  
      ret completeFrame(req);
1047  
    }
1048  
            
1049  
    if (eq(uri, "/rewritesTable")) {
1050  
      new L<Map> data;
1051  
      for (BEA input : beaList("Input"))
1052  
        for (BEA r : beaBackRefs(input, "Rewrite"))
1053  
          data.add(litorderedmap(
1054  
            "Input" := input.text(),
1055  
            "Rewrite" := tok_dropCurlyBrackets(r.text()),
1056  
            "Rewrite Type" := r~.rewriteType));
1057  
            
1058  
      req.framer.title = "Rewrites table";
1059  
      req.framer.add(p());
1060  
      req.framer.add(htmlTable2(data));
1061  
      ret completeFrame(req);
1062  
    }
1063  
1064  
    if (eq(uri, "/patternRewritesTable")) {
1065  
      new L<Map> data;
1066  
      for (BEA pat : beaListAny("Pattern", "Syntactic Pattern"))
1067  
        for (BEA r : beaBackRefs(pat, "Rewrite"))
1068  
          if (cget isRewriteOf(r) == pat) {
1069  
            PairS example = calculations.exampleForPatternRewrite(pat, r);
1070  
            data.add(litorderedmap(
1071  
              "Pattern" := ahref(beaURL(pat), htmlEncode2(pat.text())),
1072  
              "Rewrite" := ahref(beaURL(r), htmlEncode2(tok_dropCurlyBrackets(r.text()))),
1073  
              "Example" := joinWithBR(htmlEncode2(example.a), htmlEncode2("=> " + example.b)),
1074  
              "Rewrite Type" := htmlEncode2_str(r~.rewriteType),
1075  
              "Cmds" := hPopDownButton(
1076  
                targetBlank(conceptEditLink(r), "Edit"),
1077  
              )));
1078  
          }
1079  
            
1080  
      req.framer.title = "Pattern rewrites table";
1081  
      req.framer.add(p());
1082  
      req.framer.add(htmlTable2_noHtmlEncode(data));
1083  
      ret completeFrame(req);
1084  
    }
1085  
1086  
    if (eq(uri, "/syntacticPatternsWithoutRewrites")) {
1087  
      HCRUD crud = makeBEATypeCrud(req, "Syntactic Pattern");
1088  
      HCRUD_Concepts<BEA> data = cast crud.data;
1089  
      
1090  
      IF1 prev = data.customFilter;
1091  
      data.customFilter = list -> {
1092  
        list = filter(list, c -> empty(beaBackRefs(c, "Rewrite")));
1093  
        ret postProcess(prev, list);
1094  
      };
1095  
      crud.customTitle = "Syntactic patterns without rewrites";
1096  
      ret serveCRUD(req, BEA, crud);
1097  
    }
1098  
      
1099  
    if (eq(uri, "/inputsWithoutRewrites")) {
1100  
      HCRUD crud = makeBEATypeCrud(req, "input");
1101  
      HCRUD_Concepts<BEA> data = cast crud.data;
1102  
      
1103  
      IF1 prev = data.customFilter;
1104  
      data.customFilter = list -> {
1105  
        list = filter(list, c -> empty(beaBackRefs(c, "Rewrite")));
1106  
        ret postProcess(prev, list);
1107  
      };
1108  
      crud.customTitle = "Inputs without rewrites";
1109  
      ret serveCRUD(req, BEA, crud);
1110  
    }
1111  
      
1112  
    if (eq(uri, "/inputsWithRewrites")) {
1113  
      HCRUD crud = makeBEATypeCrud(req, "input");
1114  
      HCRUD_Concepts<BEA> data = cast crud.data;
1115  
      
1116  
      IF1 prev = data.customFilter;
1117  
      data.customFilter = list -> {
1118  
        list = filter(list, c -> nempty(beaBackRefs(c, "Rewrite")));
1119  
        ret postProcess(prev, list);
1120  
      };
1121  
      crud.customTitle = "Inputs with rewrites";
1122  
      ret serveCRUD(req, BEA, crud);
1123  
    }
1124  
      
1125  
    if (eq(uri, "/inputsWithoutMatches")) {
1126  
      HCRUD crud = makeBEATypeCrud(req, "input");
1127  
      HCRUD_Concepts<BEA> data = cast crud.data;
1128  
      
1129  
      IF1 prev = data.customFilter;
1130  
      data.customFilter = list -> {
1131  
        list = filter(list, c -> empty(beaBackRefs(c, "Match")));
1132  
        ret postProcess(prev, list);
1133  
      };
1134  
      crud.customTitle = "Inputs without matches";
1135  
      ret serveCRUD(req, BEA, crud);
1136  
    }
1137  
      
1138  
    if (eq(uri, "/patterns"))
1139  
      ret renderBEAObjectTable(req, "pattern");
1140  
1141  
    if (eq(uri, "/syntacticPatterns"))
1142  
      ret renderBEAObjectTable(req, "Syntactic Pattern");
1143  
1144  
    if (eq(uri, "/matches"))
1145  
      ret renderBEAObjectTable(req, "match");
1146  
1147  
    if (eq(uri, "/rewrites"))
1148  
      ret renderBEAObjectTable(req, "rewrite");
1149  
1150  
    if (eq(uri, "/aiTasks"))
1151  
      ret renderBEAObjectTable(req, "AI Task");
1152  
1153  
    if (eq(uri, "/query"))
1154  
      ret calculations.serveQueryPage(req);
1155  
      
1156  
    if (eq(uri, "/queryHome"))
1157  
      ret calculations.serveQueryPage(req, true);
1158  
      
1159  
    if (eq(uri, "/saveInput")) {
1160  
      if (userAgentIsBot(currentUserAgent())) ret "You're a bot";
1161  
      
1162  
      S text = trim(req.get("text"));
1163  
      S info = trim(req.get("info"));
1164  
      S uncleanedText = trim(req.get("uncleanedText"));
1165  
      if (empty(text)) ret subBot_serve500("Input empty");
1166  
      if (l(text) > maxInputLength) ret subBot_serve500("Input too long");
1167  
      BEA input = uniqCI BEA(type := "Input", +text, createdBy := user(req));
1168  
      saveUserAgent(input);
1169  
      if (nempty(info) || nempty(uncleanedText))
1170  
        cnew BEA(type := "Input Source", +input, source := info, +uncleanedText);
1171  
      ret hrefresh(or2(req.get("redirect"), baseLink + "/"));
1172  
    }
1173  
      
1174  
    if (eq(uri, "/markUseful")) {
1175  
      BEA o = beaGet objectID(req);
1176  
      if (o == null) ret subBot_serve500("Object not found");
1177  
      uniqCI BEA(type := "Useful", object := o, createdBy := user(req));
1178  
      ret hrefresh(or2(req.get("redirect"), baseLink + "/"));
1179  
    }
1180  
      
1181  
    if (eq(uri, "/markBad")) {
1182  
      BEA o = beaGet objectID(req);
1183  
      if (o == null) ret subBot_serve500("Object not found");
1184  
      uniqCI BEA(type := "Bad", object := o, createdBy := user(req));
1185  
      ret hrefresh(or2(req.get("redirect"), baseLink + "/"));
1186  
    }
1187  
      
1188  
    if (eq(uri, "/saveAnswer")) {
1189  
      S text = trim(req.get("text"));
1190  
      S rewriteType = or2(trim(req.get("rewriteType")), "Suggested Answer");
1191  
      long inputID = toLong(req.get("inputID"));
1192  
      BEA input = beaGet(inputID);
1193  
      if (input == null) ret subBot_serve500("Input not found");
1194  
      if (empty(text)) ret subBot_serve500("Text empty");
1195  
      if (l(text) > maxInputLength) ret subBot_serve500("Text too long");
1196  
      uniqCI BEA(type := "Rewrite", +text, isRewriteOf := input, +rewriteType, createdBy := user(req));
1197  
      ret hrefresh(or2(req.get("redirect"), baseLink + "/"));
1198  
    }
1199  
1200  
    if (eq(uri, "/javaSource")) {
1201  
      BEA o = beaGet id(req);
1202  
      ret serveText(loadTextFile(javaSourceFileForObject(o)));
1203  
    }
1204  
    
1205  
    if (startsWith(uri, "/beaHTML/", m)) {
1206  
      S rest = m.rest();
1207  
      int i = smartIndexOf(rest, "/");
1208  
      BEA o = beaGet(takeFirst(i, rest));
1209  
      S subURI = substring(rest, i+1);
1210  
      ret serveBEAHTML(req, o, subURI);
1211  
    }
1212  
    
1213  
    if (eq(uri, "/is-a-gazelle")) ret "yes";
1214  
    
1215  
    if (eq(uri, "/gazelle-server-id")) ret gazelleServerGlobalID;
1216  
    
1217  
    if (eq(uri, "/get-field")) {
1218  
      BEA obj = beaGet("obj", req);
1219  
      S field = req.get("field");
1220  
      S value = str(cget(field, obj));
1221  
      
1222  
      if (eqic(req.get("enhance"), "JavaX")) {
1223  
        new JavaXHyperlinker hl;
1224  
        hl.targetBlank = true;
1225  
        hl.addExplainer(ph -> {
1226  
          if (eq(ph.token, "from") && isInteger(ph.next))
1227  
            ph.link(ph.cIdx+2, beaURL(parseLong(ph.next)));
1228  
        });
1229  
        
1230  
        S html = hl.codeToHTML(value);
1231  
        ret
1232  
          htitle_h1(beaHTML(obj) + "." + htmlEncode2(field))
1233  
          + div(sourceCodeToHTML_noEncode(html), style := hstyle_sourceCodeLikeInRepo());
1234  
      }
1235  
      
1236  
      ret serveText(value);
1237  
    }
1238  
    
1239  
    if (sourceIsPublic || masterAuthed(req))
1240  
      if (eqicOneOf(uri, "/source", "/sources", "/src", "sourceCode", "/code"))
1241  
        ret "You will be redirected to my source code..." + hrefresh(3.0, snippetURL(programID()));
1242  
        
1243  
    BEAForward forward = findBEAShortURL(uri);
1244  
    if (forward != null)
1245  
      ret serveBEAHTML(req, forward.object, forward.subURI);
1246  
1247  
    if (exportUsersGlobally && eq(uri, "/lookup-user")) {
1248  
      S name from req;
1249  
      S passwordMD5 from req;
1250  
      S salt from req;
1251  
      
1252  
      print("lookup-user called from " + req.webRequest.clientIP() + ", name: " + name);
1253  
      
1254  
      if (!eq(salt, passwordSalt()))
1255  
        ret serveJSON(litorderedmap(error := "Bad salt"));
1256  
        
1257  
      if (empty(name))
1258  
        ret serveJSON(litorderedmap(error := "No name"));
1259  
        
1260  
      User user = conceptWhereCI User(+name);
1261  
      if (user == null)
1262  
        ret serveJSON(litorderedmap(error := "User not found"));
1263  
        
1264  
      if (!eq(getVar(user.passwordMD5), passwordMD5))
1265  
        ret serveJSON(litorderedmap(error := "Bad pw"));
1266  
      
1267  
      ret serveJSON(litorderedmap(
1268  
        userID := user.id,
1269  
        contact := getString contact(user),
1270  
        isMaster := getBoolOpt isMaster(user)
1271  
      ));
1272  
    }
1273  
    
1274  
    // add more public URLs here
1275  
      
1276  
    if (!inMasterMode(req)) null;
1277  
    
1278  
    if (eq(uri, "/uploadInputs"))
1279  
      ret serveUploadTexts(req, "Input");
1280  
    
1281  
    if (eq(uri, "/uploadPatterns"))
1282  
      ret serveUploadTexts(req, "Pattern");
1283  
      
1284  
    if (eq(uri, "/analyzeInput"))
1285  
      ret calculations.serveAnalyzeInput(req);
1286  
      
1287  
    if (eq(uri, "/allBEATypes")) {
1288  
      ret h2_title("All object types")
1289  
        + ul(renderObjectTypes());
1290  
    }
1291  
    
1292  
    if (eq(uri, "/storeMatch")) {
1293  
      BEA pattern = beaGet(req.get("pattern"));
1294  
      BEA input = beaGet(req.get("input"));
1295  
      S label = req.get("label");
1296  
      BEA match = calculations.reactInputWithPattern(input, pattern);
1297  
      if (match == null) ret "Couldn't match";
1298  
      cset(match, +label);
1299  
      ret hrefresh(or2(req.get("redirect"), beaObjectURL(match)));
1300  
    }
1301  
    
1302  
    if (eq(uri, "/saveSyntacticPattern") || eq(uri, "/savePattern")) {
1303  
      S type = cic(uri, "syntactic") ? "Syntactic Pattern" : "Pattern";
1304  
      S text = req.get("text");
1305  
      BEA fromInput = beaGet(req.get("fromInput"));
1306  
      BEA pat = uniqCI BEA(+type, +text, createdBy := user(req));
1307  
      csetIfUnset(pat, +fromInput);
1308  
      if (fromInput != null)
1309  
        calculations.reactInputWithPattern(fromInput, pat);
1310  
      ret hrefresh(or2(req.get("redirect"), baseLink + "/"));
1311  
    }
1312  
    
1313  
    if (eq(uri, "/runInstruction")) {
1314  
      BEA o = beaGet(req.get("instruction"));
1315  
      runInstruction(o);
1316  
      ret hrefresh(beaObjectURL(o));
1317  
    }
1318  
    
1319  
    if (eq(uri, "/convertResultToInput")) {
1320  
      BEA result = beaGet(req.get("result"));
1321  
      if (result == null) ret subBot_serve500("Object not found");
1322  
      S text = unquote(getString result(result));
1323  
      if (l(text) > maxInputLength) ret subBot_serve500("Input too long");
1324  
      BEA input = uniqCI BEA(type := "Input", +text);
1325  
      uniqCI BEA(type := "Input Source", +input, source := result);
1326  
      ret hrefresh(beaShortURL(input));
1327  
    }
1328  
    
1329  
    /*if (eq(uri, "/pipeStringListIntoPattern")) {
1330  
      BEA r = beaGet(req.get("resultID"));
1331  
      BEa pat = beaGet(req.get("patternID"));
1332  
      if (r == null || pat == null) ret "Object not found";
1333  
      LS result = assertStringList(cget result(r));
1334  
      for (S s : result)
1335  
        uniq BEA(type := "Statement", );
1336  
    }*/
1337  
    
1338  
    if (eq(uri, "/setWordType")) {
1339  
      BEA r = beaGet(req.get("resultID"));
1340  
      S wordType = assertNempty(req.get("wordType"));
1341  
      LS result = assertStringList(cget result(r));
1342  
      for (S s : result)
1343  
        uniqCI BEA(type := "Word type", word := s, +wordType);
1344  
      ret "OK";
1345  
    }
1346  
    
1347  
    if (eq(uri, "/reload")) {
1348  
      dm_reloadModuleIn(3);
1349  
      ret "Reloading module in 3";
1350  
    }
1351  
    
1352  
    if (eq(uri, "/performAutoRuns")) {
1353  
      BEA o = beaGet(req.get("onObject"));
1354  
      if (o == null) ret "Object not found";
1355  
      rstAutoRuns.add(o);
1356  
      ret hrefresh(beaURL(o));
1357  
    }
1358  
    
1359  
    if (eq(uri, "/reactAllInputsWithAllSyntacticPatterns")) {
1360  
      ret str(returnTimed(r { calculations.reactAllInputsWithAllSyntacticPatterns(); }));
1361  
    }
1362  
    
1363  
    if (eq(uri, "/reactAllInputsWithPattern")) {
1364  
      BEA pat = beaGet(req.get("patternID"));
1365  
      if (pat == null) ret "Pattern not found";
1366  
      ret str(returnTimed(r { calculations.reactAllInputsWithPattern(pat); }));
1367  
    }
1368  
    
1369  
    if (eq(uri, "/storeSubInput")) {
1370  
      BEA input = beaGet(req.get("input"));
1371  
      if (input == null) ret "Input not found";
1372  
      S text = req.get("text");
1373  
      S type = eqic(req.get("label"), "good") ? "Sub-Input" : "Bad Sub-Input";
1374  
      BEA o = uniqCI BEA(
1375  
        +type,
1376  
        +input,
1377  
        +text,
1378  
        createdBy := currentUser());
1379  
      ret hrefresh(or2(req.get("redirect"), beaURL(o)));
1380  
    }
1381  
    
1382  
    if (eq(uri, "/performAutoRunOnAllObjects")) {
1383  
      BEA autoRun = beaGet autoRun(req);
1384  
      performAutoRunOnAllObjects(autoRun);
1385  
      ret "OK";
1386  
    }
1387  
    
1388  
    if (eq(uri, "/autoMigrate")) {
1389  
      BEA o = beaGet id(req);
1390  
      if (o == null) ret "Object not found";
1391  
      S url = beaURL(o);
1392  
      ret hsansserif() + "Migrated " + beaHTML(o) + " to " + className(autoMigrateToCustomClass(o))
1393  
        + hrefresh(1.0, url);
1394  
    }
1395  
      
1396  
    if (eq(uri, "/migrateToBase")) {
1397  
      BEA o = beaGet id(req);
1398  
      if (o == null) ret "Object not found";
1399  
      S url = beaURL(o);
1400  
      ret hsansserif() + "Migrated " + beaHTML(o) + " to " + className(autoMigrateToBase(o))
1401  
        + hrefresh(1.0, url);
1402  
    }
1403  
      
1404  
    if (eq(uri, "/convertSyntacticToSemanticMatches")) {
1405  
      BEA match = beaGet matchID(req);
1406  
      if (match == null) ret "Match not found";
1407  
      ret ul_htmlEncode2(calculations.convertSyntacticToSemanticMatches(match));
1408  
    }
1409  
    
1410  
    if (eq(uri, "/convertSyntacticToSemanticMatchesForWholePattern")) {
1411  
      BEA pat = beaGet patternID(req);
1412  
      if (pat == null) ret "Pattern not found";
1413  
      ret ul_htmlEncode2(calculations.convertSyntacticToSemanticMatchesForWholePattern(pat));
1414  
    }
1415  
    
1416  
    if (eq(uri, "/createPatternListFromUsefulSyntacticPatterns"))
1417  
      ret hrefresh(beaURL(calc.createPatternListFromUsefulSyntacticPatterns()));
1418  
      
1419  
    if (eq(uri, "/createConceptShadows"))
1420  
      ret createConceptShadows();
1421  
1422  
    if (eq(uri, "/compareConceptShadows")) {
1423  
      if (conceptShadows == null) ret createConceptShadows();
1424  
      L<ConceptShadow> newShadows = allConceptShadows();
1425  
      L<CreatedDeletedChanged<ConceptShadow>> diff
1426  
        = diffConceptShadows(conceptShadows, newShadows);
1427  
      ret subBot_serveText(nDiffs(diff) + ":\n\n"
1428  
        + pnlToString(diff));
1429  
    }
1430  
1431  
    if (eq(uri, "/includeScript")) {
1432  
      S snippetID = req.get("id");
1433  
      O wired = hotwire(snippetID);
1434  
      ret "Wired: " + wired;
1435  
    }
1436  
    
1437  
    if (eq(uri, "/dependents")) {
1438  
      BEA o = beaGet id(req);
1439  
      ret ul(lmap htmlEncode2_str(objectWithDependents(o)));
1440  
    }
1441  
    
1442  
    if (eq(uri, "/prototypeUsers")) {
1443  
      BEA o = beaGet id(req);
1444  
      ret ul(lmap htmlEncode2_str(prototypeUsers(o)));
1445  
    }
1446  
    
1447  
    // Reminder: We're still in master mode
1448  
    
1449  
    if (eq(uri, "/activateObject")) {
1450  
      BEA o = beaGet id(req);
1451  
      if (o == null) ret "Object not found";
1452  
      cset(o, meta_shouldActivate := true);
1453  
      callActivate(o);
1454  
      ret "Object activated." + hrefresh(1.0, beaURL(o));
1455  
    }
1456  
    
1457  
    if (eq(uri, "/deactivateObject")) {
1458  
      BEA o = beaGet id(req);
1459  
      if (o == null) ret "Object not found";
1460  
      cset(o, meta_shouldActivate := false);
1461  
      callDeactivate(o);
1462  
      ret "Object deactivated." + hrefresh(1.0, beaURL(o));
1463  
    }
1464  
    
1465  
    if (eq(uri, "/reactivateDynamicObject")) {
1466  
      BEA o = beaGet id(req);
1467  
      if (o == null) ret "Object not found";
1468  
      long id = o.id;
1469  
      reactivateAndMigratePrototypeUsers(o);
1470  
      ret "OK" + hrefresh(1.0, beaURL(id));
1471  
    }
1472  
    
1473  
    if (eq(uri, "/activateDynamicObject")) {
1474  
      BEA o = beaGet id(req);
1475  
      if (o == null) ret "Object not found";
1476  
      compileAndLoadObject(o);
1477  
      o = beaGet id(req);
1478  
      S url = or2(req.get("redirect"), beaURL(o));
1479  
      ret hsansserif() + "Migrated " + beaHTML(o) + " to " + className(o)
1480  
        + hrefresh(1.0, url);
1481  
    }
1482  
    
1483  
    if (eq(uri, "/callAnyMethod")) {
1484  
      BEA o = beaGet id(req);
1485  
      if (o == null) ret "Object not found";
1486  
      S name = req.get("name");
1487  
      bool showPrints = eq("1", req.get("showPrints"));
1488  
1489  
      new L args;
1490  
      for (S key, val : req.params()) {
1491  
        LS groups = regexpExtractGroups("^(arg|conceptArg)(\\d+)$", key);
1492  
        if (groups != null) {
1493  
          int idx = parseInt(second(groups));
1494  
          S type = first(groups);
1495  
          O arg = eq(type, "conceptArg") ? getConcept(parseLong(val))
1496  
            : val;
1497  
          listSet(args, idx-1, arg);
1498  
        }
1499  
      }
1500  
      
1501  
      S arg = req.get("arg");
1502  
      if (arg != null) listSet(args, 0, arg);
1503  
      
1504  
      S result = hijackPrintPlusResult_text(() -> {
1505  
        try {
1506  
          ret call(o, name, toObjectArray(args));
1507  
        } catch e {
1508  
          printStackTrace(e);
1509  
          null;
1510  
        }
1511  
      });
1512  
      ret serveText("Result of " + o + " . " + name + "(" + joinWithComma(args) + "):\n" + result);
1513  
    }
1514  
    
1515  
    if (eq(uri, "/download"))
1516  
      ret subBot_serveFileWithName("gazelle-database." + ymd_minus_hms() + ".gz", conceptsFile());
1517  
      
1518  
    if (eq(uri, "/errorSource"))
1519  
      ret subBot_serveText(loadTextFile(transpilerErrorSourceFile()));
1520  
      
1521  
    if (eq(uri, "/changeUserPassword")) {
1522  
      S name = req.get("name"), newPW = req.get("newPW");
1523  
      if (empty(name)) ret "Need 'name' parameter (user name)";
1524  
      if (empty(newPW)) ret "Need 'newPW' parameter";
1525  
      User user = conceptWhereCI User(+name);
1526  
      cset(user, passwordMD5 := SecretValue(hashPW(newPW)));
1527  
      ret "Password for " + htmlEncode2(user) + " updated!";
1528  
    }
1529  
    
1530  
    // add more master-mode URLs here
1531  
    
1532  
  } // end of serveOtherPage2
1533  
  
1534  
  record noeq BEAForward(BEA object, S subURI) {}
1535  
  
1536  
  Cl<BEA> beaShortURLObjects() {
1537  
    ret filter createdByMasterUser(beaList("BEA Short URL"));
1538  
  }
1539  
  
1540  
  Cl<BEA> masterObjects(Cl<BEA> l) {
1541  
    ret filter createdByMasterUser(l);
1542  
  }
1543  
1544  
  bool isBEAShortURLObject(BEA o) {
1545  
    ret o != null && o.typeIs("BEA Short URL") && createdByMasterUser(o);
1546  
  }
1547  
  
1548  
  BEAForward findBEAShortURL(S uri) {
1549  
    new Matches m;
1550  
    S uriWithSlash = addSlashSuffix(uri);
1551  
    for (BEA o : beaShortURLObjects()) pcall {
1552  
      S uriToTest = getStringOpt uri(o);
1553  
      if (startsWith_addingSlash(uriWithSlash, uriToTest, m)) {
1554  
        BEA o2 = cast cget object(o);
1555  
        if (o2 != null) {
1556  
          S subURI = m.rest();
1557  
          if (l(uriWithSlash) > l(uri))
1558  
            subURI = dropSlashSuffix(subURI);
1559  
          ret new BEAForward(o2, subURI);
1560  
        }
1561  
      }
1562  
    }
1563  
    null;
1564  
  }
1565  
  
1566  
  transient L<ConceptShadow> conceptShadows;
1567  
1568  
  O createConceptShadows() {  
1569  
    time "Make shadows" {
1570  
      S profile = profileThisThreadToString(r {
1571  
        conceptShadows = allConceptShadows();
1572  
      });
1573  
    }
1574  
    ret subBot_serveText(n2(conceptShadows, "shadow") + " made in " + lastTiming_formatted() + "\n\n" + profile);
1575  
  }
1576  
  
1577  
  HCRUD makeBEATypeCrud(Req req, S type, HTMLFramer1 framer default req.framer) {
1578  
    HCRUD crud = makeCRUDBase(BEA, req, framer);
1579  
    crud.baseLink = req.uri();
1580  
    HCRUD_Concepts data = cast crud.data;
1581  
    data.itemName = () -> firstToUpper(type);
1582  
    data.addCIFilter(type := eqic(type, "(no type)") ? null : type);
1583  
    
1584  
    if (eqicOneOf(type, "Input", "Pattern", "AI Task")) {
1585  
      IF0<MapSO> prev = data.emptyObject;
1586  
      data.emptyObject = () -> {
1587  
        MapSO item = data.emptyObject_fallback(prev);
1588  
        item.put(text := ""); // show text field when creating new objects
1589  
        ret item;
1590  
      };
1591  
    }
1592  
    
1593  
    optimizeBEACrud(crud);
1594  
    ret crud;
1595  
  }
1596  
  
1597  
  O renderBEAObjectTable(Req req, S type) {
1598  
    HCRUD crud = makeBEATypeCrud(req, type);
1599  
    ret serveCRUD(req, BEA, crud);
1600  
  }
1601  
  
1602  
  O serveUploadTexts(Req req, S type) {
1603  
    S inputs = req.get("text");
1604  
    
1605  
    new LS output;
1606  
    
1607  
    if (nempty(inputs)) {
1608  
      for (S text : tlft(inputs)) {
1609  
        Pair<BEA, Bool> p = uniqCI2_sync BEA(+type, +text);
1610  
        if (cget uploadedBy(p.a) == null)
1611  
          cset(p.a, uploadedBy := req.auth.user);
1612  
        output.add(type + " " + (p.b ? "added" : "exists")
1613  
          + " (ID " + p.a.id + "): " + text);
1614  
      }
1615  
    }
1616  
    
1617  
    ret h2("Upload " + plural(type))
1618  
      + hpostform(
1619  
          p(plural(type) + " (one per line):")
1620  
        + p(htextarea(inputs, name := "text"))
1621  
        + pIfNempty(htmlEncode_nlToBR(lines(output)))
1622  
        + hsubmit("Upload " + plural(type)));
1623  
  }
1624  
  
1625  
  Cl<BEA> beaObjectsOfType(S type) {
1626  
    ret conceptsWhereCI BEA(+type);
1627  
  }
1628  
  
1629  
  void reactAllInputsWithSomePatterns {
1630  
    calculations.reactAllInputsWithSomePatterns();
1631  
  }
1632  
  
1633  
  swappable S navDiv() {
1634  
    ret div_vbar(newNavLinks(),
1635  
      style := "margin-bottom: 0.5em");
1636  
  }
1637  
  
1638  
  swappable LS newNavLinks() {
1639  
    Req req = currentReq();
1640  
    HCRUD crud = makeCRUD(BEA, req);
1641  
1642  
    S allObjectTypesLink = ahref(baseLink + "/allBEATypes", "All object types");
1643  
1644  
    BEAHTMLFramer framer = cast req.framer;
1645  
    framer.unnamedPopDownButton.addAll(
1646  
      ahref(baseLink + "/stats", "Stats"),
1647  
      !isMasterAuthed() ? null : ahref(baseLink + "/download", "DB download"),
1648  
      targetBlank(getProgramURL(), "Source code"),
1649  
      !isMasterAuthed() ? null : ahref(baseLink + "/refchecker", "Reference checker"),
1650  
      ahref(baseLink + "/inputsWithRewrites", "Inputs with rewrites"),
1651  
      ahref(baseLink + "/rewritesTable", "Rewrites table"),
1652  
      ahref(baseLink + "/patternRewritesTable", "Pattern rewrites table"),
1653  
      ahref(baseLink + "/matchesTable", "Matches table"),
1654  
      ahref(baseLink + "/matchesTable?syntacticOnly=1", "Syntactic matches table"),
1655  
      //ahref("https://gazelle.rocks/htmlBot/238410", "Classic Gazelle"),
1656  
    );
1657  
1658  
    new LS items;
1659  
    if (showInputAndPatternNavItems)
1660  
      addAll(items,
1661  
        ahref(baseLink + "/query", "Query"),
1662  
        ahref(baseLink + "/newSearch", span_title("New BEA-based searched in development", "New Search (dev.)")),
1663  
        beaNavLink("Input", crud),
1664  
        beaNavLink("Pattern", crud),
1665  
        beaNavLink("Syntactic Pattern", crud),
1666  
        ahref(baseLink + "/syntacticPatternsWithoutRewrites", (showInputCounts ? n2(syntacticPatternsWithoutRewrites!) + " " : "") + "Syntactic patterns w/o rewrites"),
1667  
        beaNavLink("Match", crud),
1668  
        beaNavLink("Rewrite", crud),
1669  
        beaNavLink("AI Task", crud),
1670  
        ahref(baseLink + "/inputsWithoutMatches", (showInputCounts ? n2(inputsWithoutMatches!) + " " : "") + "Inputs w/o matches"),
1671  
        ahref(baseLink + "/inputsWithoutRewrites", (showInputCounts ? n2(inputsWithoutRewrites!) + " " : "") + "Inputs w/o rewrites"),
1672  
      );
1673  
      /*hPopDownButtonWithText("Bot Forum", navLinks(flat := true, withStats := false)),*/
1674  
      
1675  
    addAll(items,
1676  
      inlineSwappable navDiv_allObjectTypes(this, () ->
1677  
        allObjectTypesLink + " " + hPopDownButton(
1678  
            listPlus(renderObjectTypes(10),
1679  
              allObjectTypesLink))),
1680  
      framer.unnamedPopDownButton.width(350).height(500).html());
1681  
    ret items;
1682  
  }
1683  
  
1684  
  // crud is just the cached BEAObject crud to check for creation rights
1685  
  S beaNavLink(S type, HCRUD crud, int count default beaCount(type)) {
1686  
    S plural = firstToLower(plural(type));
1687  
    S link = baseLink + "/" + camelCase(plural);
1688  
    
1689  
    ret ahref(link, n2(count) + " " + firstToUpper(plural)) + (!crud.actuallyAllowCreate() ? "" : " " + ahref(addParamToURL(link, cmd := "new"), "+"));
1690  
  }
1691  
  
1692  
  S beaObjectURL(BEA o) {
1693  
    ret conceptLink(o, currentReq());
1694  
    /*ret o == null ?:
1695  
      addParamsToURL(baseLink + "/crud/BEAObject",
1696  
        selectObj := o.id) + "#" + o.id;*/
1697  
  }
1698  
  
1699  
  S matchDescHTML(BEA m) {
1700  
    pcall {
1701  
      BEA pat = cast cget pattern(m);
1702  
      SS mapping = cast cget mapping(m);
1703  
      ret ahref_undecorated(crudLink(m), htmlEncode2(quote(getString text(pat)))
1704  
        + "<br>&nbsp; with " + renderEqualsCommaProperties(mapping));
1705  
    }
1706  
    ret htmlEncode2(str(m));
1707  
  }
1708  
  
1709  
  Cl<BEA> beaList() {
1710  
    ret list BEA();
1711  
  }
1712  
  
1713  
  Cl<BEA> beaList(S type, O... params) {
1714  
    ret conceptsWhereCI BEA(paramsPlus_inFront(params, +type));
1715  
  }
1716  
  
1717  
  Cl<BEA> beaListAny(S... types) {
1718  
    ret concatLists(lmap beaList(litciset(types)));
1719  
  }
1720  
  
1721  
  Cl<BEA> beaListAny(Cl<S> types) {
1722  
    ret concatLists(lmap beaList(asCISet(types)));
1723  
  }
1724  
  
1725  
  BEA beaGet(long id) {
1726  
    ret getConceptOpt BEA(id);
1727  
  }
1728  
  
1729  
  BEA beaGet(S id) {
1730  
    ret beaGet(parseFirstLong(id));
1731  
  }
1732  
  
1733  
  bool beaTypeIs(BEA o, S type) {
1734  
    ret o != null && o.typeIs(type);
1735  
  }
1736  
  
1737  
  BEA mapMethodLike beaGet(S key, Req req) {
1738  
    ret beaGet(req.get(key));
1739  
  }
1740  
1741  
  BEA mapMethodLike cgetBEA aka beaGet(S field, BEA o) {
1742  
    ret (BEA) cget(field, o);
1743  
  }
1744  
  
1745  
  BEA cgetBEA aka beaGet(BEA o, S field) {
1746  
    ret cgetBEA(field, o);
1747  
  }
1748  
  
1749  
  S beaLinkHTML aka beaToHTML aka beaHTML(BEA o, bool targetBlank default false) {  
1750  
    ret o == null ?: ahref_targetBlankIf(targetBlank, conceptLink(o), htmlEncode2_nlToBr(str(o)));
1751  
  }
1752  
  
1753  
  S beaHTML_targetBlank(BEA o) {  
1754  
    ret o == null ?: targetBlank(conceptLink(o), htmlEncode2_nlToBr(str(o)));
1755  
  }
1756  
  
1757  
  S beaHTML_justID(BEA o) { ret o == null ?: ahref(beaShortURL(o), o.id); }
1758  
  
1759  
  S beaFullURL(BEA o) {
1760  
    ret o == null ?: mainDomainWithProtocol + beaURL(o);
1761  
  }
1762  
  
1763  
  S beaURL(long id) {
1764  
    ret baseLink + "/" + id;
1765  
  }
1766  
  
1767  
  S beaShortURL aka beaURL(BEA o) {
1768  
    ret o == null ?: baseLink + "/" + o.id;
1769  
  }
1770  
1771  
  Cl<BEA> beaBackRefs(BEA o, S type) {
1772  
    ret objectsWhereCI(findBackRefs BEA(o), +type);
1773  
  }
1774  
  
1775  
  Cl<BEA> beaBackRefs(BEA o) {
1776  
    ret findBackRefs BEA(o);
1777  
  }
1778  
  
1779  
  @Override swappable O serveDefaultPage(DynGazelleRocks.Req req) {
1780  
    HTMLFramer1 framer = req.framer;
1781  
    framer.add(centerGazelleLogo());
1782  
    ret completeFrame(req);
1783  
  }
1784  
  
1785  
  S centerGazelleLogo() {
1786  
    ret hcenter3(hsnippetimg_scaleToWidth(200, #1102967, 200, 110, title := "Gazelle"), style := "margin-top: 100px");
1787  
  }
1788  
  
1789  
  S html_loggedIn(bool offerLogInLink default true) {
1790  
    User user = user(currentReq());
1791  
    ret user == null
1792  
      ? (offerLogInLink ? ahref(baseLink + "/login", "Log in") : "Not logged in")
1793  
      : "Logged in as " + htmlEncode2(user.name);
1794  
  }
1795  
  
1796  
  swappable void distributeNewObject_impl(BEA o) {
1797  
    if (newObjectIsWorthNotification(o))
1798  
      distributeNewObject_impl_noCheck(o);
1799  
  }
1800  
  
1801  
  swappable bool newObjectIsWorthNotification(BEA o) {
1802  
    ret o.id != 0 && !o.typeIsOneOf("Match", "Live WebSocket");
1803  
  }
1804  
  
1805  
  swappable void distributeNewObject_impl_noCheck(BEA o) {
1806  
    distributeNotification("New object: " + o);
1807  
  }
1808  
  
1809  
  void performAutoRuns(BEA o) enter {
1810  
    //print("performAutoRuns", o);
1811  
    
1812  
    for (BEA autoRun : beaList("Auto Run")) {
1813  
      if (!isTrue(getOpt enabled(autoRun)))
1814  
        continue with print("Not enabled: " + autoRun);
1815  
      performAutoRunOnObject(autoRun, o);
1816  
    }
1817  
  }
1818  
  
1819  
  void performAutoRunOnAllObjects(BEA autoRun) {
1820  
    for (BEA o : list(BEA))
1821  
      performAutoRunOnObject(autoRun, o);
1822  
  }
1823  
  
1824  
  void performAutoRunOnObject(BEA autoRun, BEA o) {
1825  
    ping();
1826  
    S type = getString onChangedObjectOfType(autoRun);
1827  
    if (!o.typeIs(type)) ret; // with print("Wrong type: " + type);
1828  
    
1829  
    print("Running " + autoRun);
1830  
    BEA procedure = cast cget procedure(autoRun);
1831  
    
1832  
    S internalCode = getString internalCode(procedure);
1833  
    printVars_str(+internalCode, +o);
1834  
    
1835  
    if (eqic(internalCode, "convertInputToPattern")) {
1836  
      S text = o.text();
1837  
      if (!containsAngleBracketVars(text)) ret with print("No angle bracket vars");
1838  
      BEA p = uniqCI_returnIfNew BEA(type := "Pattern", +text);
1839  
      cset(p, fromInput := o, byProcedure := procedure, byAutoRun := autoRun);
1840  
      print(+p);
1841  
    } else if (eqic(internalCode, "convertInputToSyntacticPattern")) {
1842  
      S text = o.text();
1843  
      if (!containsStars(text)) ret with print("No stars");
1844  
      BEA p = uniqCI_returnIfNew BEA(type := "Syntactic Pattern", +text);
1845  
      cset(p, fromInput := o, byProcedure := procedure, byAutoRun := autoRun);
1846  
      print(+p);
1847  
    } else if (eqic(internalCode, "dropPunctuationFromPattern")) {
1848  
      S text = o.text();
1849  
      S text2 = dropPunctuation(text);
1850  
      if (eq(text, text2)) ret;
1851  
      
1852  
      BEA p = uniqCI BEA(
1853  
        type := o.type(),
1854  
        text := text2);
1855  
      cset(o, withoutPunctuation := p);
1856  
    } else if (eqic(internalCode, "makeSyntacticPattern")) {
1857  
      print(sp := calculations.makeSyntacticPattern(o));
1858  
    } else if (eqic(internalCode, "runFunctionOnInput")) {
1859  
      print("runFunctionOnInput");
1860  
      BEA function = beaGet(o, "function");
1861  
      BEA input = beaGet(o, "input");
1862  
      if (function == null || input == null) ret with print("Missing parameters");
1863  
      if (cget result(o) != null) ret with print("Has result");
1864  
1865  
      BEA result = calculations.reactFunctionWithInput(calculations.new BackEndAlgorithm, function, input);
1866  
      print(+result);
1867  
      if (result != null) {
1868  
        cset(result, request := o);
1869  
        cset(o, +result);
1870  
      }
1871  
    } else
1872  
      print("Unknown internal code");
1873  
  }
1874  
1875  
  BEA findInput(S text) {
1876  
    ret conceptWhereIC(BEA, type := "Input", +text);
1877  
  }
1878  
  
1879  
  S addRewriteHTML(BEA o) {
1880  
    ret ahref(addParamsToURL(crudLink(BEA),
1881  
      cmd := "new",
1882  
      title := "Add Rewrite",
1883  
      f_type := "Rewrite",
1884  
      f_text := getStringOpt text(o),
1885  
      f_isRewriteOf := o.id, metaInfo_isRewriteOf := "concept",
1886  
    ), "Add Rewrite");
1887  
  }
1888  
  
1889  
  @Override
1890  
  O serveIntegerLink(DynGazelleRocks.Req req, long id) {
1891  
    BEA o = getConceptOpt BEA(id);
1892  
    if (o != null)
1893  
      ret htitle(str(o)) + hrefresh(conceptLink_long(o));
1894  
    ret super.serveIntegerLink(req, id);
1895  
  }
1896  
  
1897  
  void distributeTestNotification() {
1898  
    distributeNotification("It is " + localTimeWithSeconds());
1899  
  }
1900  
  
1901  
  void distributeNotification(S text) {
1902  
    notificationQ.add(r {
1903  
      /*for (User user)
1904  
        if (nemptyAfterTrim(user.notificationSetting))
1905  
          sendNotification(text);*/
1906  
          
1907  
        S jsCode =
1908  
          "window.createNotification({ theme: 'success', showDuration: 3000 })("
1909  
          + jsonEncodeMap(message := text) + ");";
1910  
          
1911  
      sendJavaScriptToAllWebSockets(jsCode);
1912  
    });
1913  
  }
1914  
  
1915  
  void sendJavaScriptToAllWebSockets(S jsCode) {
1916  
    for (Pair<virtual WebSocket, WebSocketInfo> p : syncMapToPairs(webSockets)) {
1917  
      pcall(p.a, "send", jsonEncodeMap(eval := jsCode));
1918  
    }
1919  
  }
1920  
  
1921  
  void runInstruction(BEA o) {
1922  
    if (o == null) ret;
1923  
    try {
1924  
      BEA instruction = o;
1925  
      if (o.typeIs("Step in script"))
1926  
        instruction = (BEA) cget(o, "instruction");
1927  
      
1928  
      if (instruction.typeIs("Instruction | List objects by type")) {
1929  
        saveInstructionResult(o, beaList(getString typeToList(instruction)));
1930  
        ret;
1931  
      }
1932  
      
1933  
      if (instruction.typeIs("Instruction | List object types"))
1934  
        ret with saveInstructionResult(o, distinctCIFieldValuesOfConcepts(BEA, "type"));
1935  
1936  
      if (instruction.typeIs("Instruction | Filter list by text starting with")) {
1937  
        // find list made before
1938  
        BEA scriptRun = cgetBEA scriptRun(o);
1939  
        if (scriptRun == null) fail("Need to be run as part of script");
1940  
        L<BEA> steps = scriptRunSteps(scriptRun);
1941  
        int idx = indexOf(steps, o);
1942  
        if (idx < 0) fail("Step not found in script run");
1943  
        
1944  
        L<BEA> list = firstNotNull(map(reversed(takeFirst(steps, idx)),
1945  
          step -> optCast L(cget data(cgetBEA result(step)))));
1946  
         
1947  
        S prefix = getString prefix(instruction);
1948  
        L<BEA> filtered = filter(list, obj -> swic(obj.text(), prefix));
1949  
        saveInstructionResult(o, filtered);
1950  
        ret;
1951  
      }
1952  
      
1953  
      if (instruction.typeIs("Script")) {
1954  
        BEA script = instruction;
1955  
        BEA scriptRun = cnew BEA(type := "Script Run", +script);
1956  
        
1957  
        // Make an instance of all the instructions
1958  
        
1959  
        int i = 0;
1960  
        new L<BEA> steps;
1961  
        while not null (instruction = (BEA) cget(instruction, "step" + (++i))) {
1962  
          BEA step = cnew(BEA, type := "Step in script", step := i, +scriptRun, +instruction);
1963  
          steps.add(step);
1964  
        }
1965  
        
1966  
        cset(scriptRun, +steps);
1967  
        
1968  
        // TODO: run steps?
1969  
1970  
        ret;
1971  
      }
1972  
      
1973  
      cnew BEA(type := "Instruction Error", instruction := o, error := "Unknown instruction type");
1974  
    } catch e {
1975  
      cnew BEA(type := "Instruction Error", instruction := o, error := getStackTrace(e));
1976  
    }
1977  
  }
1978  
1979  
  BEA saveInstructionResult(BEA instruction, O data) {
1980  
    BEA result = cnew BEA(type := "Instruction Result",
1981  
      +instruction, +data);
1982  
    cset(instruction, +result);
1983  
    ret result;
1984  
  }
1985  
  
1986  
  L<BEA> scriptRunSteps(BEA scriptRun) {
1987  
    ret (L) cget steps(scriptRun);
1988  
  }
1989  
  
1990  
  S addCommentHTML(BEA o, O... _) {
1991  
    optPar S redirectAfterSave;
1992  
    optPar S defaultComment;
1993  
    optPar S text = "Add comment";
1994  
    
1995  
    ret ahref(addParamsToURL(crudLink(BEA),
1996  
      cmd := "new",
1997  
      title := "Add Comment",
1998  
      f_type := "Comment",
1999  
      f_on := o.id,
2000  
      f_text := unnull(defaultComment),
2001  
      +redirectAfterSave,
2002  
      autofocus := "f_text",
2003  
      metaInfo_on := "concept"), text);
2004  
  }
2005  
  
2006  
  S formatSubInput(BEA input, S text, S anchor) {
2007  
    S redirect = addAnchorToURL(currentReq->uriWithParams(), anchor);
2008  
    int goodCount = countConceptsCI BEA(type := "Sub-Input", +input, +text);
2009  
    int badCount = countConceptsCI BEA(type := "Bad Sub-Input", +input, +text);
2010  
    ret calculations.bestInputHTML(text)
2011  
      + " " + small(joinNemptiesWithSpace(
2012  
        ahref_noFollow(addParamsToURL(
2013  
          baseLink + "/storeSubInput", 
2014  
          label := "good",
2015  
          +text,
2016  
          input := input.id,
2017  
          +redirect),
2018  
        unicode_thumbsUp()),
2019  
        goodCount == 0 ? "" : n2(goodCount),
2020  
        ahref_noFollow(addParamsToURL(
2021  
          baseLink + "/storeSubInput", 
2022  
          label := "bad",
2023  
          +text,
2024  
          input := input.id,
2025  
          +redirect),
2026  
        unicode_thumbsDown()),
2027  
        badCount == 0 ? "" : n2(badCount)
2028  
      ));
2029  
  }
2030  
2031  
  LS renderObjectTypes(int max default Int.MAX_VALUE) {
2032  
    MultiSet<S> ms = distinctCIFieldValuesOfConcepts_multiSet(BEA, "type");
2033  
    ret mapPairs(takeFirst(max, multiSetToPairsByPopularity(ms)),
2034  
      (type, count) -> {
2035  
        S _type = or2(type, "(no type)");
2036  
        ret n2(count) + " " + ahref(baseLink + "/beaCRUD/" + urlencode(_type), htmlEncode2(_type));
2037  
      });
2038  
  }
2039  
  
2040  
  BEA autoMigrateToBase(BEA o) {
2041  
    ret migrateToClass(o, BEA);
2042  
  }
2043  
  
2044  
  BEA autoMigrateToCustomClass(BEA o) {
2045  
    ret migrateToClass(o, defaultCustomClass(o));
2046  
  }
2047  
  
2048  
  BEA migrateToClass(BEA o, Class<? extends BEA> targetClass) {
2049  
    // Is the object actually with us?
2050  
    if (o == null || o._concepts != db_mainConcepts()) null;
2051  
    
2052  
    // Do we switch to another class?
2053  
    if (targetClass != null && targetClass != _getClass(o)) {
2054  
      // make an unlisted copy
2055  
      
2056  
      BEA newObject = unlistedCopyToClass_withConverter_pcall(targetClass, o);
2057  
      
2058  
      // give new object chance to grab stuff from old one
2059  
      // while that one is still activated.
2060  
      // Note that new object is not YET activated at this point
2061  
      // and it isn't connected to the concepts either
2062  
      // DON'T use beaCall or cCall because enter() doesn't work
2063  
      // on "dead" objects.
2064  
      callOpt(newObject, "_fixAfterMigration", o);
2065  
      
2066  
      // actually replace in DB
2067  
      ret replaceConceptAndUpdateRefs(o, newObject);
2068  
    }
2069  
    ret o;
2070  
  }
2071  
2072  
  void setCodeState(BEA o, long timestamp, S codeHash, O state) {
2073  
    if (empty(state)) state = null;/* else
2074  
      state = "[" + formatGMTWithMilliseconds_24(timestamp) + "] "
2075  
        + (empty(codeHash) ? "" : "Code hash: " + codeHash + ". ")
2076  
        + state;*/
2077  
    cset(o, meta_codeState := state);
2078  
  }
2079  
2080  
  S codeForObject(BEA o) {
2081  
    ret getStringOpt meta_code(o);
2082  
  }
2083  
  
2084  
  S actualCodeHash(BEA o) {
2085  
    ret md5OrNull(codeForObject(o));
2086  
  }
2087  
  
2088  
  bool codeHashUpToDate(BEA o) {
2089  
    ret eq(actualCodeHash(o), getStringOpt meta_codeHash(o));
2090  
  }
2091  
  
2092  
  bool compileErrorInObject(BEA o) {
2093  
    ret swic(getStringOpt meta_codeState(o), "error.");
2094  
  }
2095  
  
2096  
  bool isDependentOn(BEA a, BEA b) {
2097  
    ret contains(optCast(Concept.RefL.class,
2098  
      getOpt meta_dependsOnCode(a)), b);
2099  
  }
2100  
  
2101  
  Cl<BEA> objectWithDependents(BEA o) {
2102  
    ret stepAllAndGet(TransitiveHull<>(x -> filter(y -> isDependentOn(y, x), list(BEA)), o));
2103  
  }
2104  
  
2105  
  BEA compileAndLoadObject(BEA o) {
2106  
    dm_mediumRefreshTranspiler();
2107  
    Cl<BEA> withDependents = objectWithDependents(o);
2108  
    
2109  
    // So you can prevent broken dependent objects from
2110  
    // breaking the compile of the main object (by setting meta_shouldActivate=false on the bad object)
2111  
    withDependents = filter(withDependents,
2112  
      x -> x == o || !isFalse(getBoolOpt meta_shouldActivate(x)));
2113  
    print("Dependents for " + o + ": " + withDependents);
2114  
    for (BEA x : withDependents)
2115  
      compileAndLoadObject_withoutDependents(x);
2116  
    ret (BEA) getMigration(o);
2117  
  }
2118  
  
2119  
  srecord UseClassDecl(long objID, S innerClassName) {}
2120  
  
2121  
  UseClassDecl parseUseClassDecl(S useClass) {
2122  
    LS tok = javaTokC(useClass);
2123  
    if (l(tok) == 3 && eqGet(tok, 1, ".") && isIdentifier(tok.get(2)) && isIntegerOrIdentifier(tok.get(0))) {
2124  
      S innerClassName = tok.get(2);
2125  
      long objID = trailingLong_regexp(first(tok));
2126  
      ret new UseClassDecl(objID, innerClassName);
2127  
    }
2128  
    null;
2129  
  }
2130  
2131  
  Class protoClass(BEA o) {  
2132  
    BEA proto = cast cget meta_prototype(o);
2133  
    S useClass = cast cget meta_useClass(o);
2134  
    
2135  
    if (nempty(useClass)) {
2136  
      UseClassDecl uc = parseUseClassDecl(useClass);
2137  
      if (uc != null) {
2138  
        S innerClassName = uc.innerClassName;
2139  
        long objID = uc.objID;
2140  
        proto = beaGet(objID);
2141  
        if (proto == null)
2142  
          fail("useClass invalid reference: " + objID + " not found");
2143  
        if (!usesLiveCustomCode(proto))
2144  
          fail("Prototype is not custom or not loaded: " + proto);
2145  
        Class protoClass = proto.getClass();
2146  
        Class outerClass = getOuterClass(protoClass, _defaultClassFinder());
2147  
        printVars(id := o.id, +useClass, +innerClassName, +protoClass, +outerClass);
2148  
        Class innerClass = getInnerClass(outerClass, innerClassName, _defaultClassFinder());
2149  
        printVars(+innerClass);
2150  
        if (innerClass == null)
2151  
          fail("Inner class not found: " + outerClass + " . " + innerClassName);
2152  
          
2153  
        cset(o, meta_dependsOnCode := ll(proto));
2154  
        ret innerClass;
2155  
      } else
2156  
        fail("meta_useClass has invalid syntax: " + useClass);
2157  
    }
2158  
      
2159  
    Class protoClass = null;
2160  
    if (proto != null) {
2161  
      if (!usesLiveCustomCode(proto))
2162  
        fail("Prototype is not custom or not loaded: " + proto);
2163  
      protoClass = proto.getClass();
2164  
    }
2165  
    
2166  
    ret protoClass;
2167  
  }
2168  
  
2169  
  S setCodeHash(BEA o) {
2170  
    S code = codeForObject(o);
2171  
    S codeHash = empty(code) ? null : md5(code);
2172  
    cset(o, meta_codeHash := codeHash);
2173  
    ret codeHash;
2174  
  }
2175  
  
2176  
  void compileAndLoadObject_withoutDependents(BEA o) ctex {
2177  
    print("compileAndLoadObject_withoutDependents", o);
2178  
    assertSame(db_mainConcepts(), o._concepts);
2179  
    ClassLoader cl = dm_moduleClassLoader();
2180  
2181  
    long timestamp = now();
2182  
    setCodeState(o, timestamp, null, "Compiling");
2183  
2184  
    S codeHash = null;
2185  
    transpileAndCompileForHotwiring_src.set(null);
2186  
2187  
    try {
2188  
      // Check meta_code and meta_prototype (latter overwrites the former)
2189  
      Class protoClass = protoClass(o);
2190  
      
2191  
      codeHash = setCodeHash(o);
2192  
      S code = codeForObject(o);
2193  
2194  
      if (empty(code)) {
2195  
        if (protoClass != null) {
2196  
          // no extra code, just instantiate prototype class
2197  
          o = migrateToClass(o, protoClass);
2198  
          setCodeState(o, timestamp, null, "Migrated to proto class " + shortClassName(protoClass));
2199  
          callActivate(o);
2200  
        } else {
2201  
          // Nothing to activate - migrate back to base
2202  
          o = autoMigrateToBase(o);
2203  
          setCodeState(o, timestamp, null, null);
2204  
        }
2205  
        ret;
2206  
      }
2207  
      
2208  
      actualCompile(o);
2209  
      
2210  
      reactivateAndMigratePrototypeUsers(o);
2211  
      
2212  
      print("Done compiling");
2213  
    } on fail e {
2214  
      cset(o, meta_javaClass := null);
2215  
      setCodeState(o, timestamp, codeHash, "Error. " + exceptionToStringShort(e));
2216  
    }
2217  
  }
2218  
  
2219  
  void setCodeHashAndActualCompile(BEA o) {
2220  
    setCodeHash(o);
2221  
    actualCompile(o);
2222  
  }
2223  
  
2224  
  void actualCompile(BEA o) {
2225  
    Class protoClass = protoClass(o);
2226  
    
2227  
    // safety check for reference (not currently used, we just check for master user status)
2228  
    cset(o, meta_codeSafety := null/*codeSafetyCheckResult(code)*/);
2229  
2230  
    DynClassName dcn = new(o.id, aGlobalID(), o.id);
2231  
    cset(o, meta_javaClass := dcn.fullClassName());
2232  
    
2233  
    S code = codeForObject(o);
2234  
    LS tok = javaTok(code);
2235  
    S baseClass = "BEAObject";
2236  
    new LS implementedInterfaces;
2237  
    
2238  
    // "implements ...";
2239  
    // e.g. "implements IF1<S>;"
2240  
    // e.g. "implements B123.IBla;"
2241  
    // e.g. "implements BBla from 12345;"
2242  
    
2243  
    for (int i : jfindAll(tok, "implements", (_tok, nIdx) -> {
2244  
      S left = get(_tok, nIdx-1);
2245  
      ret !(isIdentifier(left) || eq(left, ">"));
2246  
    })) {
2247  
      int iSemi = tok_findEndOfStatement(tok, i)-1;
2248  
      LS tokWhat = subList(tok, i+1, iSemi);
2249  
      S className;
2250  
      if (nCodeTokens(tokWhat) == 3 && eq(tokWhat.get(3), "from")) {
2251  
        className = tokWhat.get(1);
2252  
        className = "B" + tokWhat.get(5) + "." + className;
2253  
      } else
2254  
        className = join(dropFirstAndLast(tokWhat));
2255  
      className = expandShortClassRef(className);
2256  
      implementedInterfaces.add(className);
2257  
      clearTokens(tok, i, iSemi+2);
2258  
    }
2259  
2260  
    // e.g. "extends BWithResources;"
2261  
    int tokIdx;
2262  
    if ((tokIdx = jfind_any(tok,
2263  
        "extends <id>;",
2264  
        "extends <id>.<id>;",
2265  
        "extends <int>.<id>;",
2266  
        "extends <id>.<id>.<id>;")) > 0) {
2267  
      int j = indexOf(tok, ";", tokIdx);
2268  
      baseClass = joinSubList(tok, tokIdx+2, j-1);
2269  
      baseClass = expandShortClassRef(baseClass);
2270  
      clearTokens(tok, tokIdx, j+2);
2271  
    }
2272  
    
2273  
    jreplace(tok, "extends <id><.<id>>", "extends $2<" + dcn.middleClassName() + ".$5>");
2274  
2275  
    // e.g. "extends BBla from 12345;"
2276  
    if ((tokIdx = jfind_any(tok,
2277  
      "extends <id> from *;",
2278  
      "extends <id><<id>> from *;",
2279  
      "extends <id><<id>.<id>> from *;",
2280  
    )) > 0) {
2281  
      int iFrom = indexOf(tok, tokIdx, "from");
2282  
      S className = joinSubList(tok, tokIdx+2, iFrom-1);
2283  
      baseClass = expandShortClassRef("B" + parseFirstLong_regexp(tok.get(iFrom+2)) + "." + className);
2284  
      int j = indexOf(tok, ";", iFrom);
2285  
      clearTokens(tok, tokIdx, j+2);
2286  
    }
2287  
    
2288  
    new LS imports;
2289  
    
2290  
    // e.g. "import BBla from 12345;"
2291  
    while ((tokIdx = jfind_any(tok,
2292  
      "import <id> from <int>;",
2293  
      "import <id> from <id>;",
2294  
      "import static <id> from <int>;",
2295  
      "import static <id> from <id>;")) > 0) {
2296  
      
2297  
      int i = tokIdx+2;
2298  
      if (eqGet(tok, i, "static")) i += 2;
2299  
      int iStart = i;
2300  
      S className = tok.get(i);
2301  
      S bClass = addPrefix("B", tok.get(i += 4));
2302  
      S name = expandShortClassRef(bClass + "." + className);
2303  
      imports.add(joinSubList(tok, tokIdx, iStart-1) + " " + name + ";");
2304  
      int j = indexOf(tok, ";", tokIdx);
2305  
      clearTokens(tok, tokIdx, j+2);
2306  
    }
2307  
    
2308  
    S global = getGlobalCode(tok);
2309  
    code = join(tok);
2310  
    
2311  
    S src =
2312  
      lines(imports)
2313  
      + "mainPackage " + dcn.makePackageName() + "\n"
2314  
      + "mainClassName " + dcn.makeMainClassName() + "\n"
2315  
      + "concept " + dcn.makeBEAClassName() + " extends " +
2316  
        (protoClass == null ? baseClass : protoClass.getName().replace("$", "."))
2317  
      + (empty(implementedInterfaces) ? "" : " implements "
2318  
        + joinWithComma(implementedInterfaces))
2319  
      + " {\n" + code + "\n}\n"
2320  
      + global + "\n"
2321  
      + customCodePostlude();
2322  
    
2323  
    //print("SRC> ", src);
2324  
    ClassLoader cl = dm_moduleClassLoader();
2325  
    var files = concatLists(
2326  
      filesFromClassLoader(cl),
2327  
      filesFromClassLoader(getVirtualParent(cl)));
2328  
    
2329  
    print("Compiling with " + n2(files, "byte code path"));
2330  
    //pnlIndent(files);
2331  
    
2332  
    javaCompileToJar_localLibraries.set(files);
2333  
    File bytecode;
2334  
    new LS libs;
2335  
    try {
2336  
      bytecode = transpileAndCompileForHotwiring(src, libs);
2337  
      libs.remove(str(parseSnippetID(mainLibID)));
2338  
      print(+libs);
2339  
    } finally {
2340  
      S javaSrc = transpileAndCompileForHotwiring_src!;
2341  
      saveTextFile(javaSourceFileForObject(o), javaSrc);
2342  
      
2343  
      // add all mentioned objects as as dependency
2344  
      new LinkedHashSet<Concept> dependencies;
2345  
      for (S id : tok_identifiers(javaTok(javaSrc))) {
2346  
        long objID = regexpToLongIC("^b_?(\\d+)", id);
2347  
        if (objID != 0 && objID != o.id)
2348  
          dependencies.add(getConceptOrMarkMissingObject BEA(objID));
2349  
      }
2350  
      cset(o, meta_libraries := nullIfEmpty(joinWithSpace(libs)));
2351  
      cset(o, meta_dependsOnCode := nullIfEmpty(asList(dependencies)));
2352  
    }
2353  
    renameFileVerbose(bytecode, dcn.byteCodeFile());
2354  
    
2355  
    cset(o, meta_javaClass := dcn.fullClassName());
2356  
  }
2357  
  
2358  
  // TODO: aren't prototype users a part of objectWithDependents?
2359  
  void reactivateAndMigratePrototypeUsers(BEA o) {
2360  
    var p = reactivateDynamicObject(o);
2361  
    if (p == null) fail("Activation of " + o.id + " failed. " + getOpt meta_codeState(o));
2362  
    File deadPath = byteCodePathForClass(o); // TODO: isn't that the new path?
2363  
    print("New dead class path: " + deadPath + " (total: " + n2(deadClassPaths) + ")");
2364  
    deadClassPaths.add(deadPath);
2365  
    o = p.a;
2366  
    assertNotNull("Activated object", o);
2367  
    S codeHash = getStringOpt meta_codeHash(o);
2368  
    long timestamp = now();
2369  
    setCodeState(o, timestamp, codeHash, "Custom code loaded");
2370  
    print("Custom code loaded");
2371  
    
2372  
    migratePrototypeUsers(o);
2373  
  }
2374  
2375  
  // translate B123.Bla
2376  
  S expandShortClassRef(S name) {  
2377  
    LS groups = regexpGroups("^B?(\\d+)(\\..+)$", name);
2378  
    if (groups != null) {
2379  
      long baseID = parseLong(first(groups));
2380  
      BEA baseObj = beaGet(baseID);
2381  
      name = mainClassNameForObject(baseObj) + second(groups);
2382  
      if (empty(name)) fail("Referenced object doesn't have a Java class: " + baseID);
2383  
    }
2384  
    ret name;
2385  
  }
2386  
2387  
  void migratePrototypeUsers(BEA o) {  
2388  
    // migrate prototypeUsers to new class
2389  
    // (only those who don't add any code are included right now)
2390  
    for (BEA derivative : prototypeUsers(o)) pcall {
2391  
      print("Migrating prototype user " + derivative + " of " + o + " to new base class " + o.getClass());
2392  
      migrateToClass(derivative, o.getClass());
2393  
    }
2394  
  }
2395  
  
2396  
  Cl<BEA> prototypeUsers(BEA o) {
2397  
    TransitiveHull<BEA> th = new(x -> filter(
2398  
      conceptsWhere BEA(meta_prototype := x),
2399  
      d -> empty(codeForObject(d))), o);
2400  
    ret setMinus_inPlace(stepAllAndGet(th), o);
2401  
  }
2402  
  
2403  
  Pair<BEA, Bool> reactivateDynamicObject(BEA o) {
2404  
    try {
2405  
      ret reactivateDynamicObject_impl(o);
2406  
    } catch print e {
2407  
      cset(o, meta_javaClass := null);
2408  
      cset(o, meta_codeState := exceptionToStringShort(e));
2409  
      null;
2410  
    }
2411  
  }
2412  
  
2413  
  bool usesLiveCustomCode(BEA o) {
2414  
    ret startsWith(className(o), packagePrefix());
2415  
  }
2416  
  
2417  
  // pair(possibly updated object reference, migrated or not) 
2418  
  Pair<BEA, Bool> reactivateDynamicObject_impl(BEA o) ctex {
2419  
    assertNotNull(o);
2420  
    assertSame(db_mainConcepts(), o._concepts);
2421  
    DynClassName dcn = dcnForObject(o);
2422  
    if (dcn == null) {
2423  
      cset(o, meta_codeState := null);
2424  
      ret pair(o, false);
2425  
    }
2426  
    File byteCode = dcn.byteCodeFile();
2427  
    
2428  
    loadObjectsLibraries(o);
2429  
    dm_addByteCodePathToModuleClassLoader(byteCode);
2430  
    
2431  
    S fullName = dcn.fullClassNameForVM();
2432  
    print(+fullName);
2433  
    ClassLoader cl = dm_moduleClassLoader();
2434  
    Class targetClass = cl.loadClass(fullName);
2435  
    print(+targetClass);
2436  
    assertNotNull("Class not found: " + fullName, targetClass);
2437  
    o = migrateToClass(o, targetClass);
2438  
    assertNotNull("autoMigrated", o);
2439  
2440  
    callActivate(o);
2441  
    cset(o, meta_codeState := "[" + formatGMTWithMilliseconds_24() + "] " + "Custom code reloaded");
2442  
    ret pair(o, true);
2443  
  }
2444  
2445  
  File javaSourceFileForObject(BEA o) {
2446  
    ret programFile("BEA Java Sources/" + o.id + ".java");
2447  
  }
2448  
  
2449  
  File byteCodeFile(DynClassName name) {
2450  
    ret name == null ?: programFile("Object ByteCode/" + name.objectID + "-" + name.globalID + ".jar");
2451  
  }
2452  
  
2453  
  DynClassName dcnForObject(BEA o) {
2454  
    S javaClassName = getString meta_javaClass(o);
2455  
    ret DynClassName_parse(javaClassName);
2456  
  }
2457  
  
2458  
  File byteCodeFileForObject(BEA o) {
2459  
    DynClassName dcn = dcnForObject(o);
2460  
    ret dcn?.byteCodeFile();
2461  
  }
2462  
  
2463  
  BEA autoMigrateUnlistedOrKeep(BEA o) {
2464  
    ret canAutoMigrate(o) ? unlistedCopyToClass_withConverter(defaultCustomClass(o), o) : o;
2465  
  }
2466  
  
2467  
  bool canAutoMigrate(BEA o) {
2468  
    ret o != null && !eqOneOf(defaultCustomClass(o), _getClass(o), null);
2469  
  }
2470  
  
2471  
  Class<? extends BEA> defaultCustomClass(BEA o) {
2472  
    if (o == null) null;
2473  
    BEA entry = conceptWhereCI BEA(type := "Auto Custom Class", forType := o.type());
2474  
    //printVars_str defaultCustomClass(+o, +entry);
2475  
    if (entry == null) null;
2476  
    S className = getString toClassName(entry);
2477  
    //printVars_str defaultCustomClass(+className);
2478  
    Class c = findClassThroughDefaultClassFinder(className);
2479  
    //printVars_str defaultCustomClass(+c);
2480  
    ret c;
2481  
  }
2482  
  
2483  
  S queryLink(S algorithm, S q) {
2484  
    ret addParamsToURL(baseLink + "/query", +algorithm, +q);
2485  
  }
2486  
2487  
  S currentUserAgent() { ret userAgent(currentReq()); }
2488  
  
2489  
  S userAgent(Req req) {  
2490  
    if (req == null) null;
2491  
    ret mapGet(req.webRequest.headers(), "user-agent");
2492  
  }
2493  
2494  
  void saveUserAgent(BEA input) {  
2495  
    S userAgent = currentUserAgent();
2496  
    if (nempty(userAgent) && !userAgentIsBot(userAgent))
2497  
      uniqCI2 BEA(type := "Input Source",  +input, +userAgent);
2498  
  }
2499  
2500  
  bool isObjectWithCode(BEA o) {
2501  
    ret o != null && o.hasCustomCode();
2502  
  }
2503  
2504  
  bool createdByMasterUser(BEA o) {
2505  
    User user = optCast User(cget createdBy(o));
2506  
    ret user != null && user.isMaster;
2507  
  }
2508  
2509  
  void autoActivateDynamicObject(BEA o) {
2510  
    if (!autoActivateDynamicObjects) ret;
2511  
    S code = codeForObject(o);
2512  
    if (empty(code)) ret;
2513  
    if (!createdByMasterUser(o)) ret;
2514  
    S codeState = getString meta_codeState(o);
2515  
    S time = leadingAngleBracketStuff(codeState);
2516  
    long timestamp = empty(time) ? 0 : parseDateWithMillisecondsGMT(time);
2517  
    if (o._modified <= timestamp) ret;
2518  
    //S hash = regexpExtractIC("code hash: (\\w+)?", codeState);
2519  
    S hash = getString meta_codeHash(o);
2520  
    if (eq(hash, md5(code)))
2521  
      ret; // Code unchanged
2522  
    print("Auto-activating dynamic object: " + o);
2523  
    compileAndLoadObject(o);
2524  
    sleepSeconds(1); // safety sleep
2525  
  }
2526  
2527  
  S customCodePostlude() {  
2528  
    ret "!include early #1031282\n\n";
2529  
  }
2530  
  
2531  
  S makeJavaClassName(BEA o) {
2532  
    ret /*"b" + o.id + "_" +*/ aGlobalID();
2533  
  }
2534  
  
2535  
  S packagePrefix() {
2536  
    ret "dyn.b_";
2537  
  }
2538  
  
2539  
  S makePackageName(BEA o) {
2540  
    ret packagePrefix() + o.id;
2541  
  }
2542  
  
2543  
  BEA websiteEnhancersObject() {
2544  
    Cl<BEA> l = beaList("Live Website Enhancers");
2545  
    if (l(l) > 1) warn("Multiple Live Website Enhancers: " + l);
2546  
    ret first(l);
2547  
  }
2548  
  
2549  
  BEA getWebsiteEnhancer(S key) {
2550  
    ret beaGet(websiteEnhancersObject(), key);
2551  
  }
2552  
  
2553  
  S newLinkForCRUD(HCRUD crud, Class c) {
2554  
    if (c == User) ret baseLink + "/register";
2555  
    else ret super.newLinkForCRUD(crud, c);
2556  
  }
2557  
  
2558  
  S callHTMLFixer(O caller, S html) {
2559  
    BEA fixer = beaGet htmlFixer(websiteEnhancersObject());
2560  
    ret or((S) callOpt(fixer, "fixHTML", html, caller), html);
2561  
  }
2562  
  
2563  
  @Override
2564  
  O html3(DynGazelleRocks.Req req) { ret html3((Req) req); }
2565  
  O html3(Req req) {
2566  
    assertSame(realMC(), mc());
2567  
    assertSame(main beaMod(), me());
2568  
    vmBus_send logMethodCall(this, "html3", req);
2569  
    vmBus_send html3(this, req);
2570  
    
2571  
    BHTTPRequest reified = null;
2572  
    if (reifyHTTPRequests())
2573  
      reified = cnew BHTTPRequest(
2574  
        type := "Live HTTP Request",
2575  
        +req,
2576  
        uri := req.uri(),
2577  
        ip := req.webRequest.clientIP(),
2578  
        user := user(req));
2579  
2580  
    long time = sysNow();
2581  
    try {
2582  
      ret html4(req);
2583  
    } finally {
2584  
      time = sysNow()-time;
2585  
      if (reified != null)
2586  
        cset(reified, type := "Past HTTP Request", processingTime := time);
2587  
    }
2588  
  }
2589  
2590  
  O html4(Req req) {
2591  
    // generally save all "q" params as input
2592  
    // except if it's a live search
2593  
    // and only if someone is logged in
2594  
    if (saveAllQsAsInput && !cic(req.uri(), "/live")
2595  
      && user(req) != null) {
2596  
      S text = trim(req.get("q"));
2597  
      if (nempty(text)) {
2598  
        BEA input = uniqCI BEA(type := "Input", +text);
2599  
        uniqCI BEA(type := "Input Source", +input, source := "Web Request (q parameter)");
2600  
      }
2601  
    }
2602  
2603  
    O response = html5(req);
2604  
    if (response cast S)
2605  
      response = routeThroughAll(pagePostProcessors, response);
2606  
    ret response;
2607  
  }
2608  
  
2609  
  transient L<IF1<S>> pagePostProcessors = syncL();
2610  
  
2611  
  O html5(Req req) {
2612  
    S uri = req.uri();
2613  
    for (BEA rewrite : filter createdByMasterUser(beaList("Live URL Rewrite"))) pcall {
2614  
      S input = getString input(rewrite);
2615  
      bool ci = isTrueOpt caseInsensitive(rewrite);
2616  
      vmBus_send testingURLRewrite(rewrite, uri);
2617  
      if (eqOrEqic(ci, uri, input)) {
2618  
        vmBus_send applyingURLRewrite(rewrite, uri);
2619  
        S output = getString output(rewrite);
2620  
        /*
2621  
        SS newParams = paramsFromURL(output);
2622  
        req.uri = urlWithoutQuery(output);
2623  
        ModifiedWebRequest request2 = webRequest_modifyURI(req.webRequest, req.uri);
2624  
        request2.params = req.params = joinMaps(newParams, req.params());
2625  
        vmBus_send urlRewrite_newURI(req.uri);
2626  
        vmBus_send urlRewrite_newParams(req.params);
2627  
        break;
2628  
        */
2629  
        ret hrefresh(appendParamsToURL(output, req.params()));
2630  
      }
2631  
    }
2632  
    
2633  
    ret super.html3(req);
2634  
  }
2635  
  
2636  
  @Override
2637  
  O serveOtherPage(DynGazelleRocks.Req req) { ret serveOtherPage((Req) req); }
2638  
  O serveOtherPage(Req req) {
2639  
    if (eq(req.uri, "/login"))
2640  
      ret serveAuthForm(null);
2641  
      
2642  
    if (startsWith_addingSlash(req.uri, "/myLoginData"))
2643  
      ret serveMyLoginData(req);
2644  
      
2645  
    //try object serveSpecialCRUD(req);
2646  
      
2647  
    ret super.serveOtherPage(req);
2648  
  }
2649  
2650  
  // CRUD for non-master users (filtered)
2651  
  /*O serveSpecialCRUD(Req req) {
2652  
    S uri = req.uri();
2653  
    if (!uri.startsWith(crudBase())) null;
2654  
    
2655  
    //printVars_str serveSpecialCRUD(+uri);
2656  
      if (eq(uri, dropUriPrefix(baseLink, crudLink(UploadedImage)))) {
2657  
        HCRUD crud = makeCRUD(c, req);
2658  
        crud.filte
2659  
        ret serveCRUD(req, c, crud);
2660  
      }
2661  
  }*/
2662  
  
2663  
  
2664  
  void cleanMeUp_deactivateBEAObjects {
2665  
    Cl<BEA> objects = filter(beaList(), o -> hasMethod(o, "_deactivate"));
2666  
    print("Deactivating " + nObjects(objects) + " for shutdown");
2667  
    for (BEA bea : objects)
2668  
      callDeactivate(bea);
2669  
    if (nempty(objects)) print("Done");
2670  
  }
2671  
  
2672  
  // parses class names of the form
2673  
  //     dyn.b_123_bla.B456
2674  
  // or: dyn.b_123_bla.B456$Inner
2675  
  DynClassName DynClassName_parse(S className) {
2676  
    className = replace(className, "$", ".");
2677  
    LS groups = regexpFirstGroups("^dyn\\.b_(\\d+)_([a-z]+)\\.B(\\d+)\\$?(.*)", className);
2678  
    if (verboseClassLoading) printVars("DynClassName_parse", +className, +groups);
2679  
    if (groups != null)
2680  
      ret new DynClassName(
2681  
        parseLong(first(groups)),
2682  
        second(groups),
2683  
        parseLong(third(groups)),
2684  
        fourth(groups));
2685  
        
2686  
    ret DynClassName_parseOther(className);
2687  
  }
2688  
  
2689  
  // parses class names of the form
2690  
  //     dyn.b_123_bla.Anything
2691  
  DynClassName DynClassName_parseOther(S className) {
2692  
    className = replace(className, "$", ".");
2693  
    LS groups = regexpFirstGroups("^dyn\\.b_(\\d+)_([a-z]+)\\.(.*)", className);
2694  
    if (verboseClassLoading) printVars("DynClassName_parseOther", +className, +groups);
2695  
    
2696  
    if (groups != null)
2697  
      ret new DynClassName(
2698  
        parseLong(first(groups)),
2699  
        second(groups),
2700  
        0,
2701  
        third(groups));
2702  
        
2703  
    null;
2704  
  }
2705  
  
2706  
  S DynClassName_mainPrefix() { ret "dyn."; }
2707  
  S DynClassName_mainPrefixForVM() { ret "dyn."; }
2708  
  
2709  
  class DynClassName {
2710  
    long compileBaseObjectID;
2711  
    S globalID;
2712  
    long objectID; // can be 0 if innerClass is the class we look for
2713  
    S innerClass; // optional
2714  
    
2715  
    *(long *compileBaseObjectID, S *globalID, long *objectID) {}
2716  
    *(long *compileBaseObjectID, S *globalID, long *objectID, S *innerClass) {}
2717  
    
2718  
    S middleClassName() { ret objectID == 0 ? "" : "B" + objectID; }
2719  
    
2720  
    S makePackageName() { ret dropDotSuffix(DynClassName_mainPrefix()); }
2721  
    S makeMainClassName() { ret "b_" + compileBaseObjectID + "_" + globalID; }
2722  
    S makeBEAClassName() { ret joinNempties("$", middleClassName(), innerClass); }
2723  
    
2724  
    S fullClassName() {
2725  
      ret joinWithDot(makePackageName(), makeMainClassName(), makeBEAClassName());
2726  
    }
2727  
    
2728  
    S fullClassNameForVM() {
2729  
      ret makePackageName()
2730  
        + "." + makeMainClassName() + "$" + makeBEAClassName();
2731  
    }
2732  
    
2733  
    File byteCodeFile() {
2734  
      ret programFile("Object ByteCode/" + baseSystemVersion
2735  
        + "/" + compileBaseObjectID + "/" + globalID + ".jar");
2736  
    }
2737  
  } // end of DynClassName
2738  
2739  
  S mainClassNameForObject(BEA o) {
2740  
    if (o == null) null;
2741  
    S fullBaseClass = getString meta_javaClass(o);
2742  
      
2743  
    // Note: We are doing some DynClassName_parse stuff here basically
2744  
    ret takeFirst(fullBaseClass, lastIndexOf(fullBaseClass, "."));
2745  
  }
2746  
  
2747  
  void loadObjectsLibraries(BEA o) {
2748  
    LS libs = splitAtSpace_trim(getString meta_libraries(o));
2749  
    // TODO: use loaded version of library instead of latest
2750  
    if (addLibrariesToClassLoader(dm_moduleClassLoader(), libs))
2751  
      print("Loaded libs for " + o + ": " + libs);
2752  
  }
2753  
2754  
  void callActivate(BEA bea) {
2755  
    if (getBoolOpt meta_shouldActivate(bea, true)) {
2756  
      loadObjectsLibraries(bea);
2757  
      pcallOpt(bea, "_activate");
2758  
    }
2759  
  }
2760  
  
2761  
  void callDeactivate(BEA bea) {
2762  
    pcallOpt(bea, "_deactivate");
2763  
  }
2764  
  
2765  
  void onNewWebSocket(WebSocketInfo ws) {
2766  
    super.onNewWebSocket(ws);
2767  
    
2768  
    Req req = cast ws.req;
2769  
    S uri = ws.uri;
2770  
    BEA reifiedWebSocket = null;
2771  
    
2772  
    if (webSocketsToBEA()) { time "Reify WebSocket" {
2773  
      var webRequest = req?.webRequest;
2774  
      var cookie = cookieFromWebRequest(webRequest);
2775  
      //var headers = webRequest?.headers();
2776  
      User user = user(req);
2777  
      reifiedWebSocket = cnew(webSocketClass,
2778  
        webSocketInfo := ws,
2779  
        type := "Live WebSocket",
2780  
        +uri,
2781  
        //hasRequest := req != null,
2782  
        //hasWebRequest := webRequest != null,
2783  
        //cookie := dropLast(4, cookie),
2784  
        //headers := keysList(headers),
2785  
        //auth := req?.auth,
2786  
        +user);
2787  
      ws.dbRepresentation = reifiedWebSocket;
2788  
      //if (isTrue(ws.misc.get("jqueryLoaded")))
2789  
        ws.eval(replaceDollarVars(
2790  
          [[if (typeof $ !== "undefined") $(".webSocketPlaceholder").html($html);]],
2791  
          html := jsQuote(beaHTML(reifiedWebSocket))));
2792  
    }}
2793  
    
2794  
    onNewWebSocket2(ws);
2795  
    
2796  
    BEAForward forward = null;
2797  
2798  
    if (eq(uri, "/")) {    
2799  
      BEA o = beaGet(beaHomePageID);
2800  
      if (o != null)
2801  
        forward = new BEAForward(o, dropLeadingSlash(uri));
2802  
    }
2803  
    
2804  
    if (forward == null) {
2805  
      new Matches m;
2806  
      if (startsWith(uri, "/beaHTML/", m)) {
2807  
        BEA handler = beaGet(m.rest());
2808  
        if (handler != null)
2809  
          forward = new BEAForward(handler, afterSlash(m.rest()));
2810  
      } else
2811  
        forward = findBEAShortURL(uri);
2812  
    }
2813  
      
2814  
    printVars onNewWebSocket(+uri, +forward);
2815  
      
2816  
    if (forward != null) {
2817  
      var handler = forward.object;
2818  
      req.subURI = forward.subURI;
2819  
      
2820  
      print("Have WebSocket " + reifiedWebSocket + "/" + ws + " at " + uri + " for " + handler);
2821  
      if (!handler._isAlive())
2822  
        ret with print("HAndler not alive!?");
2823  
2824  
      cset(reifiedWebSocket, +handler);
2825  
      beaPcall(handler, "useWebSocket", ws);
2826  
    }
2827  
  }
2828  
  
2829  
  swappable void onNewWebSocket2(WebSocketInfo ws) {}
2830  
  
2831  
  transient Class<? extends BWebSocket> webSocketClass = BWebSocket;
2832  
  
2833  
  void onWebSocketClose(WebSocketInfo ws) enter {
2834  
    super.onWebSocketClose(ws);
2835  
    retireReifiedWebSocket((BWebSocket) ws.dbRepresentation);
2836  
  }
2837  
  
2838  
  void retireReifiedWebSocket(BWebSocket ws) {
2839  
    if (!keepReifiedWebSockets())
2840  
      cdelete(ws);
2841  
    else
2842  
      cset(ws, type := "Closed WebSocket");
2843  
  }
2844  
  
2845  
  transient timedCached[10.0] bool webSocketsToBEA() {
2846  
    ret getBoolOpt webSocketsToBEA(liveGazelleSettings());
2847  
  }
2848  
  
2849  
  transient timedCached[10.0] bool keepReifiedWebSockets() {
2850  
    ret getBoolOpt keepReifiedWebSockets(liveGazelleSettings());
2851  
  }
2852  
  
2853  
  transient timedCached[10.0] bool reifyHTTPRequests() {
2854  
    ret getBoolOpt reifyHTTPRequests(liveGazelleSettings());
2855  
  }
2856  
  
2857  
  BSettings gazelleSettings() {
2858  
    ret conceptWhere(BSettings);
2859  
  }
2860  
  
2861  
  BEA liveGazelleSettings() {
2862  
    ret first(filter createdByMasterUser(beaList("Live Gazelle Settings")));
2863  
  }
2864  
  
2865  
  O serveWithNavigation aka withFrame(S html) {
2866  
    var framer = framer();
2867  
    framer.add(html);
2868  
    ret completeFrame();
2869  
  }
2870  
  
2871  
  @Override
2872  
  O completeFrame_base(DynGazelleRocks.Req req) {
2873  
    cast req to Req;
2874  
    req.framer.addNavItems(pageBy_navItems(req.makers));
2875  
    ret super.completeFrame_base(req);
2876  
  }
2877  
2878  
  LS pageBy_navItems(Cl/O... makers) {
2879  
    makers = nonNulls(makers);
2880  
    if (empty(makers)) ret ll();
2881  
    
2882  
    L<BEA> beas = instancesOf BEA(makers);
2883  
    L nonBEAs = nonInstancesOf BEA(makers);
2884  
    
2885  
    ret concatLists(
2886  
      map(beas, o ->
2887  
        targetBlank(beaURL(o),
2888  
          repS(2, html_gazelle_madeByIcon()),
2889  
          title := "Page made by: " + o)),
2890  
          
2891  
      empty(nonBEAs) ? ll() : ll(new HPopDownButton(empty(beas) ? "Page by" : "Page also by",
2892  
        map(makers, o -> {
2893  
          if (o cast BEA) ret beaHTML_targetBlank(o);
2894  
          ret htmlEncode2(shortenStr(o, 40));
2895  
        })).html()));
2896  
  }
2897  
2898  
  // it's swappable
2899  
  LLS renderStats_base() {
2900  
    ret listPlusItems_inPlace(super.renderStats_base(),
2901  
      ll("Live / dead class paths", n2(classLoaderPathsCount(dm_moduleClassLoader())) + " / " + n2(deadClassPaths)),
2902  
    );
2903  
  }
2904  
  
2905  
  @Override
2906  
  swappable void makeNavItems(DynGazelleRocks.Req req, HTMLFramer1 framer) {
2907  
    super.makeNavItems(req, framer);
2908  
    S webSocketPlaceholder = span("", class := "webSocketPlaceholder");
2909  
    if (moveNavItemsToMisc) {
2910  
      var navItems = cloneList(framer.navItems);
2911  
      framer.clearNavItems();
2912  
      framer.addNavItem(hPopDownButtonWithText("Misc",
2913  
        map(navItems, ni -> framer.renderNavItem(ni)))
2914  
        + " " + webSocketPlaceholder);
2915  
    } else
2916  
      framer.addNavItem(webSocketPlaceholder);
2917  
    if (findConcept(CMissingObject) != null)
2918  
      framer.addNavItem(makeClassNavItem(CMissingObject, req));
2919  
  }
2920  
2921  
  // when HTML is longer than this bytes, restrict to fixed height and show the "more" button
2922  
  // we could always do this, but it's probably quite some overhead
2923  
  // (we currently do it once per table cell)
2924  
  transient int showMoreThreshold = 500;
2925  
  transient int showMoreHeight = 250;
2926  
  
2927  
  swappable S addShowMoreButton(S html) {  
2928  
    if (l(html) < showMoreThreshold) ret html;
2929  
    ret HDivWithVerticalExpandButton(showMoreHeight, html).html();
2930  
  }
2931  
  
2932  
  bool deleteQsWhenEmpty() {
2933  
    BSettings settings = gazelleSettings();
2934  
    ret settings != null && settings.deleteQsWhenEmpty;
2935  
  }
2936  
  
2937  
  Set<Long> activateOnlyTheseIDs() {
2938  
    if (empty(activateOnlyTheseIDs)) null;
2939  
    ret mapToLinkedHashSet parseLong(tok_integers(activateOnlyTheseIDs));
2940  
  }
2941  
  
2942  
  Set<Long> dontActivateTheseIDs() {
2943  
    if (empty(dontActivateTheseIDs)) null;
2944  
    ret mapToLinkedHashSet parseLong(tok_integers(dontActivateTheseIDs));
2945  
  }
2946  
  
2947  
  GazelleBEA beaMod() { this; }
2948  
  
2949  
  !include #1031610 // Methods for BEAObject as well as GazelleBEA
2950  
2951  
  S getGlobalCode(LS tok) {  
2952  
    S global = "";
2953  
    
2954  
    // I guess exports {} is the new preferred syntax
2955  
    int idx;
2956  
    while ((idx = jfind_any(tok, "global {", "exports {")) >= 0) {
2957  
      int j = findEndOfBracketPart(tok, idx+2);
2958  
      global = joinSubList(tok, idx+3, j-1);
2959  
      clearTokens(tok, idx, j+1);
2960  
    }
2961  
    
2962  
    ret global;
2963  
  }
2964  
  
2965  
  S getGlobalCode(BEA o) {
2966  
    ret getGlobalCode(javaTok(codeForObject(o)));
2967  
  }
2968  
  
2969  
  AutoCloseable beaEnter(BEA o) {
2970  
    if (o == null) null;
2971  
    ret combineAutoCloseables(
2972  
      tempSetTL(beaThreadOwner, o),
2973  
      enter());
2974  
  }
2975  
  
2976  
  // GUI stuff
2977  
  
2978  
  afterVisualize { addControls(); }
2979  
  
2980  
  void addControls {
2981  
  }
2982  
  
2983  
  swappable S beaObjectToString_long(BEA o) { 
2984  
    ret beaObjectToString_long_static(o);
2985  
  }
2986  
  
2987  
  sS beaObjectToString_long_static(BEA o) {
2988  
    if (o == null) ret "null";
2989  
    
2990  
    S type = strOrNull(o.typeForToString());
2991  
    S s = or2(type, "(no type)");
2992  
    
2993  
    s +=  " " + o.id;
2994  
    
2995  
    s += appendBracketed(strOrNull(o~.label));
2996  
2997  
    if (eqic(type, "Match"))
2998  
      s += " " + o.~mapping
2999  
        /*+ appendBracketed(o.~input + " + " + o~.pattern)*/;
3000  
3001  
    bool enabled = eq(true, getOpt enabled(o));
3002  
    if (enabled) s += " [enabled]";
3003  
    
3004  
    S purpose = getStringOpt purpose(o);
3005  
    if (nempty(purpose))
3006  
      s += " " + quote(purpose);
3007  
    
3008  
    S text = or2(o.text(),
3009  
      getStringOpt name(o),
3010  
      getStringOpt internalCode(o));
3011  
    if (text != null)
3012  
      s += " " + quote(text);
3013  
      
3014  
    O fc = o~.functionCalled;
3015  
    if (fc != null) s += " " + fc;
3016  
      
3017  
    O result = o~.result;
3018  
    if (result != null) s += " = " + shorten_str(result);
3019  
      
3020  
    ret s;
3021  
  }
3022  
  
3023  
  // for instance replacement
3024  
  bool useErrorHandling() { false; }
3025  
  
3026  
  User findOrCreateUserForLogin(S name, S pw) {
3027  
    try object User u = super.findOrCreateUserForLogin(name, pw);
3028  
    
3029  
    // At this point we know there is no user with that name
3030  
    
3031  
    /*virtual User u = vmBus_query lookupGazelleUser(name, pw, passwordSalt());
3032  
    if (u == null) null;
3033  
    
3034  
    S contact = getString contact(u);
3035  
    bool isMaster = getBoolOpt isMaster(u);*/
3036  
    
3037  
    if (empty(grabUsersFromDomain)) null;
3038  
    
3039  
    pcall {
3040  
      var passwordMD5 = SecretValue(hashPW(pw));
3041  
      S url = addSlashSuffix(grabUsersFromDomain) + "lookup-user";
3042  
      Map map = jsonDecodeMap(postPage(url, +name, +passwordMD5, salt := passwordSalt()));
3043  
  
3044  
      print("Got map? " + (map != null));
3045  
      S error = cast mapGet(map, "error");
3046  
      if (nempty(error))
3047  
        ret null with print(+error);
3048  
        
3049  
      if (map != null)
3050  
        ret cnew User(+name,
3051  
          contact := (S) map.get("contact"),
3052  
          +passwordMD5,
3053  
          isMaster := isTrue(map.get("isMaster")),
3054  
          copiedFromMainDB := true);
3055  
    }
3056  
    
3057  
    null;
3058  
  }
3059  
  
3060  
  S conceptLink(Concept c) {
3061  
    if (c cast BEA) ret beaShortURL(c);
3062  
    ret super.conceptLink(c);
3063  
  }
3064  
  
3065  
  S conceptLink_long(Concept c) {
3066  
    ret super.conceptLink(c);
3067  
  }
3068  
  
3069  
  S conceptToHTML_targetBlank(Concept c) {
3070  
    ret c == null ? "" : targetBlank(conceptLink(c), htmlEncode2(c));
3071  
  }
3072  
  
3073  
  Req newReq() {
3074  
    ret new Req;
3075  
  }
3076  
  
3077  
  class Req extends DynGazelleRocks.Req {
3078  
    // objects involved in handling the request
3079  
    Set makers = syncCompactSet();
3080  
    new Timestamp started;
3081  
    
3082  
    void add(O html) {
3083  
      framer.add(html);
3084  
    }
3085  
    
3086  
    bool debug() {
3087  
      ret eq(get("debug"), "1");
3088  
    }
3089  
    
3090  
    BEAHTMLFramer framer() { ret (BEAHTMLFramer) framer; }
3091  
  }
3092  
  
3093  
  @Override swappable O serveHomePage() {
3094  
    BEA o = beaGet(beaHomePageID);
3095  
    if (o != null) {
3096  
      Req req = currentReq();
3097  
      ret serveBEAHTML(req, o, dropLeadingSlash(req.uri()));
3098  
    }
3099  
    ret super.serveHomePage();
3100  
  }
3101  
  
3102  
  Req currentReq() {
3103  
    ret (Req) super.currentReq();
3104  
  }
3105  
3106  
  void cleanReifiedWebSockets enter {
3107  
    for (BWebSocket o : instancesOf BWebSocket(cloneList(beaList("Live WebSocket"))))
3108  
      if (o.webSocket() == null)
3109  
        retireReifiedWebSocket(o);
3110  
  }
3111  
  
3112  
  Class<? extends Concept> defaultCRUDClass() { ret BEA; }
3113  
  
3114  
  O serveFavIcon() {
3115  
    O response = super.serveFavIcon();
3116  
    print("serveFavIcon: " + response);
3117  
    ret response;
3118  
  }
3119  
  
3120  
  bool alwaysShowLogInLink() { true; }
3121  
  
3122  
  S logInLink() { ret baseLink + "/login"; }
3123  
  
3124  
  S defaultRedirectAfterLogin() { ret baseLink + "/crud/BEAObject"; }
3125  
  
3126  
  @Override void fillReqAuthFromCookie(DynGazelleRocks.Req req,
3127  
    S cookie, AuthedDialogID auth) {
3128  
    super.fillReqAuthFromCookie(req, cookie, auth);
3129  
    
3130  
    if (makeAnonymousUsers && auth != null && auth.user! == null) {
3131  
      S pw = aGlobalID();
3132  
      User userObj = cnew User(
3133  
        passwordMD5 := SecretValue(hashPW(pw)),
3134  
        password := SecretValue(pw)); // store pw clear so we can export it to user
3135  
      cset(userObj, name := "guest" + userObj.id);
3136  
      cset(auth, user := userObj);
3137  
      vmBus_send userCreated(userObj);
3138  
    }
3139  
  }
3140  
  
3141  
  bool userHasData(User user) {
3142  
    pcall {
3143  
      Cl<Concept> refs = allBackRefs(user);
3144  
      refs = withoutInstancesOf AuthedDialogID(refs);
3145  
      refs = withoutInstancesOf BWebSocket(refs);
3146  
      pnl("USERDATA", refs);
3147  
      ret nempty(refs);
3148  
    }
3149  
    true;
3150  
  }
3151  
  
3152  
  bool anonymousUserWarning(Req req, HTMLFramer1 framer) {
3153  
    User user = user(req);
3154  
    if (print(confirm := empty(req.get("confirmLogout")))
3155  
      && print(isAnonymousUser := isAnonymousUser(user))
3156  
      && print(userHasData := userHasData(user))) {
3157  
      framer.add(h2("Anonymous User Warning"));
3158  
      framer.add(p("You are currently logged in as anonymous user " + b(htmlEncode2(user)) + " and seem to have some data stored on the server. Do you want to download your login data before logging out?"));
3159  
      framer.add(p(joinWithSpace(
3160  
        hlinkButton(baseLink + "/myLoginData", "Download Login Data"),
3161  
        hlinkButton(baseLink + req.uri + "?logout=1&confirmLogout=1", "Just log me out")
3162  
      )));
3163  
      true;
3164  
    }
3165  
    false;
3166  
  }
3167  
  
3168  
  @Override O handleLogout(DynGazelleRocks.Req req) {
3169  
    cast req to Req;
3170  
    print("handleLogout " + user(req));
3171  
    if (anonymousUserWarning(req, framer(req)))
3172  
      ret completeFrame(req);
3173  
3174  
    print("Actual logout");
3175  
    ret super.handleLogout(req);
3176  
  }
3177  
  
3178  
  bool isAnonymousUser(User user) {
3179  
    // anonymous users have their password stored as plain text
3180  
    ret cget password(user) != null;
3181  
  }
3182  
  
3183  
  O serveMyLoginData(Req req) {
3184  
    User user = user(req);
3185  
    SecretValue<S> pw = cast cget password(user);
3186  
    bool isAnon = pw != null;
3187  
      
3188  
    if (!isAnon) {
3189  
      req.framer.add(p("You are not an anonymous user. Only anonymous users can download their login data."));
3190  
      ret beaMod().completeFrame(req);
3191  
    }
3192  
  
3193  
    if (eq(req.uri(), "/myLoginData/download")) {
3194  
      S host = mapGet(req.headers(), "host");
3195  
      S text = jsonEncode_breakAtLevel1(litorderedmap(
3196  
        site := host,
3197  
        user := user.name,
3198  
        password := pw!,
3199  
        date := dateWithSecondsUTC(),
3200  
        ip := getClientIPFromHeaders(req.headers())));
3201  
      S filename = "my-login-at-" + host + ".txt";
3202  
      ret subBot_noCacheHeaders(addFilenameHeader(filename, subBot_serveWithContentType(text, binaryMimeType())));
3203  
    }
3204  
    
3205  
    req.framer.add(hcenter(linesLL(
3206  
      h1(req.framer.htmlTitle("Download Login Data")),
3207  
      p("You are currently logged in as an anonymous user and are able to upload data to the site. In order to access, modify or delete this data later, you should definitely download your login data now. Otherwise you risk losing this data and/or being unable to delete it."),
3208  
      p(hbuttonLink(baseLink + "/myLoginData/download", "Download my login data"))
3209  
    )));
3210  
    ret completeFrame(req);
3211  
  }
3212  
  
3213  
  HTMLFramer1 framer(Req req) {
3214  
    if (req.framer == null) makeFramer(req);
3215  
    ret req.framer;
3216  
  }
3217  
  
3218  
  bool userCanSeeObject(User user, O o) {
3219  
    if (user == null) false;
3220  
    if (user.isMaster) true;
3221  
    o = derefRef(o);
3222  
    
3223  
    if (user == o) true;
3224  
    
3225  
    if (o cast Concept) {
3226  
      if (cget createdBy(o) == user) true;
3227  
      if (isTrueOpt isPublic(o)) true;
3228  
    }
3229  
    false;
3230  
  }
3231  
  
3232  
  @Override File uploadsBaseDir() {
3233  
    ret programFile("uploads");
3234  
  }
3235  
  
3236  
  class BEAHTMLFramer extends HTMLFramer1 {
3237  
    new HTMLPopDownButton unnamedPopDownButton;
3238  
  }
3239  
  
3240  
  S makeAbsoluteURL(Req req, S url) {
3241  
    ret main makeAbsoluteURL(
3242  
      (req.isHttps() ? "https://" : "http://") + req.domain(),
3243  
      url);
3244  
  }
3245  
3246  
  O serveBEAHTML(Req req, BEA o, S subURI) {  
3247  
    req.noSpam();
3248  
    req.subURI = subURI;
3249  
    req.makers.add(o);
3250  
      
3251  
    framer(req).title = str(o);
3252  
3253  
    IF0 result = call_optional(o, "html", req);
3254  
    if (result != null) ret result!;
3255  
    result = call_optional(o, "html", req.webRequest);
3256  
    if (result != null) ret result!;
3257  
    ret call(o, "html");
3258  
  }
3259  
  
3260  
  S optBeaHTML(O o, bool targetBlank default false) {
3261  
    if (o cast BEA) ret beaHTML(o, targetBlank);
3262  
    ret htmlEncode2(str(o));
3263  
  }
3264  
  
3265  
  S beaHTML_preferRendered(BEA o, bool targetBlank default false) {
3266  
   if (o != null && o.canRenderHTML())
3267  
      ret targetBlankIf(targetBlank, o.myUri(), htmlEncode2_nlToBr(str(o)));
3268  
    ret beaHTML(o, targetBlank);
3269  
  }
3270  
  
3271  
  // API
3272  
  
3273  
  // e.g. for webssh.gaz.ai - check if request is master authed
3274  
  bool checkCookie(S cookie, S domain) {
3275  
    print("checkCookie " + takeFirst(4, cookie));
3276  
    AuthedDialogID auth = authObject(cookie);
3277  
    if (auth == null) false;
3278  
    new Req req; // dummy request object
3279  
    fillReqAuthFromCookie(req, cookie, auth);
3280  
    ret masterAuthed(req);
3281  
  }
3282  
  
3283  
  bool masterAuthed(WebSocketInfo ws) {
3284  
    ret masterAuthed(ws?.req);
3285  
  }
3286  
  
3287  
  S redirectToLoginURL(Req req) {
3288  
    ret addParamsToURL(baseLink + "/login", redirect := req.uri());
3289  
  }
3290  
  
3291  
  O redirectToLogin(Req req) {
3292  
    ret subBot_serveRedirect(redirectToLoginURL(req));
3293  
  }
3294  
  
3295  
  S redirectUnlessMasterAuthed(WebSocketInfo ws) {
3296  
    if (masterAuthed(ws)) null;
3297  
    ret hrefresh(redirectToLoginURL((Req) ws.req));
3298  
  }
3299  
  
3300  
  S redirectToLogin_hrefresh(Req req) {
3301  
    ret hrefresh(addParamsToURL(baseLink + "/login", redirect := req.uri()));
3302  
  }
3303  
  
3304  
  @Override
3305  
  O html(IWebRequest request) enter {
3306  
    bool profile = eq(request.get("_profile"), "1");
3307  
    if (profile) {
3308  
      // Should reset _profile but whatever
3309  
      ret subBot_serveText(profileThisThreadToString(() -> {
3310  
        super.html(request);
3311  
      }));
3312  
    }
3313  
      
3314  
    ret super.html(request);
3315  
  }
3316  
  
3317  
  <A> A findService(Class<A> type) {
3318  
    if (type == null) null;
3319  
    S name = shortClassName(type);
3320  
    ret (A) findService(name);
3321  
  }
3322  
  
3323  
  BEA findService(S name) {
3324  
    if (empty(name)) null;
3325  
    var services = cloneList(serviceIndex.getAll(name));
3326  
    if (l(services) > 1)
3327  
      warn("Duplicate service " + name + ": " + joinWithSlash(services));
3328  
    ret first(services);
3329  
  }
3330  
  
3331  
  S beaAttribution(BEA o) {
3332  
    ret o == null ?: html_gazelle_madeByIcon(beaURL(o));
3333  
  }
3334  
  
3335  
  // data directory specifically for an object
3336  
  File beaDataDir(BEA o) {
3337  
    if (o == null) null;
3338  
    long id = o.id;
3339  
    ret id == 0 ? null : programFile("BEA Data " + id);
3340  
  }
3341  
  
3342  
  S beaTypeCRUDURL(S type) {
3343  
    ret baseLink + "/beaCRUD/" + urlencode(type);
3344  
  }
3345  
 
3346  
  @Override
3347  
  O serveCRUD(DynNewBot2.Req req) {
3348  
    if (eq(req.uri(), "/crud/BEA"))
3349  
      ret hrefresh(baseLink + "/crud/BEAObject");
3350  
    ret super.serveCRUD(req);
3351  
  }
3352  
  
3353  
  Cl<User> allUsers() { ret list(User); }
3354  
} // end of module / end of GazelleBEA
3355  
3356  
// not used. also, we can't extend User anymore as it's in loadableUtils
3357  
/*extend User {
3358  
  S notificationSetting;
3359  
}*/
3360  
3361  
concept BEAObject > ConceptWithGlobalID {
3362  
  // optional new Ref<UserPost> mirrorPost;
3363  
  UserPost mirrorPost() { ret (UserPost) cget mirrorPost(this); }
3364  
  
3365  
  delegate WebSocketInfo to GazelleBEA.
3366  
3367  
  void change :: after {
3368  
    var mod = beaMod();
3369  
    if (mod != null) {
3370  
      mod.rstUpdateBEAMirrors.add(this);
3371  
      if (mod.autoActivateDynamicObjects)
3372  
        mod.rstAutoActivateDynamicObjects.add(this);
3373  
    }
3374  
  }
3375  
3376  
  void delete :: before {
3377  
    cdelete(mirrorPost());
3378  
    pcallOpt(this, "_deactivate");
3379  
  }
3380  
  
3381  
  void updateMirrorPost {
3382  
    GazelleBEA mod = beaMod();
3383  
    if (isDeleted() || !mod.mirrorBEAObjects) ret;
3384  
3385  
    if (mirrorPost() == null)
3386  
      cset(this, mirrorPost := cnew UserPost(
3387  
        type := "BEA Object",
3388  
        creator := mod.internalUser(),
3389  
        botInfo := "BEA Mirror Bot"));
3390  
3391  
    S text = structureString();
3392  
3393  
    cset(mirrorPost(), 
3394  
      title := str(this),
3395  
      +text);
3396  
  }
3397  
3398  
  S structureString() {
3399  
    S text = "Error";
3400  
    pcall {
3401  
      structure_Data data = new {
3402  
        structure_ClassInfo newClass(Class c) {
3403  
          structure_ClassInfo info = super.newClass(c);
3404  
          if (c == Concept.Ref.class) {
3405  
            info.special = true;
3406  
            info.serializeObject = o -> {
3407  
              Concept cc = cast deref((Concept.Ref) o);
3408  
              //append("cu CRef " + (cc != null ? str(cc.id) : "null"), 3);
3409  
              if (cc != null)
3410  
                append("CRef(id=" + cc.id + ", c=" + quote(dynShortClassName(cc)) + ")", 6);
3411  
              else
3412  
                append("CRef", 1);
3413  
            };
3414  
          }
3415  
          ret info;
3416  
        }
3417  
3418  
        void setFields(structure_ClassInfo info, L<Field> fields) {
3419  
          if (isSubclassOf(info.c, BEA)) {
3420  
            // Don't serialize "refs" and "backRefs" fields
3421  
            removeAll(fields,
3422  
              getField(BEA, "refs"),
3423  
              getField(BEA, "backRefs"));
3424  
          }
3425  
          super.setFields(info, fields);
3426  
        }
3427  
      };
3428  
      
3429  
      S struct = structure(this, data);
3430  
      struct = structure_convertTokenMarkersToExplicit(struct);
3431  
      struct = dropLoadableUtilsPackageFromStruct(struct);
3432  
      text = indentStructureString_firstLevels(1, struct);
3433  
    }
3434  
3435  
    ret text;
3436  
  }
3437  
      
3438  
  toString {
3439  
    ret shorten(toString_long());
3440  
  }
3441  
  
3442  
  S toString_long() {
3443  
    var mod = beaMod();
3444  
    if (mod != null) ret mod.beaObjectToString_long(this);
3445  
    ret "[NO MODULE] " + mod.beaObjectToString_long_static(this);
3446  
  }
3447  
  
3448  
  bool isAlive aka _isAlive() {
3449  
    ret !_conceptsDefunctOrUnregistered();
3450  
  }
3451  
  
3452  
  bool hasCustomCode() {
3453  
    ret cget meta_code(this) != null
3454  
      || cget meta_prototype(this) != null
3455  
      || cget meta_useClass(this) != null;
3456  
  }
3457  
  
3458  
  bool customCodeLoaded() {
3459  
    S className = getStringOpt meta_javaClass(this);
3460  
    if (className != null) ret eq(replace(className(this), "$", "."), className);
3461  
    
3462  
    // quick & dirty test only checking for inner class name
3463  
    S useClass = getStringOpt meta_useClass(this);
3464  
    GazelleBEA.UseClassDecl uc = beaMod().parseUseClassDecl(useClass);
3465  
    if (uc != null) ret eq(shortClassName(this), uc.innerClassName);
3466  
    
3467  
    false;
3468  
  }
3469  
3470  
  // Note: beaGet is not mapMethodLike in here
3471  
  
3472  
  // access typical fields BEA objects have
3473  
  
3474  
  S type() { ret getStringOpt type(this); }
3475  
  bool typeIs(S type) { ret eqic(type(), type); }
3476  
  bool typeIsOneOf aka typeIsAny(S... types) { ret eqicOneOf(type(), types); }
3477  
  
3478  
  S text() { ret getStringOpt text(this); }
3479  
  
3480  
  BEA input() { ret (BEA) cget input(this); }
3481  
  BEA isRewriteOf() { ret (BEA) cget isRewriteOf(this); }
3482  
  
3483  
  S inputText() {
3484  
    BEA input = or(input(), isRewriteOf());
3485  
    ret input?.text();
3486  
  }
3487  
  
3488  
  SS mapping() {
3489  
    ret keysAndValuesToString(cgetOpt Map(this, "mapping"));
3490  
  }
3491  
  
3492  
  LS directCmds() { ret ll(); }
3493  
  
3494  
  Cl<BEA> allObjects() { ret list(_concepts, BEA); }
3495  
  
3496  
  S baseLink() { ret beaMod().baseLink; }
3497  
  
3498  
  BEA beaGet(S field, BEA o) { ret beaMod().beaGet(field, o); }
3499  
  BEA beaGet(long id) { ret beaMod().beaGet(id); }
3500  
  
3501  
  // TODO: this is confusing, it differs from beaMod().beaGet(S id)
3502  
  BEA beaGet(S field) { ret beaMod().beaGet(field, this); }
3503  
  
3504  
  !include #1031610 // Methods for BEAObject as well as GazelleBEA
3505  
3506  
  gazelle.main.GazelleBEA beaMod() {
3507  
    ret _concepts == null
3508  
      ? /*null*/ // XXX - considering this object dead in this case?
3509  
        main beaMod() 
3510  
      : (GazelleBEA) _concepts.miscMapGet(DynNewBot2);
3511  
  }
3512  
  
3513  
  Cl<BEA> beaList aka beaAll() { ret beaMod().beaList(); }
3514  
  Cl<BEA> beaListAny(S... types) { ret beaMod().beaListAny(types); }
3515  
  Cl<BEA> beaList(S type, O... params) { ret beaMod().beaList(type, params); }
3516  
  
3517  
  S beaHTML(BEA o, bool targetBlank default false) { ret beaMod().beaHTML(o, targetBlank); }
3518  
  
3519  
  S beaShortURL aka beaURL(BEA o) { ret beaMod().beaURL(o); }
3520  
  
3521  
  S fixHTML aka fixHtml aka htmlFixer aka htmlFix(S html) { ret beaMod().callHTMLFixer(this, html); }
3522  
  
3523  
  bool createdByMasterUser(BEA o) { ret beaMod().createdByMasterUser(o); }
3524  
  
3525  
  S myURI aka myUri() {
3526  
    S shortURI = first(myShortURLs());
3527  
    if (shortURI != null)
3528  
      ret beaMod().baseLink + shortURI;
3529  
    ret beaMod().baseLink + "/beaHTML/" + id;
3530  
  }
3531  
  
3532  
  void massageItemMapForList(MapSO map) {}
3533  
  
3534  
  BEA mapMethodLike beaGet(S key, GazelleBEA.Req req) {
3535  
    ret beaMod().beaGet(key, req);
3536  
  }
3537  
  
3538  
  selfType me() { this; }
3539  
  
3540  
  O completeFrame(GazelleBEA.Req req) { ret beaMod().completeFrame(req); }
3541  
  
3542  
  S typeForToString() { ret strOrEmpty(cget type(this)); }
3543  
  
3544  
  bool masterAuthed(GazelleBEA.Req req) {
3545  
    ret beaMod().masterAuthed(req);
3546  
  }
3547  
  
3548  
  GazelleBEA.BEAHTMLFramer framer(GazelleBEA.Req req) {
3549  
    ret (GazelleBEA.BEAHTMLFramer) beaMod().framer(req);
3550  
  }
3551  
  
3552  
  User user(GazelleBEA.Req req) {
3553  
    ret beaMod().user(req);
3554  
  }
3555  
  
3556  
  GazelleBEA.Req currentReq() {
3557  
    ret beaMod().currentReq();
3558  
  }
3559  
  
3560  
  bool isHomePage() { ret beaMod().beaHomePageID == id; }
3561  
  
3562  
  S optBeaHTML(O o, bool targetBlank default false) {
3563  
    ret beaMod().optBeaHTML(o, targetBlank);
3564  
  }
3565  
  
3566  
  bool haveShortURL(S uri) {
3567  
    var forward = beaMod().findBEAShortURL(uri);
3568  
    ret forward != null && forward.object == this;
3569  
  }
3570  
  
3571  
  Cl<S> myShortURLs() {
3572  
    ret collectStrings uri(filter(findBackRefs BEA(this), o -> beaMod().isBEAShortURLObject(o)));
3573  
  }
3574  
  
3575  
  bool canRenderHTML() {
3576  
    ret hasMethodNamed(this, "html");
3577  
  }
3578  
  
3579  
  S beaAttribution(BEA o) {
3580  
    ret beaMod().beaAttribution(o);
3581  
  }
3582  
  
3583  
  S madeByMeStamp() { ret beaAttribution(this); }
3584  
  
3585  
  <A> A findService(Class<A> type) {
3586  
    ret beaMod().findService(type);
3587  
  }
3588  
  
3589  
  BEA findService(S name) {
3590  
    ret beaMod().findService(name);
3591  
  }
3592  
  
3593  
  Cl<BEA> masterObjects(Cl<BEA> l) {
3594  
    ret beaMod().masterObjects(l);
3595  
  }
3596  
  
3597  
  S redirectUnlessMasterAuthed(WebSocketInfo ws) {
3598  
    ret beaMod().redirectUnlessMasterAuthed(ws);
3599  
  }
3600  
  
3601  
  bool masterAuthed(WebSocketInfo ws) {
3602  
    ret beaMod().masterAuthed(ws);
3603  
  }
3604  
  
3605  
  File myDataDir() {
3606  
    ret assertNotNull(beaMod().beaDataDir(this));
3607  
  }
3608  
} // end of BEAObject / end of class BEAObject
3609  
3610  
beaConcept BEARegExp {
3611  
  S text;
3612  
  bool caseInsensitive = true;
3613  
  
3614  
  bool valid() { ret nempty(text); }
3615  
  
3616  
  java.util.regex.Pattern compile() {
3617  
    ret compileRegexpPossiblyIC_unicodeCase(text, caseInsensitive);
3618  
  }
3619  
}
3620  
3621  
beaConcept BEARegExpReplacement > BEARegExp {
3622  
  S replacement;
3623  
3624  
  S apply(S text) {
3625  
    try {
3626  
      ret regexpReplace_directWithRefs(compile().matcher(text), unnull(replacement));
3627  
    } catch e {
3628  
      fail(format_quoted("Was searching * in * ", this.text, text));
3629  
    }
3630  
  }
3631  
  
3632  
  LS directCmds() {
3633  
    ret listPlus(super.directCmds(),
3634  
      !valid() ? "Note: not valid"
3635  
      : targetBlank(beaMod().queryLink("Apply regular expression replacement to all inputs", str(id)), "Apply to all inputs"));
3636  
  }
3637  
}
3638  
3639  
beaConcept BEAPatternList {
3640  
  new RefL<BEA> patterns;
3641  
  
3642  
  toString {
3643  
    ret super.toString() + ": " + nPatterns(patterns);
3644  
  }
3645  
}
3646  
3647  
static GazelleBEA beaMod() {
3648  
  //ret (GazelleBEA) botMod();
3649  
  ret (GazelleBEA) dm_current_mandatory();
3650  
}
3651  
3652  
set flag hotwire_here.
3653  
3654  
// share ISpec interface with sub-modules
3655  
static JavaXClassLoader hotwire_makeClassLoader(L<File> files) {
3656  
  ClassLoader cl = myClassLoader();
3657  
   ret new JavaXClassLoaderWithParent2(null, files, cl, ll(/*TODO*/));
3658  
}
3659  
3660  
// class BWebSocket
3661  
beaConcept BWebSocket {
3662  
  transient GazelleBEA.WebSocketInfo webSocketInfo;
3663  
  transient WithTimestamp<byte[]> screenShot;
3664  
  transient Set<Runnable> onScreenShotChanged = syncLinkedHashSet();
3665  
  
3666  
  virtual WebSocket webSocket() {
3667  
    ret getWeakRef(webSocketInfo?.webSocket);
3668  
  }
3669  
  
3670  
  void sendJavaScript(S js) {
3671  
    if (empty(js)) ret;
3672  
    virtual WebSocket ws = webSocket();
3673  
    call(ws, "send", jsonEncode(litorderedmap(eval := js)));
3674  
  }
3675  
  
3676  
  void setScreenShot(WithTimestamp<byte[]> screenShot) {
3677  
    this.screenShot = screenShot;
3678  
    vmBus_send screenShotChanged(this, screenShot);
3679  
    print("Listeners: " + cloneList(onScreenShotChanged));
3680  
    pcallFAll(onScreenShotChanged);
3681  
  }
3682  
}
3683  
3684  
concept BWithResources > BEA {
3685  
  transient new CloseablesHolder resources;
3686  
  transient Q q = startQ(); // object's queue
3687  
  L<WithTimestamp<S>> log; // object's log
3688  
  transient bool activated;
3689  
  bool meta_shouldActivate = true;
3690  
  
3691  
  AutoCloseable enter() {
3692  
    ret beaMod().beaEnter(this);
3693  
  }
3694  
  
3695  
  void mapMethodLike beaPrintVars(O... _) {
3696  
    beaPrint(renderVars(_));
3697  
  }
3698  
  
3699  
  void _handleException(Throwable e) {
3700  
    beaPrint(getStackTrace(e));
3701  
  }
3702  
  
3703  
  bool _active() { ret activated; }
3704  
  
3705  
  // TODO: sync
3706  
  final void _activate {
3707  
    addToQ(r {
3708  
      if (activated) ret;
3709  
      set activated;
3710  
      _activateImpl();
3711  
    });
3712  
  }
3713  
3714  
  void _activateImpl() {}
3715  
3716  
  void _deactivate {
3717  
    addToQ(r {
3718  
      _deactivateImpl();
3719  
      resources.close();
3720  
      unset activated;
3721  
    });
3722  
  }
3723  
  
3724  
  // must be idempotent
3725  
  void _deactivateImpl() {}
3726  
  
3727  
  void delete :: before {
3728  
    _deactivate();
3729  
    waitForQToEmpty(q());
3730  
  }
3731  
  
3732  
  void addResource aka ownResource(AutoCloseable r) {
3733  
    resources.add(r);
3734  
  }
3735  
  
3736  
  void clearBEAPrintLog() {
3737  
    inQ(r {
3738  
      log = null;
3739  
      change();
3740  
    });
3741  
  }
3742  
  
3743  
  // per printed line
3744  
  int maxBeaPrintLength() { ret 10000; }
3745  
  
3746  
  <A> A beaPrintAndReturn(S s default "", A a) {
3747  
    beaPrint(s, a);
3748  
    ret a;
3749  
  }
3750  
  
3751  
  // if you change this, gotta recompile many objects
3752  
  // <A> A beaPrint(S s default "", A o) enter {
3753  
  void beaPrint(S s default "", O o) enter {
3754  
    inQ(r {
3755  
      S text = combinePrintParameters(s, o);
3756  
      text = shorten(maxBeaPrintLength(), text);
3757  
      if (log == null) log = new L;
3758  
      synchronized(log) {
3759  
        truncateListFromStart(log, maxLogSize()-1);
3760  
        printIndent(id + "|", text);
3761  
        log.add(WithTimestamp(text));
3762  
      }
3763  
      change();
3764  
    });
3765  
    //ret o;
3766  
  }
3767  
  
3768  
  // can change through dynamic field
3769  
  int maxLogSize() { ret getIntOpt maxLogSize(this, 10); }
3770  
  
3771  
  selfType me() { this; }
3772  
  
3773  
  // run directly if in queue
3774  
  void inQ(Runnable r) {
3775  
    if (r == null) ret;
3776  
    if (isInQ(q))
3777  
      r.run();
3778  
    else
3779  
      addToQ(r);
3780  
  }
3781  
  
3782  
  // always add to end of queue even when called in queue
3783  
  /*synchronized*/ void addToQ(Runnable r) {
3784  
    if (r == null) ret;
3785  
    getQ().add(rEnter {
3786  
      try {
3787  
        r.run();
3788  
      } catch e {
3789  
        beaPrint(getStackTrace(e));
3790  
      }
3791  
    });
3792  
  }
3793  
  
3794  
  /*synchronized*/ Q getQ aka q() {
3795  
    /*if (q == null) {
3796  
      if (beaMod().verboseQStartsAndStops) print("Starting Q for " + me());
3797  
      q = new Q(str(me())) {
3798  
        void onIdle() { if (beaMod().deleteQsWhenEmpty()) deleteQIfEmpty(); }
3799  
      };
3800  
    }*/
3801  
    ret q;
3802  
  }
3803  
  
3804  
  /*synchronized void deleteQIfEmpty() {
3805  
    if (q == null) ret;
3806  
    synchronized(q.mutex()) {
3807  
      if (q.isEmpty()) {
3808  
        if (beaMod().verboseQStartsAndStops) print("Stopping Q for " + me());
3809  
        dispose q;
3810  
      }
3811  
    }
3812  
  }*/
3813  
  
3814  
  void useWebSocket(GazelleBEA.WebSocketInfo ws) {}
3815  
}
3816  
3817  
concept BSettings {
3818  
  bool deleteQsWhenEmpty; // experimental
3819  
}
3820  
3821  
beaConcept BHTTPRequest {
3822  
  transient GazelleBEA.Req req;
3823  
}
3824  
3825  
sclass WebSocketSet {
3826  
  delegate WebSocketInfo to GazelleBEA.
3827  
  
3828  
  Set<WebSocketInfo> set = syncWeakSet();
3829  
  event countChanged;
3830  
  event wsAdded(WebSocketInfo ws);
3831  
  event wsRemoved(WebSocketInfo ws);
3832  
  
3833  
  void add(WebSocketInfo ws) {
3834  
    if (ws == null) ret;
3835  
    ws.onClose(() -> remove(ws));
3836  
    if (set.add(ws)) {
3837  
      wsAdded(ws);
3838  
      countChanged();
3839  
    }
3840  
  }
3841  
  
3842  
  void remove(WebSocketInfo ws) {
3843  
    if (set.remove(ws)) {
3844  
      wsRemoved(ws);
3845  
      countChanged();
3846  
    }
3847  
  }
3848  
  
3849  
  int size() { ret l(set); }
3850  
  bool empty() { ret size() == 0; }
3851  
  bool nempty() { ret size() != 0; }
3852  
  
3853  
  L<WebSocketInfo> getList() { ret cloneList(set); }
3854  
  
3855  
  void eval(S js, O... _) {
3856  
    broadcast(jsonEvalMsg(js, _));
3857  
  }
3858  
  
3859  
  void broadcast(S data, WebSocketInfo excludeSender default null) {
3860  
    var sockets = cloneList(set);
3861  
    //beaPrint("Broadcasting to " + n2(sockets, "web socket") + ": " + shorten(data, 20));
3862  
    for (WebSocketInfo ws : sockets) pcall {
3863  
      if (ws == excludeSender) continue;
3864  
      try {
3865  
        ws.send(data);
3866  
      } catch e {
3867  
        //print("Removing faulty WebSocket");
3868  
        remove(ws);
3869  
        ws.close();
3870  
      }
3871  
    }
3872  
  }
3873  
}
3874  
3875  
// a BEA object managing a set of websockets
3876  
3877  
concept BWithWebSockets extends BWithResources {
3878  
  delegate WebSocketInfo to GazelleBEA.
3879  
  
3880  
  transient new WebSocketSet webSockets;
3881  
  transient new FlexibleRateTimer timer;
3882  
  double updateFrequency = 0.5; // Hz
3883  
  
3884  
  {
3885  
    webSockets.onCountChanged(r { inQ(r webSocketCountChanged) });
3886  
    webSockets.onWsAdded(lambda1 wsAdded);
3887  
    webSockets.onWsRemoved(lambda1 wsRemoved);
3888  
  }
3889  
  
3890  
  void webSocketCountChanged {}
3891  
  void wsAdded(WebSocketInfo ws) {}
3892  
  void wsRemoved(WebSocketInfo ws) {}
3893  
    
3894  
  @Override void useWebSocket(WebSocketInfo ws) {
3895  
    if (acceptWebSocket(ws))
3896  
      webSockets.add(ws);
3897  
    else
3898  
      ws.send(jsonEncodeMap(webSocketAccepted := false));
3899  
  }
3900  
  
3901  
  // overridable
3902  
  bool acceptWebSocket(WebSocketInfo ws) { true; }
3903  
}
3904  
3905  
// a BEA object that delivers ALL its content by websocket
3906  
3907  
sclass BByWebSocket extends BWithResources {
3908  
  delegate WebSocketInfo to GazelleBEA.
3909  
  delegate Req to GazelleBEA.
3910  
  
3911  
  noeq record Rendered(S html, Runnable afterSent) {
3912  
    *(S *html) {}
3913  
  }
3914  
  
3915  
  O html(Req req) {
3916  
    new HInitWebSocket iws;
3917  
    iws.autoOpen = false;
3918  
    if (req.isPost())
3919  
      iws.params.put("_hasPostData", "1");
3920  
    iws.reconnectParams = "reconnect=1";
3921  
    
3922  
    ret hhtml(linesLL(
3923  
      hhead(nemptyLinesLL(
3924  
        hmobilefix(),
3925  
        iws!,
3926  
        !eq(req.get("wsVerbose"), "1") ? null : hjs([[wsVerbose = true;]]),
3927  
        // hold POST params for websocket
3928  
        !req.isPost() ? null : hjs_escapedDollarVars([[
3929  
          var _postParams = $params;
3930  
          wsOnOpen(function() { wsSend(JSON.stringify({"postData": _postParams})); });
3931  
        ]], params := req.params()),
3932  
        hsansserif(),
3933  
        hjs(js_nodeScriptReplace2()), // We need it later anyway, so let's use it for inserting the loadingContent too (only if JS enabled)
3934  
      )),
3935  
      hbody(
3936  
        // Satisfy the noscripters
3937  
          hnoscript(noScriptContent())
3938  
        + loadingContent()
3939  
        + hjs([[
3940  
          var lc = document.getElementById("loading-content");
3941  
          if (lc) lc.style.display = "table";
3942  
          console.log("Opening WebSocket"); ws.open();
3943  
        ]])
3944  
      ) // end of body
3945  
    );
3946  
  }
3947  
  
3948  
  S loadingContent() {
3949  
    ret hcss("body { background-color: #ecf0f1; #loading-content { display: none; }")
3950  
      + hfullcenter(hsnippetimg(#1102948, title := "Initializing WebSocket")
3951  
      + hnoscript(noScriptContent()),
3952  
      id := "loading-content");
3953  
  }
3954  
  
3955  
  // what to show to Non-JavaScript users.
3956  
  // Can be a hrefresh.
3957  
  S noScriptContent() {
3958  
    ret "Please enable JavaScript to see this page.";
3959  
  }
3960  
  
3961  
  // override me
3962  
  Rendered htmlX(WebSocketInfo ws) {
3963  
    ret new Rendered(html(ws));
3964  
  }
3965  
  
3966  
  // or me
3967  
  S html(WebSocketInfo ws) {
3968  
    ret hhead(linesLL(
3969  
      htitle("Hello world"),
3970  
      hsansserif(),
3971  
    )
3972  
      + hbody(hfullcenter(span("Actual contents!", style := "font-size: 30px")));
3973  
  }
3974  
  
3975  
  Rendered htmlX_safe(WebSocketInfo ws) {
3976  
    try {
3977  
      ret htmlX(ws);
3978  
    } catch print e {
3979  
      ret new Rendered("Error: " + e);
3980  
    }
3981  
  }
3982  
  
3983  
  /*void handleWSMessage(S s) {
3984  
    pcall-short {
3985  
      Map json = jsonDecodeMap(s);
3986  
      handleWSMessage(json);
3987  
    }
3988  
  }
3989  
  
3990  
  // override me
3991  
  void handleWSMessage(Map map) {
3992  
  }*/
3993  
  
3994  
  bool isReconnected(WebSocketInfo ws) {
3995  
    ret ws != null && eq(ws.get("reconnect"), "1");
3996  
  }
3997  
  
3998  
  // override me
3999  
  void handleReconnect(WebSocketInfo ws) {}
4000  
  
4001  
  void useWebSocket(WebSocketInfo ws) {
4002  
    try {
4003  
      bool re = isReconnected(ws);
4004  
      beaPrint("Have " + stringIf(re, "reconnected ") + " websocket: " + ws);
4005  
      //ws.onStringMessage(s -> handleWSMessage(s));
4006  
      if (re) {
4007  
        handleReconnect(ws);
4008  
      } else {
4009  
        double delay = min(10.0, parseDouble(ws.get("_wsDelay")));
4010  
        sleepSeconds(delay);
4011  
        Rendered html = htmlX_safe(ws);
4012  
        if (html != null && html.html != null) {
4013  
          sendHTML(ws, html.html);
4014  
          callF(html.afterSent);
4015  
        }
4016  
      }
4017  
    } catch e {
4018  
      beaPrint(e);
4019  
    }
4020  
  }
4021  
  
4022  
  void sendHTML(WebSocketInfo ws, S html) {
4023  
    ws?.eval(js_replaceHTML(html));
4024  
  }
4025  
  
4026  
  Rendered renderedIfNempty(S html) {
4027  
    ret empty(html) ? null : new Rendered(html);
4028  
  }
4029  
} // end of BByWebSocket
4030  
4031  
sS mainLibID;

Author comment

Began life as a copy of #1031418

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj, onxytkatvevr

No comments. add comment

Snippet ID: #1032712
Snippet name: GazelleBEA [LIVE]
Eternal ID of this version: #1032712/95
Text MD5: 891bd39614cf530fbd088be272d3bb97
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-11-16 16:30:51
Source code size: 133025 bytes / 4031 lines
Pitched / IR pitched: No / No
Views / Downloads: 264 / 628
Version history: 94 change(s)
Referenced in: [show references]