001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.console.freemarker; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import org.apache.juneau.marshall.collections.*; 024import org.apache.juneau.console.*; 025 026/** 027 * Declares a derived data source: transforms run in the browser on each new frame of another source, published on 028 * <c>data:<as></c>. 029 * 030 * <p> 031 * The built-in transforms are <c>bucket</c>, <c>filter</c>, <c>groupBy</c> and <c>topN</c>; app transforms are 032 * registered under dotted names. A string arg that is exactly <c>{token}</c> is filled from params. 033 * 034 * <h5 class='section'>Example:</h5> 035 * <p class='bjava'> 036 * <jv>page</jv>.data(Derived.<jsm>from</jsm>(<js>"orgRequests"</js>) 037 * .transform(<js>"groupBy"</js>, JsonMap.<jsm>of</jsm>(<js>"by"</js>, List.<jsm>of</jsm>(<js>"reason"</js>), 038 * <js>"measures"</js>, List.<jsm>of</jsm>(<js>"count"</js>))) 039 * .as(<js>"noqcByReason"</js>)); 040 * </p> 041 * 042 * @since 10.0.0 043 */ 044public final class Derived { 045 046 private final String from; 047 private String id; 048 private final List<JsonMap> transforms = new ArrayList<>(); 049 private final List<Subscription> params = new ArrayList<>(); 050 051 private Derived(String from) { 052 Topics.data(from); 053 this.from = DataSourceDef.checkedId(from); 054 } 055 056 /** 057 * @param source The input source id. 058 * @return A new declaration. 059 * @throws IllegalArgumentException E-40 if the id is not a valid topic key. 060 */ 061 public static Derived from(String source) { 062 return new Derived(source); 063 } 064 065 /** 066 * Appends a transform. 067 * 068 * @param name A registered transform (bare built-in or dotted app name). 069 * @param args The transform's arguments, or <jk>null</jk>. The map is copied one level deep; nested values are 070 * shared, so do not mutate them after the call. 071 * @return This object. 072 */ 073 public Derived transform(String name, JsonMap args) { 074 DataSourceDef.extensionName(name, "transform"); 075 var t = JsonMap.of("use", name); 076 t.put("args", args == null ? new JsonMap() : new JsonMap(args)); 077 transforms.add(t); 078 return this; 079 } 080 081 /** @param subs Subscriptions with role <c>params</c>. @return This object. */ 082 public Derived params(Subscription...subs) { 083 for (var s : subs) { 084 if (s == null) 085 throw iaex(DataSourceDef.E76, id != null ? id : from, "subscription is null"); 086 params.add(s); 087 } 088 return this; 089 } 090 091 /** 092 * Names the output. 093 * 094 * @param value The derived source id; the topic is <c>data:<value></c>. 095 * @return This object. 096 */ 097 public Derived as(String value) { 098 Topics.data(value); 099 if (value.equals(from)) 100 throw iaex(DataSourceDef.E76, value, "a derived source cannot read its own topic"); 101 id = DataSourceDef.checkedId(value); 102 return this; 103 } 104 105 /** @return The output id, or <jk>null</jk> before {@link #as(String)}. */ 106 public String id() { return id; } 107 108 /** 109 * The <c>contract.dataSources[]</c> entry. 110 * 111 * @return A new map. 112 * @throws IllegalStateException If {@link #as(String)} was not called. 113 */ 114 public JsonMap toMap() { 115 if (id == null) 116 throw isex("Derived.from('%s'): as(id) not called", from); 117 var subs = new ArrayList<Object>(); 118 for (var s : params) { 119 var m = s.toMap(); 120 if (! "params".equals(m.get("as"))) 121 throw iaex(DataSourceDef.E76, id, 122 f("subscription to '%s' has role '%s'; a derived source accepts params", s.topic(), m.get("as"))); 123 subs.add(m); 124 } 125 var copies = transforms.stream().map(t -> { 126 var c = new JsonMap(t); 127 c.put("args", new JsonMap((JsonMap)t.get("args"))); 128 return c; 129 }).toList(); 130 return JsonMap.of("id", id, "kind", "derived", "from", from, "transforms", copies, "subscribes", subs); 131 } 132}