View Javadoc
1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2024, 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  
15  package ch.qos.logback.core.model.processor;
16  
17  import ch.qos.logback.core.Context;
18  import ch.qos.logback.core.joran.GenericXMLConfigurator;
19  import ch.qos.logback.core.joran.event.SaxEvent;
20  import ch.qos.logback.core.joran.event.SaxEventRecorder;
21  import ch.qos.logback.core.joran.spi.JoranException;
22  import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
23  import ch.qos.logback.core.model.IncludeModel;
24  import ch.qos.logback.core.model.Model;
25  import ch.qos.logback.core.spi.ErrorCodes;
26  import ch.qos.logback.core.util.Loader;
27  import ch.qos.logback.core.util.OptionHelper;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.net.MalformedURLException;
33  import java.net.URI;
34  import java.net.URL;
35  import java.util.List;
36  import java.util.function.Supplier;
37  
38  import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG;
39  import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG;
40  
41  /**
42   * @since 1.5.5
43   */
44  public class IncludeModelHandler extends ModelHandlerBase {
45      boolean inError = false;
46      private String attributeInUse;
47      private boolean optional;
48  
49      public IncludeModelHandler(Context context) {
50          super(context);
51      }
52  
53      static public IncludeModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
54          return new IncludeModelHandler(context);
55      }
56  
57      @Override
58      protected Class<IncludeModel> getSupportedModelClass() {
59          return IncludeModel.class;
60      }
61  
62      @Override
63      public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
64          IncludeModel includeModel = (IncludeModel) model;
65  
66          this.optional = OptionHelper.toBoolean(includeModel.getOptional(), false);
67  
68          if (!checkAttributes(includeModel)) {
69              inError = true;
70              return;
71          }
72  
73          InputStream in = getInputStream(mic, includeModel);
74          if(in == null) {
75              inError = true;
76              return;
77          }
78  
79          SaxEventRecorder recorder = null;
80  
81          try {
82              recorder = populateSaxEventRecorder(in);
83  
84              List<SaxEvent> saxEvents = recorder.getSaxEventList();
85              if (saxEvents.isEmpty()) {
86                  addWarn("Empty sax event list");
87                  return;
88              }
89  
90              //trimHeadAndTail(saxEvents);
91  
92              Supplier<? extends GenericXMLConfigurator> jcSupplier = mic.getConfiguratorSupplier();
93              if (jcSupplier == null) {
94                  addError("null configurator supplier. Abandoning inclusion of [" + attributeInUse + "]");
95                  inError = true;
96                  return;
97              }
98  
99              GenericXMLConfigurator genericXMLConfigurator = jcSupplier.get();
100             genericXMLConfigurator.getRuleStore().addPathPathMapping(INCLUDED_TAG, CONFIGURATION_TAG);
101 
102             Model modelFromIncludedFile = genericXMLConfigurator.buildModelFromSaxEventList(recorder.getSaxEventList());
103             if (modelFromIncludedFile == null) {
104                 addError(ErrorCodes.EMPTY_MODEL_STACK);
105                 return;
106             }
107 
108             includeModel.getSubModels().addAll(modelFromIncludedFile.getSubModels());
109 
110         } catch (JoranException e) {
111             inError = true;
112             addError("Error processing XML data in [" + attributeInUse + "]", e);
113         }
114     }
115 
116     public SaxEventRecorder populateSaxEventRecorder(final InputStream inputStream) throws JoranException {
117         SaxEventRecorder recorder = new SaxEventRecorder(context);
118         recorder.recordEvents(inputStream);
119         return recorder;
120     }
121 
122     private void trimHeadAndTail( List<SaxEvent> saxEventList) {
123         // Let's remove the two <included> events before
124         // adding the events to the player.
125 
126         // note saxEventList.size() changes over time as events are removed
127 
128         if (saxEventList.size() == 0) {
129             return;
130         }
131 
132         SaxEvent first = saxEventList.get(0);
133         if (first != null && first.qName.equalsIgnoreCase(INCLUDED_TAG)) {
134             saxEventList.remove(0);
135         }
136 
137         SaxEvent last = saxEventList.get(saxEventList.size() - 1);
138         if (last != null && last.qName.equalsIgnoreCase(INCLUDED_TAG)) {
139             saxEventList.remove(saxEventList.size() - 1);
140         }
141     }
142 
143     InputStream getInputStream(ModelInterpretationContext mic, IncludeModel includeModel) {
144         URL inputURL = getInputURL(mic, includeModel);
145         if (inputURL == null)
146             return null;
147 
148         ConfigurationWatchListUtil.addToWatchList(context, inputURL);
149         return openURL(inputURL);
150     }
151 
152     InputStream openURL(URL url) {
153         try {
154             return url.openStream();
155         } catch (IOException e) {
156             optionalWarning("Failed to open [" + url.toString() + "]");
157             return null;
158         }
159     }
160 
161     private boolean checkAttributes(IncludeModel includeModel) {
162         String fileAttribute = includeModel.getFile();
163         String urlAttribute = includeModel.getUrl();
164         String resourceAttribute = includeModel.getResource();
165 
166         int count = 0;
167 
168         if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) {
169             count++;
170         }
171         if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) {
172             count++;
173         }
174         if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) {
175             count++;
176         }
177 
178         if (count == 0) {
179             addError("One of \"path\", \"resource\" or \"url\" attributes must be set.");
180             return false;
181         } else if (count > 1) {
182             addError("Only one of \"file\", \"url\" or \"resource\" attributes should be set.");
183             return false;
184         } else if (count == 1) {
185             return true;
186         }
187         throw new IllegalStateException("Count value [" + count + "] is not expected");
188     }
189 
190     URL getInputURL(ModelInterpretationContext mic, IncludeModel includeModel) {
191         String fileAttribute = includeModel.getFile();
192         String urlAttribute = includeModel.getUrl();
193         String resourceAttribute = includeModel.getResource();
194 
195         if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) {
196             this.attributeInUse = mic.subst(fileAttribute);
197             return filePathAsURL(attributeInUse);
198         }
199 
200         if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) {
201             this.attributeInUse = mic.subst(urlAttribute);
202             return attributeToURL(attributeInUse);
203         }
204 
205         if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) {
206             this.attributeInUse = mic.subst(resourceAttribute);
207             return resourceAsURL(attributeInUse);
208         }
209         // given preceding checkAttributes() check we cannot reach this line
210         throw new IllegalStateException("A URL stream should have been returned at this stage");
211 
212     }
213 
214     URL filePathAsURL(String path) {
215         URI uri = new File(path).toURI();
216         try {
217             return uri.toURL();
218         } catch (MalformedURLException e) {
219             // impossible to get here
220             e.printStackTrace();
221             return null;
222         }
223     }
224 
225     URL attributeToURL(String urlAttribute) {
226         try {
227             return new URL(urlAttribute);
228         } catch (MalformedURLException mue) {
229             String errMsg = "URL [" + urlAttribute + "] is not well formed.";
230             addError(errMsg, mue);
231             return null;
232         }
233     }
234 
235     URL resourceAsURL(String resourceAttribute) {
236         URL url = Loader.getResourceBySelfClassLoader(resourceAttribute);
237         if (url == null) {
238             optionalWarning("Could not find resource corresponding to [" + resourceAttribute + "]");
239             return null;
240         } else
241             return url;
242     }
243 
244     private void optionalWarning(String msg) {
245         if (!optional) {
246             addWarn(msg);
247         }
248     }
249 }