DslAccessible replaces hard-coded package DSL white-listing

Applying the DslAccessible annotation on any method allows it to be called by 
DSL


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/f564aa81
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/f564aa81
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/f564aa81

Branch: refs/heads/master
Commit: f564aa813a311f9b06d9ab4ef5142875f971d50a
Parents: 60c5c6f
Author: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com>
Authored: Tue Dec 13 17:18:24 2016 +0200
Committer: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com>
Committed: Tue Dec 13 17:18:24 2016 +0200

----------------------------------------------------------------------
 .../camp/brooklyn/spi/dsl/DslAccessible.java    | 28 ++++++++++++++++++++
 .../spi/dsl/DslDeferredFunctionCall.java        | 27 ++++++++++++++++---
 .../spi/dsl/methods/BrooklynDslCommon.java      | 26 ++++++++++++++++++
 .../brooklyn/spi/dsl/methods/DslComponent.java  | 16 +++++++++++
 .../camp/brooklyn/spi/dsl/DslYamlTest.java      | 28 ++++++++++++++++++--
 .../spi/dsl/methods/DslTestObjects.java         |  5 ++++
 6 files changed, 125 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslAccessible.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslAccessible.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslAccessible.java
new file mode 100644
index 0000000..6c9de73
--- /dev/null
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslAccessible.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.camp.brooklyn.spi.dsl;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface DslAccessible {
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
index 638f36f..3353151 100644
--- 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
@@ -17,8 +17,11 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon;
@@ -28,12 +31,16 @@ import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.Reflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 
 public class DslDeferredFunctionCall extends 
BrooklynDslDeferredSupplier<Object> {
+    private static final Logger log = 
LoggerFactory.getLogger(DslDeferredFunctionCall.class);
+    private static final Set<Method> DEPRECATED_ACCESS_WARNINGS = 
Collections.newSetFromMap(new ConcurrentHashMap<Method, Boolean>());
 
     private static final long serialVersionUID = 3243262633795112155L;
 
@@ -161,10 +168,24 @@ public class DslDeferredFunctionCall extends 
BrooklynDslDeferredSupplier<Object>
     }
 
     private static void checkCallAllowed(Method m) {
+        DslAccessible dslAccessible = m.getAnnotation(DslAccessible.class);
+        boolean isAnnotationAllowed = dslAccessible != null;
+        if (isAnnotationAllowed) return;
+
+        // TODO white-list using brooklyn.properties (at runtime)
+
         Class<?> clazz = m.getDeclaringClass();
-        if (clazz.getPackage() == null || // Proxy objects don't have a package
-                
!(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName())))
-            throw new IllegalArgumentException("Not permitted to invoke 
function on '"+clazz+"' (outside allowed package scope)");
+        Package whiteListPackage = BrooklynDslCommon.class.getPackage();
+        boolean isPackageAllowed = (clazz.getPackage() != null && // Proxy 
objects don't have a package
+                    
clazz.getPackage().getName().startsWith(whiteListPackage.getName()));
+        if (isPackageAllowed) {
+            if (DEPRECATED_ACCESS_WARNINGS.add(m)) {
+                log.warn("Deprecated since 0.11.0. The method '" + 
m.toString() + "' called by DSL should be white listed using the " + 
DslAccessible.class.getSimpleName() + " annotation. Support for DSL callable 
methods under the " + whiteListPackage + " will be fremoved in a future 
release.");
+            }
+            return;
+        }
+
+        throw new IllegalArgumentException("Not permitted to invoke function 
on '"+clazz+"' (outside allowed package scope)");
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
index bd2cbaf..c17382d 100644
--- 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -37,6 +37,7 @@ import 
org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys;
 import 
org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiator;
 import org.apache.brooklyn.camp.brooklyn.spi.creation.EntitySpecConfiguration;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
@@ -83,37 +84,48 @@ public class BrooklynDslCommon {
 
     // Access specific entities
 
+    @DslAccessible
     public static DslComponent self() {
         return new DslComponent(Scope.THIS);
     }
+    @DslAccessible
     public static DslComponent entity(Object id) {
         return DslComponent.newInstance(Scope.GLOBAL, id);
     }
+    @DslAccessible
     public static DslComponent parent() {
         return new DslComponent(Scope.PARENT);
     }
+    @DslAccessible
     public static DslComponent child(Object id) {
         return DslComponent.newInstance(Scope.CHILD, id);
     }
+    @DslAccessible
     public static DslComponent sibling(Object id) {
         return DslComponent.newInstance(Scope.SIBLING, id);
     }
+    @DslAccessible
     public static DslComponent descendant(Object id) {
         return DslComponent.newInstance(Scope.DESCENDANT, id);
     }
+    @DslAccessible
     public static DslComponent ancestor(Object id) {
         return DslComponent.newInstance(Scope.ANCESTOR, id);
     }
+    @DslAccessible
     public static DslComponent root() {
         return new DslComponent(Scope.ROOT);
     }
+    @DslAccessible
     public static DslComponent scopeRoot() {
         return new DslComponent(Scope.SCOPE_ROOT);
     }
     // prefer the syntax above to the below now, but not deprecating the below
+    @DslAccessible
     public static DslComponent component(String id) {
         return component("global", id);
     }
+    @DslAccessible
     public static DslComponent component(String scope, String id) {
         if (!DslComponent.Scope.isValid(scope)) {
             throw new IllegalArgumentException(scope + " is not a valid 
scope");
@@ -123,10 +135,12 @@ public class BrooklynDslCommon {
 
     // Access things on entities
 
+    @DslAccessible
     public static BrooklynDslDeferredSupplier<?> config(String keyName) {
         return new DslComponent(Scope.THIS, "").config(keyName);
     }
 
+    @DslAccessible
     public static BrooklynDslDeferredSupplier<?> config(BrooklynObjectInternal 
obj, String keyName) {
         return new DslBrooklynObjectConfigSupplier(obj, keyName);
     }
@@ -194,22 +208,26 @@ public class BrooklynDslCommon {
         }
     }
 
+    @DslAccessible
     public static BrooklynDslDeferredSupplier<?> attributeWhenReady(String 
sensorName) {
         return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName);
     }
 
+    @DslAccessible
     public static BrooklynDslDeferredSupplier<?> entityId() {
         return new DslComponent(Scope.THIS, "").entityId();
     }
 
     /** Returns a {@link Sensor}, looking up the sensor on the context if 
available and using that,
      * or else defining an untyped (Object) sensor */
+    @DslAccessible
     public static BrooklynDslDeferredSupplier<Sensor<?>> sensor(Object 
sensorName) {
         return new DslComponent(Scope.THIS, "").sensor(sensorName);
     }
     
     /** Returns a {@link Sensor} declared on the type (e.g. entity class) 
declared in the first argument. */
     @SuppressWarnings({ "unchecked", "rawtypes" })
+    @DslAccessible
     public static Sensor<?> sensor(String clazzName, String sensorName) {
         try {
             // TODO Should use catalog's classloader, rather than 
ClassLoaderUtils; how to get that? Should we return a future?!
@@ -238,6 +256,7 @@ public class BrooklynDslCommon {
 
     // Build complex things
 
+    @DslAccessible
     public static EntitySpecConfiguration entitySpec(Map<String, Object> 
arguments) {
         return new EntitySpecConfiguration(arguments);
     }
@@ -249,6 +268,7 @@ public class BrooklynDslCommon {
      * bundles).
      */
     @SuppressWarnings("unchecked")
+    @DslAccessible
     public static Object object(Map<String, Object> arguments) {
         ConfigBag config = ConfigBag.newInstance(arguments);
         String typeName = 
BrooklynYamlTypeInstantiator.InstantiatorFromKey.extractTypeName("object", 
config).orNull();
@@ -285,6 +305,7 @@ public class BrooklynDslCommon {
     // String manipulation
 
     /** Return the expression as a literal string without any further parsing. 
*/
+    @DslAccessible
     public static Object literal(Object expression) {
         return expression;
     }
@@ -293,6 +314,7 @@ public class BrooklynDslCommon {
      * Returns a formatted string or a {@link BrooklynDslDeferredSupplier} if 
the arguments
      * are not yet fully resolved.
      */
+    @DslAccessible
     public static Object formatString(final String pattern, final 
Object...args) {
         if (resolved(args)) {
             // if all args are resolved, apply the format string now
@@ -302,6 +324,7 @@ public class BrooklynDslCommon {
         }
     }
 
+    @DslAccessible
     public static Object regexReplacement(final Object source, final Object 
pattern, final Object replacement) {
         if (resolved(Arrays.asList(source, pattern, replacement))) {
             return (new Functions.RegexReplacer(String.valueOf(pattern), 
String.valueOf(replacement))).apply(String.valueOf(source));
@@ -642,6 +665,7 @@ public class BrooklynDslCommon {
      * The name of the appropriate {@link ExternalConfigSupplier} is captured, 
along with the key of
      * the desired config value.
      */
+    @DslAccessible
     public static DslExternal external(final String providerName, final String 
key) {
         return new DslExternal(providerName, key);
     }
@@ -698,6 +722,7 @@ public class BrooklynDslCommon {
     }
 
     public static class Functions {
+        @DslAccessible
         public static Object regexReplacement(final Object pattern, final 
Object replacement) {
             if (resolved(pattern, replacement)) {
                 return new 
org.apache.brooklyn.util.text.StringFunctions.RegexReplacer(String.valueOf(pattern),
 String.valueOf(replacement));
@@ -788,6 +813,7 @@ public class BrooklynDslCommon {
             }
         }
 
+        @DslAccessible
         public static Object wrap(Entity entity) {
             return DslComponent.newInstance(Scope.GLOBAL, new 
EntitySupplier(entity.getId()));
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
index 37b9c59..a947fee 100644
--- 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -31,6 +31,7 @@ import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.Sensor;
 import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslFunctionSource;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
@@ -324,41 +325,52 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
 
     // DSL words which move to a new component
     
+    @DslAccessible
     public DslComponent entity(Object id) {
         return DslComponent.newInstance(this, Scope.GLOBAL, id);
     }
+    @DslAccessible
     public DslComponent child(Object id) {
         return DslComponent.newInstance(this, Scope.CHILD, id);
     }
+    @DslAccessible
     public DslComponent sibling(Object id) {
         return DslComponent.newInstance(this, Scope.SIBLING, id);
     }
+    @DslAccessible
     public DslComponent descendant(Object id) {
         return DslComponent.newInstance(this, Scope.DESCENDANT, id);
     }
+    @DslAccessible
     public DslComponent ancestor(Object id) {
         return DslComponent.newInstance(this, Scope.ANCESTOR, id);
     }
+    @DslAccessible
     public DslComponent root() {
         return new DslComponent(this, Scope.ROOT);
     }
+    @DslAccessible
     public DslComponent scopeRoot() {
         return new DslComponent(this, Scope.SCOPE_ROOT);
     }
     
     @Deprecated /** @deprecated since 0.7.0 */
+    @DslAccessible
     public DslComponent component(Object id) {
         return DslComponent.newInstance(this, Scope.GLOBAL, id);
     }
     
+    @DslAccessible
     public DslComponent self() {
         return new DslComponent(this, Scope.THIS);
     }
     
+    @DslAccessible
     public DslComponent parent() {
         return new DslComponent(this, Scope.PARENT);
     }
     
+    @DslAccessible
     public DslComponent component(String scope, Object id) {
         if (!DslComponent.Scope.isValid(scope)) {
             throw new IllegalArgumentException(scope + " is not a valid 
scope");
@@ -368,6 +380,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
 
     // DSL words which return things
 
+    @DslAccessible
     public BrooklynDslDeferredSupplier<?> entityId() {
         return new EntityId(this);
     }
@@ -411,6 +424,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
         }
     }
 
+    @DslAccessible
     public BrooklynDslDeferredSupplier<?> attributeWhenReady(final String 
sensorName) {
         return new AttributeWhenReady(this, sensorName);
     }
@@ -468,6 +482,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
         }
     }
 
+    @DslAccessible
     public BrooklynDslDeferredSupplier<?> config(final String keyName) {
         return new DslConfigSupplier(this, keyName);
     }
@@ -532,6 +547,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
     // TODO
     // public BrooklynDslDeferredSupplier<?> relation(BrooklynObjectInternal 
obj, final String relationName) {...}
 
+    @DslAccessible
     public BrooklynDslDeferredSupplier<Sensor<?>> sensor(final Object 
sensorIndicator) {
         return new DslSensorSupplier(this, sensorIndicator);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
 
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
index aca1aba..2196df9 100644
--- 
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
+++ 
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java
@@ -521,7 +521,9 @@ public class DslYamlTest extends AbstractYamlTest {
     }
 
     public static class InaccessibleType {
-        public static void isEvaluated() {}
+        public static boolean doesFail() {return true;}
+        @DslAccessible
+        public static boolean doesSucceed() {return true;}
     }
 
     @Test
@@ -530,7 +532,7 @@ public class DslYamlTest extends AbstractYamlTest {
                 "services:",
                 "- type: " + BasicApplication.class.getName(),
                 "  brooklyn.config:",
-                "    dest: $brooklyn:config(\"targetValue\").isEvaluated()");
+                "    dest: $brooklyn:config(\"targetValue\").doesFail()");
         app.config().set(ConfigKeys.newConfigKey(InaccessibleType.class, 
"targetValue"), new InaccessibleType());
         try {
             getConfigEventually(app, DEST);
@@ -541,6 +543,28 @@ public class DslYamlTest extends AbstractYamlTest {
     }
 
     @Test
+    public void testDeferredDslAccessible() throws Exception {
+        final Entity app = createAndStartApplication(
+                "services:",
+                "- type: " + BasicApplication.class.getName(),
+                "  brooklyn.config:",
+                "    dest: $brooklyn:config(\"targetValue\").doesSucceed()");
+        app.config().set(ConfigKeys.newConfigKey(InaccessibleType.class, 
"targetValue"), new InaccessibleType());
+        assertEquals(getConfigEventually(app, DEST), Boolean.TRUE);
+    }
+
+    @Test
+    public void testDeferredDslWhiteListPackage() throws Exception {
+        final Entity app = createAndStartApplication(
+                "services:",
+                "- type: " + BasicApplication.class.getName(),
+                "  brooklyn.config:",
+                "    dest: 
$brooklyn:config(\"targetValue\").isSupplierEvaluated()");
+        app.config().set(ConfigKeys.newConfigKey(TestDslSupplierValue.class, 
"targetValue"), new TestDslSupplierValue());
+        assertEquals(getConfigEventually(app, DEST), Boolean.TRUE);
+    }
+
+    @Test
     public void testDeferredDslUserSuppliedPackage() throws Exception {
         final Entity app = createAndStartApplication(
                 "services:",

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/f564aa81/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslTestObjects.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslTestObjects.java
 
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslTestObjects.java
index 60225af..3918ebd 100644
--- 
a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslTestObjects.java
+++ 
b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslTestObjects.java
@@ -15,6 +15,7 @@
  */
 package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods;
 
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslFunctionSource;
 import org.apache.brooklyn.util.core.task.DeferredSupplier;
 import org.apache.brooklyn.util.core.task.ImmediateSupplier;
@@ -29,15 +30,18 @@ public class DslTestObjects {
             this.supplier = supplier;
         }
 
+        @DslAccessible
         public Object getSupplier() {
             return supplier;
         }
     }
 
     public static class TestDslSupplierValue {
+        @DslAccessible
         public boolean isSupplierEvaluated() {
             return true;
         }
+        @DslAccessible
         public DslComponent self() {
             return BrooklynDslCommon.self();
         }
@@ -73,6 +77,7 @@ public class DslTestObjects {
             throw new IllegalStateException("Not to be called");
         }
 
+        @DslAccessible
         public boolean isSupplierCallable() {
             return true;
         }

Reply via email to