001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.joran.action; 015 016import java.io.File; 017import java.io.IOException; 018import java.io.InputStream; 019import java.net.MalformedURLException; 020import java.net.URI; 021import java.net.URL; 022import java.util.List; 023 024import org.xml.sax.Attributes; 025 026import ch.qos.logback.core.joran.event.SaxEvent; 027import ch.qos.logback.core.joran.event.SaxEventRecorder; 028import ch.qos.logback.core.joran.spi.ActionException; 029import ch.qos.logback.core.joran.spi.JoranException; 030import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext; 031import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 032import ch.qos.logback.core.model.IncludeModel; 033import ch.qos.logback.core.model.Model; 034import ch.qos.logback.core.util.Loader; 035import ch.qos.logback.core.util.OptionHelper; 036 037import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG; 038 039/** 040 * 041 * @author ceki 042 * 043 */ 044public class IncludeAction extends Action { 045 046 private static final String FILE_ATTR = "file"; 047 private static final String URL_ATTR = "url"; 048 private static final String RESOURCE_ATTR = "resource"; 049 private static final String OPTIONAL_ATTR = "optional"; 050 051 private String attributeInUse; 052 private boolean optional; 053 054 Model parentModel; 055 IncludeModel includeModel; 056 boolean inError = false; 057 058 @Override 059 public void begin(SaxEventInterpretationContext ec, String name, Attributes attributes) throws ActionException { 060 061 parentModel = null; 062 includeModel = null; 063 064 SaxEventRecorder recorder = new SaxEventRecorder(context); 065 066 String optionalStr = attributes.getValue(OPTIONAL_ATTR); 067 068 createModelForAlternateUse(ec, name, attributes, optionalStr); 069 070 071 this.attributeInUse = null; 072 this.optional = OptionHelper.toBoolean(optionalStr, false); 073 074 if (!checkAttributes(attributes)) { 075 inError = true; 076 return; 077 } 078 079 InputStream in = getInputStream(ec, attributes); 080 081 try { 082 if (in != null) { 083 parseAndRecord(in, recorder); 084 // remove the <included> tag from the beginning and </included> from the end 085 trimHeadAndTail(recorder); 086 087 // offset = 2, because we need to get past this element as well as the end 088 // element 089 ec.getSaxEventInterpreter().getEventPlayer().addEventsDynamically(recorder.getSaxEventList(), 2); 090 } 091 } catch (JoranException e) { 092 addError("Error while parsing " + attributeInUse, e); 093 } finally { 094 close(in); 095 } 096 097 } 098 099 // 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}