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.joran.action;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.net.MalformedURLException;
20  import java.net.URI;
21  import java.net.URL;
22  import java.util.List;
23  
24  import org.xml.sax.Attributes;
25  
26  import ch.qos.logback.core.joran.event.SaxEvent;
27  import ch.qos.logback.core.joran.event.SaxEventRecorder;
28  import ch.qos.logback.core.joran.spi.ActionException;
29  import ch.qos.logback.core.joran.spi.JoranException;
30  import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
31  import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
32  import ch.qos.logback.core.model.IncludeModel;
33  import ch.qos.logback.core.model.Model;
34  import ch.qos.logback.core.util.Loader;
35  import ch.qos.logback.core.util.OptionHelper;
36  
37  import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG;
38  
39  /**
40   * 
41   * @author ceki
42   *
43   */
44  public class IncludeAction extends Action {
45  
46      private static final String FILE_ATTR = "file";
47      private static final String URL_ATTR = "url";
48      private static final String RESOURCE_ATTR = "resource";
49      private static final String OPTIONAL_ATTR = "optional";
50  
51      private String attributeInUse;
52      private boolean optional;
53  
54      Model parentModel;
55      IncludeModel includeModel;
56      boolean inError = false;
57      
58      @Override
59      public void begin(SaxEventInterpretationContext ec, String name, Attributes attributes) throws ActionException {
60  
61          parentModel = null;
62          includeModel = null;
63          
64          SaxEventRecorder recorder = new SaxEventRecorder(context);
65          
66          String optionalStr = attributes.getValue(OPTIONAL_ATTR);
67          
68          createModelForAlternateUse(ec, name, attributes, optionalStr);
69          
70          
71          this.attributeInUse = null;
72          this.optional = OptionHelper.toBoolean(optionalStr, false);
73          
74          if (!checkAttributes(attributes)) {
75              inError = true;
76              return;
77          }
78           
79          InputStream in = getInputStream(ec, attributes);
80  
81          try {
82              if (in != null) {
83                  parseAndRecord(in, recorder);
84                  // remove the <included> tag from the beginning and </included> from the end
85                  trimHeadAndTail(recorder);
86  
87                  // offset = 2, because we need to get past this element as well as the end
88                  // element
89                  ec.getSaxEventInterpreter().getEventPlayer().addEventsDynamically(recorder.getSaxEventList(), 2);
90              }
91          } catch (JoranException e) {
92              addError("Error while parsing  " + attributeInUse, e);
93          } finally {
94              close(in);
95          }
96  
97      }
98  
99      // model created for later use, not necessarily for configuration purposes.
100     private void createModelForAlternateUse(SaxEventInterpretationContext seic, String name, Attributes attributes,
101             String optionalStr) {
102         this.includeModel = new IncludeModel();
103         this.includeModel.setOptional(optionalStr);
104         fillInIncludeModelAttributes(includeModel, name, attributes);
105         if (!seic.isModelStackEmpty()) {
106             parentModel = seic.peekModel();
107         }
108         final int lineNumber = getLineNumber(seic);
109         this.includeModel.setLineNumber(lineNumber);
110         seic.pushModel(includeModel);
111     }
112 
113     private void fillInIncludeModelAttributes(IncludeModel includeModel, String name, Attributes attributes) {
114         this.includeModel.setTag(name);
115         String fileAttribute = attributes.getValue(FILE_ATTR);
116         String urlAttribute = attributes.getValue(URL_ATTR);
117         String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
118         
119         this.includeModel.setFile(fileAttribute);
120         this.includeModel.setUrl(urlAttribute);
121         this.includeModel.setResource(resourceAttribute);
122         
123     }
124 
125     void close(InputStream in) {
126         if (in != null) {
127             try {
128                 in.close();
129             } catch (IOException e) {
130             }
131         }
132     }
133 
134     private boolean checkAttributes(Attributes attributes) {
135         String fileAttribute = attributes.getValue(FILE_ATTR);
136         String urlAttribute = attributes.getValue(URL_ATTR);
137         String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
138 
139         int count = 0;
140 
141         if (!OptionHelper.isNullOrEmpty(fileAttribute)) {
142             count++;
143         }
144         if (!OptionHelper.isNullOrEmpty(urlAttribute)) {
145             count++;
146         }
147         if (!OptionHelper.isNullOrEmpty(resourceAttribute)) {
148             count++;
149         }
150 
151         if (count == 0) {
152             addError("One of \"path\", \"resource\" or \"url\" attributes must be set.");
153             return false;
154         } else if (count > 1) {
155             addError("Only one of \"file\", \"url\" or \"resource\" attributes should be set.");
156             return false;
157         } else if (count == 1) {
158             return true;
159         }
160         throw new IllegalStateException("Count value [" + count + "] is not expected");
161     }
162 
163     URL attributeToURL(String urlAttribute) {
164         try {
165             return new URL(urlAttribute);
166         } catch (MalformedURLException mue) {
167             String errMsg = "URL [" + urlAttribute + "] is not well formed.";
168             addError(errMsg, mue);
169             return null;
170         }
171     }
172 
173     InputStream openURL(URL url) {
174         try {
175             return url.openStream();
176         } catch (IOException e) {
177             optionalWarning("Failed to open [" + url.toString() + "]");
178             return null;
179         }
180     }
181 
182     URL resourceAsURL(String resourceAttribute) {
183         URL url = Loader.getResourceBySelfClassLoader(resourceAttribute);
184         if (url == null) {
185             optionalWarning("Could not find resource corresponding to [" + resourceAttribute + "]");
186             return null;
187         } else
188             return url;
189     }
190 
191     private void optionalWarning(String msg) {
192         if (!optional) {
193             addWarn(msg);
194         }
195     }
196 
197     URL filePathAsURL(String path) {
198         URI uri = new File(path).toURI();
199         try {
200             return uri.toURL();
201         } catch (MalformedURLException e) {
202             // impossible to get here
203             e.printStackTrace();
204             return null;
205         }
206     }
207 
208     URL getInputURL(SaxEventInterpretationContext ec, Attributes attributes) {
209         String fileAttribute = attributes.getValue(FILE_ATTR);
210         String urlAttribute = attributes.getValue(URL_ATTR);
211         String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
212 
213         if (!OptionHelper.isNullOrEmpty(fileAttribute)) {
214             this.attributeInUse = ec.subst(fileAttribute);
215             return filePathAsURL(attributeInUse);
216         }
217 
218         if (!OptionHelper.isNullOrEmpty(urlAttribute)) {
219             this.attributeInUse = ec.subst(urlAttribute);
220             return attributeToURL(attributeInUse);
221         }
222 
223         if (!OptionHelper.isNullOrEmpty(resourceAttribute)) {
224             this.attributeInUse = ec.subst(resourceAttribute);
225             return resourceAsURL(attributeInUse);
226         }
227         // given previous checkAttributes() check we cannot reach this line
228         throw new IllegalStateException("A URL stream should have been returned");
229 
230     }
231 
232     InputStream getInputStream(SaxEventInterpretationContext ec, Attributes attributes) {
233         URL inputURL = getInputURL(ec, attributes);
234         if (inputURL == null)
235             return null;
236 
237         ConfigurationWatchListUtil.addToWatchList(context, inputURL);
238         return openURL(inputURL);
239     }
240 
241     private void trimHeadAndTail(SaxEventRecorder recorder) {
242         // Let's remove the two <included> events before
243         // adding the events to the player.
244 
245         // note saxEventList.size() changes over time as events are removed 
246         
247         List<SaxEvent> saxEventList = recorder.getSaxEventList();
248 
249         if (saxEventList.size() == 0) {
250             return;
251         }
252 
253         SaxEvent first = saxEventList.get(0);
254         if (first != null && first.qName.equalsIgnoreCase(INCLUDED_TAG)) {
255             saxEventList.remove(0);
256         }
257         
258         SaxEvent last = saxEventList.get(saxEventList.size() - 1);
259         if (last != null && last.qName.equalsIgnoreCase(INCLUDED_TAG)) {
260             saxEventList.remove(saxEventList.size() - 1);
261         }
262     }
263 
264     private void parseAndRecord(InputStream inputSource, SaxEventRecorder recorder) throws JoranException {
265         recorder.setContext(context);
266         recorder.recordEvents(inputSource);
267     }
268 
269     @Override
270     public void end(SaxEventInterpretationContext seic, String name) throws ActionException {
271         
272         if(inError)
273             return;
274         
275         Model m = seic.peekModel();
276 
277         if (m != includeModel) {
278             addWarn("The object at the of the stack is not the model [" + includeModel.idString()
279                     + "] pushed earlier.");
280             addWarn("This is wholly unexpected.");
281         }
282 
283         // do not pop nor add to parent if there is no parent
284         if (parentModel != null) {
285             parentModel.addSubModel(includeModel);
286             seic.popModel();
287         }
288     }
289 }