001    //$HeadURL: https://sushibar/svn/deegree/base/trunk/resources/eclipse/svn_classfile_header_template.xml $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    
037    package org.deegree.tools.xml;
038    
039    import static java.lang.System.out;
040    import static org.deegree.framework.log.LoggerFactory.getLogger;
041    import static org.deegree.framework.xml.DOMPrinter.nodeToString;
042    import static org.deegree.framework.xml.XMLTools.getNode;
043    import static org.deegree.framework.xml.XMLTools.getNodes;
044    import static org.deegree.ogcbase.CommonNamespaces.getNamespaceContext;
045    
046    import java.io.BufferedReader;
047    import java.io.File;
048    import java.io.IOException;
049    import java.io.InputStreamReader;
050    import java.net.MalformedURLException;
051    import java.net.URL;
052    import java.util.LinkedList;
053    import java.util.List;
054    import java.util.StringTokenizer;
055    
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.util.Pair;
058    import org.deegree.framework.util.StringPair;
059    import org.deegree.framework.xml.NamespaceContext;
060    import org.deegree.framework.xml.XMLFragment;
061    import org.deegree.framework.xml.XMLParsingException;
062    import org.jaxen.JaxenException;
063    import org.jaxen.XPath;
064    import org.jaxen.dom.DOMXPath;
065    import org.w3c.dom.Node;
066    import org.xml.sax.SAXException;
067    
068    /**
069     * <code>SimpleValidator</code> is a simple xpath based "validator". It can be used to crudely
070     * check XML documents for existing nodes. A sample rule file can be found right beneath in this
071     * package.
072     *
073     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
074     * @author last edited by: $Author:$
075     *
076     * @version $Revision:$, $Date:$
077     */
078    public class SimpleValidator {
079    
080        static final NamespaceContext nsContext = getNamespaceContext();
081    
082        private static final ILogger LOG = getLogger( SimpleValidator.class );
083    
084        private LinkedList<Rule> rules;
085    
086        /**
087         * Initializes from configuration URL. File should be encoded in UTF-8.
088         *
089         * @param config
090         * @throws IOException
091         */
092        public SimpleValidator( URL config ) throws IOException {
093            rules = new LinkedList<Rule>();
094    
095            BufferedReader in = new BufferedReader( new InputStreamReader( config.openStream() ) );
096    
097            StringBuffer buf = new StringBuffer( 65536 );
098            String s;
099            while ( ( s = in.readLine() ) != null ) {
100                if ( s.startsWith( "#" ) ) {
101                    continue;
102                }
103                buf.append( s ).append( " " );
104            }
105    
106            in.close();
107    
108            StringTokenizer tok = new StringTokenizer( buf.toString() );
109            String cur = tok.nextToken();
110            while ( tok.hasMoreTokens() ) {
111                Pair<Rule, String> p = parseRule( tok, cur );
112                cur = p.second;
113                rules.add( p.first );
114            }
115    
116            LOG.logDebug( "Parsed rule file with " + rules.size() + " rules." );
117        }
118    
119        private static Pair<Rule, String> parseRule( StringTokenizer tok, String first )
120                                throws IOException {
121            String id = first;
122            String s = tok.nextToken();
123    
124            if ( s.equalsIgnoreCase( "if" ) ) {
125                String test = tok.nextToken();
126                if ( !tok.nextToken().equalsIgnoreCase( "then" ) ) {
127                    throw new IOException( "Missing 'then' after " + test + "." );
128                }
129    
130                Pair<Rule, String> then = parseRule( tok, tok.nextToken() );
131    
132                return new Pair<Rule, String>( new Rule( id, test, then.first ), then.second );
133            }
134    
135            if ( s.equalsIgnoreCase( "oneof" ) ) {
136                String base = tok.nextToken();
137                List<String> choices = new LinkedList<String>();
138    
139                s = tok.nextToken();
140                while ( tok.hasMoreTokens() && s.equalsIgnoreCase( "choice" ) ) {
141                    choices.add( tok.nextToken() );
142                    if ( tok.hasMoreTokens() ) {
143                        s = tok.nextToken();
144                    }
145                }
146    
147                return new Pair<Rule, String>( new Rule( id, base, choices ), s );
148            }
149    
150            boolean isBoolean = false;
151    
152            if ( s.equalsIgnoreCase( "istrue" ) ) {
153                s = tok.nextToken();
154                // if ( !s.startsWith( "boolean(" ) ) {
155                // s = "boolean(" + s + ")";
156                // }
157                isBoolean = true;
158            }
159    
160            String next = tok.hasMoreTokens() ? tok.nextToken() : null;
161            return new Pair<Rule, String>( new Rule( id, s, isBoolean ), next );
162        }
163    
164        /**
165         * @param n
166         * @return a list of errors. A pair will include the id of the failed rule, and the context node
167         *         as string (if applicable) or null (if not).
168         */
169        public LinkedList<StringPair> validate( Node n ) {
170            LinkedList<StringPair> list = new LinkedList<StringPair>();
171    
172            for ( Rule r : rules ) {
173                if ( !r.eval( n ) ) {
174                    list.addAll( r.errors );
175                }
176            }
177    
178            return list;
179        }
180    
181        /**
182         * @param args
183         */
184        public static void main( String[] args ) {
185            if ( args.length < 2 ) {
186                out.println( "Usage:" );
187                out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
188                return;
189            }
190    
191            boolean verbose = args.length > 2 && args[2].equals( "-v" );
192    
193            try {
194                XMLFragment doc = new XMLFragment( new File( args[1] ) );
195                SimpleValidator val = new SimpleValidator( new File( args[0] ).toURI().toURL() );
196    
197                LinkedList<StringPair> errors = val.validate( doc.getRootElement() );
198                if ( errors.size() > 0 ) {
199                    out.println( "Errors:" );
200                    for ( StringPair p : errors ) {
201                        if ( verbose ) {
202                            out.println( p );
203                        } else {
204                            out.print( p.first );
205                            if ( errors.indexOf( p ) != errors.size() - 1 ) {
206                                out.print( ", " );
207                            }
208                        }
209                    }
210                    if ( !verbose ) {
211                        out.println();
212                    }
213                } else {
214                    out.println( "All rules passed." );
215                }
216            } catch ( MalformedURLException e ) {
217                out.println( "Error: one of the files is not a valid filename." );
218                out.println( "Usage:" );
219                out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
220            } catch ( IOException e ) {
221                out.println( "Error: second file cannot be read." );
222                out.println( "Usage:" );
223                out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
224            } catch ( SAXException e ) {
225                out.println( "Error: second file is not parsable XML." );
226                out.println( "Usage:" );
227                out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
228            }
229        }
230    
231        private static class Rule {
232    
233            String id;
234    
235            List<StringPair> errors;
236    
237            private String xpath;
238    
239            private Rule then;
240    
241            private List<String> choices;
242    
243            private boolean isBoolean;
244    
245            Rule( String id, String xpath ) {
246                this.xpath = xpath;
247                this.id = id;
248                errors = new LinkedList<StringPair>();
249            }
250    
251            Rule( String id, String xpath, Rule then ) {
252                this( id, xpath );
253                this.then = then;
254            }
255    
256            Rule( String id, String base, List<String> choices ) {
257                this( id, base );
258                this.choices = choices;
259            }
260    
261            Rule( String id, String xpath, boolean isBoolean ) {
262                this( id, xpath );
263                this.isBoolean = isBoolean;
264            }
265    
266            boolean eval( Node n ) {
267                try {
268    
269                    if ( isBoolean ) {
270                        try {
271                            XPath xpath = new DOMXPath( this.xpath );
272                            xpath.setNamespaceContext( nsContext );
273                            boolean res = xpath.booleanValueOf( n );
274                            if ( !res ) {
275                                errors.add( new StringPair( id, "The IsTrue expression evaluated to false." ) );
276                            }
277                            return res;
278                        } catch ( JaxenException e ) {
279                            errors.add( new StringPair( id, "The xpath expression contained an error: "
280                                                            + e.getLocalizedMessage() ) );
281                            return false;
282                        }
283                    }
284    
285                    if ( choices != null ) {
286                        boolean isOk = false;
287    
288                        Node baseNode = getNode( n, xpath, nsContext );
289    
290                        if ( baseNode == null ) {
291                            errors.add( new StringPair( id, "(node not found)" ) );
292                            return false;
293                        }
294    
295                        for ( String x : choices ) {
296                            Node tmp = getNode( baseNode, x, nsContext );
297                            isOk = isOk || tmp != null;
298                        }
299    
300                        if ( !isOk ) {
301                            errors.add( new StringPair( id, nodeToString( baseNode, "UTF-8" ) ) );
302                        }
303    
304                        return isOk;
305                    }
306    
307                    List<Node> tmps = getNodes( n, xpath, nsContext );
308    
309                    if ( then == null ) {
310                        if ( tmps.size() == 0 ) {
311                            errors.add( new StringPair( id, null ) );
312                        }
313                        return tmps.size() != 0;
314                    }
315    
316                    if ( tmps.size() == 0 ) {
317                        return true;
318                    }
319    
320                    boolean res = true;
321    
322                    for ( Node tmp : tmps ) {
323                        if ( then.eval( tmp ) ) {
324                            continue;
325                        }
326    
327                        res = false;
328    
329                        errors.add( new StringPair( then.id, nodeToString( tmp, "UTF-8" ) ) );
330                    }
331    
332                    return res;
333                } catch ( XMLParsingException e ) {
334                    return false;
335                }
336            }
337    
338        }
339    
340    }