Author: radu Date: Mon Aug 17 12:08:51 2015 New Revision: 1696254 URL: http://svn.apache.org/r1696254 Log: SLING-4953 - Enhance the JavaScript API provided by the org.apache.sling.scripting.javascript bundle
* added a wrapper for Map objects so that Map keys are mapped as object properties for the JavaScript object representing the map * added a 'properties' property for Javascript objects representing resources Added: sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMap.java sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMapTest.java Modified: sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java Modified: sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java?rev=1696254&r1=1696253&r2=1696254&view=diff ============================================================================== --- sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java (original) +++ sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java Mon Aug 17 12:08:51 2015 @@ -42,6 +42,7 @@ import org.apache.sling.scripting.javasc import org.apache.sling.scripting.javascript.helper.SlingWrapFactory; import org.apache.sling.scripting.javascript.wrapper.ScriptableCalendar; import org.apache.sling.scripting.javascript.wrapper.ScriptableItemMap; +import org.apache.sling.scripting.javascript.wrapper.ScriptableMap; import org.apache.sling.scripting.javascript.wrapper.ScriptableNode; import org.apache.sling.scripting.javascript.wrapper.ScriptablePrintWriter; import org.apache.sling.scripting.javascript.wrapper.ScriptableProperty; @@ -99,7 +100,7 @@ public class RhinoJavaScriptEngineFactor ScriptableResource.class, ScriptableNode.class, ScriptableProperty.class, ScriptableItemMap.class, ScriptablePrintWriter.class, ScriptableVersionHistory.class, - ScriptableVersion.class, ScriptableCalendar.class + ScriptableVersion.class, ScriptableCalendar.class, ScriptableMap.class }; /** Added: sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMap.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMap.java?rev=1696254&view=auto ============================================================================== --- sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMap.java (added) +++ sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMap.java Mon Aug 17 12:08:51 2015 @@ -0,0 +1,88 @@ +/* + * 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.sling.scripting.javascript.wrapper; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.scripting.javascript.SlingWrapper; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Undefined; + +/** + * The {@code ScriptableMap} wrapper provides easier access to a map's values by setting the map's keys as properties to the JavaScript + * object representing the {@link Map}. + */ +public class ScriptableMap extends ScriptableBase implements SlingWrapper { + + public static final String CLASSNAME = "Map"; + private static final Class<?> [] WRAPPED_CLASSES = { Map.class }; + + private Map<String, Object> map = new HashMap<String, Object>(); + + public void jsConstructor(Object map) { + this.map = (Map) map; + } + + @Override + public Object get(String name, Scriptable start) { + final Object fromSuperClass = super.get(name, start); + if (fromSuperClass != Scriptable.NOT_FOUND) { + return fromSuperClass; + } + + if (map == null) { + return Undefined.instance; + } + + Object result = map.get(name); + if (result == null) { + result = getNative(name, start); + } + return result; + } + + @Override + public Object getDefaultValue(Class<?> typeHint) { + return map; + } + + @Override + protected Object getWrappedObject() { + return map; + } + + @Override + protected Class<?> getStaticType() { + return Map.class; + } + + @Override + public String getClassName() { + return CLASSNAME; + } + + @Override + public Class<?>[] getWrappedClasses() { + return WRAPPED_CLASSES; + } + + @Override + public Object unwrap() { + return map; + } +} Modified: sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java?rev=1696254&r1=1696253&r2=1696254&view=diff ============================================================================== --- sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java (original) +++ sling/trunk/bundles/scripting/javascript/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java Mon Aug 17 12:08:51 2015 @@ -18,6 +18,7 @@ package org.apache.sling.scripting.javas import org.apache.commons.collections.IteratorUtils; import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; import org.apache.sling.scripting.javascript.SlingWrapper; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; @@ -51,6 +52,7 @@ import org.slf4j.LoggerFactory; * <li>[Resource[]] getChildren()</li> * <li>[Resource[]] listChildren()</li> * <li>[Boolean] isResourceType(String)</li> + * <li>[Object] properties</li> * </ul> */ public class ScriptableResource extends ScriptableObject implements @@ -63,6 +65,7 @@ public class ScriptableResource extends private static final Class<?>[] WRAPPED_CLASSES = { Resource.class }; private Resource resource; + private ValueMap properties; public ScriptableResource() { } @@ -307,6 +310,13 @@ public class ScriptableResource extends return Undefined.instance; } + public Object jsGet_properties() { + if (properties == null) { + properties = resource.adaptTo(ValueMap.class); + } + return properties; + } + // --------- ScriptableObject API @Override Added: sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMapTest.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMapTest.java?rev=1696254&view=auto ============================================================================== --- sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMapTest.java (added) +++ sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/javascript/wrapper/ScriptableMapTest.java Mon Aug 17 12:08:51 2015 @@ -0,0 +1,63 @@ +/* + * 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.sling.scripting.javascript.wrapper; + +import java.util.HashMap; +import javax.script.ScriptException; + +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.scripting.RepositoryScriptingTestBase; +import org.apache.sling.scripting.javascript.internal.ScriptEngineHelper; +import org.junit.After; +import org.junit.Before; + +public class ScriptableMapTest extends RepositoryScriptingTestBase { + + private ValueMap valueMap; + private ScriptEngineHelper.Data data; + + @Before + public void setUp() throws Exception { + super.setUp(); + valueMap = new ValueMapDecorator(new HashMap<String, Object>() {{ + put("a", "a"); + put("b", 1); + }}); + data = new ScriptEngineHelper.Data(); + data.put("properties", valueMap); + } + + @After + public void tearDown() throws Exception { + valueMap.clear(); + data.clear(); + super.tearDown(); + } + + public void testPropertyAccess() throws ScriptException { + assertEquals("a", script.eval("properties['a']", data)); + assertEquals("a", script.eval("properties.a", data)); + assertEquals(1, script.eval("properties['b']", data)); + assertEquals(1, script.eval("properties.b", data)); + assertEquals(null, script.eval("properties['c']", data)); + } + + public void testJavaMethods() throws ScriptException { + assertEquals(2, script.eval("properties.size()", data)); + } +} \ No newline at end of file Modified: sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java?rev=1696254&r1=1696253&r2=1696254&view=diff ============================================================================== --- sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java (original) +++ sling/trunk/bundles/scripting/javascript/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java Mon Aug 17 12:08:51 2015 @@ -19,25 +19,37 @@ package org.apache.sling.scripting.wrapper; import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.Iterator; import java.util.List; - +import java.util.Map; import javax.jcr.Item; import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; import javax.jcr.RepositoryException; +import javax.jcr.Value; +import org.apache.jackrabbit.JcrConstants; import org.apache.sling.api.SlingConstants; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceMetadata; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; import org.apache.sling.commons.testing.sling.MockResourceResolver; import org.apache.sling.jcr.resource.JcrResourceConstants; import org.apache.sling.scripting.RepositoryScriptingTestBase; import org.apache.sling.scripting.javascript.internal.ScriptEngineHelper; import org.mozilla.javascript.Wrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ScriptableResourceTest extends RepositoryScriptingTestBase { @@ -49,6 +61,8 @@ public class ScriptableResourceTest exte private static final String RESOURCE_SUPER_TYPE = "testWrappedResourceSuperType"; + private static final Logger LOGGER = LoggerFactory.getLogger(ScriptableResourceTest.class); + @Override protected void setUp() throws Exception { super.setUp(); @@ -215,6 +229,17 @@ public class ScriptableResourceTest exte assertEquals(true, script.eval("resource.adaptTo(Packages.java.util.Date) == undefined", data)); } + public void testProperties() throws Exception { + final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data(); + Calendar date = new GregorianCalendar(); + node.setProperty(JcrConstants.JCR_LASTMODIFIED, date); + node.setProperty("test", "testProperties"); + node.getSession().save(); + data.put("resource", new TestResource(node)); + assertEquals(date.getTimeInMillis(), script.eval("(resource.properties['jcr:lastModified']).getTimeInMillis()", data)); + assertEquals("testProperties", script.eval("resource.properties.test", data)); + } + private void assertEquals(Node expected, Object actual) { while (actual instanceof Wrapper) { actual = ((Wrapper) actual).unwrap(); @@ -271,6 +296,75 @@ public class ScriptableResourceTest exte public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { if (type == Node.class || type == Item.class) { return (AdapterType) node; + } else if (type == ValueMap.class) { + try { + + PropertyIterator iterator = node.getProperties(); + Map<String, Object> properties = new HashMap<String, Object>(); + while (iterator.hasNext()) { + Property prop = iterator.nextProperty(); + if (prop.isMultiple()) { + Value[] values = prop.getValues(); + Object[] array = new Object[values.length]; + int index = 0; + for (Value value : values) { + switch (value.getType()) { + case PropertyType.BINARY: + array[index++] = value.getBinary(); + break; + case PropertyType.BOOLEAN: + array[index++] = value.getBoolean(); + break; + case PropertyType.DATE: + array[index++] = value.getDate(); + break; + case PropertyType.DECIMAL: + array[index++] = value.getDecimal(); + break; + case PropertyType.DOUBLE: + array[index++] = value.getDouble(); + break; + case PropertyType.LONG: + array[index++] = value.getLong(); + break; + default: + array[index++] = value.getString(); + break; + } + } + properties.put(prop.getName(), array); + + } else { + Value value = prop.getValue(); + switch (value.getType()) { + case PropertyType.BINARY: + properties.put(prop.getName(), value.getBinary()); + break; + case PropertyType.BOOLEAN: + properties.put(prop.getName(), value.getBoolean()); + break; + case PropertyType.DATE: + properties.put(prop.getName(), value.getDate()); + break; + case PropertyType.DECIMAL: + properties.put(prop.getName(), value.getDecimal()); + break; + case PropertyType.DOUBLE: + properties.put(prop.getName(), value.getDouble()); + break; + case PropertyType.LONG: + properties.put(prop.getName(), value.getLong()); + break; + default: + properties.put(prop.getName(), value.getString()); + break; + } + } + } + return ((AdapterType) (new ValueMapDecorator(properties))); + } catch (RepositoryException e) { + LOGGER.error("Unable to adapt resource " + getPath() + " to a ValueMap.", e); + } } return null;