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 final void doConfigure(URL url) throws JoranException {
057        InputStream in = null;
058        try {
059            informContextOfURLUsedForConfiguration(getContext(), url);
060            URLConnection urlConnection = url.openConnection();
061            // per http://jira.qos.ch/browse/LOGBACK-117  LBCORE-105
062            // per http://jira.qos.ch/browse/LOGBACK-163  LBCORE-127
063            urlConnection.setUseCaches(false);
064
065            in = urlConnection.getInputStream();
066            doConfigure(in, url.toExternalForm());
067        } catch (IOException ioe) {
068            String errMsg = "Could not open URL [" + url + "].";
069            addError(errMsg, ioe);
070            throw new JoranException(errMsg, ioe);
071        } finally {
072            if (in != null) {
073                try {
074                    in.close();
075                } catch (IOException ioe) {
076                    String errMsg = "Could not close input stream";
077                    addError(errMsg, ioe);
078                    throw new JoranException(errMsg, ioe);
079                }
080            }
081        }
082    }
083
084    public final void doConfigure(String filename) throws JoranException {
085        doConfigure(new File(filename));
086    }
087
088    public final void doConfigure(File file) throws JoranException {
089        FileInputStream fis = null;
090        try {
091            URL url = file.toURI().toURL();
092            informContextOfURLUsedForConfiguration(getContext(), url);
093            fis = new FileInputStream(file);
094            doConfigure(fis, url.toExternalForm());
095        } catch (IOException ioe) {
096            String errMsg = "Could not open [" + file.getPath() + "].";
097            addError(errMsg, ioe);
098            throw new JoranException(errMsg, ioe);
099        } finally {
100            if (fis != null) {
101                try {
102                    fis.close();
103                } catch (java.io.IOException ioe) {
104                    String errMsg = "Could not close [" + file.getName() + "].";
105                    addError(errMsg, ioe);
106                    throw new JoranException(errMsg, ioe);
107                }
108            }
109        }
110    }
111
112    public static void informContextOfURLUsedForConfiguration(Context context, URL url) {
113        ConfigurationWatchListUtil.setMainWatchURL(context, url);
114    }
115
116    public final void doConfigure(InputStream inputStream) throws JoranException {
117        doConfigure(new InputSource(inputStream));
118    }
119
120    public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
121        InputSource inputSource = new InputSource(inputStream);
122        inputSource.setSystemId(systemId);
123        doConfigure(inputSource);
124    }
125
126    protected abstract void addElementSelectorAndActionAssociations(RuleStore rs);
127
128    protected abstract void setImplicitRuleSupplier(SaxEventInterpreter interpreter);
129
130    protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) {
131        // nothing by default
132    }
133
134    protected ElementPath initialElementPath() {
135        return new ElementPath();
136    }
137
138    protected void buildSaxEventInterpreter(List<SaxEvent> saxEvents) {
139        RuleStore rs = getRuleStore();
140        addElementSelectorAndActionAssociations(rs);
141        this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);
142        SaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext();
143        interpretationContext.setContext(context);
144        setImplicitRuleSupplier(saxEventInterpreter);
145    }
146
147    public RuleStore getRuleStore() {
148        if(this.ruleStore == null) {
149            this.ruleStore = new SimpleRuleStore(context);
150        }
151        return this.ruleStore;
152    }
153
154    protected void buildModelInterpretationContext() {
155        this.modelInterpretationContext = new ModelInterpretationContext(context);
156        addDefaultNestedComponentRegistryRules(modelInterpretationContext.getDefaultNestedComponentRegistry());
157    }
158
159    // this is the most inner form of doConfigure whereto other doConfigure
160    // methods ultimately delegate
161    public final void doConfigure(final InputSource inputSource) throws JoranException {
162
163        context.fireConfigurationEvent(newConfigurationStartedEvent(this));
164        long threshold = System.currentTimeMillis();
165
166        SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
167        List<SaxEvent> saxEvents = recorder.getSaxEventList();
168        if (saxEvents.isEmpty()) {
169            addWarn("Empty sax event list");
170            return;
171        }
172        Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
173        if (top == null) {
174            addError(ErrorCodes.EMPTY_MODEL_STACK);
175            return;
176        }
177        sanityCheck(top);
178        processModel(top);
179
180        // no exceptions at this level
181        StatusUtil statusUtil = new StatusUtil(context);
182        if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
183            addInfo("Registering current configuration as safe fallback point");
184            registerSafeConfiguration(top);
185            context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));
186        } else {
187            context.fireConfigurationEvent(newConfigurationEndedWithXMLParsingErrorsEvent(this));
188        }
189
190
191    }
192
193    public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {
194        SaxEventRecorder recorder = new SaxEventRecorder(context);
195        recorder.recordEvents(inputSource);
196        return recorder;
197    }
198
199    public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {
200        buildSaxEventInterpreter(saxEvents);
201        playSaxEvents();
202        Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();
203        return top;
204    }
205
206    private void playSaxEvents() throws JoranException {
207        saxEventInterpreter.getEventPlayer().play();
208    }
209
210    public void processModel(Model model) {
211        buildModelInterpretationContext();
212        this.modelInterpretationContext.setTopModel(model);
213        modelInterpretationContext.setConfiguratorHint(this);
214        DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
215        addModelHandlerAssociations(defaultProcessor);
216
217        // disallow simultaneous configurations of the same context
218        ReentrantLock configurationLock   = context.getConfigurationLock();
219
220        try {
221            configurationLock.lock();
222            defaultProcessor.process(model);
223        } finally {
224            configurationLock.unlock();
225        }
226    }
227
228    /**
229     * Perform sanity check and issue warning if necessary.
230     *
231     * Default implementation does nothing.
232     *
233     * @param topModel
234     * @since 1.3.2 and 1.4.2
235     */
236    protected void sanityCheck(Model topModel) {
237
238    }
239
240    protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
241    }
242
243    /**
244     * Register the current event list in currently in the interpreter as a safe
245     * configuration point.
246     *
247     * @since 0.9.30
248     */
249    public void registerSafeConfiguration(Model top) {
250        context.putObject(SAFE_JORAN_CONFIGURATION, top);
251    }
252
253    /**
254     * Recall the event list previously registered as a safe point.
255     */
256    public Model recallSafeConfiguration() {
257        return (Model) context.getObject(SAFE_JORAN_CONFIGURATION);
258    }
259}