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.HashSet;
033import java.util.Set;
034
035import org.apache.commons.digester3.Digester;
036import org.apache.commons.digester3.binder.AbstractRulesModule;
037import org.xml.sax.InputSource;
038
039/**
040 * {@link org.apache.commons.digester3.binder.RulesModule} implementation that allows loading rules from
041 * XML files.
042 *
043 * @since 3.0
044 */
045public abstract class FromXmlRulesModule
046    extends AbstractRulesModule
047{
048
049    private static final String DIGESTER_PUBLIC_ID = "-//Apache Commons //DTD digester-rules XML V1.0//EN";
050
051    private static final String DIGESTER_DTD_PATH = "digester-rules.dtd";
052
053    private final URL xmlRulesDtdUrl = FromXmlRulesModule.class.getResource( DIGESTER_DTD_PATH );
054
055    private final Set<String> systemIds = new HashSet<String>();
056
057    private String rootPath;
058
059    /**
060     * {@inheritDoc}
061     */
062    @Override
063    protected void configure()
064    {
065        if ( !systemIds.isEmpty() )
066        {
067            throw new IllegalStateException( "Re-entry is not allowed." );
068        }
069
070        try
071        {
072            loadRules();
073        }
074        finally
075        {
076            systemIds.clear();
077        }
078    }
079
080    /**
081     *
082     */
083    protected abstract void loadRules();
084
085    /**
086     * Reads the XML rules from the given {@code org.xml.sax.InputSource}.
087     *
088     * @param inputSource The {@code org.xml.sax.InputSource} where reading the XML rules from.
089     */
090    protected final void loadXMLRules( InputSource inputSource )
091    {
092        if ( inputSource == null )
093        {
094            throw new IllegalArgumentException( "Argument 'inputSource' must be not null" );
095        }
096
097        String systemId = inputSource.getSystemId();
098        if ( systemId != null && !systemIds.add( systemId ) )
099        {
100            addError( "XML rules file '%s' already bound", systemId );
101        }
102
103        XmlRulesModule xmlRulesModule = new XmlRulesModule( new NameSpaceURIRulesBinder( rulesBinder() ),
104                                                            getSystemIds(), rootPath );
105        Digester digester = newLoader( xmlRulesModule )
106                .register( DIGESTER_PUBLIC_ID, xmlRulesDtdUrl.toString() )
107                .setXIncludeAware( true )
108                .setValidating( true )
109                .newDigester();
110
111        try
112        {
113            digester.parse( inputSource );
114        }
115        catch ( Exception e )
116        {
117            addError( "Impossible to load XML defined in the InputSource '%s': %s", inputSource.getSystemId(),
118                      e.getMessage() );
119        }
120    }
121
122    /**
123     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.InputStream}.
124     *
125     * @param input The {@code java.io.InputStream} where reading the XML rules from.
126     */
127    protected final void loadXMLRules( InputStream input )
128    {
129        if ( input == null )
130        {
131            throw new IllegalArgumentException( "Argument 'input' must be not null" );
132        }
133
134        loadXMLRules( new InputSource( input ) );
135    }
136
137    /**
138     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.Reader}.
139     *
140     * @param reader The {@code java.io.Reader} where reading the XML rules from.
141     */
142    protected final void loadXMLRules( Reader reader )
143    {
144        if ( reader == null )
145        {
146            throw new IllegalArgumentException( "Argument 'input' must be not null" );
147        }
148
149        loadXMLRules( new InputSource( reader ) );
150    }
151
152    /**
153     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.File}.
154     *
155     * @param file The {@code java.io.File} where reading the XML rules from.
156     */
157    protected final void loadXMLRules( File file )
158    {
159        if ( file == null )
160        {
161            throw new IllegalArgumentException( "Argument 'input' must be not null" );
162        }
163
164        try
165        {
166            loadXMLRules( file.toURI().toURL() );
167        }
168        catch ( MalformedURLException e )
169        {
170            rulesBinder().addError( e );
171        }
172    }
173
174    /**
175     * Opens a new {@code org.xml.sax.InputSource} given a URI in String representation.
176     *
177     * @param uri The URI in String representation where reading the XML rules from.
178     */
179    protected final void loadXMLRules( String uri )
180    {
181        if ( uri == null )
182        {
183            throw new IllegalArgumentException( "Argument 'uri' must be not null" );
184        }
185
186        try
187        {
188            loadXMLRules( new URL( uri ) );
189        }
190        catch ( MalformedURLException e )
191        {
192            rulesBinder().addError( e );
193        }
194    }
195
196    /**
197     * Opens a new {@code org.xml.sax.InputSource} given a {@code java.net.URL}.
198     *
199     * @param url The {@code java.net.URL} where reading the XML rules from.
200     */
201    protected final void loadXMLRules( URL url )
202    {
203        if ( url == null )
204        {
205            throw new IllegalArgumentException( "Argument 'url' must be not null" );
206        }
207
208        try
209        {
210            URLConnection connection = url.openConnection();
211            connection.setUseCaches( false );
212            InputStream stream = connection.getInputStream();
213            InputSource source = new InputSource( stream );
214            source.setSystemId( url.toExternalForm() );
215
216            loadXMLRules( source );
217        }
218        catch ( Exception e )
219        {
220            rulesBinder().addError( e );
221        }
222    }
223
224    /**
225     * Opens a new {@code org.xml.sax.InputSource} given an XML document in textual form.
226     *
227     * @param xmlText The XML document in textual form where reading the XML rules from.
228     */
229    protected final void loadXMLRulesFromText( String xmlText )
230    {
231        if ( xmlText == null )
232        {
233            throw new IllegalArgumentException( "Argument 'xmlText' must be not null" );
234        }
235
236        loadXMLRules( new StringReader( xmlText ) );
237    }
238
239    /**
240     * Set the root path (will be used when composing modules).
241     *
242     * @param rootPath The root path
243     */
244    protected final void useRootPath( String rootPath )
245    {
246        this.rootPath = rootPath;
247    }
248
249    /**
250     * Returns the XML source SystemIds load by this module.
251     *
252     * @return The XML source SystemIds load by this module
253     */
254    public final Set<String> getSystemIds()
255    {
256        return unmodifiableSet( systemIds );
257    }
258
259}