001    package 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    
022    import static java.util.Collections.unmodifiableSet;
023    import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
024    
025    import java.io.File;
026    import java.io.InputStream;
027    import java.io.Reader;
028    import java.io.StringReader;
029    import java.net.MalformedURLException;
030    import java.net.URL;
031    import java.net.URLConnection;
032    import java.util.ArrayList;
033    import java.util.HashSet;
034    import java.util.List;
035    import java.util.Set;
036    
037    import org.apache.commons.digester3.Digester;
038    import org.apache.commons.digester3.binder.AbstractRulesModule;
039    import 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     */
047    public 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    }