001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/portal/standard/context/control/ContextSaveListener.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    
037    package org.deegree.portal.standard.context.control;
038    
039    import java.io.File;
040    import java.io.FileOutputStream;
041    import java.io.OutputStream;
042    
043    import javax.servlet.http.HttpServletRequest;
044    import javax.servlet.http.HttpSession;
045    import javax.xml.transform.Source;
046    import javax.xml.transform.Transformer;
047    import javax.xml.transform.TransformerFactory;
048    import javax.xml.transform.dom.DOMSource;
049    import javax.xml.transform.stream.StreamResult;
050    
051    import org.deegree.enterprise.control.FormEvent;
052    import org.deegree.enterprise.control.RPCMember;
053    import org.deegree.enterprise.control.RPCMethodCall;
054    import org.deegree.enterprise.control.RPCParameter;
055    import org.deegree.enterprise.control.RPCStruct;
056    import org.deegree.enterprise.control.RPCUtils;
057    import org.deegree.enterprise.control.RPCWebEvent;
058    import org.deegree.framework.log.ILogger;
059    import org.deegree.framework.log.LoggerFactory;
060    import org.deegree.framework.util.StringTools;
061    import org.deegree.framework.xml.XMLFragment;
062    import org.deegree.i18n.Messages;
063    import org.deegree.model.spatialschema.Envelope;
064    import org.deegree.portal.Constants;
065    import org.deegree.portal.PortalException;
066    import org.deegree.portal.context.ViewContext;
067    import org.deegree.portal.context.XMLFactory;
068    import org.w3c.dom.Document;
069    
070    /**
071     * This class saves a new context based on changes made by the user (on the client) and based on the original context
072     * xml. <br/>
073     * Files are saved under .../WEB-INF/xml/users/some_user, where some_user is passed as an RPC parameter. Files should be
074     * saved with .xml extension because the default load context listener class looks up those files. <br/>
075     * 
076     * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
077     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
078     * @author last edited by: $Author: jmays $
079     * 
080     * @version $Revision: 21347 $, $Date: 2009-12-09 17:04:39 +0100 (Mi, 09 Dez 2009) $
081     */
082    public class ContextSaveListener extends AbstractContextListener {
083    
084        private static final ILogger LOG = LoggerFactory.getLogger( ContextSaveListener.class );
085    
086        private static String userDir = "WEB-INF/conf/igeoportal/users/";
087    
088        private static String contextDir = "WEB-INF/conf/igeoportal/";
089    
090        /*
091         * (non-Javadoc)
092         * 
093         * @see org.deegree.enterprise.control.WebListener#actionPerformed(org.deegree.enterprise.control.FormEvent)
094         */
095        @Override
096        public void actionPerformed( FormEvent event ) {
097    
098            RPCWebEvent rpc = (RPCWebEvent) event;
099            try {
100                validate( rpc );
101            } catch ( PortalException e ) {
102                LOG.logError( e.getMessage(), e );
103                gotoErrorPage( Messages.getMessage( "IGEO_STD_CNTXT_INVALID_RPC", "ContextSave", e.getMessage() ) );
104                return;
105            }
106    
107            String newContext = null;
108            try {
109                newContext = storeContext( rpc );
110            } catch ( Exception e ) {
111                LOG.logError( e.getMessage(), e );
112                gotoErrorPage( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_SAVE_CNTXT" ) );
113                return;
114            }
115    
116            // forward to new page
117            this.getRequest().setAttribute( Constants.MESSAGE,
118                                            Messages.getMessage( "IGEO_STD_CNTXT_SUCCESS_SAVE_CNTXT", newContext ) );
119        }
120    
121        /**
122         * stores the current context of the user with a defined name
123         * 
124         * @param event
125         * @return name of the context that has been stored
126         * @throws PortalException
127         */
128        private String storeContext( RPCWebEvent event )
129                                throws PortalException {
130    
131            RPCMethodCall mc = event.getRPCMethodCall();
132            RPCParameter[] pars = mc.getParameters();
133            RPCStruct struct = (RPCStruct) pars[0].getValue();
134    
135            // read base context
136            StringBuffer path2Dir = new StringBuffer( getHomePath() );
137            path2Dir.append( contextDir );
138    
139            // access base context
140            HttpSession session = ( (HttpServletRequest) getRequest() ).getSession();
141            ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT );
142            // change values: BBOX and Layer List
143            Envelope bbox = extractBBox( (RPCStruct) struct.getMember( Constants.RPC_BBOX ).getValue(), null );
144            changeBBox( vc, bbox );
145            RPCMember[] layerList = ( (RPCStruct) struct.getMember( "layerList" ).getValue() ).getMembers();
146            changeLayerList( vc, layerList );
147    
148            // save new context
149            // get map context value
150            String username = "default";
151            try {
152                String sid = RPCUtils.getRpcPropertyAsString( struct, "sessionID" );
153                LOG.logDebug( "sessionID ", sid );
154                username = getUserName( sid );
155                if ( username == null ) {
156                    username = "default";
157                }
158                LOG.logDebug( "username ", username );
159            } catch ( Exception e ) {
160                LOG.logError( e.getMessage(), e );
161            }
162    
163            String newContext = RPCUtils.getRpcPropertyAsString( struct, "newContext" );
164    
165            // check for init params, adjusting the file name input of the user
166            if ( getInitParameter( "RESTRICT_CHARS" ) != null || getInitParameter( "ADD_XML_SUFFIX" ) != null ) {
167                newContext = fixFileName( newContext, getInitParameter( "RESTRICT_CHARS" ),
168                                          getInitParameter( "ADD_XML_SUFFIX" ) );
169            }
170    
171            path2Dir = new StringBuffer( getHomePath() );
172            path2Dir.append( userDir );
173            path2Dir.append( username );
174            File file = new File( path2Dir.toString() );
175            if ( !file.exists() ) {
176                // create directory if not exists
177                file.mkdir();
178            }
179            path2Dir.append( "/" );
180            path2Dir.append( newContext );
181    
182            saveDocument( vc, path2Dir.toString() );
183    
184            return newContext;
185        }
186    
187        /**
188         * This method replaces all forbidden characters with underscores ("_") and sets the file extension to ".xml".
189         * Accepted characters are: [a-zA-Z_0-9-.]
190         * 
191         * @param newContext
192         * @param restrictedChars
193         *            may be null
194         * @param addXmlSuffix
195         *            may be null
196         * @return updated version of the passed String
197         */
198        private String fixFileName( String newContext, String restrictedChars, String addXmlSuffix ) {
199    
200            if ( restrictedChars != null ) {
201                String regex = null;
202                if ( restrictedChars.length() == 0 ) {
203                    // replace everything that is not a word character [a-zA-Z_0-9]
204                    regex = "[^\\w]";
205                } else {
206                    // replace everything that is not a word character [a-zA-Z_0-9] or one of the restrictedChars
207                    regex = "[^\\w" + restrictedChars + "]";
208                }
209    
210                // replace forbidden characters with underscore
211                newContext = newContext.replaceAll( regex, "_" );
212                // trimm double underscores
213                while ( newContext.contains( "__" ) ) {
214                    newContext = newContext.replace( "__", "_" );
215                }
216            }
217    
218            if ( "true".equals( addXmlSuffix ) ) {
219                // add proper file ending ".xml"
220                if ( !newContext.endsWith( ".xml" ) ) {
221                    newContext += ".xml";
222                }
223            }
224            return newContext;
225        }
226    
227        /**
228         * saves the new context as xml
229         * 
230         * @param vc
231         * @param filename
232         * @throws PortalException
233         */
234        public static final void saveDocument( ViewContext vc, String filename )
235                                throws PortalException {
236            try {
237                XMLFragment xml = XMLFactory.export( vc );
238                FileOutputStream fos = new FileOutputStream( filename );
239                xml.write( fos );
240                fos.close();
241            } catch ( Exception e ) {
242                LOG.logError( e.getMessage(), e );
243                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_SAVE_FILE", filename ) );
244            }
245        }
246    
247        /**
248         * validates the incoming RPC event
249         * 
250         * @param rpc
251         * @throws PortalException
252         */
253        private void validate( RPCWebEvent rpc )
254                                throws PortalException {
255            RPCMethodCall mc = rpc.getRPCMethodCall();
256            RPCParameter param = mc.getParameters()[0];
257            RPCStruct struct = (RPCStruct) param.getValue();
258            RPCMember username = struct.getMember( "sessionID" );
259            if ( username == null ) {
260                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_MISSING_PARAM", "sessionID", "ContextSave" ) );
261            }
262            RPCMember newContext = struct.getMember( "newContext" );
263            if ( newContext == null ) {
264                throw new PortalException(
265                                           Messages.getMessage( "IGEO_STD_CNTXT_MISSING_PARAM", "newContext", "ContextSave" ) );
266            }
267            RPCMember layerList = struct.getMember( "layerList" );
268            if ( layerList == null ) {
269                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_MISSING_PARAM", "layerList", "ContextSave" ) );
270            }
271            // TODO validate box: should do this in a common (static) method
272            // for many listeners that need a bbox
273        }
274    
275        /**
276         * common method to save xml
277         * 
278         * @param os
279         * @param doc
280         * @throws PortalException
281         */
282        protected static void internalSave( OutputStream os, Document doc )
283                                throws PortalException {
284            try {
285                Source source = new DOMSource( doc );
286                Transformer transformer = TransformerFactory.newInstance().newTransformer();
287                transformer.transform( source, new StreamResult( os ) );
288            } catch ( Exception e ) {
289                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_INTERNAL_SAVE",
290                                                                StringTools.stackTraceToString( e.getStackTrace() ) ) );
291            }
292        }
293    
294    }