UNOMI-400 Refactoring of hardcoded property accessors (#218)

* UNOMI-400 Refactoring of hardcoded property accessors

* UNOMI-400 More refactoring on property accessors & added unit test

(cherry picked from commit 5de4cab84c003688fc72664a9fbda4c33143d880)
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistry.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistry.java
new file mode 100644
index 0000000..59a70b5
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistry.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions;
+
+import org.apache.unomi.api.*;
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.plugins.baseplugin.conditions.accessors.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * This class contains the registry of all the hardcoded property accessors.
+ * For the moment this list of accessors is hardcoded, but in a future update it could be made dynamic.
+ */
+public class HardcodedPropertyAccessorRegistry {
+
+    private static final Logger logger = LoggerFactory.getLogger(HardcodedPropertyAccessorRegistry.class.getName());
+
+    Map<String, HardcodedPropertyAccessor> propertyAccessors = new HashMap<>();
+
+    public HardcodedPropertyAccessorRegistry() {
+        propertyAccessors.put(Item.class.getName(), new ItemAccessor(this));
+        propertyAccessors.put(MetadataItem.class.getName(), new MetadataItemAccessor(this));
+        propertyAccessors.put(Metadata.class.getName(), new MetadataAccessor(this));
+        propertyAccessors.put(TimestampedItem.class.getName(), new TimestampedItemAccessor(this));
+        propertyAccessors.put(Event.class.getName(), new EventAccessor(this));
+        propertyAccessors.put(Profile.class.getName(), new ProfileAccessor(this));
+        propertyAccessors.put(Consent.class.getName(), new ConsentAccessor(this));
+        propertyAccessors.put(Session.class.getName(), new SessionAccessor(this));
+        propertyAccessors.put(Rule.class.getName(), new RuleAccessor(this));
+        propertyAccessors.put(Goal.class.getName(), new GoalAccessor(this));
+        propertyAccessors.put(CustomItem.class.getName(), new CustomItemAccessor(this));
+        propertyAccessors.put(Campaign.class.getName(), new CampaignAccessor(this));
+        propertyAccessors.put(Map.class.getName(), new MapAccessor(this));
+    }
+
+    public static class NextTokens {
+        public String propertyName;
+        public String leftoverExpression;
+    }
+
+    protected NextTokens getNextTokens(String expression) {
+        if (expression.startsWith("[\"")) {
+            int lookupNameBeginPos = "[\"".length();
+            int lookupNameEndPos = expression.indexOf("\"]", lookupNameBeginPos);
+            return buildNextTokens(expression, lookupNameBeginPos, lookupNameEndPos, lookupNameEndPos+2);
+        } else if (expression.startsWith(".")) {
+            int lookupNameBeginPos = ".".length();
+            int lookupNameEndPos = findNextStartDelimiterPos(expression, lookupNameBeginPos);
+            return buildNextTokens(expression, lookupNameBeginPos, lookupNameEndPos, lookupNameEndPos);
+        } else {
+            int lookupNameBeginPos = 0;
+            int lookupNameEndPos = findNextStartDelimiterPos(expression, lookupNameBeginPos);
+            return buildNextTokens(expression, lookupNameBeginPos, lookupNameEndPos, lookupNameEndPos);
+        }
+    }
+
+    private NextTokens buildNextTokens(String expression, int lookupNameBeginPos, int lookupNameEndPos, int leftoverStartPos) {
+        NextTokens nextTokens = new NextTokens();
+        if (lookupNameEndPos >= lookupNameBeginPos) {
+            nextTokens.propertyName = expression.substring(lookupNameBeginPos, lookupNameEndPos);
+            nextTokens.leftoverExpression = expression.substring(leftoverStartPos);
+            if ("".equals(nextTokens.leftoverExpression)) {
+                nextTokens.leftoverExpression = null;
+            }
+        } else {
+            nextTokens.propertyName = expression.substring(lookupNameBeginPos);
+            nextTokens.leftoverExpression = null;
+        }
+        return nextTokens;
+    }
+
+    private int findNextStartDelimiterPos(String expression, int lookupNameBeginPos) {
+        int lookupNameEndPos;
+        int dotlookupNameEndPos = expression.indexOf(".", lookupNameBeginPos);
+        int squareBracketlookupNameEndPos = expression.indexOf("[\"", lookupNameBeginPos);
+        if (dotlookupNameEndPos >= lookupNameBeginPos && squareBracketlookupNameEndPos >= lookupNameBeginPos) {
+            lookupNameEndPos = Math.min(dotlookupNameEndPos, squareBracketlookupNameEndPos);
+        } else if (dotlookupNameEndPos >= lookupNameBeginPos) {
+            lookupNameEndPos = dotlookupNameEndPos;
+        } else if (squareBracketlookupNameEndPos >= lookupNameBeginPos) {
+            lookupNameEndPos = squareBracketlookupNameEndPos;
+        } else {
+            lookupNameEndPos = -1;
+        }
+        return lookupNameEndPos;
+    }
+
+
+    public Object getProperty(Object object, String expression) {
+        if (expression == null) {
+            return object;
+        }
+        if (expression.trim().equals("")) {
+            return object;
+        }
+        NextTokens nextTokens = getNextTokens(expression);
+        List<Class<?>> lookupClasses = new ArrayList<>();
+        lookupClasses.add(object.getClass());
+        lookupClasses.add(object.getClass().getSuperclass());
+        lookupClasses.addAll(Arrays.asList(object.getClass().getInterfaces()));
+        for (Class<?> lookupClass : lookupClasses) {
+            HardcodedPropertyAccessor propertyAccessor = propertyAccessors.get(lookupClass.getName());
+            if (propertyAccessor != null) {
+                Object result = propertyAccessor.getProperty(object, nextTokens.propertyName, nextTokens.leftoverExpression);
+                if (!HardcodedPropertyAccessor.PROPERTY_NOT_FOUND_MARKER.equals(result)) {
+                    return result;
+                }
+            }
+        }
+        logger.warn("Couldn't find any property access for class {} and expression {}", object.getClass().getName(), expression);
+        return HardcodedPropertyAccessor.PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
index 743148c..a3cc0d0 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
@@ -23,8 +23,7 @@
 import org.apache.commons.lang3.StringUtils;
 import org.apache.unomi.api.*;
 import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.rules.Rule;
-import org.apache.unomi.scripting.ExpressionFilter;
+import org.apache.unomi.plugins.baseplugin.conditions.accessors.HardcodedPropertyAccessor;
 import org.apache.unomi.scripting.ExpressionFilterFactory;
 import org.apache.unomi.scripting.SecureFilteringClassLoader;
 import org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper;
@@ -51,14 +50,14 @@
     private static final Logger logger = LoggerFactory.getLogger(PropertyConditionEvaluator.class.getName());
 
     private static final SimpleDateFormat yearMonthDayDateFormat = new SimpleDateFormat("yyyyMMdd");
-    public static final String NOT_OPTIMIZED_MARKER = "$$$###NOT_OPTIMIZED###$$$";
 
-    private Map<String, Map<String, ExpressionAccessor>> expressionCache = new HashMap<>(64);
+    private final Map<String, Map<String, ExpressionAccessor>> expressionCache = new HashMap<>(64);
     private boolean usePropertyConditionOptimizations = true;
-    private static ClassLoader secureFilteringClassLoader = new SecureFilteringClassLoader(PropertyConditionEvaluator.class.getClassLoader());
+    private static final ClassLoader secureFilteringClassLoader = new SecureFilteringClassLoader(PropertyConditionEvaluator.class.getClassLoader());
+    private static final HardcodedPropertyAccessorRegistry hardcodedPropertyAccessorRegistry = new HardcodedPropertyAccessorRegistry();
     private ExpressionFilterFactory expressionFilterFactory;
 
-    private boolean useOGNLScripting = Boolean.parseBoolean(System.getProperty("org.apache.unomi.security.properties.useOGNLScripting", "false"));
+    private final boolean useOGNLScripting = Boolean.parseBoolean(System.getProperty("org.apache.unomi.security.properties.useOGNLScripting", "false"));
 
     public void setUsePropertyConditionOptimizations(boolean usePropertyConditionOptimizations) {
         this.usePropertyConditionOptimizations = usePropertyConditionOptimizations;
@@ -268,7 +267,7 @@
     protected Object getPropertyValue(Item item, String expression) throws Exception {
         if (usePropertyConditionOptimizations) {
             Object result = getHardcodedPropertyValue(item, expression);
-            if (!NOT_OPTIMIZED_MARKER.equals(result)) {
+            if (!HardcodedPropertyAccessor.PROPERTY_NOT_FOUND_MARKER.equals(result)) {
                 return result;
             }
         }
@@ -283,7 +282,7 @@
     protected Object getHardcodedPropertyValue(Item item, String expression) {
         // the following are optimizations to avoid using the expressions that are slower. The main objective here is
         // to avoid the most used expression that may also trigger calls to the Java Reflection API.
-        return getItemProperty(item, expression);
+        return hardcodedPropertyAccessorRegistry.getProperty(item, expression);
     }
 
     protected Object getOGNLPropertyValue(Item item, String expression) throws Exception {
@@ -304,21 +303,6 @@
         return null;
     }
 
-    private Object getNestedPropertyValue(String expressionPart, Map<String, Object> properties) {
-        int nextDotPos = expressionPart.indexOf(".");
-        if (nextDotPos > -1) {
-            String mapKey = expressionPart.substring(0, nextDotPos);
-            Object mapValue = properties.get(mapKey);
-            if (mapValue == null) {
-                return null;
-            }
-            String nextExpression = expressionPart.substring(nextDotPos + 1);
-            return getNestedPropertyValue(nextExpression, (Map<String, Object>) mapValue);
-        } else {
-            return properties.get(expressionPart);
-        }
-    }
-
     private class ClassLoaderClassResolver extends DefaultClassResolver {
         private ClassLoader classLoader;
 
@@ -423,230 +407,4 @@
         }
     }
 
-    private Object getEventProperty(Event event, String expression) {
-        if (expression.startsWith("properties.")) {
-            return getNestedPropertyValue(expression.substring("properties.".length()), event.getProperties());
-        }
-        if ("scope".equals(expression)) {
-            return event.getScope();
-        }
-        if ("eventType".equals(expression)) {
-            return event.getEventType();
-        }
-        if (expression.startsWith("profile")) {
-            if ("profile".equals(expression)) {
-                return event.getProfile();
-            } else {
-                return getProfileProperty(event.getProfile(), expression.substring("profile".length()+1));
-            }
-        }
-        if ("profileId".equals(expression)) {
-            return event.getProfileId();
-        }
-        if (expression.startsWith("session")) {
-            if ("session".equals(expression)) {
-                return event.getSession();
-            } else {
-                return getSessionProperty(event.getSession(), expression.substring("session".length()+1));
-            }
-        }
-        if ("sessionId".equals(expression)) {
-            return event.getSessionId();
-        }
-        if (expression.startsWith("source")) {
-            if ("source".equals(expression)) {
-                return event.getSource();
-            } else {
-                return getItemProperty(event.getSource(), expression.substring("source".length()+1));
-            }
-        }
-        if (expression.startsWith("target")) {
-            if ("target".equals(expression)) {
-                return event.getTarget();
-            } else {
-                return getItemProperty(event.getTarget(), expression.substring("target".length()+1));
-            }
-        }
-        if ("timeStamp".equals(expression)) {
-            return event.getTimeStamp();
-        }
-        if ("itemId".equals(expression)) {
-            return event.getItemId();
-        }
-        if ("itemType".equals(expression)) {
-            return event.getItemType();
-        }
-        logger.warn("Requested unimplemented property {} on Event object", expression);
-        return NOT_OPTIMIZED_MARKER;
-    }
-
-    private Object getSessionProperty(Session session, String expression) {
-        if ("scope".equals(expression)) {
-            return session.getScope();
-        }
-        if ("timeStamp".equals(expression)) {
-            return session.getTimeStamp();
-        }
-        if ("duration".equals(expression)) {
-            return session.getDuration();
-        }
-        if ("size".equals(expression)) {
-            return session.getSize();
-        }
-        if ("lastEventDate".equals(expression)) {
-            return session.getLastEventDate();
-        }
-        if (expression.startsWith("properties.")) {
-            return getNestedPropertyValue(expression.substring("properties.".length()), session.getProperties());
-        }
-        if (expression.startsWith("systemProperties.")) {
-            return getNestedPropertyValue(expression.substring("systemProperties.".length()), session.getSystemProperties());
-        }
-        if ("itemId".equals(expression)) {
-            return session.getItemId();
-        }
-        if ("itemType".equals(expression)) {
-            return session.getItemType();
-        }
-        if (expression.startsWith("profile")) {
-            if ("profile".equals(expression)) {
-                return session.getProfile();
-            } else {
-                return getProfileProperty((Profile) session.getProfile(), expression.substring("profile".length()+1));
-            }
-        }
-        if ("profileId".equals(expression)) {
-            return session.getProfileId();
-        }
-        logger.warn("Requested unimplemented property {} on Session object", expression);
-        return NOT_OPTIMIZED_MARKER;
-    }
-
-    private Object getProfileProperty(Profile profile, String expression) {
-        if ("segments".equals(expression)) {
-            return profile.getSegments();
-        }
-        if (expression.startsWith("consents")) {
-            if ("consents".equals(expression)) {
-                return profile.getConsents();
-            } else {
-                String consentLookupName = null;
-                String leftoverExpression = expression;
-                if (expression.startsWith("consents[\"")) {
-                    int lookupNameBeginPos = "consents[\"".length();
-                    int lookupNameEndPos = expression.indexOf("\"].", lookupNameBeginPos);
-                    if (lookupNameEndPos > lookupNameBeginPos) {
-                        consentLookupName = expression.substring(lookupNameBeginPos, lookupNameEndPos);
-                        leftoverExpression = expression.substring(lookupNameEndPos+3);
-                    } else {
-                        consentLookupName = expression.substring(lookupNameBeginPos);
-                        leftoverExpression = null;
-                    }
-                } else if (expression.startsWith("consents.")) {
-                    int lookupNameBeginPos = "consents.".length();
-                    int lookupNameEndPos = expression.indexOf(".", lookupNameBeginPos);
-                    if (lookupNameEndPos > lookupNameBeginPos) {
-                        consentLookupName = expression.substring(lookupNameBeginPos, lookupNameEndPos);
-                        leftoverExpression = expression.substring(lookupNameEndPos+1);
-                    } else {
-                        consentLookupName = expression.substring(lookupNameBeginPos);
-                        leftoverExpression = expression.substring(lookupNameEndPos);
-                    }
-                }
-                Consent consent = profile.getConsents().get(consentLookupName);
-                if (consent == null) {
-                    return null;
-                }
-                if (leftoverExpression == null) {
-                    return consent;
-                }
-                return getConsentProperty(consent, leftoverExpression);
-            }
-        }
-        if (expression.startsWith("scores.")) {
-            return profile.getScores().get(expression.substring("scores.".length()));
-        }
-        if (expression.startsWith("properties.")) {
-            return getNestedPropertyValue(expression.substring("properties.".length()), profile.getProperties());
-        }
-        if (expression.startsWith("systemProperties.")) {
-            return getNestedPropertyValue(expression.substring("systemProperties.".length()), profile.getSystemProperties());
-        }
-        if ("itemId".equals(expression)) {
-            return profile.getItemId();
-        }
-        if ("itemType".equals(expression)) {
-            return profile.getItemType();
-        }
-        if ("mergedWith".equals(expression)) {
-            return profile.getMergedWith();
-        }
-        logger.warn("Requested unimplemented property {} on Profile object", expression);
-        return NOT_OPTIMIZED_MARKER;
-    }
-
-    private Object getCustomItemProperty(CustomItem customItem, String expression) {
-        if (expression.startsWith("properties.")) {
-            return getNestedPropertyValue(expression.substring("properties.".length()), customItem.getProperties());
-        }
-        if ("itemId".equals(expression)) {
-            return customItem.getItemId();
-        }
-        if ("itemType".equals(expression)) {
-            return customItem.getItemType();
-        }
-        if ("scope".equals(expression)) {
-            return customItem.getScope();
-        }
-        logger.warn("Requested unimplemented property {} on CustomItem object", expression);
-        return NOT_OPTIMIZED_MARKER;
-    }
-
-    private Object getRuleProperty(Rule rule, String expression) {
-        if ("itemId".equals(expression)) {
-            return rule.getItemId();
-        }
-        if ("itemType".equals(expression)) {
-            return rule.getItemType();
-        }
-        if ("scope".equals(expression)) {
-            return rule.getScope();
-        }
-        logger.warn("Requested unimplemented property {} on Rule object", expression);
-        return NOT_OPTIMIZED_MARKER;
-    }
-
-    private Object getItemProperty(Item item, String expression) {
-        if (item instanceof Profile) {
-            return getProfileProperty((Profile) item, expression);
-        } else if (item instanceof Session) {
-            return getSessionProperty((Session) item, expression);
-        } else if (item instanceof Rule) {
-            return getRuleProperty((Rule) item, expression);
-        } else if (item instanceof Event) {
-            return getEventProperty((Event) item, expression);
-        } else if (item instanceof CustomItem) {
-            return getCustomItemProperty((CustomItem) item, expression);
-        } else {
-            logger.warn("Requested unrecognized property {} on {} class", expression, item.getClass().getName());
-            return NOT_OPTIMIZED_MARKER;
-        }
-    }
-
-    private Object getConsentProperty(Consent consent, String expression) {
-        if ("typeIdentifier".equals(expression)) {
-            return consent.getTypeIdentifier();
-        } else if ("scope".equals(expression)) {
-            return consent.getScope();
-        } else if ("status".equals(expression)) {
-            return consent.getStatus();
-        } else if ("statusDate".equals(expression)) {
-            return consent.getStatusDate();
-        } else if ("revokeDate".equals(expression)) {
-            return consent.getRevokeDate();
-        } else {
-            logger.warn("Requested unrecognized property {} on Consent object {}", expression, consent);
-            return NOT_OPTIMIZED_MARKER;
-        }
-    }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CampaignAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CampaignAccessor.java
new file mode 100644
index 0000000..5736780
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CampaignAccessor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class CampaignAccessor extends HardcodedPropertyAccessor<Campaign> {
+    public CampaignAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Campaign object, String propertyName, String leftoverExpression) {
+        if ("startDate".equals(propertyName)) {
+            return object.getStartDate();
+        } else if ("endDate".equals(propertyName)) {
+            return object.getEndDate();
+        } else if ("cost".equals(propertyName)) {
+            return object.getCost();
+        } else if ("currency".equals(propertyName)) {
+            return object.getCurrency();
+        } else if ("primaryGoal".equals(propertyName)) {
+            return object.getPrimaryGoal();
+        } else if ("timezone".equals(propertyName)) {
+            return object.getTimezone();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ConsentAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ConsentAccessor.java
new file mode 100644
index 0000000..1b052b9
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ConsentAccessor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Consent;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class ConsentAccessor extends HardcodedPropertyAccessor<Consent> {
+
+    public ConsentAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Consent object, String propertyName, String leftoverExpression) {
+        if ("typeIdentifier".equals(propertyName)) {
+            return object.getTypeIdentifier();
+        } else if ("scope".equals(propertyName)) {
+            return object.getScope();
+        } else if ("status".equals(propertyName)) {
+            return object.getStatus();
+        } else if ("statusDate".equals(propertyName)) {
+            return object.getStatusDate();
+        } else if ("revokeDate".equals(propertyName)) {
+            return object.getRevokeDate();
+        } else {
+            return PROPERTY_NOT_FOUND_MARKER;
+        }
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CustomItemAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CustomItemAccessor.java
new file mode 100644
index 0000000..66ceab2
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/CustomItemAccessor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.CustomItem;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class CustomItemAccessor extends HardcodedPropertyAccessor<CustomItem> {
+
+    public CustomItemAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(CustomItem object, String propertyName, String leftoverExpression) {
+        if ("properties".equals(propertyName)) {
+            return registry.getProperty(object.getProperties(), leftoverExpression);
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/EventAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/EventAccessor.java
new file mode 100644
index 0000000..ba10bd9
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/EventAccessor.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class EventAccessor extends HardcodedPropertyAccessor<Event> {
+
+    public EventAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Event object, String propertyName, String leftoverExpression) {
+        if ("properties".equals(propertyName)) {
+            return registry.getProperty(object.getProperties(), leftoverExpression);
+        }
+        if ("eventType".equals(propertyName)) {
+            return object.getEventType();
+        }
+        if ("profile".equals(propertyName)) {
+            return registry.getProperty(object.getProfile(), leftoverExpression);
+        }
+        if ("profileId".equals(propertyName)) {
+            return object.getProfileId();
+        }
+        if ("session".equals(propertyName)) {
+            return registry.getProperty(object.getSession(), leftoverExpression);
+        }
+        if ("sessionId".equals(propertyName)) {
+            return object.getSessionId();
+        }
+        if ("source".equals(propertyName)) {
+            return registry.getProperty(object.getSource(), leftoverExpression);
+        }
+        if ("target".equals(propertyName)) {
+            return registry.getProperty(object.getTarget(), leftoverExpression);
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/GoalAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/GoalAccessor.java
new file mode 100644
index 0000000..03e0b6c
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/GoalAccessor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class GoalAccessor extends HardcodedPropertyAccessor<Goal> {
+
+    public GoalAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Goal object, String propertyName, String leftoverExpression) {
+        if ("campaignId".equals(propertyName)) {
+            return object.getCampaignId();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/HardcodedPropertyAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/HardcodedPropertyAccessor.java
new file mode 100644
index 0000000..7a414fd
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/HardcodedPropertyAccessor.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+/**
+ * Hardcoded property accessors serve two purpose:
+ * - control access to object fields, only expose the ones that are "safe" to use
+ * - prevent using Java Reflection API that is both slower and potentially unsafe as there could be potential to abuse it.
+ */
+public abstract class HardcodedPropertyAccessor<T> {
+
+    public static final String PROPERTY_NOT_FOUND_MARKER = "$$$###PROPERTY_NOT_FOUND###$$$";
+
+    protected HardcodedPropertyAccessorRegistry registry;
+
+    public HardcodedPropertyAccessor(HardcodedPropertyAccessorRegistry registry) {
+        this.registry = registry;
+    }
+
+    public abstract Object getProperty(T object, String propertyName, String leftoverExpression);
+
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ItemAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ItemAccessor.java
new file mode 100644
index 0000000..7dc9913
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ItemAccessor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Item;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class ItemAccessor extends HardcodedPropertyAccessor<Item> {
+
+    public ItemAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Item object, String propertyName, String leftoverExpression) {
+        if ("itemId".equals(propertyName)) {
+            return object.getItemId();
+        }
+        if ("itemType".equals(propertyName)) {
+            return object.getItemType();
+        }
+        if ("scope".equals(propertyName)) {
+            return object.getScope();
+        }
+        if ("version".equals(propertyName)) {
+            return object.getVersion();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MapAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MapAccessor.java
new file mode 100644
index 0000000..fb04257
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MapAccessor.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+import java.util.Map;
+
+public class MapAccessor extends HardcodedPropertyAccessor<Map> {
+    public MapAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Map object, String propertyName, String leftoverExpression) {
+        Object mapValue = object.get(propertyName);
+        if (mapValue == null) {
+            return null;
+        }
+        if (leftoverExpression != null) {
+            return registry.getProperty(mapValue, leftoverExpression);
+        } else {
+            return mapValue;
+        }
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataAccessor.java
new file mode 100644
index 0000000..c4effd1
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataAccessor.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class MetadataAccessor extends HardcodedPropertyAccessor<Metadata> {
+
+    public MetadataAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Metadata object, String propertyName, String leftoverExpression) {
+        if ("id".equals(propertyName)) {
+            return object.getId();
+        } else if ("name".equals(propertyName)) {
+            return object.getName();
+        } else if ("description".equals(propertyName)) {
+            return object.getDescription();
+        } else if ("scope".equals(propertyName)) {
+            return object.getScope();
+        } else if ("tags".equals(propertyName)) {
+            return object.getTags();
+        } else if ("systemTags".equals(propertyName)) {
+            return object.getSystemTags();
+        } else if ("enabled".equals(propertyName)) {
+            return object.isEnabled();
+        } else if ("missingPlugins".equals(propertyName)) {
+            return object.isMissingPlugins();
+        } else if ("hidden".equals(propertyName)) {
+            return object.isHidden();
+        } else if ("readOnly".equals(propertyName)) {
+            return object.isReadOnly();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataItemAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataItemAccessor.java
new file mode 100644
index 0000000..1c787a3
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/MetadataItemAccessor.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.MetadataItem;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class MetadataItemAccessor extends HardcodedPropertyAccessor<MetadataItem> {
+    public MetadataItemAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(MetadataItem object, String propertyName, String leftoverExpression) {
+        if ("metadata".equals(propertyName)) {
+            registry.getProperty(object.getMetadata(), leftoverExpression);
+        }
+        return null;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ProfileAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ProfileAccessor.java
new file mode 100644
index 0000000..d008cfd
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/ProfileAccessor.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class ProfileAccessor extends HardcodedPropertyAccessor<Profile> {
+
+    public ProfileAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Profile object, String propertyName, String leftoverExpression) {
+        if ("segments".equals(propertyName)) {
+            return object.getSegments();
+        }
+        if ("consents".equals(propertyName)) {
+            return registry.getProperty(object.getConsents(), leftoverExpression);
+        }
+        if ("scores".equals(propertyName)) {
+            return registry.getProperty(object.getScores(), leftoverExpression);
+        }
+        if ("properties".equals(propertyName)) {
+            return registry.getProperty(object.getProperties(), leftoverExpression);
+        }
+        if ("systemProperties".equals(propertyName)) {
+            return registry.getProperty(object.getSystemProperties(), leftoverExpression);
+        }
+        if ("mergedWith".equals(propertyName)) {
+            return object.getMergedWith();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/RuleAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/RuleAccessor.java
new file mode 100644
index 0000000..e398798
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/RuleAccessor.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class RuleAccessor extends HardcodedPropertyAccessor<Rule> {
+
+    public RuleAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Rule object, String propertyName, String leftoverExpression) {
+        if ("linkedItems".equals(propertyName)) {
+            return object.getLinkedItems();
+        } else if ("priority".equals(propertyName)) {
+            return object.getPriority();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/SessionAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/SessionAccessor.java
new file mode 100644
index 0000000..a3da510
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/SessionAccessor.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.Session;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class SessionAccessor extends HardcodedPropertyAccessor<Session> {
+
+    public SessionAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(Session object, String propertyName, String leftoverExpression) {
+        if ("duration".equals(propertyName)) {
+            return object.getDuration();
+        }
+        if ("size".equals(propertyName)) {
+            return object.getSize();
+        }
+        if ("lastEventDate".equals(propertyName)) {
+            return object.getLastEventDate();
+        }
+        if ("properties".equals(propertyName)) {
+            return registry.getProperty(object.getProperties(), leftoverExpression);
+        }
+        if ("systemProperties".equals(propertyName)) {
+            return registry.getProperty(object.getSystemProperties(), leftoverExpression);
+        }
+        if ("profile".equals(propertyName)) {
+            return registry.getProperty(object.getProfile(), leftoverExpression);
+        }
+        if ("profileId".equals(propertyName)) {
+            return object.getProfileId();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/TimestampedItemAccessor.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/TimestampedItemAccessor.java
new file mode 100644
index 0000000..7916a28
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/accessors/TimestampedItemAccessor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions.accessors;
+
+import org.apache.unomi.api.TimestampedItem;
+import org.apache.unomi.plugins.baseplugin.conditions.HardcodedPropertyAccessorRegistry;
+
+public class TimestampedItemAccessor extends HardcodedPropertyAccessor<TimestampedItem> {
+
+    public TimestampedItemAccessor(HardcodedPropertyAccessorRegistry registry) {
+        super(registry);
+    }
+
+    @Override
+    public Object getProperty(TimestampedItem object, String propertyName, String leftoverExpression) {
+        if ("timeStamp".equals(propertyName)) {
+            return object.getTimeStamp();
+        }
+        return PROPERTY_NOT_FOUND_MARKER;
+    }
+}
diff --git a/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistryTest.java b/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistryTest.java
new file mode 100644
index 0000000..03bdb30
--- /dev/null
+++ b/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/HardcodedPropertyAccessorRegistryTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.unomi.plugins.baseplugin.conditions;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class HardcodedPropertyAccessorRegistryTest {
+
+    HardcodedPropertyAccessorRegistry registry = new HardcodedPropertyAccessorRegistry();
+
+    @Test
+    public void testGetNextTokens() {
+        assertTokens("test", "test", null);
+        assertTokens("test.test", "test", ".test");
+        assertTokens("test..", "test", "..");
+        assertTokens("test...", "test", "...");
+        assertTokens(".test", "test", null);
+        assertTokens(".test[abc]", "test[abc]", null);
+        assertTokens("[abc]", "[abc]", null);
+        assertTokens("[\"abc\"]", "abc", null);
+        assertTokens(".test[\"abc\"]", "test", "[\"abc\"]");
+        assertTokens("..test", "", ".test");
+        assertTokens(".[test", "[test", null);
+        assertTokens("[\"test\"][\"a\"]", "test", "[\"a\"]");
+        assertTokens("test[\"a\"].c", "test", "[\"a\"].c");
+        assertTokens("[\"b\"]", "b", null);
+        assertTokens("[\"b\"].c", "b", ".c");
+        assertTokens("[\"b.c\"].c", "b.c", ".c");
+        assertTokens("[\"b\"test\"].c", "b\"test", ".c");
+        assertTokens("[\"b\"]test\"].c", "b", "test\"].c");
+        assertTokens("[\"b\\.\\\"]c\"].c", "b\\.\\", "c\"].c");
+        assertTokens("[]", "[]", null);
+    }
+
+    private void assertTokens(String expression, String expectedPropertyName, String expectedLeftoverExpression) {
+        HardcodedPropertyAccessorRegistry.NextTokens nextTokens = registry.getNextTokens(expression);
+        assertEquals("Property name value was wrong", expectedPropertyName, nextTokens.propertyName);
+        assertEquals("Leftover expression value was wrong", expectedLeftoverExpression, nextTokens.leftoverExpression);
+    }
+}
diff --git a/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluatorTest.java b/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluatorTest.java
index af039a5..1ba3c72 100644
--- a/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluatorTest.java
+++ b/plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluatorTest.java
@@ -18,6 +18,7 @@
 
 import ognl.MethodFailedException;
 import org.apache.unomi.api.*;
+import org.apache.unomi.plugins.baseplugin.conditions.accessors.HardcodedPropertyAccessor;
 import org.apache.unomi.scripting.ExpressionFilter;
 import org.apache.unomi.scripting.ExpressionFilterFactory;
 import org.junit.Before;
@@ -33,7 +34,6 @@
 
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNull;
-import static org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator.NOT_OPTIMIZED_MARKER;
 import static org.junit.Assert.assertFalse;
 
 public class PropertyConditionEvaluatorTest {
@@ -94,7 +94,7 @@
         assertEquals("Unexisting property should be null", null, propertyConditionEvaluator.getHardcodedPropertyValue(mockProfile, "properties.email"));
 
         // here let's make sure our reporting of non optimized expressions works.
-        assertEquals("Should have received the non-optimized marker string", NOT_OPTIMIZED_MARKER, propertyConditionEvaluator.getHardcodedPropertyValue(mockSession, "profile.non-existing-field"));
+        assertEquals("Should have received the non-optimized marker string", HardcodedPropertyAccessor.PROPERTY_NOT_FOUND_MARKER, propertyConditionEvaluator.getHardcodedPropertyValue(mockSession, "profile.non-existing-field"));
 
     }