Merge pull request #94 from astappiev/patch-2

fix: Incorrect Last-Modified header for resources
diff --git a/api/pom.xml b/api/pom.xml
index e243ed1..7673f97 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -68,6 +68,32 @@
                 </configuration>
             </plugin>
 
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*/README.md</exclude>
+                        <exclude>**/*/MANIFEST.MF</exclude>
+                        <exclude>.git</exclude>
+                        <exclude>.gitignore</exclude>
+                        <exclude>**/target/**/*</exclude>
+                        <exclude>src/main/resources/META-INF/licenses/*-LICENSE.txt</exclude>
+                        <exclude>src/main/resources/META-INF/licenses/*-LICENSE.TXT</exclude>
+                        <exclude>src/main/resources/META-INF/services/**</exclude>
+                        <exclude>DEPENDENCIES</exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
             <!-- compilation and compression of our javascripts -->
             <plugin>
                 <artifactId>myfaces-javascript-plugin</artifactId>
diff --git a/assembly/pom.xml b/assembly/pom.xml
index caef2bf..fd23968 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -37,8 +37,8 @@
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>dependency-maven-plugin</artifactId>
+                <version>1.0</version>
                 <executions>
-
                     <execution>
                         <id>copy-javadoc</id>
                         <phase>package</phase>
diff --git a/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java b/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
index 3ebd16f..e2797e6 100644
--- a/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
+++ b/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
@@ -68,7 +68,6 @@
 import io.quarkus.arc.deployment.BeanRegistrarBuildItem;
 import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
 import io.quarkus.arc.deployment.ContextRegistrarBuildItem;
-import io.quarkus.arc.processor.ContextRegistrar;
 import io.quarkus.deployment.annotations.BuildProducer;
 import io.quarkus.deployment.annotations.BuildStep;
 import io.quarkus.deployment.annotations.ExecutionTime;
@@ -173,7 +172,7 @@
             FaceletsResourceResolver.class.getName(),
             FlowDefinition.class.getName()
     };
-    
+
     private static final String[] FACTORIES =
     {
         FactoryFinder.APPLICATION_FACTORY,
@@ -232,29 +231,24 @@
     @BuildStep
     void buildCdiScopes(BuildProducer<ContextRegistrarBuildItem> contextRegistrar) throws IOException
     {
-        contextRegistrar.produce(new ContextRegistrarBuildItem(new ContextRegistrar()
-        {
-            @Override
-            public void register(ContextRegistrar.RegistrationContext registrationContext)
-            {
-                registrationContext.configure(ViewScoped.class)
-                        .normal()
-                        .contextClass(QuarkusViewScopeContext.class)
-                        .done();
-                registrationContext.configure(FacesScoped.class)
-                        .normal()
-                        .contextClass(QuarkusFacesScopeContext.class)
-                        .done();
-                registrationContext.configure(ViewTransientScoped.class)
-                        .normal()
-                        .contextClass(QuarkusViewTransientScopeContext.class)
-                        .done();
-                registrationContext.configure(FlowScoped.class)
-                        .normal()
-                        .contextClass(QuarkusFlowScopedContext.class)
-                        .done();
-            }
-        }));
+        contextRegistrar.produce(new ContextRegistrarBuildItem(registrationContext -> {
+            registrationContext.configure(ViewScoped.class)
+                    .normal()
+                    .contextClass(QuarkusViewScopeContext.class)
+                    .done();
+            registrationContext.configure(FacesScoped.class)
+                    .normal()
+                    .contextClass(QuarkusFacesScopeContext.class)
+                    .done();
+            registrationContext.configure(ViewTransientScoped.class)
+                    .normal()
+                    .contextClass(QuarkusViewTransientScopeContext.class)
+                    .done();
+            registrationContext.configure(FlowScoped.class)
+                    .normal()
+                    .contextClass(QuarkusFlowScopedContext.class)
+                    .done();
+        }, ViewScoped.class, FacesScoped.class, ViewTransientScoped.class, FlowScoped.class));
     }
 
     @BuildStep
diff --git a/extensions/quarkus/pom.xml b/extensions/quarkus/pom.xml
index 2d0e6c1..4dfe0f7 100644
--- a/extensions/quarkus/pom.xml
+++ b/extensions/quarkus/pom.xml
@@ -43,7 +43,7 @@
     </modules>
 
     <properties>
-        <quarkus.version>1.2.1.Final</quarkus.version>
+        <quarkus.version>1.3.1.Final</quarkus.version>
     </properties>
 
     <build>
@@ -55,6 +55,29 @@
                     <excludes>**/*$$accessor.java</excludes>
                 </configuration>
             </plugin>
+
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*/README.md</exclude>
+                        <exclude>**/*/MANIFEST.MF</exclude>
+                        <exclude>.git</exclude>
+                        <exclude>.gitignore</exclude>
+                        <exclude>**/target/**/*</exclude>
+                        <exclude>src/main/resources/META-INF/services/**</exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/extensions/quarkus/showcase/pom.xml b/extensions/quarkus/showcase/pom.xml
index 2d98fcc..0651ea9 100644
--- a/extensions/quarkus/showcase/pom.xml
+++ b/extensions/quarkus/showcase/pom.xml
@@ -25,8 +25,8 @@
     <name>Apache MyFaces Core 2.3-next - Extensions - Quarkus - Showcase</name>
 
     <properties>
-        <quarkus.version>1.2.1.Final</quarkus.version>
-        <myfaces.version>2.3-next-SNAPSHOT</myfaces.version>
+        <quarkus.version>1.3.1.Final</quarkus.version>
+        <myfaces.version>2.3-next-M2</myfaces.version>
         <failsafe.version>2.22.0</failsafe.version>
         <surefire.version>2.22.0</surefire.version>
 
@@ -70,6 +70,12 @@
             <version>2.0.1.Final</version>
         </dependency>
 
+        <dependency> <!-- workaround for https://github.com/quarkusio/quarkus/issues/7359 -->
+            <groupId>com.fasterxml.woodstox</groupId>
+            <artifactId>woodstox-core</artifactId>
+            <version>5.0.2</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-junit5</artifactId>
@@ -83,6 +89,12 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+            <version>4.5.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
             <version>3.6.2</version>
diff --git a/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesNativeIT.java b/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesNativeIT.java
index 5674136..4ec83b9 100644
--- a/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesNativeIT.java
+++ b/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesNativeIT.java
@@ -1,3 +1,21 @@
+/*
+ * 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.myfaces.core.extensions.quarkus.showcase;
 
 import io.quarkus.test.junit.NativeImageTest;
diff --git a/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesShowcaseTest.java b/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesShowcaseTest.java
index d4fe663..0f595bc 100644
--- a/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesShowcaseTest.java
+++ b/extensions/quarkus/showcase/src/test/java/org/apache/myfaces/core/extensions/quarkus/showcase/QuarkusMyFacesShowcaseTest.java
@@ -1,3 +1,21 @@
+/*
+ * 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.myfaces.core.extensions.quarkus.showcase;
 
 import com.gargoylesoftware.htmlunit.BrowserVersion;
diff --git a/impl/src/main/java/org/apache/myfaces/component/validate/WholeBeanValidator.java b/impl/src/main/java/org/apache/myfaces/component/validate/WholeBeanValidator.java
index 9b52958..d55f115 100644
--- a/impl/src/main/java/org/apache/myfaces/component/validate/WholeBeanValidator.java
+++ b/impl/src/main/java/org/apache/myfaces/component/validate/WholeBeanValidator.java
@@ -364,7 +364,7 @@
     /**

      * Set the Bean Validation validation groups.

      * @param validationGroups The validation groups String, separated by

-     *                         {@link BeanValidator#VALIDATION_GROUPS_DELIMITER}.

+     *                         {@link javax.faces.validator.BeanValidator#VALIDATION_GROUPS_DELIMITER}.

      */

     public void setValidationGroups(ValidateWholeBeanComponent component, final String validationGroups)

     {

diff --git a/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java b/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
index 5b72d9c..bf663fe 100755
--- a/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
+++ b/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
@@ -18,6 +18,7 @@
  */
 package org.apache.myfaces.config;
 
+import java.lang.invoke.MethodHandles;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.faces.application.ProjectStage;
@@ -788,12 +789,12 @@
     
     /**
      * Defines if the clientbehavior scripts are passed as string or function to the jsf.util.chain.
-     * "As string" is actually the default behavior of both MyFaces (until 3.0) and Mojarra.
+     * "As string" is actually the default behavior of both MyFaces (until 2.3-next) and Mojarra.
      * "As function" is quite usefull for CSP as no string needs to be evaluated as function.
      * 
      * Our jsf.util.chain supports both of course.
      */
-    @JSFWebConfigParam(name="org.apache.myfaces.RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING", since="3.0", defaultValue = "false")
+    @JSFWebConfigParam(name="org.apache.myfaces.RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING", since="2.3-next", defaultValue = "false")
     public static final String RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING = "org.apache.myfaces.RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING";
     public static final boolean RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING_DEFAULT = false;
     
@@ -805,9 +806,18 @@
      * rendering if a view is not transient and server side state saving is in use.     
      */
     @JSFWebConfigParam(since="2.3.6", defaultValue="false", expectedValues="true,false")
-    protected static final String ALWAYS_FORCE_SESSION_CREATION = 
+    public static final String ALWAYS_FORCE_SESSION_CREATION = 
             "org.apache.myfaces.ALWAYS_FORCE_SESSION_CREATION";
-    public final static boolean ALWAYS_FORCE_SESSION_CREATION_DEFAULT = false;
+    protected final static boolean ALWAYS_FORCE_SESSION_CREATION_DEFAULT = false;
+    
+    /**
+     * Defines if MethodHandles and LambdaMetafactory instead of Reflection should be used for getter/setter.
+     */
+    @JSFWebConfigParam(since="2.3-next", defaultValue="true", expectedValues="true,false", tags="performance")
+    public static final String USE_METHOD_HANDLES = 
+            "org.apache.myfaces.USE_METHOD_HANDLES";
+    protected final static boolean USE_METHOD_HANDLES_DEFAULT = true;
+    
     
     // we need it, applicationImpl not ready probably
     private ProjectStage projectStage = ProjectStage.Production;
@@ -888,6 +898,7 @@
     private int websocketMaxConnections = WEBSOCKET_MAX_CONNECTIONS_DEFAULT;
     private boolean renderClientBehaviorScriptsAsString = RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING_DEFAULT;
     private boolean alwaysForceSessionCreation = ALWAYS_FORCE_SESSION_CREATION_DEFAULT;
+    private boolean useMethodHandles = USE_METHOD_HANDLES_DEFAULT;
     
     private static final boolean MYFACES_IMPL_AVAILABLE;
     private static final boolean RI_IMPL_AVAILABLE;
@@ -1303,6 +1314,21 @@
         cfg.alwaysForceSessionCreation = getBoolean(extCtx, ALWAYS_FORCE_SESSION_CREATION,
                 ALWAYS_FORCE_SESSION_CREATION_DEFAULT);
 
+        cfg.useMethodHandles = getBoolean(extCtx, USE_METHOD_HANDLES,
+                USE_METHOD_HANDLES_DEFAULT);
+        if (cfg.useMethodHandles)
+        {
+            try
+            {
+                 MethodHandles.class.getMethod("privateLookupIn", Class.class,
+                        MethodHandles.Lookup.class);
+            }
+            catch (NoSuchMethodException e)
+            {
+                cfg.useMethodHandles = false;
+            }
+        }
+
         return cfg;
     }
 
@@ -1762,5 +1788,10 @@
     {
         return alwaysForceSessionCreation;
     }
+
+    public boolean isUseMethodHandles()
+    {
+        return useMethodHandles;
+    }
 }
 
diff --git a/impl/src/main/java/org/apache/myfaces/el/ELResolverBuilderForFaces.java b/impl/src/main/java/org/apache/myfaces/el/ELResolverBuilderForFaces.java
index f294922..5bb2804 100644
--- a/impl/src/main/java/org/apache/myfaces/el/ELResolverBuilderForFaces.java
+++ b/impl/src/main/java/org/apache/myfaces/el/ELResolverBuilderForFaces.java
@@ -46,6 +46,7 @@
 import org.apache.myfaces.el.resolver.ScopedAttributeResolver;
 import org.apache.myfaces.el.resolver.implicitobject.ImplicitObjectResolver;
 import org.apache.myfaces.config.MyfacesConfig;
+import org.apache.myfaces.el.resolver.MethodHandleBeanELResolver;
 import org.apache.myfaces.util.lang.ClassUtils;
 
 /**
@@ -139,8 +140,15 @@
         list.add(new MapELResolver());
         list.add(new ListELResolver());
         list.add(new ArrayELResolver());
-        list.add(new BeanELResolver());
-        
+        if (myfacesConfig.isUseMethodHandles())
+        {
+            list.add(new MethodHandleBeanELResolver());
+        }
+        else
+        {
+            list.add(new BeanELResolver());
+        }
+
         // give the user a chance to sort the resolvers
         sortELResolvers(list, Scope.Faces);
         
diff --git a/impl/src/main/java/org/apache/myfaces/el/resolver/FacesCompositeELResolver.java b/impl/src/main/java/org/apache/myfaces/el/resolver/FacesCompositeELResolver.java
index 5442eac..2f09a88 100644
--- a/impl/src/main/java/org/apache/myfaces/el/resolver/FacesCompositeELResolver.java
+++ b/impl/src/main/java/org/apache/myfaces/el/resolver/FacesCompositeELResolver.java
@@ -30,9 +30,6 @@
  * This composite el resolver will be used at the top level resolver for faces
  * ({@link javax.faces.application.Application#getELResolver()})
  * and jsp (the one we add with {@link javax.servlet.jsp.JspApplicationContext#addELResolver(javax.el.ELResolver)}.
- * It keeps track of its scope to let the variable resolver {@link org.apache.myfaces.el.VariableResolverImpl}
- * know in which scope it is executed. This is
- * necessarry to call either the faces or the jsp resolver head.
  * </p>
  * <p>
  * This implementation does nothing if there is no actual faces context. This is necessarry since we registered our
diff --git a/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java b/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java
new file mode 100644
index 0000000..6588d0e
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java
@@ -0,0 +1,137 @@
+/*

+ * 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.myfaces.el.resolver;

+

+import java.util.Map;

+import java.util.Objects;

+import java.util.concurrent.ConcurrentHashMap;

+

+import javax.el.BeanELResolver;

+import javax.el.ELContext;

+import javax.el.ELException;

+import javax.el.PropertyNotWritableException;

+import org.apache.myfaces.util.lang.MethodHandleUtils;

+

+public class MethodHandleBeanELResolver extends BeanELResolver

+{

+    private final ConcurrentHashMap<String, Map<String, MethodHandleUtils.LambdaPropertyDescriptor>> cache;

+

+    public MethodHandleBeanELResolver()

+    {

+        cache = new ConcurrentHashMap<>(1000);

+    }

+

+    @Override

+    public Class<?> getType(ELContext context, Object base, Object property)

+    {

+        Objects.requireNonNull(context);

+        if (base == null || property == null)

+        {

+            return null;

+        }

+

+        context.setPropertyResolved(base, property);

+

+        return getPropertyDescriptor(base, property).getPropertyType();

+    }

+

+    @SuppressWarnings("unchecked")

+    @Override

+    public Object getValue(ELContext context, Object base, Object property)

+    {        

+        Objects.requireNonNull(context);

+        if (base == null || property == null)

+        {

+            return null;

+        }

+

+        context.setPropertyResolved(base, property);

+

+        try

+        {

+            return getPropertyDescriptor(base, property).getReadFunction().apply(base);

+        }

+        catch (Exception e)

+        {

+            throw new ELException(e);

+        }

+    }

+

+    @SuppressWarnings("unchecked")

+    @Override

+    public void setValue(ELContext context, Object base, Object property, Object value)

+    {

+        Objects.requireNonNull(context);

+        if (base == null || property == null)

+        {

+            return;

+        }

+

+        context.setPropertyResolved(base, property);

+

+        MethodHandleUtils.LambdaPropertyDescriptor propertyDescriptor = getPropertyDescriptor(base, property);

+        if (propertyDescriptor.getWriteFunction() == null)

+        {

+            throw new PropertyNotWritableException("Property \"" + (String) property

+                    + "\" in \"" + base.getClass().getName() + "\" is not writable!");

+        }

+

+        try

+        {

+            propertyDescriptor.getWriteFunction().accept(base, value);

+        }

+        catch (Exception e)

+        {

+            throw new ELException(e);

+        }

+    }

+

+    @Override

+    public boolean isReadOnly(ELContext context, Object base, Object property)

+    {

+        Objects.requireNonNull(context);

+        if (base == null || property == null)

+        {

+            return false;

+        }

+

+        context.setPropertyResolved(base, property);

+

+        return getPropertyDescriptor(base, property).getWriteFunction() == null;

+    }

+

+    @Override

+    public Class<?> getCommonPropertyType(ELContext context, Object base)

+    {

+        if (base != null)

+        {

+            return Object.class;

+        }

+

+        return null;

+    }

+

+    protected MethodHandleUtils.LambdaPropertyDescriptor getPropertyDescriptor(Object base, Object property)

+    {

+        Map<String, MethodHandleUtils.LambdaPropertyDescriptor> beanCache = cache.computeIfAbsent(

+                base.getClass().getName(), k -> MethodHandleUtils.getLambdaPropertyDescriptors(base.getClass()));

+        return beanCache.get((String) property);

+    }

+

+}

diff --git a/impl/src/main/java/org/apache/myfaces/util/ExternalSpecifications.java b/impl/src/main/java/org/apache/myfaces/util/ExternalSpecifications.java
index c3cf527..cd0214f 100644
--- a/impl/src/main/java/org/apache/myfaces/util/ExternalSpecifications.java
+++ b/impl/src/main/java/org/apache/myfaces/util/ExternalSpecifications.java
@@ -50,7 +50,7 @@
         {
             try
             {
-                available = (ClassUtils.classForName("javax.validation.Validation") != null);
+                available = ClassUtils.classForName("javax.validation.Validation") != null;
             }
             catch(ClassNotFoundException e)
             {
diff --git a/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java b/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java
new file mode 100644
index 0000000..7152823
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java
@@ -0,0 +1,244 @@
+/*

+ * 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.myfaces.util.lang;

+

+import java.beans.Introspector;

+import java.beans.PropertyDescriptor;

+import java.lang.invoke.CallSite;

+import java.lang.invoke.LambdaConversionException;

+import java.lang.invoke.LambdaMetafactory;

+import java.lang.invoke.MethodHandle;

+import java.lang.invoke.MethodHandles;

+import java.lang.invoke.MethodType;

+import java.lang.reflect.Method;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.function.BiConsumer;

+import java.util.function.Function;

+import java.util.function.ObjDoubleConsumer;

+import java.util.function.ObjIntConsumer;

+import java.util.function.ObjLongConsumer;

+import javax.el.ELException;

+

+public class MethodHandleUtils

+{

+    private static Method privateLookupIn;

+

+    static

+    {

+        try

+        {

+            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class,

+                    MethodHandles.Lookup.class);

+        }

+        catch (Exception e)

+        {

+        }

+    }

+

+    public static class LambdaPropertyDescriptor

+    {

+        private PropertyDescriptor wrapped;

+        private Function<Object, Object> readFunction;

+        private BiConsumer<Object, Object> writeFunction;

+

+        public PropertyDescriptor getWrapped()

+        {

+            return wrapped;

+        }

+

+        public Class<?> getPropertyType()

+        {

+            return wrapped.getPropertyType();

+        }

+

+        public Function<Object, Object> getReadFunction()

+        {

+            return readFunction;

+        }

+

+        public BiConsumer<Object, Object> getWriteFunction()

+        {

+            return writeFunction;

+        }

+    }

+

+    public static Map<String, LambdaPropertyDescriptor> getLambdaPropertyDescriptors(Class<?> target)

+    {

+        try

+        {            

+            PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(target).getPropertyDescriptors();

+            if (propertyDescriptors == null || propertyDescriptors.length == 0)

+            {

+                return Collections.emptyMap();

+            }

+

+            HashMap<String, LambdaPropertyDescriptor> properties = new HashMap<>(propertyDescriptors.length);

+

+            MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupIn.invoke(null, target,

+                    MethodHandles.lookup());

+            

+            for (PropertyDescriptor pd : Introspector.getBeanInfo(target).getPropertyDescriptors())

+            {

+                LambdaPropertyDescriptor lpd = new LambdaPropertyDescriptor();

+                lpd.wrapped = pd;

+ 

+                Method getter = pd.getReadMethod();

+                if (getter != null)

+                {

+                    MethodHandle getterHandle = lookup.unreflect(getter);

+                    CallSite getterCallSite = LambdaMetafactory.metafactory(lookup,

+                            "apply",

+                            MethodType.methodType(Function.class),

+                            MethodType.methodType(Object.class, Object.class),

+                            getterHandle,

+                            getterHandle.type());

+                    lpd.readFunction = (Function) getterCallSite.getTarget().invokeExact();

+                }

+

+                Method setter = pd.getWriteMethod();

+                if (setter != null)

+                {

+                    MethodHandle setterHandle = lookup.unreflect(setter);

+                    lpd.writeFunction = createSetter(lookup, lpd, setterHandle);

+                }

+                

+                properties.put(pd.getName(), lpd);

+            }

+            

+            return properties;

+        }

+        catch (Throwable e)

+        {

+            throw new ELException(e);

+        }

+    }

+

+    @SuppressWarnings("unchecked")

+    protected static BiConsumer createSetter(MethodHandles.Lookup lookup, LambdaPropertyDescriptor propertyInfo,

+            MethodHandle setterHandle)

+            throws LambdaConversionException, Throwable

+    {

+        Class<?> propertyType = propertyInfo.getPropertyType();

+        // special handling for primitives required, see https://dzone.com/articles/setters-method-handles-and-java-11

+        if (propertyType.isPrimitive())

+        {

+            if (propertyType == double.class)

+            {

+                ObjDoubleConsumer consumer = (ObjDoubleConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjDoubleConsumer.class, double.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (double) b);

+            }

+            else if (propertyType == int.class)

+            {

+                ObjIntConsumer consumer = (ObjIntConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjIntConsumer.class, int.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (int) b);

+            }

+            else if (propertyType == long.class)

+            {

+                ObjLongConsumer consumer = (ObjLongConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjLongConsumer.class, long.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (long) b);

+            }

+            else if (propertyType == float.class)

+            {

+                ObjFloatConsumer consumer = (ObjFloatConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjFloatConsumer.class, float.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (float) b);

+            }

+            else if (propertyType == byte.class)

+            {

+                ObjByteConsumer consumer = (ObjByteConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjByteConsumer.class, byte.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (byte) b);

+            }

+            else if (propertyType == char.class)

+            {

+                ObjCharConsumer consumer = (ObjCharConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjCharConsumer.class, char.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (char) b);

+            }

+            else if (propertyType == short.class)

+            {

+                ObjShortConsumer consumer = (ObjShortConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjShortConsumer.class, short.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (short) b);

+            }

+            else if (propertyType == boolean.class)

+            {

+                ObjBooleanConsumer consumer = (ObjBooleanConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjBooleanConsumer.class, boolean.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (boolean) b);

+            }

+            else

+            {

+                throw new RuntimeException("Type is not supported yet: " + propertyType.getName());

+            }

+        }

+        else

+        {

+            return (BiConsumer) createSetterCallSite(lookup, setterHandle, BiConsumer.class, Object.class).getTarget()

+                    .invokeExact();

+        }

+    }

+

+    protected static CallSite createSetterCallSite(MethodHandles.Lookup lookup, MethodHandle setter,

+            Class<?> interfaceType, Class<?> valueType)

+            throws LambdaConversionException

+    {

+        return LambdaMetafactory.metafactory(lookup,

+                "accept",

+                MethodType.methodType(interfaceType),

+                MethodType.methodType(void.class, Object.class, valueType),

+                setter,

+                setter.type());

+    }

+

+    @FunctionalInterface

+    public interface ObjFloatConsumer<T extends Object>

+    {

+        public void accept(T t, float i);

+    }

+

+    @FunctionalInterface

+    public interface ObjByteConsumer<T extends Object>

+    {

+        public void accept(T t, byte i);

+    }

+

+    @FunctionalInterface

+    public interface ObjCharConsumer<T extends Object>

+    {

+        public void accept(T t, char i);

+    }

+

+    @FunctionalInterface

+    public interface ObjShortConsumer<T extends Object>

+    {

+        public void accept(T t, short i);

+    }

+

+    @FunctionalInterface

+    public interface ObjBooleanConsumer<T extends Object>

+    {

+        public void accept(T t, boolean i);

+    }

+}

diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/BeanPropertyTagRule.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/BeanPropertyTagRule.java
index f33708e..5493e7a 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/BeanPropertyTagRule.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/BeanPropertyTagRule.java
@@ -20,6 +20,7 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.function.BiConsumer;
 
 import javax.faces.view.facelets.FaceletContext;
 import javax.faces.view.facelets.MetaRule;
@@ -40,18 +41,38 @@
     @Override
     public Metadata applyRule(String name, TagAttribute attribute, MetadataTarget meta)
     {
-        Method m = meta.getWriteMethod(name);
-
-        // if the property is writable
-        if (m != null)
+        if (meta instanceof MethodHandleMetadataTargetImpl)
         {
-            if (attribute.isLiteral())
+            BiConsumer<Object, Object> f = ((MethodHandleMetadataTargetImpl) meta).getWriteFunction(name);
+
+            // if the property is writable
+            if (f != null)
             {
-                return new LiteralPropertyMetadata(m, attribute);
+                if (attribute.isLiteral())
+                {
+                    return new LiteralPropertyMetadata(meta.getPropertyType(name), f, attribute);
+                }
+                else
+                {
+                    return new DynamicPropertyMetadata(meta.getPropertyType(name), f, attribute);
+                }
             }
-            else
+        }
+        else
+        {
+            Method m = meta.getWriteMethod(name);
+
+            // if the property is writable
+            if (m != null)
             {
-                return new DynamicPropertyMetadata(m, attribute);
+                if (attribute.isLiteral())
+                {
+                    return new LiteralPropertyMetadata(meta.getPropertyType(name), m, attribute);
+                }
+                else
+                {
+                    return new DynamicPropertyMetadata(meta.getPropertyType(name), m, attribute);
+                }
             }
         }
 
@@ -60,27 +81,53 @@
     
     final static class LiteralPropertyMetadata extends Metadata
     {
+        private final Class<?> propertyType;
         private final Method method;
+        private final BiConsumer<Object, Object> function;
         private final TagAttribute attribute;
-        private Object[] value;
+        private Object value;
+        private Object[] valueArgs;
 
-        public LiteralPropertyMetadata(Method method, TagAttribute attribute)
+        public LiteralPropertyMetadata(Class<?> propertyType, Method method, TagAttribute attribute)
         {
+            this.propertyType = propertyType;
             this.method = method;
+            this.function = null;
+            this.attribute = attribute;
+        }
+        
+        public LiteralPropertyMetadata(Class<?> propertyType, BiConsumer<Object, Object> function,
+                TagAttribute attribute)
+        {
+            this.propertyType = propertyType;
+            this.method = null;
+            this.function = function;
             this.attribute = attribute;
         }
 
         @Override
         public void applyMetadata(FaceletContext ctx, Object instance)
         {
-            if (value == null)
-            {
-                String str = this.attribute.getValue();
-                value = new Object[] { ctx.getExpressionFactory().coerceToType(str, method.getParameterTypes()[0]) };
-            }
             try
             {
-                method.invoke(instance, this.value);
+                if (method != null)
+                {
+                    if (valueArgs == null)
+                    {
+                        String str = this.attribute.getValue();
+                        valueArgs = new Object[] { ctx.getExpressionFactory().coerceToType(str, propertyType) };
+                    }
+                    method.invoke(instance, valueArgs);
+                }
+                else if (function != null)
+                {
+                    if (value == null)
+                    {
+                        String str = this.attribute.getValue();
+                        value = ctx.getExpressionFactory().coerceToType(str, propertyType);
+                    }
+                    function.accept(instance, value);
+                }
             }
             catch (InvocationTargetException e)
             {
@@ -96,23 +143,41 @@
 
     final static class DynamicPropertyMetadata extends Metadata
     {
+        private final Class<?> propertyType;
         private final Method method;
+        private final BiConsumer<Object, Object> function;
         private final TagAttribute attribute;
-        private final Class<?> type;
 
-        public DynamicPropertyMetadata(Method method, TagAttribute attribute)
+        public DynamicPropertyMetadata(Class<?> propertyType, Method method, TagAttribute attribute)
         {
+            this.propertyType = propertyType;
             this.method = method;
-            this.type = method.getParameterTypes()[0];
+            this.function = null;
             this.attribute = attribute;
         }
 
+        public DynamicPropertyMetadata(Class<?> propertyType, BiConsumer<Object, Object> function,
+                TagAttribute attribute)
+        {
+            this.propertyType = propertyType;
+            this.method = null;
+            this.function = function;
+            this.attribute = attribute;
+        }
+        
         @Override
         public void applyMetadata(FaceletContext ctx, Object instance)
         {
             try
             {
-                method.invoke(instance, new Object[] { attribute.getObject(ctx, type) });
+                if (method != null)
+                {
+                    method.invoke(instance, new Object[] { attribute.getObject(ctx, propertyType) });
+                }
+                else if (function != null)
+                {
+                    function.accept(instance, attribute.getObject(ctx, propertyType));
+                }
             }
             catch (InvocationTargetException e)
             {
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MetaRulesetImpl.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MetaRulesetImpl.java
index 5405b39..e5f7988 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MetaRulesetImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MetaRulesetImpl.java
@@ -34,6 +34,7 @@
 import java.util.WeakHashMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.apache.myfaces.config.MyfacesConfig;
 import org.apache.myfaces.util.lang.Assert;
 import org.apache.myfaces.view.facelets.PassthroughRule;
 import org.apache.myfaces.view.facelets.tag.jsf.PassThroughLibrary;
@@ -324,7 +325,14 @@
         {
             try
             {
-                meta = new MetadataTargetImpl(_type);
+                if (MyfacesConfig.getCurrentInstance().isUseMethodHandles())
+                {
+                    meta = new MethodHandleMetadataTargetImpl(_type);
+                }
+                else
+                {
+                    meta = new MetadataTargetImpl(_type);
+                }
             }
             catch (IntrospectionException e)
             {
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodHandleMetadataTargetImpl.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodHandleMetadataTargetImpl.java
new file mode 100644
index 0000000..f810fc9
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodHandleMetadataTargetImpl.java
@@ -0,0 +1,115 @@
+/*

+ * 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.myfaces.view.facelets.tag;

+

+import java.beans.IntrospectionException;

+import java.beans.PropertyDescriptor;

+import java.lang.reflect.Method;

+import java.util.Map;

+import java.util.function.BiConsumer;

+import java.util.function.Function;

+import javax.faces.view.facelets.MetadataTarget;

+import org.apache.myfaces.util.lang.MethodHandleUtils;

+

+public class MethodHandleMetadataTargetImpl extends MetadataTarget

+{

+    private final Map<String, MethodHandleUtils.LambdaPropertyDescriptor> propertyDescriptors;

+    private final Class<?> type;

+

+    public MethodHandleMetadataTargetImpl(Class<?> type) throws IntrospectionException

+    {

+        this.type = type;

+        this.propertyDescriptors = MethodHandleUtils.getLambdaPropertyDescriptors(type);

+    }

+

+    @Override

+    public PropertyDescriptor getProperty(String name)

+    {

+        MethodHandleUtils.LambdaPropertyDescriptor lpd = getLambdaProperty(name);

+        if (lpd == null)

+        {

+            return null;

+        }

+

+        return lpd.getWrapped();

+    }

+

+    @Override

+    public Class<?> getPropertyType(String name)

+    {

+        MethodHandleUtils.LambdaPropertyDescriptor lpd = getLambdaProperty(name);

+        if (lpd == null)

+        {

+            return null;

+        }

+

+        return lpd.getPropertyType();

+    }

+

+    @Override

+    public Method getReadMethod(String name)

+    {

+        throw new UnsupportedOperationException("Not supported!");

+    }

+

+    @Override

+    public Class<?> getTargetClass()

+    {

+        return type;

+    }

+

+    @Override

+    public Method getWriteMethod(String name)

+    {

+        throw new UnsupportedOperationException("Not supported!");

+    }

+

+    @Override

+    public boolean isTargetInstanceOf(Class type)

+    {

+        return type.isAssignableFrom(type);

+    }

+ 

+    public MethodHandleUtils.LambdaPropertyDescriptor getLambdaProperty(String name)

+    {

+        return propertyDescriptors.get(name);

+    }

+

+    public Function<Object, Object> getReadFunction(String name)

+    {

+        MethodHandleUtils.LambdaPropertyDescriptor lpd = getLambdaProperty(name);

+        if (lpd == null)

+        {

+            return null;

+        }

+        

+        return lpd.getReadFunction();

+    }

+

+    public BiConsumer<Object, Object> getWriteFunction(String name)

+    {

+        MethodHandleUtils.LambdaPropertyDescriptor lpd = getLambdaProperty(name);

+        if (lpd == null)

+        {

+            return null;

+        }

+        

+        return lpd.getWriteFunction();

+    }

+}

diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodRule.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodRule.java
index b76abd5..4b9db45 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodRule.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/MethodRule.java
@@ -20,6 +20,7 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.function.BiConsumer;
 
 import javax.el.MethodExpression;
 import javax.faces.view.facelets.FaceletContext;
@@ -71,36 +72,55 @@
 
     private class MethodExpressionMetadata extends Metadata
     {
-        private final Method _method;
-        private final TagAttribute _attribute;
-        private Class<?>[] _paramList;
-        private Class<?> _returnType;
+        private final Method method;
+        private final BiConsumer<Object, Object> function;
+        private final TagAttribute attribute;
+        private Class<?>[] paramList;
+        private Class<?> returnType;
 
         public MethodExpressionMetadata(Method method, TagAttribute attribute, Class<?> returnType, 
-                                        Class<?>[] paramList)
+                Class<?>[] paramList)
         {
-            _method = method;
-            _attribute = attribute;
-            _paramList = paramList;
-            _returnType = returnType;
+            this.method = method;
+            this.function = null;
+            this.attribute = attribute;
+            this.paramList = paramList;
+            this.returnType = returnType;
+        }
+        
+        public MethodExpressionMetadata(BiConsumer<Object, Object> function, TagAttribute attribute,
+                Class<?> returnType, Class<?>[] paramList)
+        {
+            this.method = null;
+            this.function = function;
+            this.attribute = attribute;
+            this.paramList = paramList;
+            this.returnType = returnType;
         }
 
         @Override
         public void applyMetadata(FaceletContext ctx, Object instance)
         {
-            MethodExpression expr = _attribute.getMethodExpression(ctx, _returnType, _paramList);
+            MethodExpression expr = attribute.getMethodExpression(ctx, returnType, paramList);
 
             try
             {
-                _method.invoke(instance, new Object[] { expr });
+                if (method != null)
+                {
+                    method.invoke(instance, new Object[] { expr });
+                }
+                else if (function != null)
+                {
+                    function.accept(instance, expr);
+                }
             }
             catch (InvocationTargetException e)
             {
-                throw new TagAttributeException(_attribute, e.getCause());
+                throw new TagAttributeException(attribute, e.getCause());
             }
             catch (Exception e)
             {
-                throw new TagAttributeException(_attribute, e);
+                throw new TagAttributeException(attribute, e);
             }
         }
     }
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentRule.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentRule.java
index df641a8..b4ece12 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentRule.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentRule.java
@@ -27,6 +27,7 @@
 import javax.faces.view.facelets.Metadata;
 import javax.faces.view.facelets.MetadataTarget;
 import javax.faces.view.facelets.TagAttribute;
+import org.apache.myfaces.view.facelets.tag.MethodHandleMetadataTargetImpl;
 
 /**
  * Copy of org.apache.myfaces.view.facelets.tag.jsf.ComponentRule
@@ -132,6 +133,27 @@
                 
                 return new ValueExpressionMetadata(name, type, attribute);
             }
+            else if (meta instanceof MethodHandleMetadataTargetImpl)
+            {
+                if (((MethodHandleMetadataTargetImpl) meta).getWriteFunction(name) == null)
+                {
+                    Class<?> type = meta.getPropertyType(name);
+                    if (type == null)
+                    {
+                        if (((MethodHandleMetadataTargetImpl) meta).getLambdaProperty(name) == null)
+                        {
+                            // this was an attribute literal, but not property
+                            warnAttr(attribute, meta.getTargetClass(), name);
+                        }
+
+                        return new LiteralAttributeMetadata(name, attribute.getValue());
+                    }
+                    else
+                    {
+                        return new TypedLiteralAttributeMetadata(name, type, attribute);
+                    }
+                }
+            }
             else if (meta.getWriteMethod(name) == null)
             {
                 Class<?> type = meta.getPropertyType(name);
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetaRulesetImpl.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetaRulesetImpl.java
index 4195297..5d4d3ed 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetaRulesetImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetaRulesetImpl.java
@@ -38,7 +38,9 @@
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.apache.myfaces.config.MyfacesConfig;
 import org.apache.myfaces.util.lang.Assert;
+import org.apache.myfaces.view.facelets.tag.MethodHandleMetadataTargetImpl;
 import org.apache.myfaces.view.facelets.tag.NullMetadata;
 
 public class CompositeMetaRulesetImpl extends MetaRuleset
@@ -213,7 +215,14 @@
         {
             try
             {
-                meta = new MetadataTargetImpl(_type);
+                if (MyfacesConfig.getCurrentInstance().isUseMethodHandles())
+                {
+                    meta = new MethodHandleMetadataTargetImpl(_type);
+                }
+                else
+                {
+                    meta = new MetadataTargetImpl(_type);
+                }
             }
             catch (IntrospectionException e)
             {
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetadataTargetImpl.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetadataTargetImpl.java
index 6b8c548..c775f4c 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetadataTargetImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeMetadataTargetImpl.java
@@ -40,32 +40,29 @@
  */
 final class CompositeMetadataTargetImpl extends MetadataTarget
 {
-    private final Map<String, PropertyDescriptor> _pd;
-    
-    private final MetadataTarget _delegate;
-    
-    private final BeanInfo _beanInfo;
+    private final Map<String, PropertyDescriptor> descriptors;
+    private final MetadataTarget delegate;
+    private final BeanInfo beanInfo;
 
     public CompositeMetadataTargetImpl(MetadataTarget delegate, BeanInfo beanInfo) throws IntrospectionException
     {
-        _delegate = delegate;
-        _beanInfo = beanInfo;
+        this.delegate = delegate;
+        this.beanInfo = beanInfo;
+        this.descriptors = new HashMap<>();
         
-        _pd = new HashMap<String, PropertyDescriptor>();
-        
-        for (PropertyDescriptor descriptor : _beanInfo.getPropertyDescriptors())
+        for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors())
         {
-            _pd.put(descriptor.getName(), descriptor);
+            this.descriptors.put(descriptor.getName(), descriptor);
         }
     }
 
     @Override
     public PropertyDescriptor getProperty(String name)
     {
-        PropertyDescriptor pd = _delegate.getProperty(name); 
+        PropertyDescriptor pd = delegate.getProperty(name); 
         if (pd == null)
         {
-            pd = _pd.get(name);
+            pd = descriptors.get(name);
         }
         return pd;
     }
@@ -79,7 +76,7 @@
             Object type = pd.getValue("type");
             if (type != null)
             {
-                type = ((ValueExpression)type).getValue(FacesContext.getCurrentInstance().getELContext());
+                type = ((ValueExpression) type).getValue(FacesContext.getCurrentInstance().getELContext());
                 if (type instanceof String)
                 {
                     try
@@ -114,7 +111,7 @@
     @Override
     public Class<?> getTargetClass()
     {
-        return _delegate.getTargetClass();
+        return delegate.getTargetClass();
     }
 
     @Override
@@ -132,6 +129,6 @@
     @Override
     public boolean isTargetInstanceOf(Class type)
     {
-        return _delegate.isTargetInstanceOf(type);
+        return delegate.isTargetInstanceOf(type);
     }
 }
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentRule.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentRule.java
index d39937e..461c4c5 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentRule.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentRule.java
@@ -27,6 +27,7 @@
 import javax.faces.view.facelets.Metadata;
 import javax.faces.view.facelets.MetadataTarget;
 import javax.faces.view.facelets.TagAttribute;
+import org.apache.myfaces.view.facelets.tag.MethodHandleMetadataTargetImpl;
 
 /**
  * 
@@ -99,9 +100,18 @@
                 
                 return new ValueExpressionMetadata(name, type, attribute);
             }
+            else if (meta instanceof MethodHandleMetadataTargetImpl)
+            {
+                if (((MethodHandleMetadataTargetImpl) meta).getWriteFunction(name) == null)
+                {
+                    // this was an attribute literal, but not property
+                    warnAttr(attribute, meta.getTargetClass(), name);
+
+                    return new LiteralAttributeMetadata(name, attribute.getValue());
+                }
+            }
             else if (meta.getWriteMethod(name) == null)
             {
-
                 // this was an attribute literal, but not property
                 warnAttr(attribute, meta.getTargetClass(), name);