View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.subst;
15  
16  import ch.qos.logback.core.CoreConstants;
17  import ch.qos.logback.core.spi.PropertyContainer;
18  import ch.qos.logback.core.spi.ScanException;
19  import ch.qos.logback.core.subst.Node.Type;
20  import ch.qos.logback.core.util.OptionHelper;
21  
22  import java.util.List;
23  import java.util.Stack;
24  
25  /**
26   * Compiles a previously parsed Node chain into a String.
27   *
28   * @author Ceki Gülcü
29   */
30  public class NodeToStringTransformer {
31  
32      public static final String CIRCULAR_VARIABLE_REFERENCE_DETECTED = "Circular variable reference detected while parsing input [";
33      final Node node;
34      final PropertyContainer propertyContainer0;
35      final PropertyContainer propertyContainer1;
36  
37      public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0,
38              PropertyContainer propertyContainer1) {
39          this.node = node;
40          this.propertyContainer0 = propertyContainer0;
41          this.propertyContainer1 = propertyContainer1;
42      }
43  
44      public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0) {
45          this(node, propertyContainer0, null);
46      }
47  
48      public static String substituteVariable(String input, PropertyContainer pc0, PropertyContainer pc1)
49              throws ScanException {
50          Node node = tokenizeAndParseString(input);
51          NodeToStringTransformer nodeToStringTransformer = new NodeToStringTransformer(node, pc0, pc1);
52          return nodeToStringTransformer.transform();
53      }
54  
55      private static Node tokenizeAndParseString(String value) throws ScanException {
56          Tokenizer tokenizer = new Tokenizer(value);
57          List<Token> tokens = tokenizer.tokenize();
58          Parser parser = new Parser(tokens);
59          return parser.parse();
60      }
61  
62      public String transform() throws ScanException {
63          StringBuilder stringBuilder = new StringBuilder();
64          compileNode(node, stringBuilder, new Stack<Node>());
65          return stringBuilder.toString();
66      }
67  
68      private void compileNode(Node inputNode, StringBuilder stringBuilder, Stack<Node> cycleCheckStack)
69              throws ScanException {
70          Node n = inputNode;
71          while (n != null) {
72              switch (n.type) {
73              case LITERAL:
74                  handleLiteral(n, stringBuilder);
75                  break;
76              case VARIABLE:
77                  handleVariable(n, stringBuilder, cycleCheckStack);
78                  break;
79              }
80              n = n.next;
81          }
82      }
83  
84      private void handleVariable(Node n, StringBuilder stringBuilder, Stack<Node> cycleCheckStack) throws ScanException {
85  
86          // Check for recursion
87          if (haveVisitedNodeAlready(n, cycleCheckStack)) {
88              cycleCheckStack.push(n);
89              String error = constructRecursionErrorMessage(cycleCheckStack);
90              throw new IllegalArgumentException(error);
91          }
92          cycleCheckStack.push(n);
93  
94          StringBuilder keyBuffer = new StringBuilder();
95          Node payload = (Node) n.payload;
96          compileNode(payload, keyBuffer, cycleCheckStack);
97          String key = keyBuffer.toString();
98          String value = lookupKey(key);
99  
100         // empty values are considered valid
101         if (value != null) {
102             Node innerNode = tokenizeAndParseString(value);
103             compileNode(innerNode, stringBuilder, cycleCheckStack);
104             cycleCheckStack.pop();
105             return;
106         }
107 
108         // empty default literal is a valid value
109         if (n.defaultPart == null) {
110             stringBuilder.append(key + CoreConstants.UNDEFINED_PROPERTY_SUFFIX);
111             cycleCheckStack.pop();
112             return;
113         }
114 
115         Node defaultPart = (Node) n.defaultPart;
116         StringBuilder defaultPartBuffer = new StringBuilder();
117         compileNode(defaultPart, defaultPartBuffer, cycleCheckStack);
118         cycleCheckStack.pop();
119         String defaultVal = defaultPartBuffer.toString();
120         stringBuilder.append(defaultVal);
121     }
122 
123     private String lookupKey(String key) {
124         String value = propertyContainer0.getProperty(key);
125         if (value != null)
126             return value;
127 
128         if (propertyContainer1 != null) {
129             value = propertyContainer1.getProperty(key);
130             if (value != null)
131                 return value;
132         }
133 
134         value = OptionHelper.getSystemProperty(key, null);
135         if (value != null)
136             return value;
137 
138         value = OptionHelper.getEnv(key);
139         if (value != null) {
140             return value;
141         }
142 
143         return null;
144     }
145 
146     private void handleLiteral(Node n, StringBuilder stringBuilder) {
147         stringBuilder.append((String) n.payload);
148     }
149 
150     private String variableNodeValue(Node variableNode) {
151         Node payload = (Node) variableNode.payload;
152         if(payload == null) {
153             return CoreConstants.EMPTY_STRING;
154         }
155         
156         if(payload.type == Type.LITERAL) {
157             return (String) payload.payload;
158         }
159         
160         if(payload.type == Type.VARIABLE) {
161             return " ? " + variableNodeValue(payload);
162         }
163         throw new IllegalStateException("unreachable code");
164     }
165 
166     private String constructRecursionErrorMessage(Stack<Node> recursionNodes) {
167         StringBuilder errorBuilder = new StringBuilder(CIRCULAR_VARIABLE_REFERENCE_DETECTED);
168 
169         for (Node stackNode : recursionNodes) {
170             errorBuilder.append("${").append(variableNodeValue(stackNode)).append("}");
171             if (recursionNodes.lastElement() != stackNode) {
172                 errorBuilder.append(" --> ");
173             }
174         }
175         errorBuilder.append("]");
176         return errorBuilder.toString();
177     }
178 
179     /**
180      * Determine if a node has already been visited already by checking the
181      * cycleDetectionStack for its existence. This method is used -- rather than
182      * Stack.contains() -- because we want to ignore the Node's 'next' attribute
183      * when comparing for equality.
184      */
185     private boolean haveVisitedNodeAlready(Node node, Stack<Node> cycleDetectionStack) {
186         for (Node cycleNode : cycleDetectionStack) {
187             if (equalNodes(node, cycleNode)) {
188                 return true;
189             }
190         }
191         return false;
192     }
193 
194     private boolean equalNodes(Node node1, Node node2) {
195         if (node1.type != null && !node1.type.equals(node2.type))
196             return false;
197         if (node1.payload != null && !node1.payload.equals(node2.payload))
198             return false;
199         if (node1.defaultPart != null && !node1.defaultPart.equals(node2.defaultPart))
200             return false;
201 
202         return true;
203     }
204 
205 }