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.InterpretationContext;
30 import ch.qos.logback.core.joran.spi.JoranException;
31 import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
32 import ch.qos.logback.core.util.Loader;
33 import ch.qos.logback.core.util.OptionHelper;
34
35 public class IncludeAction extends Action {
36
37     private static final String INCLUDED_TAG = "included";
38     private static final String FILE_ATTR = "file";
39     private static final String URL_ATTR = "url";
40     private static final String RESOURCE_ATTR = "resource";
41     private static final String OPTIONAL_ATTR = "optional";
42
43     private String attributeInUse;
44     private boolean optional;
45
46     @Override
47     public void begin(InterpretationContext ec, String name, Attributes attributes) throws ActionException {
48
49         SaxEventRecorder recorder = new SaxEventRecorder(context);
50
51         this.attributeInUse = null;
52         this.optional = OptionHelper.toBoolean(attributes.getValue(OPTIONAL_ATTR), false);
53
54         if (!checkAttributes(attributes)) {
55             return;
56         }
57
58         InputStream in = getInputStream(ec, attributes);
59
60         try {
61             if (in != null) {
62                 parseAndRecord(in, recorder);
63                 // remove the <included> tag from the beginning and </included> from the end
64                 trimHeadAndTail(recorder);
65
66                 // offset = 2, because we need to get past this element as well as the end element
67                 ec.getJoranInterpreter().getEventPlayer().addEventsDynamically(recorder.saxEventList, 2);
68             }
69         } catch (JoranException e) {
70             addError("Error while parsing  " + attributeInUse, e);
71         } finally {
72             close(in);
73         }
74
75     }
76
77     void close(InputStream in) {
78         if (in != null) {
79             try {
80                 in.close();
81             } catch (IOException e) {
82             }
83         }
84     }
85
86     private boolean checkAttributes(Attributes attributes) {
87         String fileAttribute = attributes.getValue(FILE_ATTR);
88         String urlAttribute = attributes.getValue(URL_ATTR);
89         String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
90
91         int count = 0;
92
93         if (!OptionHelper.isEmpty(fileAttribute)) {
94             count++;
95         }
96         if (!OptionHelper.isEmpty(urlAttribute)) {
97             count++;
98         }
99         if (!OptionHelper.isEmpty(resourceAttribute)) {
100             count++;
101         }
102
103         if (count == 0) {
104             addError("One of \"path\", \"resource\" or \"url\" attributes must be set.");
105             return false;
106         } else if (count > 1) {
107             addError("Only one of \"file\", \"url\" or \"resource\" attributes should be set.");
108             return false;
109         } else if (count == 1) {
110             return true;
111         }
112         throw new IllegalStateException("Count value [" + count + "] is not expected");
113     }
114
115     URL attributeToURL(String urlAttribute) {
116         try {
117             return new URL(urlAttribute);
118         } catch (MalformedURLException mue) {
119             String errMsg = "URL [" + urlAttribute + "] is not well formed.";
120             addError(errMsg, mue);
121             return null;
122         }
123     }
124
125     InputStream openURL(URL url) {
126         try {
127             return url.openStream();
128         } catch (IOException e) {
129             optionalWarning("Failed to open [" + url.toString() + "]");
130             return null;
131         }
132     }
133
134     URL resourceAsURL(String resourceAttribute) {
135         URL url = Loader.getResourceBySelfClassLoader(resourceAttribute);
136         if (url == null) {
137             optionalWarning("Could not find resource corresponding to [" + resourceAttribute + "]");
138             return null;
139         } else
140             return url;
141     }
142
143     private void optionalWarning(String msg) {
144         if (!optional) {
145             addWarn(msg);
146         }
147     }
148
149     URL filePathAsURL(String path) {
150         URI uri = new File(path).toURI();
151         try {
152             return uri.toURL();
153         } catch (MalformedURLException e) {
154             // impossible to get here
155             e.printStackTrace();
156             return null;
157         }
158     }
159
160     URL getInputURL(InterpretationContext ec, Attributes attributes) {
161         String fileAttribute = attributes.getValue(FILE_ATTR);
162         String urlAttribute = attributes.getValue(URL_ATTR);
163         String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
164
165         if (!OptionHelper.isEmpty(fileAttribute)) {
166             this.attributeInUse = ec.subst(fileAttribute);
167             return filePathAsURL(attributeInUse);
168         }
169
170         if (!OptionHelper.isEmpty(urlAttribute)) {
171             this.attributeInUse = ec.subst(urlAttribute);
172             return attributeToURL(attributeInUse);
173         }
174
175         if (!OptionHelper.isEmpty(resourceAttribute)) {
176             this.attributeInUse = ec.subst(resourceAttribute);
177             return resourceAsURL(attributeInUse);
178         }
179         // given previous checkAttributes() check we cannot reach this line
180         throw new IllegalStateException("A URL stream should have been returned");
181
182     }
183
184     InputStream getInputStream(InterpretationContext ec, Attributes attributes) {
185         URL inputURL = getInputURL(ec, attributes);
186         if (inputURL == null)
187             return null;
188
189         ConfigurationWatchListUtil.addToWatchList(context, inputURL);
190         return openURL(inputURL);
191     }
192
193     private void trimHeadAndTail(SaxEventRecorder recorder) {
194         // Let's remove the two <included> events before
195         // adding the events to the player.
196
197         List<SaxEvent> saxEventList = recorder.saxEventList;
198
199         if (saxEventList.size() == 0) {
200             return;
201         }
202
203         SaxEvent first = saxEventList.get(0);
204         if (first != null && first.qName.equalsIgnoreCase(INCLUDED_TAG)) {
205             saxEventList.remove(0);
206         }
207
208         SaxEvent last = saxEventList.get(recorder.saxEventList.size() - 1);
209         if (last != null && last.qName.equalsIgnoreCase(INCLUDED_TAG)) {
210             saxEventList.remove(recorder.saxEventList.size() - 1);
211         }
212     }
213
214     private void parseAndRecord(InputStream inputSource, SaxEventRecorder recorder) throws JoranException {
215         recorder.setContext(context);
216         recorder.recordEvents(inputSource);
217     }
218
219     @Override
220     public void end(InterpretationContext ec, String name) throws ActionException {
221         // do nothing
222     }
223
224 }
225