001package org.apache.commons.digester3.xmlrules;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static java.util.Collections.unmodifiableSet;
023import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
024
025import java.io.File;
026import java.io.InputStream;
027import java.io.Reader;
028import java.io.StringReader;
029import java.net.MalformedURLException;
030import java.net.URL;
031import java.net.URLConnection;
032import java.util.ArrayList;
033import java.util.HashSet;
034import java.util.List;
035import java.util.Set;
036
037import org.apache.commons.digester3.Digester;
038import org.apache.commons.digester3.binder.AbstractRulesModule;
039import org.xml.sax.InputSource;
040
041/**
042 * {@link org.apache.commons.digester3.binder.RulesModule} implementation that allows loading rules from
043 * XML files.
044 *
045 * @since 3.0
046 */
047public abstract class FromXmlRulesModule
048    extends AbstractRulesModule
049{
050
051    private static final String DIGESTER_PUBLIC_ID = "-//Apache Commons //DTD digester-rules XML V1.0//EN";
052
053    private static final String DIGESTER_DTD_PATH = "digester-rules.dtd";
054
055    private final URL xmlRulesDtdUrl = FromXmlRulesModule.class.getResource( DIGESTER_DTD_PATH );
056
057    private final List<InputSource> inputSource = new ArrayList<InputSource>();
058
059    private final Set<String> systemIds = new HashSet<String>();
060
061    private String rootPath;
062
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    protected void configure()
068    {
069        if ( !inputSource.isEmpty() )
070        {
071            throw new IllegalStateException( "Re-entry is not allowed." );
072        }
073
074        try
075        {
076            loadRules();
077
078            XmlRulesModule xmlRulesModule = new XmlRulesModule( new NameSpaceURIRulesBinder( rulesBinder() ),
079                                                                getSystemIds(), rootPath );
080            Digester digester = newLoader( xmlRulesModule )
081                    .register( DIGESTER_PUBLIC_ID, xmlRulesDtdUrl.toString() )
082                    .setXIncludeAware( true )
083                    .setValidating( true )
084                    .newDigester();
085
086            for ( InputSource source : inputSource )
087            {
088                try
089                {
090                    digester.parse( source );
091                }
092                catch ( Exception e )
093                {
094                    addError( "Impossible to load XML defined in the InputSource '%s': %s", source.getSystemId(),
095                              e.getMessage() );
096                }
097            }
098        }
099        finally
100        {
101            inputSource.clear();
102        }
103    }
104
105    /**
106     *
107     */
108    protected abstract void loadRules();
109
110    /**
111     * Reads the XML rules from the given {@code org.xml.sax.InputSource}.
112     *
113     * @param inputSource The {@code org.xml.sax.InputSource} where reading the XML rules from.
114     */
115    protected final void loadXMLRules( InputSource inputSource )
116    {
117        if ( inputSource == null )
118        {
119            throw new IllegalArgumentException( "Argument 'inputSource' must be not null" );
120        }
121
122        this.inputSource.add( inputSource );
123
124        String systemId = inputSource.getSystemId();
125        if ( systemId != null && !systemIds.add( systemId ) )
126        {
127            addError( "XML rules file '%s' already bound", systemId );
128        }
129    }
130
131    /**
132     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.InputStream}.
133     *
134     * @param input The {@code java.io.InputStream} where reading the XML rules from.
135     */
136    protected final void loadXMLRules( InputStream input )
137    {
138        if ( input == null )
139        {
140            throw new IllegalArgumentException( "Argument 'input' must be not null" );
141        }
142
143        loadXMLRules( new InputSource( input ) );
144    }
145
146    /**
147     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.Reader}.
148     *
149     * @param reader The {@code java.io.Reader} where reading the XML rules from.
150     */
151    protected final void loadXMLRules( Reader reader )
152    {
153        if ( reader == null )
154        {
155            throw new IllegalArgumentException( "Argument 'input' must be not null" );
156        }
157
158        loadXMLRules( new InputSource( reader ) );
159    }
160
161    /**
162     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.File}.
163     *
164     * @param file The {@code java.io.File} where reading the XML rules from.
165     */
166    protected final void loadXMLRules( File file )
167    {
168        if ( file == null )
169        {
170            throw new IllegalArgumentException( "Argument 'input' must be not null" );
171        }
172
173        try
174        {
175            loadXMLRules( file.toURI().toURL() );
176        }
177        catch ( MalformedURLException e )
178        {
179            rulesBinder().addError( e );
180        }
181    }
182
183    /**
184     * Opens a new {@code org.xml.sax.InputSource} given a URI in String representation.
185     *
186     * @param uri The URI in String representation where reading the XML rules from.
187     */
188    protected final void loadXMLRules( String uri )
189    {
190        if ( uri == null )
191        {
192            throw new IllegalArgumentException( "Argument 'uri' must be not null" );
193        }
194
195        try
196        {
197            loadXMLRules( new URL( uri ) );
198        }
199        catch ( MalformedURLException e )
200        {
201            rulesBinder().addError( e );
202        }
203    }
204
205    /**
206     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.net.URL}.
207     *
208     * @param url The {@code java.net.URL} where reading the XML rules from.
209     */
210    protected final void loadXMLRules( URL url )
211    {
212        if ( url == null )
213        {
214            throw new IllegalArgumentException( "Argument 'url' must be not null" );
215        }
216
217        try
218        {
219            URLConnection connection = url.openConnection();
220            connection.setUseCaches( false );
221            InputStream stream = connection.getInputStream();
222            InputSource source = new InputSource( stream );
223            source.setSystemId( url.toExternalForm() );
224
225            loadXMLRules( source );
226        }
227        catch ( Exception e )
228        {
229            rulesBinder().addError( e );
230        }
231    }
232
233    /**
234     * Opens a new {@code org.xml.sax.InputSource} given an XML document in textual form.
235     *
236     * @param xmlText The XML document in textual form where reading the XML rules from.
237     */
238    protected final void loadXMLRulesFromText( String xmlText )
239    {
240        if ( xmlText == null )
241        {
242            throw new IllegalArgumentException( "Argument 'xmlText' must be not null" );
243        }
244
245        loadXMLRules( new StringReader( xmlText ) );
246    }
247
248    /**
249     * Set the root path (will be used when composing modules).
250     *
251     * @param rootPath The root path
252     */
253    protected final void useRootPath( String rootPath )
254    {
255        this.rootPath = rootPath;
256    }
257
258    /**
259     * Returns the XML source SystemIds load by this module.
260     *
261     * @return The XML source SystemIds load by this module
262     */
263    public final Set<String> getSystemIds()
264    {
265        return unmodifiableSet( systemIds );
266    }
267
268}