001/**
002 * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
003 * reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
006 * v1.0 as published by the Eclipse Foundation
007 *
008 * or (per the licensee's choosing)
009 *
010 * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
011 */
012package ch.qos.logback.core.joran;
013
014import static ch.qos.logback.core.CoreConstants.SAFE_JORAN_CONFIGURATION;
015import static ch.qos.logback.core.spi.ConfigurationEvent.*;
016
017import java.io.File;
018import java.io.FileInputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.net.URLConnection;
023import java.util.List;
024import java.util.concurrent.locks.ReentrantLock;
025
026import org.xml.sax.InputSource;
027
028import ch.qos.logback.core.Context;
029import ch.qos.logback.core.joran.event.SaxEvent;
030import ch.qos.logback.core.joran.event.SaxEventRecorder;
031import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
032import ch.qos.logback.core.joran.spi.ElementPath;
033import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
034import ch.qos.logback.core.joran.spi.JoranException;
035import ch.qos.logback.core.joran.spi.RuleStore;
036import ch.qos.logback.core.joran.spi.SaxEventInterpreter;
037import ch.qos.logback.core.joran.spi.SimpleRuleStore;
038import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
039import ch.qos.logback.core.model.Model;
040import ch.qos.logback.core.model.processor.DefaultProcessor;
041import ch.qos.logback.core.model.processor.ModelInterpretationContext;
042import ch.qos.logback.core.spi.ContextAwareBase;
043import ch.qos.logback.core.spi.ErrorCodes;
044import ch.qos.logback.core.status.StatusUtil;
045
046public abstract class GenericXMLConfigurator extends ContextAwareBase {
047
048    protected SaxEventInterpreter saxEventInterpreter;
049    protected ModelInterpretationContext modelInterpretationContext;
050
051    public ModelInterpretationContext getModelInterpretationContext() {
052        return this.modelInterpretationContext;
053    }
054    private RuleStore ruleStore;
055
056    public URL getTopURL() {
057        return topURL;
058    }
059
060    public void setTopURL(URL topURL) {
061        this.topURL = topURL;
062    }
063
064    URL topURL;
065
066    public final void doConfigure(URL url) throws JoranException {
067        InputStream in = null;
068        try {
069            topURL = url;
070            URLConnection urlConnection = url.openConnection();
071            // per http://jira.qos.ch/browse/LOGBACK-117  LBCORE-105
072            // per http://jira.qos.ch/browse/LOGBACK-163  LBCORE-127
073            urlConnection.setUseCaches(false);
074
075            in = urlConnection.getInputStream();
076            doConfigure(in, url.toExternalForm());
077        } catch (IOException ioe) {
078            String errMsg = "Could not open URL [" + url + "].";
079            addError(errMsg, ioe);
080            throw new JoranException(errMsg, ioe);
081        } finally {
082            if (in != null) {
083                try {
084                    in.close();
085                } catch (IOException ioe) {
086                    String errMsg = "Could not close input stream";
087                    addError(errMsg, ioe);
088                    throw new JoranException(errMsg, ioe);
089                }
090            }
091        }
092    }
093
094    public final void doConfigure(String filename) throws JoranException {
095        doConfigure(new File(filename));
096    }
097
098    public final void doConfigure(File file) throws JoranException {
099        FileInputStream fis = null;
100        try {
101            URL url = file.toURI().toURL();
102            topURL = url;
103            fis = new FileInputStream(file);
104            doConfigure(fis, url.toExternalForm());
105        } catch (IOException ioe) {
106            String errMsg = "Could not open [" + file.getPath() + "].";
107            addError(errMsg, ioe);
108            throw new JoranException(errMsg, ioe);
109        } finally {
110            if (fis != null) {
111                try {
112                    fis.close();
113                } catch (java.io.IOException ioe) {
114                    String errMsg = "Could not close [" + file.getName() + "].";
115                    addError(errMsg, ioe);
116                    throw new JoranException(errMsg, ioe);
117                }
118            }
119        }
120    }
121
122    public final void doConfigure(InputStream inputStream) throws JoranException {
123        doConfigure(new InputSource(inputStream));
124    }
125
126    public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
127        InputSource inputSource = new InputSource(inputStream);
128        inputSource.setSystemId(systemId);
129        doConfigure(inputSource);
130    }
131
132    protected abstract void addElementSelectorAndActionAssociations(RuleStore rs);
133
134    protected abstract void setImplicitRuleSupplier(SaxEventInterpreter interpreter);
135
136    protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) {
137        // nothing by default
138    }
139
140    protected ElementPath initialElementPath() {
141        return new ElementPath();
142    }
143
144    protected void buildSaxEventInterpreter(List<SaxEvent> saxEvents) {
145        RuleStore rs = getRuleStore();
146        addElementSelectorAndActionAssociations(rs);
147        this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);
148        SaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext();
149        interpretationContext.setContext(context);
150        setImplicitRuleSupplier(saxEventInterpreter);
151    }
152
153    public RuleStore getRuleStore() {
154        if(this.ruleStore == null) {
155            this.ruleStore = new SimpleRuleStore(context);
156        }
157        return this.ruleStore;
158    }
159
160    protected void buildModelInterpretationContext() {
161        this.modelInterpretationContext = new ModelInterpretationContext(context);
162        this.modelInterpretationContext.setTopURL(topURL);
163        addDefaultNestedComponentRegistryRules(modelInterpretationContext.getDefaultNestedComponentRegistry());
164    }
165
166    // this is the most inner form of doConfigure whereto other doConfigure
167    // methods ultimately delegate
168    public final void doConfigure(final InputSource inputSource) throws JoranException {
169
170        context.fireConfigurationEvent(newConfigurationStartedEvent(this));
171        long threshold = System.currentTimeMillis();
172
173        SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
174        List<SaxEvent> saxEvents = recorder.getSaxEventList();
175        if (saxEvents.isEmpty()) {
176            addWarn("Empty sax event list");
177            return;
178        }
179        Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
180        if (top == null) {
181            addError(ErrorCodes.EMPTY_MODEL_STACK);
182            return;
183        }
184        sanityCheck(top);
185        processModel(top);
186
187        // no exceptions at this level
188        StatusUtil statusUtil = new StatusUtil(context);
189        if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
190            addInfo("Registering current configuration as safe fallback point");
191            registerSafeConfiguration(top);
192            context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));
193        } else {
194            context.fireConfigurationEvent(newConfigurationEndedWithXMLParsingErrorsEvent(this));
195        }
196
197
198    }
199
200    public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {
201        SaxEventRecorder recorder = new SaxEventRecorder(context);
202        recorder.recordEvents(inputSource);
203        return recorder;
204    }
205
206    public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {
207        buildSaxEventInterpreter(saxEvents);
208        playSaxEvents();
209        Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();
210        return top;
211    }
212
213    private void playSaxEvents() throws JoranException {
214        saxEventInterpreter.getEventPlayer().play();
215    }
216
217    public void processModel(Model model) {
218        buildModelInterpretationContext();
219        this.modelInterpretationContext.setTopModel(model);
220        modelInterpretationContext.setConfiguratorHint(this);
221        DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
222        addModelHandlerAssociations(defaultProcessor);
223
224        // disallow simultaneous configurations of the same context
225        ReentrantLock configurationLock   = context.getConfigurationLock();
226
227        try {
228            configurationLock.lock();
229            defaultProcessor.process(model);
230        } finally {
231            configurationLock.unlock();
232        }
233    }
234
235    /**
236     * Perform sanity check and issue warning if necessary.
237     *
238     * Default implementation does nothing.
239     *
240     * @param topModel
241     * @since 1.3.2 and 1.4.2
242     */
243    protected void sanityCheck(Model topModel) {
244
245    }
246
247    protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
248    }
249
250    /**
251     * Register the current event list in currently in the interpreter as a safe
252     * configuration point.
253     *
254     * @since 0.9.30
255     */
256    public void registerSafeConfiguration(Model top) {
257        context.putObject(SAFE_JORAN_CONFIGURATION, top);
258    }
259
260    /**
261     * Recall the event list previously registered as a safe point.
262     */
263    public Model recallSafeConfiguration() {
264        return (Model) context.getObject(SAFE_JORAN_CONFIGURATION);
265    }
266
267}