1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.document.service;
35
36 import fr.paris.lutece.plugins.document.business.Document;
37 import fr.paris.lutece.plugins.document.business.DocumentHome;
38 import fr.paris.lutece.plugins.document.business.DocumentType;
39 import fr.paris.lutece.plugins.document.business.DocumentTypeHome;
40 import fr.paris.lutece.plugins.document.business.portlet.DocumentListPortletHome;
41 import fr.paris.lutece.plugins.document.business.publication.DocumentPublication;
42 import fr.paris.lutece.plugins.document.service.publishing.PublishingService;
43 import fr.paris.lutece.plugins.document.utils.IntegerUtils;
44 import fr.paris.lutece.portal.business.page.Page;
45 import fr.paris.lutece.portal.business.page.PageHome;
46 import fr.paris.lutece.portal.business.portlet.AliasPortlet;
47 import fr.paris.lutece.portal.business.portlet.AliasPortletHome;
48 import fr.paris.lutece.portal.business.portlet.Portlet;
49 import fr.paris.lutece.portal.business.portlet.PortletHome;
50 import fr.paris.lutece.portal.business.resourceenhancer.ResourceEnhancer;
51 import fr.paris.lutece.portal.business.style.ModeHome;
52 import fr.paris.lutece.portal.service.cache.ICacheKeyService;
53 import fr.paris.lutece.portal.service.content.ContentService;
54 import fr.paris.lutece.portal.service.content.PageData;
55 import fr.paris.lutece.portal.service.html.XmlTransformerService;
56 import fr.paris.lutece.portal.service.message.SiteMessageException;
57 import fr.paris.lutece.portal.service.page.PortletCacheService;
58 import fr.paris.lutece.portal.service.portal.PortalService;
59 import fr.paris.lutece.portal.service.security.LuteceUser;
60 import fr.paris.lutece.portal.service.security.SecurityService;
61 import fr.paris.lutece.portal.service.security.UserNotSignedException;
62 import fr.paris.lutece.portal.service.spring.SpringContextService;
63 import fr.paris.lutece.portal.service.template.AppTemplateService;
64 import fr.paris.lutece.portal.service.util.AppLogService;
65 import fr.paris.lutece.portal.service.util.AppPathService;
66 import fr.paris.lutece.portal.service.util.AppPropertiesService;
67 import fr.paris.lutece.portal.web.PortalJspBean;
68 import fr.paris.lutece.portal.web.constants.Parameters;
69 import fr.paris.lutece.util.ReferenceList;
70 import fr.paris.lutece.util.date.DateUtil;
71 import fr.paris.lutece.util.html.HtmlTemplate;
72 import fr.paris.lutece.util.xml.XmlUtil;
73
74 import net.sf.ehcache.CacheException;
75 import net.sf.ehcache.Ehcache;
76 import net.sf.ehcache.Element;
77 import net.sf.ehcache.event.CacheEventListener;
78
79 import org.apache.commons.lang3.StringUtils;
80
81 import java.io.FileInputStream;
82
83 import java.util.ArrayList;
84 import java.util.Enumeration;
85 import java.util.HashMap;
86 import java.util.Hashtable;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.Properties;
90 import java.util.concurrent.ConcurrentHashMap;
91 import java.util.concurrent.ConcurrentMap;
92
93 import javax.servlet.http.HttpServletRequest;
94
95 import javax.xml.transform.Source;
96 import javax.xml.transform.stream.StreamSource;
97
98
99
100
101
102 public final class DocumentContentService extends ContentService implements CacheEventListener
103 {
104
105
106 private static final String CONTENT_SERVICE_NAME = "Document Content Service";
107 private static final String SLASH = "/";
108 private static final int MODE_ADMIN = 1;
109 private static final String CONSTANT_DEFAULT_PORTLET_DOCUMENT_LIST_XSL = "WEB-INF/xsl/normal/portlet_document_list.xsl";
110 private static final String DOCUMENT_STYLE_PREFIX_ID = "document-";
111 private static final String LOCALE_EN = "en";
112 private static final String LOCALE_FR = "fr";
113
114
115 private static final String XML_TAG_CONTENT = "content";
116 private static final String XML_TAG_SITE_LOCALE = "site_locale";
117
118
119 private static final String PARAMETER_DOCUMENT_ID = "document_id";
120 private static final String PARAMETER_SITE_PATH = "site-path";
121 private static final String PARAMETER_PUBLICATION_DATE = "publication-date";
122 private static final String PARAMETER_SITE_LOCALE = "site_locale";
123
124
125 private static final String MARK_PUBLICATION = "publication";
126 private static final String MARK_DOCUMENT = "document";
127 private static final String MARK_PORTLET = "portlet";
128 private static final String MARK_CATEGORY = "categories";
129 private static final String MARK_DOCUMENT_ID = "document_id";
130 private static final String MARK_PORTLET_ID = "portlet_id";
131 private static final String MARK_PORTLET_ID_LIST = "portlet_id_list";
132 private static final String MARK_DOCUMENT_CATEGORIES_LIST = "document_categories_list";
133 private static final String MARK_URL_LOGIN = "url_login";
134 private static final String MARKER_TARGET = "target";
135 private static final String MARK_IS_EXTEND_INSTALLED = "isExtendInstalled";
136
137
138 private static final String TEMPLATE_DOCUMENT_PAGE_DEFAULT = "/skin/plugins/document/document_content_service.html";
139 private static final String TEMPLATE_DOCUMENT_CATEGORIES = "/skin/plugins/document/document_categories.html";
140
141
142 private static final String PROPERTY_DEFAULT_PORTLET_DOCUMENT_LIST_XSL = "document.contentService.defaultPortletDocumentListXSL";
143 private static final String PROPERTY_CACHE_ENABLED = "document.cache.enabled";
144 private static final String TARGET_TOP = "target=_top";
145 private static final String PROPERTY_RESOURCE_TYPE = "document";
146
147
148 private static ConcurrentMap<String, String> _keyMemory = new ConcurrentHashMap<String, String>( );
149
150
151
152 private static final String PARAMETER_PORTLET = "portlet";
153 private static final String PORTLET_CACHE_KEY_SUFFIX = "[documentContentService]";
154 private boolean _bInit;
155 private PortletCacheService _cachePortlets;
156 private ICacheKeyService _cksPortlet;
157
158
159
160
161
162
163
164
165
166
167
168
169
170 public String getPage( HttpServletRequest request, int nMode )
171 throws UserNotSignedException, SiteMessageException
172 {
173 if ( !_bInit )
174 {
175 init( );
176 }
177
178 String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
179 String strPortletId = request.getParameter( Parameters.PORTLET_ID );
180 String strSiteLocale = request.getParameter( PARAMETER_SITE_LOCALE );
181
182 if ( ( strSiteLocale == null ) || !strSiteLocale.equalsIgnoreCase( LOCALE_EN ) )
183 {
184 strSiteLocale = LOCALE_FR;
185 }
186
187 String strKey = getKey( strDocumentId, strPortletId, strSiteLocale, nMode );
188 String strPage = (String) getFromCache( strKey );
189
190 if ( strPage == null )
191 {
192
193 synchronized ( strKey )
194 {
195
196 strPage = (String) getFromCache( strKey );
197
198
199
200 if ( strPage == null )
201 {
202 AppLogService.debug( " -- Page generation " + strKey + " : doc=" + strDocumentId + " portletid=" +
203 strPortletId + "site_locale=" + strSiteLocale + "nMode=" + nMode );
204 strPage = buildPage( request, strDocumentId, strPortletId, strSiteLocale, nMode );
205
206 if ( IntegerUtils.isNumeric( strDocumentId ) )
207 {
208 int nDocumentId = IntegerUtils.convert( strDocumentId );
209 Document document = DocumentHome.findByPrimaryKeyWithoutBinaries( nDocumentId );
210
211 if ( ( document != null ) )
212 {
213 putInCache( strKey, strPage );
214 }
215 }
216 }
217 else
218 {
219 AppLogService.debug( "Page read from cache after synchronisation " + strKey );
220 }
221 }
222 }
223 else
224 {
225 AppLogService.debug( "Page read from cache " + strKey );
226 }
227
228 return strPage;
229 }
230
231
232
233
234 private void init( )
235 {
236
237
238 String strCache = AppPropertiesService.getProperty( PROPERTY_CACHE_ENABLED, "true" );
239
240 if ( strCache.equalsIgnoreCase( "true" ) )
241 {
242 initCache( getName( ) );
243 }
244
245
246 _cachePortlets = SpringContextService.getBean( "portletCacheService" );
247
248 _cksPortlet = SpringContextService.getBean( "portletCacheKeyService" );
249
250 _bInit = true;
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264 private String buildPage( HttpServletRequest request, String strDocumentId, String strPortletId,
265 String strSiteLocale, int nMode ) throws UserNotSignedException, SiteMessageException
266 {
267 int nPortletId;
268 int nDocumentId;
269 boolean bPortletExist = false;
270 Map<String, String> mapXslParams = new HashMap<String, String>( );
271
272 try
273 {
274 nPortletId = Integer.parseInt( strPortletId );
275 nDocumentId = Integer.parseInt( strDocumentId );
276 }
277 catch ( NumberFormatException nfe )
278 {
279 return PortalService.getDefaultPage( request, nMode );
280 }
281
282 Document document = DocumentHome.findByPrimaryKeyWithoutBinaries( nDocumentId );
283
284 if ( ( document == null ) || ( !document.isValid( ) ) )
285 {
286 return PortalService.getDefaultPage( request, nMode );
287 }
288
289 DocumentType type = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType( ) );
290 DocumentPublication documentPublication = PublishingService.getInstance( )
291 .getDocumentPublication( nPortletId, nDocumentId );
292
293 Map<String, Object> model = new HashMap<String, Object>( );
294
295 if ( documentPublication != null )
296 {
297
298 boolean bIsAlias = DocumentListPortletHome.checkIsAliasPortlet( documentPublication.getPortletId( ) );
299
300 if ( bIsAlias && ( documentPublication.getPortletId( ) != nPortletId ) )
301 {
302 AliasPortlet alias = (AliasPortlet) AliasPortletHome.findByPrimaryKey( nPortletId );
303 nPortletId = alias.getAliasId( );
304 strPortletId = Integer.toString( nPortletId );
305 }
306
307 if ( ( documentPublication.getPortletId( ) == nPortletId ) &&
308 ( documentPublication.getStatus( ) == DocumentPublication.STATUS_PUBLISHED ) )
309 {
310 bPortletExist = true;
311 }
312
313
314 mapXslParams.put( PARAMETER_PUBLICATION_DATE,
315 DateUtil.getDateString( documentPublication.getDatePublishing( ), request.getLocale( ) ) );
316 model.put( MARK_PUBLICATION, documentPublication );
317 }
318
319 if ( bPortletExist )
320 {
321
322 PageData data = new PageData( );
323 data.setName( document.getTitle( ) );
324 data.setPagePath( PortalService.getXPagePathContent( document.getTitle( ), 0, request ) );
325
326 Portlet portlet = PortletHome.findByPrimaryKey( nPortletId );
327 Page page = PageHome.getPage( portlet.getPageId( ) );
328 String strRole = page.getRole( );
329
330 if ( !strRole.equals( Page.ROLE_NONE ) && SecurityService.isAuthenticationEnable( ) )
331 {
332 LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );
333
334 if ( ( user == null ) && ( !SecurityService.getInstance( ).isExternalAuthentication( ) ) )
335 {
336
337 String strAccessControledTemplate = SecurityService.getInstance( ).getAccessControledTemplate( );
338 HashMap<String, Object> modelAccessControledTemplate = new HashMap<String, Object>( );
339 String strLoginUrl = SecurityService.getInstance( ).getLoginPageUrl( );
340 modelAccessControledTemplate.put( MARK_URL_LOGIN, strLoginUrl );
341
342 HtmlTemplate tAccessControled = AppTemplateService.getTemplate( strAccessControledTemplate,
343 request.getLocale( ), modelAccessControledTemplate );
344 data.setContent( tAccessControled.getHtml( ) );
345
346 return PortalService.buildPageContent( data, nMode, request );
347 }
348
349 if ( !SecurityService.getInstance( ).isUserInRole( request, strRole ) )
350 {
351
352 String strAccessDeniedTemplate = SecurityService.getInstance( ).getAccessDeniedTemplate( );
353 HtmlTemplate tAccessDenied = AppTemplateService.getTemplate( strAccessDeniedTemplate,
354 request.getLocale( ) );
355 data.setContent( tAccessDenied.getHtml( ) );
356
357 return PortalService.buildPageContent( data, nMode, request );
358 }
359 }
360
361
362 Enumeration<?> enumParam = request.getParameterNames( );
363 Hashtable<String, String> htParamRequest = new Hashtable<String, String>( );
364 String paramName = "";
365
366 while ( enumParam.hasMoreElements( ) )
367 {
368 paramName = (String) enumParam.nextElement( );
369 htParamRequest.put( paramName, request.getParameter( paramName ) );
370 }
371
372 XmlTransformerService xmlTransformerService = new XmlTransformerService( );
373 StringBuffer strXml = new StringBuffer( );
374 XmlUtil.beginElement( strXml, XML_TAG_CONTENT );
375 XmlUtil.addElement( strXml, XML_TAG_SITE_LOCALE, strSiteLocale );
376 strXml.append( document.getXmlValidatedContent( ) );
377 XmlUtil.endElement( strXml, XML_TAG_CONTENT );
378
379 String strDocument = xmlTransformerService.transformBySourceWithXslCache( strXml.toString( ),
380 type.getContentServiceXslSource( ), DOCUMENT_STYLE_PREFIX_ID + type.getStyleSheetId( nMode ),
381 htParamRequest, null );
382
383 model.put( MARK_DOCUMENT, strDocument );
384
385 if ( !document.isSkipPortlet() )
386 {
387 model.put( MARK_PORTLET, getPortlet( request, strPortletId, nMode ) );
388 }
389
390 if ( !document.isSkipCategories() )
391 {
392 model.put( MARK_CATEGORY, getRelatedDocumentsPortlet( request, document, nPortletId, nMode ) );
393 }
394
395 model.put( MARK_DOCUMENT_ID, strDocumentId );
396 model.put( MARK_PORTLET_ID, strPortletId );
397 model.put( MARK_IS_EXTEND_INSTALLED, PortalService.isExtendActivated( ) );
398
399
400 ResourceEnhancer.buildPageAddOn( model, PROPERTY_RESOURCE_TYPE, nDocumentId, strPortletId, request );
401
402 HtmlTemplate template = AppTemplateService.getTemplate( getTemplatePage( document ), request.getLocale( ),
403 model );
404
405 data.setContent( template.getHtml( ) );
406
407 return PortalService.buildPageContent( data, nMode, request );
408 }
409
410
411 return PortalService.getDefaultPage( request, nMode );
412 }
413
414
415
416
417
418
419
420
421 public boolean isInvoked( HttpServletRequest request )
422 {
423 String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
424 String strIdPortlet = request.getParameter( Parameters.PORTLET_ID );
425
426 if ( ( strDocumentId != null ) && ( strDocumentId.length( ) > 0 ) && ( strIdPortlet != null ) &&
427 ( strIdPortlet.length( ) > 0 ) )
428 {
429 return true;
430 }
431
432 return false;
433 }
434
435
436
437
438
439
440 public String getName( )
441 {
442 return CONTENT_SERVICE_NAME;
443 }
444
445
446
447
448
449
450 private String getTemplatePage( Document document )
451 {
452 if ( document.getPageTemplateDocumentId( ) != 0 )
453 {
454 String strPageTemplateDocument = DocumentHome.getPageTemplateDocumentPath( document.getPageTemplateDocumentId( ) );
455
456 return strPageTemplateDocument;
457 }
458 else
459 {
460 return TEMPLATE_DOCUMENT_PAGE_DEFAULT;
461 }
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475
476 private String getPortlet( HttpServletRequest request, String strPortletId, int nMode )
477 throws SiteMessageException
478 {
479 try
480 {
481 int nPortletId = Integer.parseInt( strPortletId );
482
483 Portlet portlet = PortletHome.findByPrimaryKey( nPortletId );
484
485
486
487
488
489 String strFilePath = AppPropertiesService.getProperty( PROPERTY_DEFAULT_PORTLET_DOCUMENT_LIST_XSL,
490 CONSTANT_DEFAULT_PORTLET_DOCUMENT_LIST_XSL );
491
492 if ( strFilePath == null )
493 {
494 return StringUtils.EMPTY;
495 }
496
497 if ( !strFilePath.startsWith( SLASH ) )
498 {
499 strFilePath = SLASH + strFilePath;
500 }
501
502 String strFileName = strFilePath.substring( strFilePath.lastIndexOf( SLASH ) + 1 );
503 strFilePath = strFilePath.substring( 0, strFilePath.lastIndexOf( SLASH ) + 1 );
504
505 FileInputStream fis = AppPathService.getResourceAsStream( strFilePath, strFileName );
506 Source xslSource = new StreamSource( fis );
507
508
509 Enumeration<?> enumParam = request.getParameterNames( );
510 Hashtable<String, String> htParamRequest = new Hashtable<String, String>( );
511 String paramName = "";
512
513 while ( enumParam.hasMoreElements( ) )
514 {
515 paramName = (String) enumParam.nextElement( );
516 htParamRequest.put( paramName, request.getParameter( paramName ) );
517 }
518
519 Properties outputProperties = ModeHome.getOuputXslProperties( nMode );
520
521
522 if ( nMode != MODE_ADMIN )
523 {
524 htParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getPortalUrl( ) );
525 }
526 else
527 {
528 htParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getAdminPortalUrl( ) );
529 htParamRequest.put( MARKER_TARGET, TARGET_TOP );
530 }
531
532 if ( _cachePortlets.isCacheEnable( ) )
533 {
534 LuteceUser user = null;
535
536 if ( SecurityService.isAuthenticationEnable( ) )
537 {
538 user = SecurityService.getInstance( ).getRegisteredUser( request );
539 }
540
541 boolean bCanBeCached = ( user != null ) ? ( portlet.canBeCachedForConnectedUsers( ) )
542 : ( portlet.canBeCachedForAnonymousUsers( ) );
543
544 if ( bCanBeCached )
545 {
546
547 htParamRequest.put( PARAMETER_PORTLET, String.valueOf( portlet.getId( ) ) );
548
549
550 String strKey = getKey( _cksPortlet.getKey( htParamRequest, nMode, user ) +
551 PORTLET_CACHE_KEY_SUFFIX );
552
553
554 String strPortlet = (String) _cachePortlets.getFromCache( strKey );
555
556 if ( strPortlet == null )
557 {
558
559 synchronized ( strKey )
560 {
561
562
563 strPortlet = (String) _cachePortlets.getFromCache( strKey );
564
565
566
567
568 if ( strPortlet == null )
569 {
570 String strXml = portlet.getXmlDocument( request );
571
572 XmlTransformerService xmlTransformerService = new XmlTransformerService( );
573 String strXslUniquePrefix = DOCUMENT_STYLE_PREFIX_ID + strFilePath + strFileName;
574
575 strPortlet = xmlTransformerService.transformBySourceWithXslCache( strXml, xslSource,
576 strXslUniquePrefix, htParamRequest, outputProperties );
577
578 _cachePortlets.putInCache( strKey, strPortlet );
579 }
580 }
581 }
582
583 return strPortlet;
584 }
585 }
586
587 String strXml = portlet.getXmlDocument( request );
588
589 XmlTransformerService xmlTransformerService = new XmlTransformerService( );
590 String strXslUniquePrefix = DOCUMENT_STYLE_PREFIX_ID + strFilePath + strFileName;
591
592 return xmlTransformerService.transformBySourceWithXslCache( strXml, xslSource, strXslUniquePrefix,
593 htParamRequest, outputProperties );
594 }
595 catch ( NumberFormatException e )
596 {
597 return null;
598 }
599 }
600
601
602
603
604
605
606
607
608
609
610
611 private String getRelatedDocumentsPortlet( HttpServletRequest request, Document document, int nPortletId, int nMode )
612 {
613 if ( ( nMode != MODE_ADMIN ) && ( document.getCategories( ) != null ) &&
614 ( document.getCategories( ).size( ) > 0 ) )
615 {
616 HashMap<String, Object> model = new HashMap<String, Object>( );
617 List<Document> listRelatedDocument = DocumentHome.findByRelatedCategories( document, request.getLocale( ) );
618
619 List<Document> listDocument = new ArrayList<Document>( );
620 ReferenceList listDocumentPortlet = new ReferenceList( );
621
622
623 for ( Document relatedDocument : listRelatedDocument )
624 {
625
626 for ( Portlet portlet : PublishingService.getInstance( )
627 .getPortletsByDocumentId( Integer.toString(
628 relatedDocument.getId( ) ) ) )
629 {
630
631 if ( ( PublishingService.getInstance( ).isPublished( relatedDocument.getId( ), portlet.getId( ) ) ) &&
632 ( portlet.getStatus( ) == Portlet.STATUS_PUBLISHED ) && ( relatedDocument.isValid( ) ) &&
633 ( relatedDocument.getId( ) != document.getId( ) ) )
634 {
635 listDocumentPortlet.addItem( Integer.toString( relatedDocument.getId( ) ),
636 Integer.toString( portlet.getId( ) ) );
637 listDocument.add( relatedDocument );
638
639 break;
640 }
641 }
642 }
643
644 model.put( MARK_DOCUMENT_CATEGORIES_LIST, listDocument );
645 model.put( MARK_PORTLET_ID_LIST, listDocumentPortlet );
646
647 HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_DOCUMENT_CATEGORIES,
648 request.getLocale( ), model );
649
650 return template.getHtml( );
651 }
652 else
653 {
654 return StringUtils.EMPTY;
655 }
656 }
657
658
659
660
661 public void dispose( )
662 {
663 }
664
665
666
667
668
669 public void notifyElementEvicted( Ehcache cache, Element element )
670 {
671 _keyMemory.remove( element.getKey( ) );
672 }
673
674
675
676
677
678 public void notifyElementExpired( Ehcache cache, Element element )
679 {
680 _keyMemory.remove( element.getKey( ) );
681 }
682
683
684
685
686
687 public void notifyElementPut( Ehcache cache, Element element )
688 throws CacheException
689 {
690 }
691
692
693
694
695
696 public void notifyElementRemoved( Ehcache cache, Element element )
697 throws CacheException
698 {
699 _keyMemory.remove( element.getKey( ) );
700 }
701
702
703
704
705
706 public void notifyElementUpdated( Ehcache cache, Element element )
707 throws CacheException
708 {
709 }
710
711
712
713
714 public void notifyRemoveAll( Ehcache cache )
715 {
716 _keyMemory.clear( );
717 }
718
719
720
721
722
723
724
725
726
727
728
729
730
731 private String getKey( String strDocumentId, String strPortletId, String strSiteLocale, int nMode )
732 {
733 String key = "D" + strDocumentId + "P" + strPortletId + "L" + strSiteLocale + "M" + nMode;
734 String keyInMemory = _keyMemory.putIfAbsent( key, key );
735
736 if ( keyInMemory != null )
737 {
738 return keyInMemory;
739 }
740
741 return key;
742 }
743
744
745
746
747
748
749 private String getKey( String strPortletKey )
750 {
751 String keyInMemory = _keyMemory.putIfAbsent( strPortletKey, strPortletKey );
752
753 if ( keyInMemory != null )
754 {
755 return keyInMemory;
756 }
757
758 return strPortletKey;
759 }
760
761
762
763
764 @Override
765 public Object clone( )
766 {
767 return new DocumentContentService( );
768 }
769
770
771
772
773
774
775 public void removeFromCache( String strDocumentId, String strPortletId )
776 {
777 if ( getCache( ) != null )
778 {
779 String strKey = getKey( strDocumentId, strPortletId, LOCALE_FR, PortalJspBean.MODE_HTML );
780
781 getCache( ).remove( strKey );
782
783 strKey = getKey( strDocumentId, strPortletId, LOCALE_EN, PortalJspBean.MODE_HTML );
784
785 getCache( ).remove( strKey );
786 }
787 }
788 }