001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2024, 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 */ 014 015package ch.qos.logback.core.model.processor; 016 017import ch.qos.logback.core.Context; 018import ch.qos.logback.core.joran.GenericXMLConfigurator; 019import ch.qos.logback.core.joran.event.SaxEvent; 020import ch.qos.logback.core.joran.event.SaxEventRecorder; 021import ch.qos.logback.core.joran.spi.JoranException; 022import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 023import ch.qos.logback.core.model.IncludeModel; 024import ch.qos.logback.core.model.Model; 025import ch.qos.logback.core.spi.ErrorCodes; 026import ch.qos.logback.core.util.Loader; 027import ch.qos.logback.core.util.OptionHelper; 028 029import java.io.File; 030import java.io.IOException; 031import java.io.InputStream; 032import java.net.MalformedURLException; 033import java.net.URI; 034import java.net.URL; 035import java.util.List; 036import java.util.function.Supplier; 037 038import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG; 039import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG; 040 041/** 042 * @since 1.5.5 043 */ 044public class IncludeModelHandler extends ModelHandlerBase { 045 boolean inError = false; 046 private String attributeInUse; 047 private boolean optional; 048 049 public IncludeModelHandler(Context context) { 050 super(context); 051 } 052 053 static public IncludeModelHandler makeInstance(Context context, ModelInterpretationContext mic) { 054 return new IncludeModelHandler(context); 055 } 056 057 @Override 058 protected Class<IncludeModel> getSupportedModelClass() { 059 return IncludeModel.class; 060 } 061 062 @Override 063 public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { 064 IncludeModel includeModel = (IncludeModel) model; 065 066 this.optional = OptionHelper.toBoolean(includeModel.getOptional(), false); 067 068 if (!checkAttributes(includeModel)) { 069 inError = true; 070 return; 071 } 072 073 InputStream in = getInputStream(mic, includeModel); 074 if(in == null) { 075 inError = true; 076 return; 077 } 078 079 SaxEventRecorder recorder = null; 080 081 try { 082 recorder = populateSaxEventRecorder(in); 083 084 List<SaxEvent> saxEvents = recorder.getSaxEventList(); 085 if (saxEvents.isEmpty()) { 086 addWarn("Empty sax event list"); 087 return; 088 } 089 090 //trimHeadAndTail(saxEvents); 091 092 Supplier<? extends GenericXMLConfigurator> jcSupplier = mic.getConfiguratorSupplier(); 093 if (jcSupplier == null) { 094 addError("null configurator supplier. Abandoning inclusion of [" + attributeInUse + "]"); 095 inError = true; 096 return; 097 } 098 099 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}