View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.web.xsl;
35  
36  import fr.paris.lutece.portal.business.file.File;
37  import fr.paris.lutece.portal.business.file.FileHome;
38  import fr.paris.lutece.portal.business.physicalfile.PhysicalFile;
39  import fr.paris.lutece.portal.business.physicalfile.PhysicalFileHome;
40  import fr.paris.lutece.portal.business.rbac.RBAC;
41  import fr.paris.lutece.portal.business.xsl.XslExport;
42  import fr.paris.lutece.portal.business.xsl.XslExportHome;
43  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
44  import fr.paris.lutece.portal.service.fileupload.FileUploadService;
45  import fr.paris.lutece.portal.service.i18n.I18nService;
46  import fr.paris.lutece.portal.service.message.AdminMessage;
47  import fr.paris.lutece.portal.service.message.AdminMessageService;
48  import fr.paris.lutece.portal.service.plugin.Plugin;
49  import fr.paris.lutece.portal.service.plugin.PluginService;
50  import fr.paris.lutece.portal.service.rbac.RBACService;
51  import fr.paris.lutece.portal.service.security.SecurityTokenService;
52  import fr.paris.lutece.portal.service.template.AppTemplateService;
53  import fr.paris.lutece.portal.service.xsl.XslExportResourceIdService;
54  import fr.paris.lutece.portal.web.admin.PluginAdminPageJspBean;
55  import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
56  import fr.paris.lutece.util.ReferenceItem;
57  import fr.paris.lutece.util.ReferenceList;
58  import fr.paris.lutece.util.filesystem.FileSystemUtil;
59  import fr.paris.lutece.util.html.HtmlTemplate;
60  
61  import org.apache.commons.fileupload.FileItem;
62  import org.apache.commons.lang3.StringUtils;
63  
64  import org.xml.sax.InputSource;
65  
66  import java.io.ByteArrayInputStream;
67  import java.io.IOException;
68  import java.io.OutputStream;
69  
70  import java.util.Collection;
71  import java.util.HashMap;
72  import java.util.Map;
73  
74  import javax.servlet.http.HttpServletRequest;
75  import javax.servlet.http.HttpServletResponse;
76  
77  import javax.xml.parsers.SAXParser;
78  import javax.xml.parsers.SAXParserFactory;
79  
80  /**
81   *
82   * class XslExportJspBean
83   *
84   */
85  public class XslExportJspBean extends PluginAdminPageJspBean
86  {
87      /**
88       * Right to manage XSL Export
89       */
90      public static final String RIGHT_MANAGE_XSL_EXPORT = "CORE_XSL_EXPORT_MANAGEMENT";
91  
92      /**
93       * Serial version UID
94       */
95      private static final long serialVersionUID = -8697851692630602527L;
96  
97      // templates
98      private static final String TEMPLATE_CREATE_XSL_EXPORT = "admin/xsl/create_xsl_export.html";
99      private static final String TEMPLATE_MODIFY_XSL_EXPORT = "admin/xsl/modify_xsl_export.html";
100 
101     // Markers
102     private static final String MARK_XSL_EXPORT = "xsl_export";
103     private static final String MARK_LIST_PLUGINS = "list_plugins";
104 
105     // parameters form
106     private static final String PARAMETER_ID_XSL_EXPORT = "id_xsl_export";
107     private static final String PARAMETER_ID_FILE = "id_file";
108     private static final String PARAMETER_TITLE = "title";
109     private static final String PARAMETER_DESCRIPTION = "description";
110     private static final String PARAMETER_EXTENSION = "extension";
111     private static final String PARAMETER_PLUGIN = "plugin";
112 
113     // other constants
114     private static final String EMPTY_STRING = "";
115 
116     // message
117     private static final String MESSAGE_CONFIRM_REMOVE_XSL_EXPORT = "portal.xsl.message.confirm_remove_xsl_export";
118     private static final String MESSAGE_MANDATORY_FIELD = "portal.util.message.mandatoryField";
119 
120     private static final String FIELD_TITLE = "portal.xsl.create_xsl_export.label_title";
121     private static final String FIELD_DESCRIPTION = "portal.xsl.create_xsl_export.label_description";
122     private static final String FIELD_EXTENSION = "portal.xsl.create_xsl_export.label_extension";
123 
124     private static final String FIELD_FILE = "portal.xsl.create_xsl_export.label_file";
125     private static final String MESSAGE_XML_NOT_VALID = "portal.xsl.message.xml_not_valid";
126     private static final String MESSAGE_PERMISSION_DENIED = "portal.xsl.message.permission_denied";
127 
128     // properties
129     private static final String PROPERTY_MODIFY_XSL_EXPORT_TITLE = "portal.xsl.modify_xsl_export.title";
130     private static final String PROPERTY_CREATE_XSL_EXPORT_TITLE = "portal.xsl.create_xsl_export.title";
131 
132     // Jsp Definition
133     private static final String ANCHOR_ADMIN_DASHBOARDS = "xslexport";
134     private static final String JSP_DO_REMOVE_XSL_EXPORT = "jsp/admin/xsl/DoRemoveXslExport.jsp";
135     private static final String JSP_XSL_EXPORT_LIST = "jsp/admin/AdminTechnicalMenu.jsp?tab=xslexportManagement#users_advanced_parameters";
136 
137     /**
138      * Gets the xsl export creation page
139      * 
140      * @param request
141      *            The HTTP request
142      * @throws AccessDeniedException
143      *             the {@link AccessDeniedException}
144      * @return The xsl export creation page
145      */
146     public String getCreateXslExport( HttpServletRequest request ) throws AccessDeniedException
147     {
148         HashMap<String, Object> model = new HashMap<>( );
149 
150         Collection<Plugin> listPlugins = PluginService.getPluginList( );
151         ReferenceListml#ReferenceList">ReferenceList refListPlugins = new ReferenceList( );
152         ReferenceItemItem.html#ReferenceItem">ReferenceItem refItem = new ReferenceItem( );
153         Plugin pluginCore = PluginService.getCore( );
154         refItem.setCode( pluginCore.getName( ) );
155         refItem.setName( pluginCore.getName( ) );
156         refListPlugins.add( refItem );
157 
158         for ( Plugin plugin : listPlugins )
159         {
160             if ( plugin != null )
161             {
162                 refItem = new ReferenceItem( );
163                 refItem.setCode( plugin.getName( ) );
164                 refItem.setName( plugin.getName( ) );
165                 refListPlugins.add( refItem );
166             }
167         }
168 
169         model.put( MARK_LIST_PLUGINS, refListPlugins );
170         model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, TEMPLATE_CREATE_XSL_EXPORT ) );
171 
172         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_CREATE, getUser( ) ) )
173         {
174             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
175         }
176 
177         setPageTitleProperty( PROPERTY_CREATE_XSL_EXPORT_TITLE );
178 
179         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_CREATE_XSL_EXPORT, getLocale( ), model );
180 
181         return getAdminPage( template.getHtml( ) );
182     }
183 
184     /**
185      * Perform the xsl export creation
186      * 
187      * @param request
188      *            The HTTP request
189      * @throws AccessDeniedException
190      *             the {@link AccessDeniedException}
191      * @return The URL to go after performing the action
192      */
193     public String doCreateXslExport( HttpServletRequest request ) throws AccessDeniedException
194     {
195         XslExportess/xsl/XslExport.html#XslExport">XslExport xslExport = new XslExport( );
196         String strError = getXslExportData( request, xslExport );
197 
198         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_CREATE, getUser( ) ) )
199         {
200             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
201         }
202 
203         if ( strError != null )
204         {
205             return strError;
206         }
207 
208         if ( !SecurityTokenService.getInstance( ).validate( request, TEMPLATE_CREATE_XSL_EXPORT ) )
209         {
210             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
211         }
212 
213         if ( xslExport.getFile( ) != null )
214         {
215             xslExport.getFile( ).setIdFile( FileHome.create( xslExport.getFile( ) ) );
216         }
217 
218         XslExportHome.create( xslExport );
219 
220         return getJspManageXslExport( request );
221     }
222 
223     /**
224      * Gets the export format modification page
225      * 
226      * @param request
227      *            The HTTP request
228      * @throws AccessDeniedException
229      *             the {@link AccessDeniedException}
230      * @return The export format creation page
231      */
232     public String getModifyXslExport( HttpServletRequest request ) throws AccessDeniedException
233     {
234         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_MODIFY, getUser( ) ) )
235         {
236             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
237         }
238 
239         XslExport xslExport;
240         String strIdXslExport = request.getParameter( PARAMETER_ID_XSL_EXPORT );
241         HashMap<String, Object> model = new HashMap<>( );
242         int nIdXslExport = Integer.parseInt( strIdXslExport );
243         xslExport = XslExportHome.findByPrimaryKey( nIdXslExport );
244         model.put( MARK_XSL_EXPORT, xslExport );
245 
246         Collection<Plugin> listPlugins = PluginService.getPluginList( );
247         ReferenceListml#ReferenceList">ReferenceList refListPlugins = new ReferenceList( );
248         ReferenceItemItem.html#ReferenceItem">ReferenceItem refItem = new ReferenceItem( );
249         Plugin pluginCore = PluginService.getCore( );
250         refItem.setCode( pluginCore.getName( ) );
251         refItem.setName( pluginCore.getName( ) );
252         refListPlugins.add( refItem );
253 
254         for ( Plugin plugin : listPlugins )
255         {
256             if ( plugin != null )
257             {
258                 refItem = new ReferenceItem( );
259                 refItem.setCode( plugin.getName( ) );
260                 refItem.setName( plugin.getName( ) );
261                 refListPlugins.add( refItem );
262             }
263         }
264 
265         model.put( MARK_LIST_PLUGINS, refListPlugins );
266         model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, TEMPLATE_MODIFY_XSL_EXPORT ) );
267 
268         setPageTitleProperty( PROPERTY_MODIFY_XSL_EXPORT_TITLE );
269 
270         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MODIFY_XSL_EXPORT, getLocale( ), model );
271 
272         return getAdminPage( template.getHtml( ) );
273     }
274 
275     /**
276      * Perform the xsl export modification
277      * 
278      * @param request
279      *            The HTTP request
280      * @throws AccessDeniedException
281      *             the {@link AccessDeniedException}
282      * @return The URL to go after performing the action
283      */
284     public String doModifyXslExport( HttpServletRequest request ) throws AccessDeniedException
285     {
286         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_MODIFY, getUser( ) ) )
287         {
288             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
289         }
290 
291         XslExport xslExport;
292         String strIdXslExport = request.getParameter( PARAMETER_ID_XSL_EXPORT );
293         int nIdXslExport = Integer.parseInt( strIdXslExport );
294         xslExport = XslExportHome.findByPrimaryKey( nIdXslExport );
295 
296         String strError = getXslExportData( request, xslExport );
297 
298         if ( strError != null )
299         {
300             return strError;
301         }
302         if ( !SecurityTokenService.getInstance( ).validate( request, TEMPLATE_MODIFY_XSL_EXPORT ) )
303         {
304             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
305         }
306 
307         // if xslExport
308         File fileStore = XslExportHome.findByPrimaryKey( nIdXslExport ).getFile( );
309 
310         if ( xslExport.getFile( ) != null )
311         {
312             // the file has been modified
313             File fileSource = xslExport.getFile( );
314             // init id file source and id physical file before update
315             fileSource.setIdFile( fileStore.getIdFile( ) );
316 
317             if ( fileStore.getPhysicalFile( ) != null )
318             {
319                 fileSource.getPhysicalFile( ).setIdPhysicalFile( fileStore.getPhysicalFile( ).getIdPhysicalFile( ) );
320             }
321 
322             FileHome.update( fileSource );
323         }
324         else
325         {
326             xslExport.setFile( fileStore );
327         }
328 
329         XslExportHome.update( xslExport );
330 
331         return getJspManageXslExport( request );
332     }
333 
334     /**
335      * Gets the confirmation page of delete xsl export
336      * 
337      * @param request
338      *            The HTTP request
339      * @throws AccessDeniedException
340      *             the {@link AccessDeniedException}
341      * @return the confirmation page of delete xsl export
342      */
343     public String getConfirmRemoveXslExport( HttpServletRequest request ) throws AccessDeniedException
344     {
345         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_DELETE, getUser( ) ) )
346         {
347             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
348         }
349 
350         String strIdXslExport = request.getParameter( PARAMETER_ID_XSL_EXPORT );
351 
352         Map<String, String> parameters = new HashMap<>( );
353         parameters.put( PARAMETER_ID_XSL_EXPORT, strIdXslExport );
354         parameters.put( SecurityTokenService.PARAMETER_TOKEN, SecurityTokenService.getInstance( ).getToken( request, JSP_DO_REMOVE_XSL_EXPORT ) );
355 
356         return AdminMessageService.getMessageUrl( request, MESSAGE_CONFIRM_REMOVE_XSL_EXPORT, JSP_DO_REMOVE_XSL_EXPORT, AdminMessage.TYPE_CONFIRMATION,
357                 parameters );
358     }
359 
360     /**
361      * Perform the export format supression
362      * 
363      * @param request
364      *            The HTTP request
365      * @throws AccessDeniedException
366      *             the {@link AccessDeniedException}
367      * @return The URL to go after performing the action
368      */
369     public String doRemoveXslExport( HttpServletRequest request ) throws AccessDeniedException
370     {
371         if ( !RBACService.isAuthorized( XslExport.RESOURCE_TYPE, RBAC.WILDCARD_RESOURCES_ID, XslExportResourceIdService.PERMISSION_DELETE, getUser( ) ) )
372         {
373             throw new AccessDeniedException( MESSAGE_PERMISSION_DENIED );
374         }
375         if ( !SecurityTokenService.getInstance( ).validate( request, JSP_DO_REMOVE_XSL_EXPORT ) )
376         {
377             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
378         }
379 
380         String strIdXslExport = request.getParameter( PARAMETER_ID_XSL_EXPORT );
381         int nIdXslExport = Integer.parseInt( strIdXslExport );
382         XslExport xslExport = XslExportHome.findByPrimaryKey( nIdXslExport );
383 
384         XslExportHome.remove( nIdXslExport );
385 
386         if ( xslExport.getFile( ) != null )
387         {
388             FileHome.remove( xslExport.getFile( ).getIdFile( ) );
389         }
390 
391         return getJspManageXslExport( request );
392     }
393 
394     /**
395      * Initiate a download of a XSL file
396      * 
397      * @param request
398      *            The request
399      * @param response
400      *            The response
401      * @throws IOException
402      *             Throw an exception if the outputstream has error.
403      */
404     public void doDownloadXslExport( HttpServletRequest request, HttpServletResponse response ) throws IOException
405     {
406         String strXslExportId = request.getParameter( PARAMETER_ID_XSL_EXPORT );
407 
408         if ( strXslExportId != null )
409         {
410             int nXslExportId = Integer.parseInt( strXslExportId );
411             XslExport xslExport = XslExportHome.findByPrimaryKey( nXslExportId );
412 
413             String strMimetype = xslExport.getFile( ).getMimeType( );
414             response.setContentType( ( strMimetype != null ) ? strMimetype : "application/octet-stream" );
415             response.setHeader( "Content-Disposition", "attachement; filename=\"" + xslExport.getFile( ).getTitle( ) + "\"" );
416 
417             OutputStream out = response.getOutputStream( );
418             PhysicalFile physicalFile = PhysicalFileHome.findByPrimaryKey( xslExport.getFile( ).getPhysicalFile( ).getIdPhysicalFile( ) );
419             out.write( physicalFile.getValue( ) );
420             out.flush( );
421             out.close( );
422         }
423     }
424 
425     /**
426      * Get the request data and if there is no error insert the data in the exportFormat object specified in parameter. return null if there is no error or else
427      * return the error page url
428      * 
429      * @param request
430      *            the request
431      * @param xslExport
432      *            the exportFormat Object
433      * @return null if there is no error or else return the error page url
434      */
435     private String getXslExportData( HttpServletRequest request, XslExport xslExport )
436     {
437         String strError = StringUtils.EMPTY;
438         String strTitle = request.getParameter( PARAMETER_TITLE );
439         String strDescription = request.getParameter( PARAMETER_DESCRIPTION );
440         String strExtension = request.getParameter( PARAMETER_EXTENSION );
441         String strPlugin = request.getParameter( PARAMETER_PLUGIN );
442         File fileSource = getFileData( PARAMETER_ID_FILE, request );
443 
444         if ( ( strTitle == null ) || strTitle.trim( ).equals( EMPTY_STRING ) )
445         {
446             strError = FIELD_TITLE;
447         }
448 
449         else
450             if ( ( strDescription == null ) || strDescription.trim( ).equals( EMPTY_STRING ) )
451             {
452                 strError = FIELD_DESCRIPTION;
453             }
454 
455             else
456                 if ( StringUtils.isBlank( strExtension ) )
457                 {
458                     strError = FIELD_EXTENSION;
459                 }
460 
461                 else
462                     if ( ( xslExport.getFile( ) == null ) && ( fileSource == null ) )
463                     {
464                         strError = FIELD_FILE;
465                     }
466 
467         if ( strPlugin == null )
468         {
469             strPlugin = StringUtils.EMPTY;
470         }
471 
472         // Mandatory fields
473         if ( !strError.equals( EMPTY_STRING ) )
474         {
475             Object [ ] tabRequiredFields = {
476                     I18nService.getLocalizedString( strError, getLocale( ) )
477             };
478 
479             return AdminMessageService.getMessageUrl( request, MESSAGE_MANDATORY_FIELD, tabRequiredFields, JSP_XSL_EXPORT_LIST, AdminMessage.TYPE_STOP );
480         }
481 
482         // Check the XML validity of the XSL stylesheet
483         if ( fileSource != null )
484         {
485             strError = isValid( fileSource.getPhysicalFile( ).getValue( ) );
486 
487             if ( strError != null )
488             {
489                 Object [ ] args = {
490                         strError
491                 };
492 
493                 return AdminMessageService.getMessageUrl( request, MESSAGE_XML_NOT_VALID, args, JSP_XSL_EXPORT_LIST, AdminMessage.TYPE_STOP );
494             }
495         }
496 
497         xslExport.setTitle( strTitle );
498         xslExport.setDescription( strDescription );
499         xslExport.setExtension( strExtension );
500         xslExport.setPlugin( strPlugin );
501 
502         xslExport.setFile( fileSource );
503 
504         return null;
505     }
506 
507     /**
508      * Use parsing for validate the modify xsl file
509      * 
510      * @param baXslSource
511      *            the xsl source
512      * @return the message exception when the validation is false
513      */
514     private String isValid( byte [ ] baXslSource )
515     {
516         String strError = null;
517 
518         try
519         {
520             SAXParserFactory factory = SAXParserFactory.newInstance( );
521             factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
522             factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
523             SAXParser analyzer = factory.newSAXParser( );
524             InputSource is = new InputSource( new ByteArrayInputStream( baXslSource ) );
525             analyzer.getXMLReader( ).parse( is );
526         }
527         catch( Exception e )
528         {
529             strError = e.getMessage( );
530         }
531 
532         return strError;
533     }
534 
535     /**
536      * return the url of manage export format
537      * 
538      * @param request
539      *            the request
540      * @return the url of manage export format
541      */
542     private String getJspManageXslExport( HttpServletRequest request )
543     {
544         return getAdminDashboardsUrl( request, ANCHOR_ADMIN_DASHBOARDS );
545     }
546 
547     /**
548      * Get a file contained in the request from the name of the parameter
549      *
550      * @param strFileInputName
551      *            name of the file parameter of the request
552      * @param request
553      *            the request
554      * @return file the file contained in the request with the given parameter key
555      */
556     private static File getFileData( String strFileInputName, HttpServletRequest request )
557     {
558         MultipartHttpServletRequestce/portal/web/upload/MultipartHttpServletRequest.html#MultipartHttpServletRequest">MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
559         FileItem fileItem = multipartRequest.getFile( strFileInputName );
560 
561         if ( ( fileItem != null ) && ( fileItem.getName( ) != null ) && !EMPTY_STRING.equals( fileItem.getName( ) ) )
562         {
563             Filertal/business/file/File.html#File">File file = new File( );
564             PhysicalFileysicalfile/PhysicalFile.html#PhysicalFile">PhysicalFile physicalFile = new PhysicalFile( );
565             physicalFile.setValue( fileItem.get( ) );
566             file.setTitle( FileUploadService.getFileNameOnly( fileItem ) );
567             file.setSize( (int) fileItem.getSize( ) );
568             file.setPhysicalFile( physicalFile );
569             file.setMimeType( FileSystemUtil.getMIMEType( FileUploadService.getFileNameOnly( fileItem ) ) );
570 
571             return file;
572         }
573 
574         return null;
575     }
576 }