001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/enterprise/servlet/WFSRequestMapping.java $
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    package org.deegree.enterprise.servlet;
037    
038    import java.io.BufferedReader;
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.io.InputStreamReader;
042    import java.net.URI;
043    import java.net.URISyntaxException;
044    import java.net.URL;
045    import java.util.ArrayList;
046    import java.util.List;
047    import java.util.Properties;
048    
049    import org.deegree.datatypes.QualifiedName;
050    import org.deegree.framework.log.ILogger;
051    import org.deegree.framework.log.LoggerFactory;
052    import org.deegree.framework.util.StringTools;
053    import org.deegree.framework.xml.NamespaceContext;
054    import org.deegree.framework.xml.XMLTools;
055    import org.deegree.ogcbase.CommonNamespaces;
056    import org.deegree.ogcbase.PropertyPath;
057    import org.deegree.ogcbase.PropertyPathFactory;
058    import org.deegree.ogcbase.PropertyPathStep;
059    import org.deegree.ogcwebservices.InvalidParameterValueException;
060    import org.w3c.dom.Node;
061    
062    /**
063     * This class respectively its method {@link #mapPropertyValue(Node)} can be used by XSLT scripts to map a node value
064     * (key) to another, corresponding value (value). The mappings are taken from the properties file
065     * <code>org.deegree.enterprise.servlet.wfsrequestmapping.properties</code>. If no matching value for a key is defined
066     * in the properties file, the returned <code>String</code> is null.
067     * <p>
068     * The node reference passed to this method must point to an element that contains a single text node, e.g.
069     * &lt;PropertyName&gt;/MyProperty/value&lt;/PropertyName&gt;
070     * </p>
071     * <p>
072     * If a special behavior is needed by a deegree WFS instance and/or you do not want to edit the default properties and
073     * use your own one you should write a class that extends this or it as pattern.
074     *
075     * @see #mapPropertyValue(Node)
076     * @see java.util.Properties
077     *
078     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
079     * @author last edited by: $Author: mschneider $
080     *
081     * @version $Revision: 21632 $, $Date: 2009-12-23 10:31:16 +0100 (Mi, 23. Dez 2009) $
082     */
083    public class WFSRequestMapping {
084    
085        private static ILogger LOG = LoggerFactory.getLogger( WFSRequestMapping.class );
086    
087        /** TODO why is this here and ununused? */
088        protected static String propertiesFile = "wfsrequestmapping.properties";
089    
090        private static Properties mapping = null;
091    
092        private static Properties prefixToNs = new Properties();
093    
094        private static Properties nsToPrefix = new Properties();
095    
096        private static NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
097        static {
098            mapping = new Properties();
099            try {
100                URL url = WFSRequestMapping.class.getResource( "/wfsrequestmapping.properties" );
101                InputStream is = null;
102                if ( url != null ) {
103                    is = url.openStream();
104                } else {
105                    is = WFSRequestMapping.class.getResourceAsStream( "wfsrequestmapping.properties" );
106                }
107                InputStreamReader isr = new InputStreamReader( is );
108                BufferedReader br = new BufferedReader( isr );
109                String line = null;
110                while ( ( line = br.readLine() ) != null ) {
111                    if ( !line.trim().startsWith( "#" ) ) {
112                        String[] tmp = StringTools.toArray( line, "=", false );
113                        if ( tmp != null && tmp[0] != null && tmp[1] != null ) {
114                            if ( tmp[0].startsWith( "$namespace." ) ) {
115                                String pre = tmp[0].substring( tmp[0].indexOf( '.' ) + 1, tmp[0].length() );
116                                prefixToNs.put( pre, tmp[1] );
117                                nsToPrefix.put( tmp[1], pre );
118                                try {
119                                    nsc.addNamespace( pre, new URI( tmp[1] ) );
120                                } catch ( URISyntaxException e ) {
121                                    e.printStackTrace();
122                                }
123                            } else {
124                                mapping.put( tmp[0], tmp[1] );
125                            }
126                        }
127                    }
128                }
129            } catch ( IOException e ) {
130                e.printStackTrace();
131            }
132        }
133    
134        /**
135         * This method can be used by XSLT scripts to map a node value (key) to another, corresponding value (value). The
136         * mappings are taken from the properties file
137         * <code>org.deegree.enterprise.servlet.wfsrequestmapping.properties</code>. If no matching value for a key is
138         * defined in the properties file, the returned <code>String</code> is null.
139         * <p>
140         * The node reference passed to this method must point to an element that contains a single text node, e.g.
141         * &lt;PropertyName&gt;/MyProperty/value&lt;/PropertyName&gt;
142         * </p>
143         *
144         * @param node
145         *            node that will be mapped
146         * @return mapping for the node as an XPath, null if no mapping is defined
147         */
148        public static String mapPropertyValue( Node node ) {
149    
150            String nde = null;
151            String key = null;
152            try {
153                nde = XMLTools.getNodeAsString( node, ".", nsc, null );
154                if ( nde.startsWith( "/" ) ) {
155                    key = '.' + nde;
156                } else if ( nde.startsWith( "." ) ) {
157                    key = nde;
158                } else {
159                    key = "./" + nde;
160                }
161            } catch ( Exception e ) {
162                e.printStackTrace();
163            }
164    
165            if ( mapping.getProperty( key ) != null ) {
166                nde = mapping.getProperty( key );
167            }
168            LOG.logDebug( "mapped property: " + nde );
169            return nde;
170        }
171    
172        /**
173         * This method can be used by XSLT scripts to map a node value (key) to another, corresponding value (value). The
174         * mappings are taken from the properties file
175         * <code>org.deegree.enterprise.servlet.wfsrequestmapping.properties</code>. If no matching value for a key is
176         * defined in the properties file, the returned <code>String</code> is null.
177         * <p>
178         * The node reference passed to this method must point to an element that contains a single text node, e.g.
179         * &lt;PropertyName&gt;/MyProperty/value&lt;/PropertyName&gt;
180         * </p>
181         *
182         * @param node
183         *            node that will be mapped
184         * @param typeName
185         *            feature type name
186         * @return mapping for the node as an XPath, null if no mapping is defined
187         */
188        public static String mapPropertyValue( Node node, String typeName ) {
189            String s = null;
190            try {
191                s = XMLTools.getNodeAsString( node, ".", nsc, null );
192                if ( s.startsWith( "/" ) ) {
193                    s = s.substring( 1, s.length() );
194                } else if ( s.startsWith( "." ) ) {
195                    s = s.substring( 2, s.length() );
196                }
197            } catch ( Exception e ) {
198                e.printStackTrace();
199            }
200            if ( s.indexOf( "SI_Gazetteer" ) > -1 ) {
201                s = ( new StringBuilder( "./" ) ).append( s ).toString();
202            } else if ( s.indexOf( typeName ) < 0 ) {
203                s = ( new StringBuilder( String.valueOf( typeName ) ) ).append( '/' ).append( s ).toString();
204            }
205            try {
206                PropertyPath pp = transformToPropertyPath( s, nsc, null );
207                s = pp.getAsString();
208            } catch ( InvalidParameterValueException e ) {
209                e.printStackTrace();
210            }
211            LOG.logDebug( ( new StringBuilder( "mapped property: " ) ).append( mapping.getProperty( s ) ).toString() );
212            String st = mapping.getProperty( s );
213            if ( st == null ) {
214                st = s;
215            }
216            return st;
217        }
218    
219        /**
220         * This method can be used by XSLT scripts to map a node value (key) to another, corresponding value (value). The
221         * mappings are taken from the properties file
222         * <code>org.deegree.enterprise.servlet.wfsrequestmapping.properties</code>. If no matching value for a key is
223         * defined in the properties file, the returned <code>String</code> is null.
224         * <p>
225         * The node reference passed to this method must point to an element that contains a single text node, e.g.
226         * &lt;PropertyName&gt;/MyProperty/value&lt;/PropertyName&gt;
227         * </p>
228         * 
229         * @param node
230         *            node that will be mapped
231         * @param typeName
232         *            feature type name
233         * @param nsp
234         * @return mapping for the node as an XPath, null if no mapping is defined
235         */
236        public static String mapPropertyValue( Node node, String typeName, String nsp ) {
237            String s = null;
238            try {
239                s = XMLTools.getNodeAsString( node, ".", nsc, null );
240                if ( s.startsWith( "/" ) ) {
241                    s = s.substring( 1, s.length() );
242                } else if ( s.startsWith( "." ) ) {
243                    s = s.substring( 2, s.length() );
244                }
245            } catch ( Exception e ) {
246                e.printStackTrace();
247            }
248            if ( s.indexOf( "SI_Gazetteer" ) > -1 ) {
249                s = ( new StringBuilder( "./" ) ).append( s ).toString();
250            } else if ( s.indexOf( typeName ) < 0 ) {
251                s = ( new StringBuilder( String.valueOf( typeName ) ) ).append( '/' ).append( s ).toString();
252            }
253            try {
254                PropertyPath pp = transformToPropertyPath( s, nsc, createNsp( nsp ) );
255                s = pp.getAsString();
256            } catch ( InvalidParameterValueException e ) {
257                e.printStackTrace();
258            }
259            LOG.logDebug( ( new StringBuilder( "mapped property: " ) ).append( mapping.getProperty( s ) ).toString() );
260            String st = mapping.getProperty( s );
261            if ( st == null ) {
262                st = s;
263            }
264            return st;
265        }
266    
267        private static Properties createNsp( String nspDec ) {
268            Properties nsp = new Properties();
269            String[] tmp = StringTools.toArray( nspDec, ";", false );
270            for ( int i = 0; i < tmp.length; i++ ) {
271                int p = tmp[i].indexOf( ':' );
272                String pre = tmp[i].substring( 0, p );
273                String val = tmp[i].substring( p + 1 );
274                nsp.put( pre, val );
275            }
276            return nsp;
277        }
278    
279        private static PropertyPath transformToPropertyPath( String propName, NamespaceContext nsContext, Properties nsp )
280                                throws InvalidParameterValueException {
281            String steps[] = propName.split( "/" );
282            List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>( steps.length );
283            for ( int i = 0; i < steps.length; i++ ) {
284                PropertyPathStep propertyStep = null;
285                QualifiedName propertyName = null;
286                String step = steps[i];
287                boolean isAttribute = false;
288                boolean isIndexed = false;
289                int selectedIndex = -1;
290                if ( step.startsWith( "@" ) ) {
291                    if ( i != steps.length - 1 ) {
292                        StringBuilder msg = new StringBuilder( "PropertyName '" );
293                        msg.append( propName ).append( "' is illegal: the attribute specifier may only " );
294                        msg.append( "be used for the final step." );
295                        throw new InvalidParameterValueException( msg.toString() );
296                    }
297                    step = step.substring( 1 );
298                    isAttribute = true;
299                }
300                if ( step.endsWith( "]" ) ) {
301                    if ( isAttribute ) {
302                        StringBuilder msg = new StringBuilder( "PropertyName '" );
303                        msg.append( propName ).append( "' is illegal: if the attribute specifier ('@') is used, " );
304                        msg.append( "index selection ('[...']) is not possible." );
305                        throw new InvalidParameterValueException( msg.toString() );
306                    }
307                    int bracketPos = step.indexOf( '[' );
308                    if ( bracketPos < 0 ) {
309                        StringBuilder msg = new StringBuilder( "PropertyName '" );
310                        msg.append( propName ).append( "' is illegal. No opening brackets found for step '" );
311                        msg.append( step ).append( "'." );
312                        throw new InvalidParameterValueException( msg.toString() );
313                    }
314                    try {
315                        selectedIndex = Integer.parseInt( step.substring( bracketPos + 1, step.length() - 1 ) );
316                    } catch ( NumberFormatException e ) {
317                        LOG.logError( e.getMessage(), e );
318                        StringBuilder msg = new StringBuilder( "PropertyName '" );
319                        msg.append( propName ).append( "' is illegal. Specified index '" );
320                        msg.append( step.substring( bracketPos + 1, step.length() - 1 ) ).append( "' is not a number." );
321                        throw new InvalidParameterValueException( msg.toString() );
322                    }
323                    step = step.substring( 0, bracketPos );
324                    isIndexed = true;
325                }
326                int colonPos = step.indexOf( ':' );
327                String prefix = "";
328                String localName = step;
329                if ( colonPos > 0 ) {
330                    prefix = step.substring( 0, colonPos );
331                    localName = step.substring( colonPos + 1 );
332                }
333                URI nsURI = null;
334                if ( nsp == null ) {
335                    nsURI = nsContext.getURI( prefix );
336                } else {
337                    try {
338                        nsURI = new URI( nsp.getProperty( prefix ) );
339                        prefix = nsToPrefix.getProperty( nsURI.toString() );                    
340                    } catch ( URISyntaxException e ) {
341                        throw new InvalidParameterValueException( e.getMessage(), e );
342                    }
343                }
344                if ( nsURI == null && prefix.length() > 0 ) {
345                    String msg = ( new StringBuilder( "PropertyName '" ) ).append( propName ).append(
346                                                                                                      "' uses an unbound namespace prefix: " ).append(
347                                                                                                                                                       prefix ).toString();
348                    throw new InvalidParameterValueException( msg );
349                }
350                propertyName = new QualifiedName( prefix, localName, nsURI );
351                if ( isAttribute ) {
352                    propertyStep = PropertyPathFactory.createAttributePropertyPathStep( propertyName );
353                } else if ( isIndexed ) {
354                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName, selectedIndex );
355                } else {
356                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName );
357                }
358                propertyPathSteps.add( propertyStep );
359            }
360    
361            return PropertyPathFactory.createPropertyPath( propertyPathSteps );
362        }
363    }