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; 018 019import java.util.*; 020 021import org.apache.juneau.marshall.collections.*; 022 023/** 024 * A custom topic declaration, for a card's {@code publishes} or the page's top-level {@code topics}. 025 * 026 * <h5 class='section'>Example:</h5> 027 * <p class='bjava'> 028 * <jc>// Published by this card's JS:</jc> 029 * <jv>card</jv>.publishes(TopicDecl.<jsm>of</jsm>(<js>"ssc.focus"</js>).retain(<jk>true</jk>)); 030 * 031 * <jc>// Published by a page script or the server:</jc> 032 * PageSpec.<jsm>create</jsm>().topic(TopicDecl.<jsm>of</jsm>(<js>"app.region-picked"</js>).retain(<jk>true</jk>) 033 * .publisher(TopicDecl.Publisher.<jsf>SCRIPT</jsf>)); 034 * </p> 035 * 036 * @since 10.0.0 037 */ 038public final class TopicDecl { 039 040 /** Who publishes a page-level topic. */ 041 public enum Publisher { 042 /** A page script. */ 043 SCRIPT, 044 /** The server, through a bridge (R-11). */ 045 SERVER, 046 /** Only ribbon {@code publish} items. */ 047 RIBBON 048 } 049 050 private static final String JOB_ALL = "job:*"; 051 052 private final String topic; 053 private Boolean retain; 054 private Publisher publisher; 055 056 private TopicDecl(String topic) { 057 this.topic = topic; 058 } 059 060 /** 061 * @param topic A custom topic or {@code ns.name:*}; or {@code job:*}, which is only valid page-level with {@link Publisher#SERVER}. 062 * @return A new declaration. 063 * @throws IllegalArgumentException E-45 for a framework family or an un-namespaced family; E-40 on bad syntax. 064 */ 065 public static TopicDecl of(String topic) { 066 if (topic == null) 067 throw Topics.iae(Topics.E40, topic); 068 var family = Topics.family(topic); 069 var declarable = Topics.DECLARABLE_FRAMEWORK.contains(family); 070 if (Topics.FRAMEWORK_FAMILIES.contains(family) && ! JOB_ALL.equals(topic) && ! declarable) 071 throw Topics.iae(Topics.E45_FRAMEWORK, family); 072 if (Topics.ROLE.matcher(family).matches() && ! Topics.FRAMEWORK_FAMILIES.contains(family)) 073 throw Topics.iae(Topics.E45_NAMESPACE, family, family); 074 if (declarable ? ! Topics.TOPIC.matcher(topic).matches() || topic.indexOf(':') < 0 075 : ! Topics.TOPIC_DECL.matcher(topic).matches()) 076 throw Topics.iae(Topics.E40, topic); 077 return new TopicDecl(topic); 078 } 079 080 /** @param v Whether the bus keeps the last value for late subscribers. Required. @return This object. */ 081 public TopicDecl retain(boolean v) { 082 retain = v; 083 return this; 084 } 085 086 /** @param p Who publishes the topic. Page-level only. @return This object. */ 087 public TopicDecl publisher(Publisher p) { 088 publisher = Objects.requireNonNull(p, "publisher"); 089 return this; 090 } 091 092 /** @return The topic. */ 093 public String topic() { 094 return topic; 095 } 096 097 /** @return The page-level {@code $defs/topicDecl} entry. */ 098 public JsonMap toMap() { 099 if (JOB_ALL.equals(topic) && publisher != Publisher.SERVER) 100 throw Topics.iae(Topics.E45_FRAMEWORK, "job"); 101 var family = Topics.family(topic); 102 if ("zoom".equals(family) || "legend".equals(family)) 103 throw Topics.iae("topic '%s': %s:<cardId> is published by its chart card; declare it with publishZoom()/publishLegend()", topic, family); 104 if ("data".equals(family) && (publisher != Publisher.SCRIPT || ! Boolean.TRUE.equals(retain))) 105 throw Topics.iae("topic '%s': a page-declared data: topic needs publisher(SCRIPT) and retain(true)", topic); 106 var m = base(); 107 if (publisher == null) 108 throw Topics.iae("topic '%s': a page-level topic needs publisher(SCRIPT|SERVER|RIBBON)", topic); 109 m.put("publisher", publisher.name().toLowerCase(Locale.ROOT)); 110 return m; 111 } 112 113 /** @return The card-level {@code $defs/publication} entry, for {@code CardSpec.publishes}. */ 114 public JsonMap toPublicationMap() { 115 if (JOB_ALL.equals(topic)) 116 throw Topics.iae(Topics.E45_FRAMEWORK, "job"); 117 if ("data".equals(Topics.family(topic))) 118 throw Topics.iae("topic '%s': data: topics are declared page-level (publisher SCRIPT) or by a data source, not by a card", topic); 119 if (Topics.DECLARABLE_FRAMEWORK.contains(Topics.family(topic)) && ! Boolean.TRUE.equals(retain)) 120 throw Topics.iae("topic '%s' is chart state; declare it with retain(true)", topic); 121 if (publisher != null) 122 throw Topics.iae("topic '%s': publisher is page-level only; a card's publishes may not set it", topic); 123 return base(); 124 } 125 126 private JsonMap base() { 127 if (retain == null) 128 throw Topics.iae("topic '%s' is declared with retain=unset here; call retain(true) or retain(false)", topic); 129 var m = new JsonMap(); 130 m.put("topic", topic); 131 m.put("retain", retain); 132 return m; 133 } 134}