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.views;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import org.apache.juneau.commons.bean.*;
022import org.apache.juneau.console.widgets.Op;
023
024/**
025 * A declarative disable-with-reason rule gating a {@link RowAction} on the row's own state, in the
026 * {@code VIEW_META} wire contract.
027 *
028 * <p>
029 * Mirrors {@code org.apache.juneau.console.widgets.ActionRef.EnabledRule} on a detail-panel
030 * action bar, but rides the wire as a plain data rule the same way a row-class
031 * rule does &mdash; a table row is polled/client-rendered rather than server-painted, so this type follows
032 * that wire shape, not {@code ActionRef.EnabledRule}'s Java-only one.  Serializes to
033 * <c>{"field":..., "op":..., "value"?:..., "reason":...}</c>, with {@link #op} carrying {@link Op#wire()}'s
034 * lowercase token (a {@link String}), not the Java enum.
035 *
036 * <p>
037 * The {@link Op#EQ eq}/{@link Op#NE ne} operators <b>require</b> a {@link #value}; the {@link Op#PRESENT
038 * present}/{@link Op#ABSENT absent} operators test only whether {@code row[field]} is non-null/non-empty and
039 * therefore <b>omit</b> {@code value} from the wire.
040 *
041 * <h5 class='section'>Presentation only, never the authorization gate:</h5>
042 * <p>
043 * A row whose field does not satisfy this rule renders its {@link RowAction} present but disabled, carrying
044 * {@link #reason} as a tooltip and {@code aria-describedby} text.  The endpoint behind the action must still
045 * perform its own real check and refuse on its own authority; this rule is never a substitute for that check.
046 *
047 * <h5 class='section'>See Also:</h5>
048 * <ul>
049 *    <li class='jc'>{@link RowAction}
050 * </ul>
051 *
052 * @since 10.0.0
053 */
054@BeanType(properties="field,op,value,reason")
055public class RowActionEnabledRule {
056
057   /** The row field this rule tests. */
058   public String field;
059
060   /** The wire token of the comparison operator (see {@link Op#wire()}). */
061   public String op;
062
063   /** The comparison value for {@code eq}/{@code ne}; omitted for {@code present}/{@code absent}. */
064   public Object value;
065
066   /** Why the action is unavailable when this rule does not match.  Required &mdash; must not be blank. */
067   public String reason;
068
069   /**
070    * Creates a value-based rule for the {@link Op#EQ eq}/{@link Op#NE ne} operators.
071    *
072    * @param field The row field to test.  Must not be <jk>null</jk> or blank.
073    * @param op The operator.  Must be {@link Op#EQ} or {@link Op#NE}.
074    * @param value The comparison value.  Must not be <jk>null</jk>.
075    * @param reason Why the action is unavailable when this rule does not match.  Must not be <jk>null</jk> or
076    *    blank.
077    * @return A new {@link RowActionEnabledRule}.
078    * @throws IllegalArgumentException If {@code field}/{@code reason} is <jk>null</jk> or blank, {@code op} is
079    *    not value-based, or {@code value} is <jk>null</jk>.
080    */
081   public static RowActionEnabledRule of(String field, Op op, Object value, String reason) {
082      if (ib(field))
083         throw iaex("RowActionEnabledRule field must not be null or blank.");
084      if (op != null && op.isCollectionOp())
085         throw iaex("RowActionEnabledRule op '%s' is a visibility-rule operator; use eq or ne.", op.wire());
086      if (op == null || ! op.requiresValue())
087         throw iaex("RowActionEnabledRule op '%s' does not take a value; use the (field, op, reason) form.",
088            op == null ? "null" : op.wire());
089      if (value == null)
090         throw iaex("RowActionEnabledRule op '%s' requires a non-null value.", op.wire());
091      if (ib(reason))
092         throw iaex("RowActionEnabledRule reason must not be null or blank.");
093      var r = new RowActionEnabledRule();
094      r.field = field;
095      r.op = op.wire();
096      r.value = value;
097      r.reason = reason;
098      return r;
099   }
100
101   /**
102    * Creates a presence-based rule for the {@link Op#PRESENT present}/{@link Op#ABSENT absent} operators.
103    *
104    * @param field The row field to test.  Must not be <jk>null</jk> or blank.
105    * @param op The operator.  Must be {@link Op#PRESENT} or {@link Op#ABSENT}.
106    * @param reason Why the action is unavailable when this rule does not match.  Must not be <jk>null</jk> or
107    *    blank.
108    * @return A new {@link RowActionEnabledRule} with no {@code value}.
109    * @throws IllegalArgumentException If {@code field}/{@code reason} is <jk>null</jk> or blank, or {@code op}
110    *    is value-based.
111    */
112   public static RowActionEnabledRule of(String field, Op op, String reason) {
113      if (ib(field))
114         throw iaex("RowActionEnabledRule field must not be null or blank.");
115      if (op == null || op.requiresValue())
116         throw iaex("RowActionEnabledRule op '%s' requires a value; use the (field, op, value, reason) form.",
117            op == null ? "null" : op.wire());
118      if (ib(reason))
119         throw iaex("RowActionEnabledRule reason must not be null or blank.");
120      var r = new RowActionEnabledRule();
121      r.field = field;
122      r.op = op.wire();
123      r.reason = reason;
124      return r;
125   }
126}