001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/portal/common/control/AbstractSimplePrintListener.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.portal.common.control;
037    
038    import java.awt.Color;
039    import java.awt.Graphics;
040    import java.awt.Image;
041    import java.awt.image.BufferedImage;
042    import java.io.File;
043    import java.io.FileOutputStream;
044    import java.io.IOException;
045    import java.io.RandomAccessFile;
046    import java.net.URL;
047    import java.text.SimpleDateFormat;
048    import java.util.ArrayList;
049    import java.util.GregorianCalendar;
050    import java.util.HashMap;
051    import java.util.List;
052    import java.util.Locale;
053    import java.util.Map;
054    import java.util.UUID;
055    
056    import javax.servlet.ServletContext;
057    import javax.servlet.http.HttpServletRequest;
058    
059    import net.sf.jasperreports.engine.JRDataSource;
060    import net.sf.jasperreports.engine.JREmptyDataSource;
061    import net.sf.jasperreports.engine.JRException;
062    import net.sf.jasperreports.engine.JasperFillManager;
063    import net.sf.jasperreports.engine.JasperPrint;
064    import net.sf.jasperreports.engine.JasperPrintManager;
065    import net.sf.jasperreports.engine.JasperRunManager;
066    
067    import org.deegree.enterprise.control.AbstractListener;
068    import org.deegree.enterprise.control.FormEvent;
069    import org.deegree.enterprise.control.RPCMember;
070    import org.deegree.enterprise.control.RPCStruct;
071    import org.deegree.enterprise.control.RPCWebEvent;
072    import org.deegree.framework.log.ILogger;
073    import org.deegree.framework.log.LoggerFactory;
074    import org.deegree.framework.util.CharsetUtils;
075    import org.deegree.framework.util.ImageUtils;
076    import org.deegree.framework.util.KVP2Map;
077    import org.deegree.framework.util.StringTools;
078    import org.deegree.framework.xml.NamespaceContext;
079    import org.deegree.framework.xml.XMLFragment;
080    import org.deegree.framework.xml.XMLParsingException;
081    import org.deegree.framework.xml.XMLTools;
082    import org.deegree.model.spatialschema.Point;
083    import org.deegree.ogcbase.CommonNamespaces;
084    import org.deegree.ogcwebservices.InconsistentRequestException;
085    import org.deegree.ogcwebservices.wms.operation.GetMap;
086    import org.deegree.portal.PortalException;
087    import org.deegree.portal.PortalUtils;
088    import org.deegree.portal.context.Layer;
089    import org.deegree.portal.context.Style;
090    import org.deegree.portal.context.ViewContext;
091    import org.deegree.security.drm.model.User;
092    import org.xml.sax.SAXException;
093    
094    /**
095     * performs a print request/event by creating a PDF document from the current map. For this JASPER is used. Well known
096     * parameters that can be passed to a jaser report are:<br>
097     * <ul>
098     * <li>MAP</li>
099     * <li>LEGEND</li>
100     * <li>DATE</li>
101     * <li>MAPSCALE</li>
102     * </ul>
103     * <br>
104     * Additionaly parameters named 'TA:XXXX' can be used. deegree will create a k-v-p for each TA:XXXX passed as part of
105     * RPC.
106     *
107     *
108     * @version $Revision: 18283 $
109     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
110     * @author last edited by: $Author: apoth $
111     *
112     * @version $Revision: 18283 $, $Date: 2009-06-30 09:23:23 +0200 (Di, 30. Jun 2009) $
113     */
114    public abstract class AbstractSimplePrintListener extends AbstractListener {
115    
116        private static ILogger LOG = LoggerFactory.getLogger( AbstractSimplePrintListener.class );
117    
118        private String defaultTemplateDir = "/WEB-INF/igeoportal/print";
119    
120        /**
121         * @param e
122         */
123        @Override
124        public void actionPerformed( FormEvent e ) {
125            RPCWebEvent rpc = (RPCWebEvent) e;
126            try {
127                validate( rpc );
128            } catch ( Exception ex ) {
129                LOG.logError( ex.getMessage(), ex );
130                gotoErrorPage( ex.getMessage() );
131            }
132    
133            ViewContext vc = getViewContext( rpc );
134            if ( vc == null ) {
135                LOG.logError( "no valid ViewContext available; maybe your session has reached timeout limit" ); //$NON-NLS-1$
136                gotoErrorPage( Messages.getString( "AbstractSimplePrintListener.MISSINGCONTEXT" ) );
137                setNextPage( "igeoportal/error.jsp" );
138                return;
139            }
140            try {
141                printMap( vc, rpc );
142            } catch ( Exception ex ) {
143                ex.printStackTrace();
144                LOG.logError( ex.getMessage(), ex );
145                gotoErrorPage( ex.getMessage() );
146            }
147        }
148    
149        /**
150         *
151         * @param vc
152         * @param rpc
153         * @throws PortalException
154         * @throws IOException
155         * @throws SAXException
156         * @throws XMLParsingException
157         * @throws InconsistentRequestException
158         */
159        private void printMap( ViewContext vc, RPCWebEvent rpc )
160                                throws PortalException, IOException, InconsistentRequestException, XMLParsingException,
161                                SAXException {
162    
163            List<String> getMap = createGetMapRequests( vc, rpc );
164            String image = performGetMapRequests( getMap );
165    
166            String legend = accessLegend( createLegendURLs( vc ) );
167    
168            String format = (String) rpc.getRPCMethodCall().getParameters()[0].getValue();
169    
170            RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[1].getValue();
171            String printTemplate = (String) struct.getMember( "TEMPLATE" ).getValue();
172            ServletContext sc = ( (HttpServletRequest) getRequest() ).getSession( true ).getServletContext();
173    
174            // read print template directory from defaultTemplateDir, or, if available, from the init
175            // parameters
176            String templateDir = getInitParameter( "TEMPLATE_DIR" );
177            if ( templateDir == null ) {
178                templateDir = defaultTemplateDir;
179            }
180    
181            String path = sc.getRealPath( templateDir ) + '/' + printTemplate + ".jasper";
182            String pathx = sc.getRealPath( templateDir ) + '/' + printTemplate + ".jrxml";
183            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
184                LOG.logDebug( "The jasper template is read from: ", path );
185                LOG.logDebug( "The jrxml template is read from: ", pathx );
186            }
187    
188            Map<String, Object> parameter = new HashMap<String, Object>();
189            parameter.put( "MAP", image );
190            parameter.put( "LEGEND", legend );
191    
192            // enable
193            if ( getInitParameter( "LOGO_URL" ) != null ){
194                String logoUrl = getHomePath() + getInitParameter( "LOGO_URL" );
195                if ( new File( logoUrl ).exists() ) {
196                    parameter.put( "LOGO_URL", logoUrl );
197                }
198            }
199            
200            parameter.put( "ROOT",getHomePath() );
201    
202            SimpleDateFormat sdf = new SimpleDateFormat( "dd.MM.yyyy", Locale.getDefault() );
203            // TODO deprecated - will be remove in future versions
204            parameter.put( "DATUM", sdf.format( new GregorianCalendar().getTime() ) );
205            // --------------------------------------------------------
206            parameter.put( "DATE", sdf.format( new GregorianCalendar().getTime() ) );
207            double scale = calcScale( pathx, getMap.get( 0 ) );
208            parameter.put( "MAPSCALE", "" + Math.round( scale ) );
209            LOG.logDebug( "print map scale: ", scale );
210            // set text area values
211            RPCMember[] members = struct.getMembers();
212            for ( int i = 0; i < members.length; i++ ) {
213                if ( members[i].getName().startsWith( "TA:" ) ) {
214                    String s = members[i].getName().substring( 3, members[i].getName().length() );
215                    String val = (String) members[i].getValue();
216                    if ( val != null ) {
217                        val = new String( val.getBytes(), CharsetUtils.getSystemCharset() );
218                    }
219                    LOG.logDebug( "text area name: ", s );
220                    LOG.logDebug( "text area value: ", val );
221                    parameter.put( s, val );
222                }
223            }
224    System.out.println(parameter);
225            if ( "application/pdf".equals( format ) ) {
226    
227                // create the pdf
228                Object result = null;
229                try {
230                    JRDataSource ds = new JREmptyDataSource();
231                    result = JasperRunManager.runReportToPdf( path, parameter, ds );
232                } catch ( JRException e ) {
233                    LOG.logError( "Template: " + path );
234                    LOG.logError( e.getLocalizedMessage(), e );
235                    throw new PortalException( Messages.getString( "AbstractSimplePrintListener.REPORTCREATION" ) );
236                } finally {
237                    File file = new File( image );
238                    file.delete();
239                    file = new File( legend );
240                    file.delete();
241                }
242    
243                forwardPDF( result );
244    
245            } else if ( "image/png".equals( format ) ) {
246    
247                // create the image
248                Image result = null;
249                try {
250                    JRDataSource ds = new JREmptyDataSource();
251                    JasperPrint prt = JasperFillManager.fillReport( path, parameter, ds );
252                    result = JasperPrintManager.printPageToImage( prt, 0, 1 );
253                } catch ( JRException e ) {
254                    LOG.logError( e.getLocalizedMessage(), e );
255                    throw new PortalException( Messages.getString( "AbstractSimplePrintListener.REPORTCREATION" ) );
256                } finally {
257                    File file = new File( image );
258                    file.delete();
259                    file = new File( legend );
260                    file.delete();
261                }
262    
263                forwardImage( result, format );
264    
265            }
266        }
267    
268        protected double calcScale( String path, String getmap )
269                                throws InconsistentRequestException, XMLParsingException, IOException, SAXException {
270            //TODO The map path is static. It should be instead read from somewhere else.
271            //A good idea would be to save the path in the web.xml of the coreesponding application,
272            //or in controller.xml of the PdfPrintListener and sends it with rpc request
273    
274            Map<String, String> model = KVP2Map.toMap( getmap );
275            model.put( "ID", "22" );
276            GetMap gm = GetMap.create( model );
277    
278            File file = new File( path );
279            XMLFragment xml = new XMLFragment( file.toURL() );
280    
281            String xpathW = "detail/band/image/reportElement[./@key = 'image-1']/@width";
282    
283            NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
284            int w = XMLTools.getRequiredNodeAsInt( xml.getRootElement(), xpathW, nsc );
285    
286            // CoordinateSystem crs = CRSFactory.create( gm.getSrs() );
287    
288            // mapsize in template in metre; templates generated by iReport are designed
289            // to assume a resolution of 72dpi
290            double ms = ( w / 72d ) * 0.0254;
291            // TODO
292            // consider no metric CRS
293            return gm.getBoundingBox().getWidth() / ms;
294        }
295    
296        /**
297         * accesses the legend URLs passed, draws the result onto an image that are stored in a temporary file. The name of
298         * the file will be returned.
299         *
300         * @param legends
301         * @return filename of image file
302         */
303        private String accessLegend( List<String[]> legends )
304                                throws IOException {
305            int width = Integer.parseInt( getInitParameter( "LEGENDWIDTH" ) );
306            int height = Integer.parseInt( getInitParameter( "LEGENDHEIGHT" ) );
307            String tmp = getInitParameter( "LEGENDBGCOLOR" );
308            if ( tmp == null ) {
309                tmp = "0xFFFFFF";
310            }
311            Color bg = Color.decode( tmp );
312            BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );
313            Graphics g = bi.getGraphics();
314            g.setColor( bg );
315            g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() );
316            g.setColor( Color.BLACK );
317            int k = 10;
318    
319            for ( int i = 0; i < legends.size(); i++ ) {
320                if ( k > bi.getHeight() ) {
321                    if ( LOG.getLevel() <= ILogger.LOG_WARNING ) {
322                        LOG.logWarning( "The necessary legend size is larger than the available legend space." );
323                    }
324                }
325                String[] s = legends.get( i );
326                if ( s[1] != null ) {
327                    LOG.logDebug( "reading legend: " + s[1] );
328                    Image img = null;
329                    try {
330                        img = ImageUtils.loadImage( new URL( s[1] ) );
331                    } catch ( Exception e ) {
332                        if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
333                            String msg = StringTools.concat( 400, "Exception for Layer: ", s[0], " - ", s[1] );
334                            LOG.logDebug( msg );
335                            LOG.logDebug( e.getLocalizedMessage() );
336                        }
337                        if ( getInitParameter( "MISSING_IMAGE" ) != null ){
338                            String missingImageUrl = getHomePath() + getInitParameter( "MISSING_IMAGE" );
339                            File missingImage = new File( missingImageUrl );
340                            if ( missingImage.exists() ) {
341                                img = ImageUtils.loadImage( missingImage );
342                            }
343                        }
344                    }
345                    if ( img != null ) {
346                        if ( img.getWidth( null ) < 50 ) {
347                            // it is assumed that no label is assigned
348                            g.drawImage( img, 0, k, null );
349                            g.drawString( s[0], img.getWidth( null ) + 10, k + img.getHeight( null ) / 2 );
350                        } else {
351                            g.drawImage( img, 0, k, null );
352                        }
353                        k = k + img.getHeight( null ) + 10;
354                    }
355                } else {
356                    g.drawString( "- " + s[0], 0, k + 10 );
357                    k = k + 20;
358                }
359            }
360            g.dispose();
361    
362            return storeImage( bi );
363        }
364    
365        /**
366         * performs the GetMap requests passed, draws the result onto an image that are stored in a temporary file. The name
367         * of the file will be returned.
368         *
369         * @param list
370         * @return filename of image file
371         * @throws PortalException
372         * @throws IOException
373         */
374        private String performGetMapRequests( List<String> list )
375                                throws PortalException, IOException {
376    
377            Map<String, String> map = KVP2Map.toMap( list.get( 0 ) );
378            map.put( "ID", "ww" );
379            GetMap getMap = null;
380            try {
381                getMap = GetMap.create( map );
382            } catch ( Exception e ) {
383                LOG.logError( e.getMessage(), e );
384                String s = Messages.format( "AbstractSimplePrintListener.GETMAPCREATION", list.get( 0 ) );
385                throw new PortalException( s );
386            }
387            BufferedImage bi = new BufferedImage( getMap.getWidth(), getMap.getHeight(), BufferedImage.TYPE_INT_ARGB );
388            Graphics g = bi.getGraphics();
389            for ( int i = 0; i < list.size(); i++ ) {
390                URL url = new URL( list.get( i ) );
391                Image img = null;
392                try {
393                    img = ImageUtils.loadImage( url );
394                } catch ( Exception e ) {
395                    //This is the case where a getmap request produces an error. This does not definitly mean that
396                    //the wms is not working, it could also be because the bounding box is not correct or too big. Try to zoom
397                    // in, you might find something
398                    LOG.logInfo( "could not load map from: ", url.toExternalForm() );
399                }
400                g.drawImage( img, 0, 0, null );
401            }
402            g.dispose();
403            return storeImage( bi );
404        }
405    
406        /**
407         * stores the passed image in the defined temporary directory and returns the dynamicly created filename
408         *
409         * @param bi
410         * @return filename of image file
411         * @throws IOException
412         */
413        private String storeImage( BufferedImage bi )
414                                throws IOException {
415    
416            String s = UUID.randomUUID().toString();
417            String tempDir = getInitParameter( "TEMPDIR" );
418            if ( !tempDir.endsWith( "/" ) ) {
419                tempDir = tempDir + '/';
420            }
421            if ( tempDir.startsWith( "/" ) ) {
422                tempDir = tempDir.substring( 1, tempDir.length() );
423            }
424            ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext();
425            String fileName = StringTools.concat( 300, sc.getRealPath( tempDir ), '/', s, ".png" );
426    
427            FileOutputStream fos = new FileOutputStream( new File( fileName ) );
428    
429            ImageUtils.saveImage( bi, fos, "png", 1 );
430            fos.close();
431    
432            return fileName;
433        }
434    
435        private void forwardPDF( Object result )
436                                throws PortalException {
437            // must be a byte array
438            String tempDir = getInitParameter( "TEMPDIR" );
439            if ( !tempDir.endsWith( "/" ) ) {
440                tempDir = tempDir + '/';
441            }
442            if ( tempDir.startsWith( "/" ) ) {
443                tempDir = tempDir.substring( 1, tempDir.length() );
444            }
445            ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext();
446    
447            String fileName = UUID.randomUUID().toString();
448            String s = StringTools.concat( 200, sc.getRealPath( tempDir ), '/', fileName, ".pdf" );
449            try {
450                RandomAccessFile raf = new RandomAccessFile( s, "rw" );
451                raf.write( (byte[]) result );
452                raf.close();
453            } catch ( Exception e ) {
454                e.printStackTrace();
455                LOG.logError( "could not write temporary pdf file: " + s, e );
456                throw new PortalException( Messages.format( "AbstractSimplePrintListener.PDFCREATION", s ), e );
457            }
458    
459            getRequest().setAttribute( "PDF", StringTools.concat( 200, tempDir, fileName, ".pdf" ) );
460        }
461    
462        private void forwardImage( Image result, String format )
463                                throws PortalException {
464    
465            format = format.substring( format.indexOf( '/' ) + 1 );
466    
467            String tempDir = getInitParameter( "TEMPDIR" );
468            if ( !tempDir.endsWith( "/" ) ) {
469                tempDir = tempDir + '/';
470            }
471            if ( tempDir.startsWith( "/" ) ) {
472                tempDir = tempDir.substring( 1, tempDir.length() );
473            }
474            ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext();
475    
476            String fileName = UUID.randomUUID().toString();
477            String s = StringTools.concat( 200, sc.getRealPath( tempDir ), "/", fileName, ".", format );
478            try {
479                // make sure we have a BufferedImage
480                if ( !( result instanceof BufferedImage ) ) {
481                    BufferedImage img = new BufferedImage( result.getWidth( null ), result.getHeight( null ),
482                                                           BufferedImage.TYPE_INT_ARGB );
483    
484                    Graphics g = img.getGraphics();
485                    g.drawImage( result, 0, 0, null );
486                    g.dispose();
487                    result = img;
488                }
489    
490                ImageUtils.saveImage( (BufferedImage) result, s, 1 );
491            } catch ( Exception e ) {
492                LOG.logError( "could not write temporary pdf file: " + s, e );
493                throw new PortalException( Messages.format( "AbstractSimplePrintListener.PDFCREATION", s ), e );
494            }
495    
496            getRequest().setAttribute( "PDF", StringTools.concat( 200, tempDir, fileName, ".", format ) );
497        }
498    
499        /**
500         * fills the passed PrintMap request template with required values
501         *
502         * @param vc
503         * @return returns a list with all base requests
504         */
505        private List<String> createGetMapRequests( ViewContext vc, RPCWebEvent rpc ) {
506    
507            User user = getUser();
508            String vsp = getVendorspecificParameters(rpc);
509    
510            // set boundingbox/envelope
511            Point[] points = vc.getGeneral().getBoundingBox();
512    
513            int width = Integer.parseInt( getInitParameter( "WIDTH" ) );
514            int height = Integer.parseInt( getInitParameter( "HEIGHT" ) );
515    
516            StringBuffer sb = new StringBuffer( 1000 );
517            sb.append( "&BBOX=" ).append( points[0].getX() ).append( ',' );
518            sb.append( points[0].getY() ).append( ',' ).append( points[1].getX() );
519            sb.append( ',' ).append( points[1].getY() ).append( "&WIDTH=" );
520            sb.append( width ).append( "&HEIGHT=" ).append( height );
521            if ( user != null ) {
522                sb.append( "&user=" ).append( user.getName() );
523                sb.append( "&password=" ).append( user.getPassword() );
524            }
525            if ( vsp != null ) {
526                sb.append( "&" ).append( vsp );
527            }
528            String[] reqs = PortalUtils.createBaseRequests( vc );
529            List<String> list = new ArrayList<String>( reqs.length );
530            for ( int i = 0; i < reqs.length; i++ ) {
531                list.add( reqs[i] + sb.toString() );
532    System.out.println(reqs[i] + sb.toString());
533                LOG.logDebug( "GetMap request:", reqs[i] + sb.toString() );
534            }
535    
536            return list;
537        }
538    
539        /**
540         * returns <code>null</code> and should be overwritten by an extending class
541         * @return <code>null</code>
542         */
543        protected String getVendorspecificParameters(RPCWebEvent rpc) {
544            // TODO Auto-generated method stub
545            return null;
546        }
547    
548        /**
549         * returns <code>null</code> and should be overwritten by an extending class
550         *
551         * @return <code>null</code>
552         */
553        protected User getUser() {
554            return null;
555        }
556    
557        /**
558         * reads the view context to print from the users session
559         *
560         * @param rpc
561         * @return the viewcontext defined by the rpc
562         */
563        abstract protected ViewContext getViewContext( RPCWebEvent rpc );
564    
565        /**
566         * returns legend access URLs for all visible layers of the passed view context. If a visible layer does not define
567         * a LegendURL
568         *
569         * @param vc
570         * @return legend access URLs for all visible layers of the passed view context. If a visible layer does not define
571         *         a LegendURL
572         */
573        private List<String[]> createLegendURLs( ViewContext vc ) {
574            Layer[] layers = vc.getLayerList().getLayers();
575            List<String[]> list = new ArrayList<String[]>();
576            for ( int i = 0; i < layers.length; i++ ) {
577                if ( !layers[i].isHidden() ) {
578                    Style style = layers[i].getStyleList().getCurrentStyle();
579                    String[] s = new String[2];
580                    s[0] = layers[i].getTitle();
581                    if ( style.getLegendURL() != null ) {
582                        s[1] = style.getLegendURL().getOnlineResource().toExternalForm();
583                    }
584                    list.add( s );
585                }
586            }
587            return list;
588        }
589    
590        /**
591         * validates the incoming request/RPC if conatins all required elements
592         *
593         * @param rpc
594         * @throws PortalException
595         */
596        protected void validate( RPCWebEvent rpc )
597                                throws PortalException {
598            RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[1].getValue();
599            if ( struct.getMember( "TEMPLATE" ) == null ) {
600                throw new PortalException( Messages.getString( "portal.common.control.VALIDATIONERROR" ) );
601            }
602    
603            if ( rpc.getRPCMethodCall().getParameters()[0].getValue() == null ) {
604                throw new PortalException( Messages.getString( "portal.common.control.VALIDATIONERROR" ) );
605            }
606        }
607    
608    }