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.newConfigurationEndedEvent;
016import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationStartedEvent;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.URL;
023import java.net.URLConnection;
024import java.util.List;
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/LBCORE-105
062            // per http://jira.qos.ch/browse/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        }
186        context.fireConfigurationEvent(newConfigurationEndedEvent(this));
187
188    }
189
190    public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {
191        SaxEventRecorder recorder = new SaxEventRecorder(context);
192        recorder.recordEvents(inputSource);
193        return recorder;
194    }
195
196    public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {
197        buildSaxEventInterpreter(saxEvents);
198        playSaxEvents();
199        Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();
200        return top;
201    }
202
203    private void playSaxEvents() throws JoranException {
204        saxEventInterpreter.getEventPlayer().play();
205    }
206
207    public void processModel(Model model) {
208        buildModelInterpretationContext();
209        this.modelInterpretationContext.setTopModel(model);
210        modelInterpretationContext.setConfiguratorHint(this);
211        DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
212        addModelHandlerAssociations(defaultProcessor);
213
214        // disallow simultaneous configurations of the same context
215        synchronized (context.getConfigurationLock()) {
216            defaultProcessor.process(model);
217        }
218    }
219
220    /**
221     * Perform sanity check and issue warning if necessary.
222     *
223     * Default implementation does nothing.
224     *
225     * @param topModel
226     * @since 1.3.2 and 1.4.2
227     */
228    protected void sanityCheck(Model topModel) {
229
230    }
231
232    protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
233    }
234
235    /**
236     * Register the current event list in currently in the interpreter as a safe
237     * configuration point.
238     *
239     * @since 0.9.30
240     */
241    public void registerSafeConfiguration(Model top) {
242        context.putObject(SAFE_JORAN_CONFIGURATION, top);
243    }
244
245    /**
246     * Recall the event list previously registered as a safe point.
247     */
248    public Model recallSafeConfiguration() {
249        return (Model) context.getObject(SAFE_JORAN_CONFIGURATION);
250    }
251}