001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.main.CmsLog; 031 032import java.beans.FeatureDescriptor; 033import java.util.Iterator; 034 035import org.apache.commons.logging.Log; 036 037import jakarta.el.ELContext; 038import jakarta.el.ELResolver; 039import jakarta.el.ImportHandler; 040import jakarta.servlet.jsp.JspContext; 041 042/** 043 * This EL resolver is a workaround for performance problems in Jetty when using EE 10. 044 * <p> 045 * When using EE 10, a new ImportELResolver is present which tries to interpret EL variables as classes. This fires for all otherwise undefined variables, 046 * of which there are many in the Mercury template at runtime, causing a lot of class loading. This leads to a huge slowdown in Jetty, but not in Tomcat. 047 * Jetty and Tomcat (currently) use the same JSP engine, which marks standalone top-level identifiers, but only Tomcat's JSP API implementation makes 048 * use of that information to optimize the lookup. 049 * 050 * <p>This resolver tries to optimize the case where it finds such a marker, effectively disabling the class lookup for standalone identifiers, 051 * while preserving it for more complex expressions like ${Foo.bar}. 052 */ 053public class CmsJspElResolver extends ELResolver { 054 055 /** The log object for this class. */ 056 private static final Log LOG = CmsLog.getLog(CmsJspElResolver.class); 057 058 /** 059 * The EL context key under which the EL parser flags a stand-alone identifier, 060 * or <code>null</code> if the EL implementation in use does not provide the flag. 061 */ 062 private static final Class<?> AST_IDENTIFIER_KEY; 063 064 /** The class for the NotFoundELResolver - we can't just use a class constant because it's not available in EE9 and older. */ 065 private static final Class<?> NOT_FOUND_EL_RESOLVER; 066 067 static { 068 Class<?> key = null; 069 try { 070 key = Class.forName("org.apache.el.parser.AstIdentifier"); 071 } catch (ClassNotFoundException e) { 072 LOG.warn( 073 "EL implementation does not flag stand-alone identifiers, JSP EL performance short-cut is disabled.", 074 e); 075 } 076 AST_IDENTIFIER_KEY = key; 077 Class<?> resolverClass = null; 078 try { 079 resolverClass = Class.forName("jakarta.servlet.jsp.el.NotFoundELResolver"); 080 } catch (ClassNotFoundException e) { 081 LOG.info("Did not find NotFoundELResolver - probably Jakarta EE 9 or older", e); 082 } 083 NOT_FOUND_EL_RESOLVER = resolverClass; 084 } 085 086 /** 087 * @see jakarta.el.ELResolver#getCommonPropertyType(jakarta.el.ELContext, java.lang.Object) 088 */ 089 @Override 090 public Class<?> getCommonPropertyType(ELContext context, Object base) { 091 092 return base == null ? String.class : null; 093 } 094 095 /** 096 * Dummy implementation that does nothing. 097 * 098 * (this is only needed for Java EE 8 compatibility - in Java EE 8, it actually overrides a method from the parent class.) 099 * 100 * @param context the EL context 101 * @param base the base object 102 * @return null 103 */ 104 public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { 105 106 return null; 107 } 108 109 /** 110 * @see jakarta.el.ELResolver#getType(jakarta.el.ELContext, java.lang.Object, java.lang.Object) 111 */ 112 @Override 113 public Class<?> getType(ELContext context, Object base, Object property) { 114 115 return null; 116 } 117 118 /** 119 * Resolves a stand-alone identifier from the scoped JSP attributes and ends the resolution, 120 * so that the identifier is not looked up as a class name.<p> 121 * 122 * All other cases are left to the standard resolver chain.<p> 123 * 124 * @see jakarta.el.ELResolver#getValue(jakarta.el.ELContext, java.lang.Object, java.lang.Object) 125 */ 126 @Override 127 public Object getValue(ELContext context, Object base, Object property) { 128 129 if ((AST_IDENTIFIER_KEY == null) || (NOT_FOUND_EL_RESOLVER == null) || (base != null) || (property == null)) { 130 return null; 131 } 132 if (!Boolean.TRUE.equals(context.getContext(AST_IDENTIFIER_KEY))) { 133 // not a stand-alone identifier, the identifier may well be an imported class name 134 return null; 135 } 136 137 if (Boolean.TRUE.equals(context.getContext(NOT_FOUND_EL_RESOLVER))) { 138 // the page uses errorOnELNotFound, the chain must reach the NotFoundELResolver 139 return null; 140 } 141 JspContext jspContext = (JspContext)context.getContext(JspContext.class); 142 if (jspContext == null) { 143 return null; 144 } 145 String name = property.toString(); 146 Object result = jspContext.findAttribute(name); 147 if (result == null) { 148 // a stand-alone identifier may still be an imported static member, this is a plain map lookup 149 result = getImportedStaticValue(context, name); 150 } 151 context.setPropertyResolved(true); 152 return result; 153 } 154 155 /** 156 * @see jakarta.el.ELResolver#isReadOnly(jakarta.el.ELContext, java.lang.Object, java.lang.Object) 157 */ 158 @Override 159 public boolean isReadOnly(ELContext context, Object base, Object property) { 160 161 return false; 162 } 163 164 /** 165 * @see jakarta.el.ELResolver#setValue(jakarta.el.ELContext, java.lang.Object, java.lang.Object, java.lang.Object) 166 */ 167 @Override 168 public void setValue(ELContext context, Object base, Object property, Object value) { 169 170 // not resolved here, the standard scoped attribute resolver handles writing 171 } 172 173 /** 174 * Returns the value of an imported static member with the given name, or <code>null</code> if there is none.<p> 175 * 176 * @param context the EL context 177 * @param name the name of the static member 178 * 179 * @return the value of the imported static member, or <code>null</code> 180 */ 181 private Object getImportedStaticValue(ELContext context, String name) { 182 183 ImportHandler importHandler = context.getImportHandler(); 184 if (importHandler == null) { 185 return null; 186 } 187 Class<?> clazz = importHandler.resolveStatic(name); 188 if (clazz == null) { 189 return null; 190 } 191 try { 192 return clazz.getField(name).get(null); 193 } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { 194 // this is checked when the static import is declared, so it should not happen here 195 LOG.debug("Unable to read imported static member \"" + name + "\".", e); 196 return null; 197 } 198 } 199 200}