/******************************************************************************* * 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.ofbiz.base.util.string; import static de.odysseus.el.tree.impl.Scanner.Symbol.END_EVAL; import static de.odysseus.el.tree.impl.Scanner.Symbol.FLOAT; import static de.odysseus.el.tree.impl.Scanner.Symbol.START_EVAL_DEFERRED; import static de.odysseus.el.tree.impl.Scanner.Symbol.START_EVAL_DYNAMIC; import javax.el.ELContext; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.PropertyNotFoundException; import de.odysseus.el.ExpressionFactoryImpl; import de.odysseus.el.misc.LocalMessages; import de.odysseus.el.tree.Bindings; import de.odysseus.el.tree.Tree; import de.odysseus.el.tree.TreeStore; import de.odysseus.el.tree.impl.Builder; import de.odysseus.el.tree.impl.Cache; import de.odysseus.el.tree.impl.Parser; import de.odysseus.el.tree.impl.Parser.ParseException; import de.odysseus.el.tree.impl.Scanner.ScanException; import de.odysseus.el.tree.impl.Scanner.Symbol; import de.odysseus.el.tree.impl.ast.AstBracket; import de.odysseus.el.tree.impl.ast.AstDot; import de.odysseus.el.tree.impl.ast.AstEval; import de.odysseus.el.tree.impl.ast.AstIdentifier; import de.odysseus.el.tree.impl.ast.AstNode; import org.apache.ofbiz.base.util.Debug; /** A facade class used to connect the OFBiz framework to the JUEL library. *<p>The Unified Expression Language specification doesn't allow assignment of * values to non-existent variables (auto-vivify) - but the OFBiz scripting * languages do. This class modifies the JUEL library behavior to enable * auto-vivify.</p> */ public class JuelConnector { protected static final String module = JuelConnector.class.getName(); /** Returns an <code>ExpressionFactory</code> instance. * @return A customized <code>ExpressionFactory</code> instance */ public static ExpressionFactory newExpressionFactory() { return new ExpressionFactoryImpl(new TreeStore(new ExtendedBuilder(), new Cache(1000))); } /** Custom <code>AstBracket</code> class that implements * <code>List</code> or <code>Map</code> auto-vivify. */ public static class ExtendedAstBracket extends AstBracket { public ExtendedAstBracket(AstNode base, AstNode property, boolean lvalue, boolean strict) { super(base, property, lvalue, strict); } @Override public void setValue(Bindings bindings, ELContext context, Object value) throws ELException { if (!lvalue) { throw new ELException(LocalMessages.get("error.value.set.rvalue")); } Object base = null; try { base = prefix.eval(bindings, context); } catch (Exception e) {} Object property = getProperty(bindings, context); if (property == null && strict) { throw new PropertyNotFoundException(LocalMessages.get("error.property.property.notfound", "null", base)); } if (base == null) { base = UelUtil.autoVivifyListOrMap(property); if (Debug.verboseOn()) { Debug.logVerbose("ExtendedAstBracket.setValue auto-vivify base: " + base + ", property = " + property, module); } prefix.setValue(bindings, context, base); } context.getELResolver().setValue(context, base, property, value); if (!context.isPropertyResolved()) { throw new PropertyNotFoundException(LocalMessages.get("error.property.property.notfound", property, base)); } } } /** Custom <code>AstDot</code> class that implements * <code>List</code> or <code>Map</code> auto-vivify. */ public static class ExtendedAstDot extends AstDot { public ExtendedAstDot(AstNode base, String property, boolean lvalue) { super(base, property, lvalue); } @Override public void setValue(Bindings bindings, ELContext context, Object value) throws ELException { if (!lvalue) { throw new ELException(LocalMessages.get("error.value.set.rvalue")); } Object base = null; try { base = prefix.eval(bindings, context); } catch (Exception e) {} Object property = getProperty(bindings, context); if (property == null && strict) { throw new PropertyNotFoundException(LocalMessages.get("error.property.property.notfound", "null", base)); } if (base == null) { base = UelUtil.autoVivifyListOrMap(property); if (Debug.verboseOn()) { Debug.logVerbose("ExtendedAstDot.setValue auto-vivify base: " + base + ", property = " + property, module); } prefix.setValue(bindings, context, base); } context.getELResolver().setValue(context, base, property, value); if (!context.isPropertyResolved()) { throw new PropertyNotFoundException(LocalMessages.get("error.property.property.notfound", property, base)); } } } /** Custom <code>Parser</code> class needed to implement auto-vivify. */ protected static class ExtendedParser extends Parser { public ExtendedParser(Builder context, String input) { super(context, input); } @Override protected AstEval eval(boolean required, boolean deferred) throws ScanException, ParseException { AstEval v = null; Symbol start_eval = deferred ? START_EVAL_DEFERRED : START_EVAL_DYNAMIC; if (this.getToken().getSymbol() == start_eval) { consumeToken(); AstNode node = expr(true); try { consumeToken(END_EVAL); } catch (ParseException e) { if (this.getToken().getSymbol() == FLOAT && node instanceof AstIdentifier) { // Handle ${someMap.${someId}} String mapKey = this.getToken().getImage().replace(".", ""); node = createAstDot(node, mapKey, true); consumeToken(); consumeToken(END_EVAL); } else { throw e; } } v = new AstEval(node, deferred); } else if (required) { fail(start_eval); } return v; } @Override protected AstBracket createAstBracket(AstNode base, AstNode property, boolean lvalue, boolean strict) { return new ExtendedAstBracket(base, property, lvalue, strict); } @Override protected AstDot createAstDot(AstNode base, String property, boolean lvalue) { return new ExtendedAstDot(base, property, lvalue); } } /** Custom <code>Builder</code> class needed to implement a custom parser. */ @SuppressWarnings("serial") protected static class ExtendedBuilder extends Builder { @Override public Tree build(String expression) throws ELException { try { return new ExtendedParser(this, expression).tree(); } catch (ScanException e) { throw new ELException(LocalMessages.get("error.build", expression, e.getMessage())); } catch (ParseException e) { throw new ELException(LocalMessages.get("error.build", expression, e.getMessage())); } } } }