Merge branch 'issue/SLING-5720'
diff --git a/bnd.bnd b/bnd.bnd
new file mode 100644
index 0000000..86d4ddc
--- /dev/null
+++ b/bnd.bnd
@@ -0,0 +1,2 @@
+Import-Package: javax.jcr;resolution:=optional;version="[2.0,3)", \\
+                *
diff --git a/pom.xml b/pom.xml
index e05a2d7..0755db9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,15 @@
             <version>16.0.2</version>
             <scope>compile</scope>
         </dependency>
+
+        <!-- This will be an optional import, needed for applying conversion rules in the ObjectConverter -->
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+
         <!-- Testing -->
         <dependency>
             <groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/JcrRules.java b/src/main/java/org/apache/sling/api/wrappers/impl/JcrRules.java
new file mode 100644
index 0000000..bbb54ff
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/wrappers/impl/JcrRules.java
@@ -0,0 +1,95 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.api.wrappers.impl;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Binary;
+import javax.jcr.Value;
+
+import org.osgi.util.converter.ConverterBuilder;
+import org.osgi.util.converter.ConverterFunction;
+import org.osgi.util.converter.TypeRule;
+
+public final class JcrRules {
+
+    private JcrRules(){}
+
+    static void addJcrRules(ConverterBuilder converterBuilder) {
+        converterBuilder.rule(new TypeRule<Value, String>(Value.class, String.class, value -> {
+            try {
+                return value.getString();
+            } catch (Exception e) {
+                return (String) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, InputStream>(Value.class, InputStream.class, value -> {
+            try {
+                return value.getStream();
+            } catch (Exception e) {
+                return (InputStream) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, Binary>(Value.class, Binary.class, value -> {
+            try {
+                return value.getBinary();
+            } catch (Exception e) {
+                return (Binary) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, Long>(Value.class, Long.class, value -> {
+            try {
+                return value.getLong();
+            } catch (Exception e) {
+                return (Long) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, Double>(Value.class, Double.class, value -> {
+            try {
+                return value.getDouble();
+            } catch (Exception e) {
+                return (Double) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, BigDecimal>(Value.class, BigDecimal.class, value -> {
+            try {
+                return value.getDecimal();
+            } catch (Exception e) {
+                return (BigDecimal) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, Calendar>(Value.class, Calendar.class, value -> {
+            try {
+                return value.getDate();
+            } catch (Exception e) {
+                return (Calendar) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+        converterBuilder.rule(new TypeRule<Value, Boolean>(Value.class, Boolean.class, value -> {
+            try {
+                return value.getBoolean();
+            } catch (Exception e) {
+                return (Boolean) ConverterFunction.CANNOT_HANDLE;
+            }
+        }));
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java b/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
index 9df344c..edd8982 100644
--- a/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
+++ b/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
@@ -27,6 +27,7 @@
 
 import org.osgi.util.converter.ConversionException;
 import org.osgi.util.converter.Converter;
+import org.osgi.util.converter.ConverterBuilder;
 import org.osgi.util.converter.Converters;
 import org.osgi.util.converter.TypeRule;
 
@@ -39,7 +40,7 @@
         private static final Converter CONVERTER;
 
         static {
-            CONVERTER = Converters.newConverterBuilder()
+            ConverterBuilder converterBuilder = Converters.newConverterBuilder()
                     .rule(new TypeRule<String, Calendar>(String.class, Calendar.class,
                             ObjectConverter::toCalendar))
                     .rule(new TypeRule<Date, Calendar>(Date.class, Calendar.class,
@@ -52,8 +53,13 @@
                             ObjectConverter::toDate))
                     .rule(new TypeRule<>(Calendar.class, ZonedDateTime.class, ObjectConverter::toZonedDateTime))
                     .rule(new TypeRule<ZonedDateTime, Calendar>(ZonedDateTime.class, Calendar.class, ObjectConverter::toCalendar))
-                    .rule(new TypeRule<ZonedDateTime, String>(ZonedDateTime.class, String.class, ObjectConverter::toString))
-                    .build();
+                    .rule(new TypeRule<ZonedDateTime, String>(ZonedDateTime.class, String.class, ObjectConverter::toString));
+            try {
+                JcrRules.addJcrRules(converterBuilder);
+            } catch (NoClassDefFoundError e) {
+                // do nothing if the JCR API is not present
+            }
+            CONVERTER = converterBuilder.build();
         }
     }
 
diff --git a/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java b/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
index 7eab819..81f42a0 100644
--- a/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
+++ b/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
@@ -18,17 +18,25 @@
  */
 package org.apache.sling.api.wrappers.impl;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
+import javax.jcr.Binary;
+import javax.jcr.Value;
+
 import org.junit.Test;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 public class ObjectConverterTest {
     
     private static final String STRING_1 = "item1";
@@ -240,5 +248,65 @@
         assertArrayEquals(new byte[0], ObjectConverter.convert(new byte[0], byte[].class));
         assertNull(ObjectConverter.convert(null, byte[].class));
     }
-    
+
+    @Test
+    public void testJcrStringValue() throws Exception {
+        Value stringValue = mock(Value.class);
+        when(stringValue.getString()).thenReturn("42");
+        when(stringValue.getLong()).thenReturn(42L);
+        when(stringValue.getDouble()).thenReturn(42.0);
+        BigDecimal bigDecimal = new BigDecimal(42);
+        when(stringValue.getDecimal()).thenReturn(bigDecimal);
+        assertEquals("42", ObjectConverter.convert(stringValue, String.class));
+        assertEquals(42L, (long) ObjectConverter.convert(stringValue, Long.class));
+        assertEquals(42.0, ObjectConverter.convert(stringValue, Double.class), 0);
+        assertEquals(bigDecimal, ObjectConverter.convert(stringValue, BigDecimal.class));
+    }
+
+    @Test
+    public void testJcrStreamValue() throws Exception {
+        Value streamValue = mock(Value.class);
+        InputStream stream = mock(InputStream.class);
+        when(streamValue.getStream()).thenReturn(stream);
+        assertEquals(stream, ObjectConverter.convert(streamValue, InputStream.class));
+    }
+
+    @Test
+    public void testJcrBinaryValue() throws Exception {
+        Value binaryValue = mock(Value.class);
+        Binary binary = mock(Binary.class);
+        when(binaryValue.getBinary()).thenReturn(binary);
+        assertEquals(binary, ObjectConverter.convert(binaryValue, Binary.class));
+    }
+
+    @Test
+    public void testJcrNumericValue() throws Exception {
+        Value numericValue = mock(Value.class);
+        when(numericValue.getLong()).thenReturn(42L);
+        when(numericValue.getString()).thenReturn("42");
+        when(numericValue.getDouble()).thenReturn(42.0);
+        BigDecimal bigDecimal = new BigDecimal(42);
+        when(numericValue.getDecimal()).thenReturn(bigDecimal);
+        assertEquals(42L, (long) ObjectConverter.convert(numericValue, Long.class));
+        assertEquals("42", ObjectConverter.convert(numericValue, String.class));
+        assertEquals(42.0, ObjectConverter.convert(numericValue, Double.class), 0);
+        assertEquals(bigDecimal, ObjectConverter.convert(numericValue, BigDecimal.class));
+    }
+
+    @Test
+    public void testJcrDateValue() throws Exception {
+        Value dateValue = mock(Value.class);
+        Calendar calendar = Calendar.getInstance();
+        when(dateValue.getDate()).thenReturn(calendar);
+        assertEquals(calendar, ObjectConverter.convert(dateValue, Calendar.class));
+    }
+
+    @Test
+    public void testBooleanValue() throws Exception {
+        Value value = mock(Value.class);
+        when(value.getBoolean()).thenReturn(true);
+        when(value.getString()).thenReturn("true");
+        assertTrue(ObjectConverter.convert(value, Boolean.class));
+        assertEquals("true", ObjectConverter.convert(value, String.class));
+    }
 }