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 )
126            {
127                if ( !systemIds.add( systemId ) )
128                {
129                    addError( "XML rules file '%s' already bound", systemId );
130                }
131            }
132        }
133    
134        /**
135         * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.InputStream}.
136         *
137         * @param input The {@code java.io.InputStream} where reading the XML rules from.
138         */
139        protected final void loadXMLRules( InputStream input )
140        {
141            if ( input == null )
142            {
143                throw new IllegalArgumentException( "Argument 'input' must be not null" );
144            }
145    
146            loadXMLRules( new InputSource( input ) );
147        }
148    
149        /**
150         * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.Reader}.
151         *
152         * @param reader The {@code java.io.Reader} where reading the XML rules from.
153         */
154        protected final void loadXMLRules( Reader reader )
155        {
156            if ( reader == null )
157            {
158                throw new IllegalArgumentException( "Argument 'input' must be not null" );
159            }
160    
161            loadXMLRules( new InputSource( reader ) );
162        }
163    
164        /**
165         * Opens a new {@code org.xml.sax.InputSource} given a {@code java.io.File}.
166         *
167         * @param file The {@code java.io.File} where reading the XML rules from.
168         */
169        protected final void loadXMLRules( File file )
170        {
171            if ( file == null )
172            {
173                throw new IllegalArgumentException( "Argument 'input' must be not null" );
174            }
175    
176            try
177            {
178                loadXMLRules( file.toURI().toURL() );
179            }
180            catch ( MalformedURLException e )
181            {
182                rulesBinder().addError( e );
183            }
184        }
185    
186        /**
187         * Opens a new {@code org.xml.sax.InputSource} given a URI in String representation.
188         *
189         * @param uri The URI in String representation where reading the XML rules from.
190         */
191        protected final void loadXMLRules( String uri )
192        {
193            if ( uri == null )
194            {
195                throw new IllegalArgumentException( "Argument 'uri' must be not null" );
196            }
197    
198            try
199            {
200                loadXMLRules( new URL( uri ) );
201            }
202            catch ( MalformedURLException e )
203            {
204                rulesBinder().addError( e );
205            }
206        }
207    
208        /**
209         * Opens a new {@code org.xml.sax.InputSource} given a {@code java.net.URL}.
210         *
211         * @param url The {@code java.net.URL} where reading the XML rules from.
212         */
213        protected final void loadXMLRules( URL url )
214        {
215            if ( url == null )
216            {
217                throw new IllegalArgumentException( "Argument 'url' must be not null" );
218            }
219    
220            try
221            {
222                URLConnection connection = url.openConnection();
223                connection.setUseCaches( false );
224                InputStream stream = connection.getInputStream();
225                InputSource source = new InputSource( stream );
226                source.setSystemId( url.toExternalForm() );
227    
228                loadXMLRules( source );
229            }
230            catch ( Exception e )
231            {
232                rulesBinder().addError( e );
233            }
234        }
235    
236        /**
237         * Opens a new {@code org.xml.sax.InputSource} given an XML document in textual form.
238         *
239         * @param xmlText The XML document in textual form where reading the XML rules from.
240         */
241        protected final void loadXMLRulesFromText( String xmlText )
242        {
243            if ( xmlText == null )
244            {
245                throw new IllegalArgumentException( "Argument 'xmlText' must be not null" );
246            }
247    
248            loadXMLRules( new StringReader( xmlText ) );
249        }
250    
251        /**
252         * Set the root path (will be used when composing modules).
253         *
254         * @param rootPath The root path
255         */
256        protected final void useRootPath( String rootPath )
257        {
258            this.rootPath = rootPath;
259        }
260    
261        /**
262         * Returns the XML source SystemIds load by this module.
263         *
264         * @return The XML source SystemIds load by this module
265         */
266        public final Set<String> getSystemIds()
267        {
268            return unmodifiableSet( systemIds );
269        }
270    
271    }