KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jahia > layout > PortletsPersistanceManager


1 //
2
// ____.
3
// __/\ ______| |__/\. _______
4
// __ .____| | \ | +----+ \
5
// _______| /--| | | - \ _ | : - \_________
6
// \\______: :---| : : | : | \________>
7
// |__\---\_____________:______: :____|____:_____\
8
// /_____|
9
//
10
// . . . i n j a h i a w e t r u s t . . .
11
//
12

13 package org.jahia.layout;
14
15 import java.io.PrintWriter JavaDoc;
16 import java.io.StringWriter JavaDoc;
17 import java.util.Enumeration JavaDoc;
18 import java.util.Hashtable JavaDoc;
19 import java.util.Vector JavaDoc;
20
21 import org.jahia.data.JahiaData;
22 import org.jahia.data.containers.JahiaContainer;
23 import org.jahia.data.containers.JahiaContainerDefinition;
24 import org.jahia.exceptions.JahiaException;
25 import org.jahia.params.ParamBean;
26 import org.jahia.registries.ServicesRegistry;
27 import org.jahia.services.acl.JahiaACLEntry;
28 import org.jahia.services.acl.JahiaBaseACL;
29 import org.jahia.services.usermanager.JahiaGroup;
30 import org.jahia.services.usermanager.JahiaGroupManagerService;
31 import org.jahia.services.usermanager.JahiaUser;
32 import org.jahia.utils.JahiaConsole;
33
34 /**
35  * This class offers operations available on portlet's storage. It is used as a
36  * facade to regroup operations on two different serializer, one XML, and one
37  * in JahiaContent (containers to be precise)
38  * It is able to store the portlets metadata either in Jahia Containers or in
39  * XML files, depending on the degree of personalization desired (shared vs
40  * personal portlets). This is why the getInstance method takes a parameters, in
41  * order to retrieve two different singleton objects that offer a purely shared
42  * or personalized portlet manager instance.
43  *
44  * @author Serge Huber
45  */

46 public class PortletsPersistanceManager {
47
48     private static PortletsPersistanceManager sharingManager = null;
49     private static PortletsPersistanceManager personalizationManager = null;
50     private static String JavaDoc theDefaultUser = "guest";
51     private DesktopBean theDesktop;
52     private PortletBean thePortlet;
53     private SkinBean theSkin;
54     private Enumeration JavaDoc thePortletList;
55     private int theColumnCount;
56
57     private boolean personalizationActivated = false; // the personalization mode
58
// represents a mode where each user can have a different set of portlets
59
// on a single page. This is achieved through the use of container entries,
60
// whose reference is retrieved from the XML serializer, allowing us to
61
// store this data for each user (one XML file per user)
62

63     /**
64      * Constructor, private because of singleton pattern
65      * @param personalizationActivated this boolean allows us to build seperate
66      * manager instance depending on whether we want the personalization features
67      * to be active or not
68      */

69     private PortletsPersistanceManager(boolean personalizationActivated)
70     {
71         this.personalizationActivated = personalizationActivated;
72         JahiaConsole.println( "PortletsPersistanceManager", "***** Starting the Portlets Persistance Manager *****" );
73     }
74
75     /**
76      * Retrieves an instance of the PortletsPersistanceManager according to the personalization
77      * mode setting.
78      *
79      * @param personalizationActivated specifies whether we want to retrieve
80      * the singleton that uses the personalization features or not.
81      *
82      * @returns the singleton PortletsPersistanceManager object according to the setting
83      * of the boolean specifying the personalization mode
84      */

85     public static synchronized PortletsPersistanceManager getInstance(boolean personalizationActivatedMode)
86     {
87         if (personalizationActivatedMode) {
88             if (personalizationManager == null) {
89                 personalizationManager =
90                     new PortletsPersistanceManager(personalizationActivatedMode);
91             }
92             return personalizationManager;
93         } else {
94             if (sharingManager == null) {
95                 sharingManager = new PortletsPersistanceManager(personalizationActivatedMode);
96             }
97             return sharingManager;
98         }
99     } // end getInstance
100

101     /**
102      * Retrieves a list of portlets for the specified page and the specified
103      * portlet group name on the page
104      *
105      * @param jParams ParamBean used to access jahia content data that store
106      * the portlet parameters in containers
107      * @param pageID the current JahiaPageID, ie the page on which the portlet
108      * layout system is present
109      * @param portletGroupName the name of the portlet group, as they may be
110      * multiple portlet groups per page
111      * @param theUserName the name of the currently logged in user
112      * @param uRLtoTemplatesDir the directory in which the personalized XML files
113      * are stored
114      *
115      * @returns an Enumeration containing objects of type PortletBean
116      */

117     public Enumeration JavaDoc getPortlets(ParamBean jParams,
118                                    JahiaData jData,
119                                    int pageID,
120                                    String JavaDoc portletGroupName,
121                                    String JavaDoc theUserName,
122                                    String JavaDoc uRLtoTemplatesDir) {
123
124         Enumeration JavaDoc personalPortletList = null;
125         Enumeration JavaDoc sharedPortletList = null;
126
127         try {
128             String JavaDoc theFile = theUserName + ".xml";
129             String JavaDoc theDefaultFile = theDefaultUser + ".xml";
130
131             if (jData == null) {
132                jData = new JahiaData(jParams); /** @todo find a lighter way to do this */
133             }
134             PortletsJahiaContentSerializer contentPortletSerializer =
135                 new PortletsJahiaContentSerializer(jData, portletGroupName);
136             sharedPortletList = contentPortletSerializer.getPortlets(theUserName);
137
138             if (personalizationActivated) {
139
140                 PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
141                                                                           theFile,
142                                                                           theDefaultFile,
143                                                                           pageID,
144                                                                           portletGroupName);
145                 personalPortletList = XMLFile.getPortlets();
146             }
147         } catch (JahiaException e) {
148             StringWriter JavaDoc strWriter = new StringWriter JavaDoc();
149             PrintWriter JavaDoc ptrWriter = new PrintWriter JavaDoc(strWriter);
150             e.printStackTrace(ptrWriter);
151             JahiaConsole.println( "PortletsPersistanceManager.getPortlets", strWriter.toString() );
152         }
153
154         try {
155             thePortletList = mergePortletLists(sharedPortletList, personalPortletList);
156         } catch (Throwable JavaDoc t) {
157             StringWriter JavaDoc strWriter = new StringWriter JavaDoc();
158             PrintWriter JavaDoc ptrWriter = new PrintWriter JavaDoc(strWriter);
159             t.printStackTrace(ptrWriter);
160             JahiaConsole.println("PortletsPersistanceManager",
161                                  "Exception generated -> " + strWriter.toString());
162         }
163         return thePortletList;
164     } // end getPortlets
165

166
167     /**
168      * Retrieves a list of portlets for the specified page and column
169      *
170      * @param jParams ParamBean object used to create JahiaData object to
171      * access page containers and access to portlet metadata stored in there
172      * @param theColumn the column number for which to retrieve the portlet
173      * list
174      * @param portletGroupName the name of the portlet group which we are accessing.
175      * This is usefuly in the case of multiple portlet sets on a single page.
176      * @param theUserName the current user name
177      * @param uRLtoTemplatesDir the explicit URL to the templates directory, where
178      * the XML files are stored.
179      *
180      * @returns an Enumeration of PortletBean objects
181      */

182     public Enumeration JavaDoc getPortletsFromColumn(ParamBean jParams,
183                                              JahiaData jData,
184                                              int theColumn,
185                                              String JavaDoc portletGroupName,
186                                              String JavaDoc theUserName,
187                                              String JavaDoc uRLtoTemplatesDir) {
188         try {
189             if (jData == null) {
190                jData = new JahiaData(jParams); /** @todo find a lighter way to do this */
191             }
192             PortletsJahiaContentSerializer jPortletSerializer=
193                 new PortletsJahiaContentSerializer(jData, portletGroupName);
194
195             /** @todo JahiaContent version missing here !! */
196
197             if (personalizationActivated) {
198                 String JavaDoc theFile = theUserName + ".xml";
199                 String JavaDoc theDefaultFile = theDefaultUser + ".xml";
200                 PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
201                                                                           theFile,
202                                                                           theDefaultFile,
203                                                                           jParams.getPageID(),
204                                                                           portletGroupName);
205                 thePortletList = XMLFile.getPortletsFromColumn(theColumn);
206             }
207         } catch (JahiaException e) {
208             StringWriter JavaDoc strWriter = new StringWriter JavaDoc();
209             PrintWriter JavaDoc ptrWriter = new PrintWriter JavaDoc(strWriter);
210             e.printStackTrace(ptrWriter);
211             JahiaConsole.println( "PortletsPersistanceManager.getPortletsFromColumn", strWriter.toString() );
212         }
213         return thePortletList;
214     } // end getPortletsFromColumn
215

216
217     /**
218      * Retrieves a portlet for the specified page and portletid
219      */

220     public PortletBean getPortlet(ParamBean jParams,
221                                   int thePortletID,
222                                   int thePageID,
223                                   String JavaDoc theUserName,
224                                   String JavaDoc uRLtoTemplatesDir) {
225         try {
226
227             if (personalizationActivated) {
228                 String JavaDoc theFile = theUserName + ".xml";
229                 String JavaDoc theDefaultFile = theDefaultUser + ".xml";
230                 PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
231                                                                           theFile,
232                                                                           theDefaultFile,
233                                                                           thePageID);
234                 thePortlet = XMLFile.getPortlet(thePortletID);
235             }
236         } catch (JahiaException e) {
237             StringWriter JavaDoc strWriter = new StringWriter JavaDoc();
238             PrintWriter JavaDoc ptrWriter = new PrintWriter JavaDoc(strWriter);
239             e.printStackTrace(ptrWriter);
240             JahiaConsole.println( "PortletsPersistanceManager.getPortlet", strWriter.toString() );
241         }
242         return thePortlet;
243     } // end getPortletByID
244

245
246
247     /**
248      * Add new portlet in the specified page
249      *
250      * @author J�r�me B�dat
251      *
252      */

253     public void addPortlet(ParamBean jParams,
254                            JahiaData jData,
255                            JahiaContainer container,
256                            int theJahiaEventPageID, String JavaDoc theJahiaEventUserName,
257                            String JavaDoc uRLtoTemplatesDir)
258     throws JahiaException {
259         /**
260          * @todo still some problems here because all the portlets are created
261          * with a position of (0,0), therefore overlapping at rendering.
262          */

263         if (personalizationActivated) {
264             // this a personal portlet, let's store it in the XML serializer
265

266             String JavaDoc theFile = theJahiaEventUserName + ".xml";
267             String JavaDoc theDefaultFile = theDefaultUser + ".xml";
268             PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
269                                                                       theFile,
270                                                                       theDefaultFile,
271                                                                       theJahiaEventPageID);
272
273             XMLFile.storePortlet(new PortletBean(container.getID()), theJahiaEventUserName);
274         } else {
275             if (jData == null) {
276                 jData = new JahiaData(jParams); /** @todo find a lighter way to do this ! */
277             }
278             JahiaContainerDefinition containerDef = container.getDefinition();
279             String JavaDoc portletGroupName = containerDef.getName();
280             PortletsJahiaContentSerializer jPortletSerializer =
281                 new PortletsJahiaContentSerializer(jData, portletGroupName);
282
283             jPortletSerializer.storePortlet(new PortletBean(container.getID()),
284                                             theJahiaEventUserName);
285         }
286     } // end addPortlet
287

288
289     /**
290      * Update portlet settings for the specified portletid
291      *
292      * @author J�r�me B�dat
293      *
294      */

295     public void setPortlet(ParamBean jParams,
296                            JahiaData jData,
297                            PortletBean thePortlet, String JavaDoc portletGroupName, int thePageID,
298                            String JavaDoc theUserName, String JavaDoc uRLtoTemplatesDir) throws JahiaException {
299         if (personalizationActivated) {
300             String JavaDoc theFile = theUserName + ".xml";
301             String JavaDoc theDefaultFile = theDefaultUser + ".xml";
302             PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
303                                                                       theFile,
304                                                                       theDefaultFile,
305                                                                       thePageID);
306             XMLFile.storePortlet(thePortlet, theUserName);
307         } else {
308             if (jData == null) {
309                 jData = new JahiaData(jParams); /** @todo find a better lighter way ! */
310             }
311             PortletsJahiaContentSerializer jPortletSerializer =
312                 new PortletsJahiaContentSerializer(jData, portletGroupName);
313             jPortletSerializer.storePortlet(thePortlet, theUserName);
314         }
315
316     } // end setPortlet
317

318
319     /**
320      * Delete a Portlet from the storage area
321      *
322      */

323     public void delPortlet(ParamBean jParams,
324                            JahiaData jData,
325                            JahiaContainer container,
326                            int theJahiaEventPageID, String JavaDoc theJahiaEventUserName,
327                            String JavaDoc uRLtoTemplatesDir) throws JahiaException {
328         if (personalizationActivated) {
329             String JavaDoc theFile = theJahiaEventUserName + ".xml";
330             String JavaDoc theDefaultFile = theDefaultUser + ".xml";
331             PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
332                                                                       theFile,
333                                                                       theDefaultFile,
334                                                                       theJahiaEventPageID);
335             XMLFile.deletePortlet(container.getID());
336         } else {
337             if (jData == null) {
338                 jData = new JahiaData(jParams); /** @todo find a better lighter way ! */
339             }
340             JahiaContainerDefinition containerDef = container.getDefinition();
341             String JavaDoc portletGroupName = containerDef.getName();
342             PortletsJahiaContentSerializer jPortletSerializer =
343                 new PortletsJahiaContentSerializer(jData, portletGroupName);
344             jPortletSerializer.removePortlet(new PortletBean(container.getID()),
345                                              theJahiaEventUserName);
346         }
347     } // end delPortlet
348

349
350     /**
351      * Add new portlet group (add new page)
352      *
353      * @author J�r�me B�dat
354      *
355      */

356     public void addPortletGroup(ParamBean jParams,
357                                 int theJahiaEventObjectID,
358                                 String JavaDoc theJahiaEventUserName,
359                                 String JavaDoc uRLtoTemplatesDir) throws JahiaException {
360         if (personalizationActivated) {
361             String JavaDoc theFile = theJahiaEventUserName + ".xml";
362             String JavaDoc theDefaultFile = theDefaultUser + ".xml";
363             PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
364                                                                       theFile,
365                                                                       theDefaultFile,
366                                                                       theJahiaEventObjectID);
367             XMLFile.addPortletGroup(theJahiaEventObjectID);
368         }
369     } // end addPortletGroup
370

371
372     /**
373      * Retrieves a column count for the specified page
374      *
375      * @author J�r�me B�dat
376      *
377      */

378     /*
379     public int getPortletsColumnCount(int thePageID, String theUserName,
380                                       String uRLtoTemplatesDir) throws JahiaException {
381         String theFile = theUserName + ".xml";
382         String theDefaultFile = theDefaultUser + ".xml";
383         PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir,
384                                                                   theFile,
385                                                                   theDefaultFile,
386                                                                   thePageID);
387         theColumnCount = XMLFile.getColumnCount();
388         return theColumnCount;
389     } // end getPortletsColumnCount
390     */

391     /**
392      * Copy the Portlet Group from the specified user XML file to the other users files
393      *
394      * @author J�r�me B�dat
395      *
396      */

397      /*
398     public void copyPortletGroup(int thePageID, String theUserName,
399                                  String uRLtoTemplatesDir) {
400         try {
401             String theFile = theUserName + ".xml";
402             String theDefaultFile = theDefaultUser + ".xml";
403             PortletsXMLSerializer XMLFile = new PortletsXMLSerializer(uRLtoTemplatesDir, theFile, theDefaultFile, thePageID);
404
405             File portletsAPIPathDir = new File( uRLtoTemplatesDir + File.separator + "portlets_api" + File.separator );
406             File[] portletsAPIFileList = portletsAPIPathDir.listFiles();
407
408             Vector sourceIDList = XMLFile.copyXMLPortletGroup(uRLtoTemplatesDir + File.separator + "portlets_api" + File.separator + theDefaultFile);
409             for(int i=0; i<portletsAPIFileList.length; i++) {
410                 if (!portletsAPIFileList[i].getAbsolutePath().equals(uRLtoTemplatesDir + File.separator + "portlets_api" + File.separator + theFile) && !portletsAPIFileList[i].getAbsolutePath().equals(uRLtoTemplatesDir + File.separator + "portlets_api" + File.separator + theDefaultFile)) {
411                     XMLFile.copyXMLPortletGroup(portletsAPIFileList[i].getAbsolutePath());
412                 }
413             }
414             unassignRightsToPortlets (sourceIDList);
415
416         } catch (JahiaException e) {
417             JahiaConsole.println( "PortletsBean -> copyPortletGroup", e.toString() );
418         }
419     } // end copyPortletGroup
420 */

421
422     /**
423      * Load a Skin from the DataBase
424      *
425      * @author J�r�me B�dat
426      *
427      */

428     public SkinBean loadSkin(String JavaDoc theType, int theID) {
429         try {
430             PortletWidgetsDBSerializer DB = PortletWidgetsDBSerializer.getInstance();
431             if (theType.equals("portlet")) {
432                 theSkin = DB.load_portlet_skin(theID);
433             } else if (theType.equals("desktop")) {
434                 theSkin = DB.load_desktop_skin(theID);
435             }
436             if (theSkin == null) {
437                 theSkin = new SkinBean(1,"","");
438             }
439         } catch (JahiaException e) {
440             theSkin = new SkinBean(1,"","");
441             JahiaConsole.println( "PortletsPersistanceManager.loadSkin", e.toString() );
442         }
443         return theSkin;
444     } // end loadSkin
445

446
447
448     //-------------------------------------------------------------------------
449
private void unassignRightsToPortlets (ParamBean jParams, Vector JavaDoc containerIDs) {
450
451         // get the administrator group name.
452
String JavaDoc adminGroupName =
453                 JahiaGroupManagerService.ADMINISTRATORS_GROUPNAME;
454
455         // get the administrator group object.
456
JahiaGroup adminGroup = ServicesRegistry.getInstance().
457                 getJahiaGroupManagerService ().
458                 lookupGroup (JahiaGroupManagerService.ADMINISTRATORS_GROUPNAME);
459
460         // get the administrator group object.
461
JahiaGroup usersGroup = ServicesRegistry.getInstance().
462                 getJahiaGroupManagerService ().
463                 lookupGroup (JahiaGroupManagerService.USERS_GROUPNAME);
464
465         // get the administrator group object.
466
JahiaGroup guestGroup = ServicesRegistry.getInstance().
467                 getJahiaGroupManagerService ().
468                 lookupGroup (JahiaGroupManagerService.GUEST_GROUPNAME);
469
470
471         // if the administrators group is not available .. just leave.
472
// ok ok .. it's a hack, but I'll fix this later. (Fulco)
473
if (adminGroup == null) {
474             return;
475         }
476
477
478         // process all the fields to reset the rights.
479
for (int index = 0; index < containerIDs.size(); index++) {
480             try {
481                 // get the field ID
482
int containerID = ((Integer JavaDoc)containerIDs.get (index)).intValue();
483
484                 // Get the container reference holding the field.
485
JahiaContainer container = ServicesRegistry.getInstance().
486                         getJahiaContainersService().loadContainerInfo (containerID);
487
488                 if (container != null) {
489                     // Get the ACL of the container
490

491                     try {
492                         JahiaBaseACL acl = new JahiaBaseACL (container.getAclID());
493                         if (acl != null) {
494
495                             // remove all the user who are not an
496
// administrator for administrator users, keep
497
// the existing entry.
498

499                             // get all the user entries.
500
Vector JavaDoc usernames = acl.getUsernameList (null);
501                             if (usernames.size() > 0) {
502                                 for (int i=0; i<usernames.size(); i++) {
503                                     String JavaDoc username = (String JavaDoc)usernames.get(i);
504                                     JahiaUser user = ServicesRegistry.
505                                             getInstance().
506                                             getJahiaUserManagerService().
507                                             lookupUser (username);
508                                     if ((user != null) &&
509                                         (!user.isMemberOfGroup (jParams.getSiteID(), adminGroupName)))
510                                     {
511                                         acl.removeUserEntry (user);
512                                     }
513                                 }
514                             }
515
516                             // disable the access to the users and guest group
517
JahiaACLEntry entry = new JahiaACLEntry (0, 0);
518                             entry.setPermission (JahiaBaseACL.READ_RIGHTS, JahiaACLEntry.ACL_YES);
519
520                             acl.setGroupEntry (usersGroup, entry);
521                             acl.setGroupEntry (guestGroup, entry);
522
523                             // set the new administration group rights
524
JahiaACLEntry adminEntry = new JahiaACLEntry ();
525                             for (int i=0; i<acl.size(); i++) {
526                                 adminEntry.setPermission (i, JahiaACLEntry.ACL_YES);
527                             }
528
529                             acl.setGroupEntry (adminGroup, adminEntry);
530                         }
531                     }
532                     catch (Throwable JavaDoc t) {
533                         // the ACL was not found .. go to the next field.
534
}
535                 }
536
537             }
538             catch (JahiaException ex) {
539                 // go to the next field.
540
}
541         }
542     }
543
544     /**
545      * This method allows us to do a merge of two portlet lists and return the
546      * result of the merge.
547      * This isn't simply a concatenation of the elements of the first list with
548      * the ones from the second, instead it check the element of the first list
549      * if they allow parameter "inheritance" from the second. In effect, this
550      * is the stuff that's going on :
551      * iteration through the first list
552      * for each element of masterList do
553      * check list two for same element
554      * if present in both list then
555      * if element of list one is blocked
556      * do nothing with element of list two (= reject it)
557      * else
558      * merge the parameters, using the list two parameters
559      * as predominant and store the portlet in the result
560      * list
561      * end if
562      * else
563      * insert the element in the result list
564      * end if
565      * end for
566      */

567     private Enumeration JavaDoc mergePortletLists( Enumeration JavaDoc masterList,
568                                            Enumeration JavaDoc slaveList )
569     throws JahiaException {
570
571         JahiaConsole.println("PortletsPersistanceManager.mergePortletLists", "Merging portlet lists....");
572
573         Hashtable JavaDoc resultList = new Hashtable JavaDoc();
574         if (masterList == null) {
575             return slaveList;
576         }
577         if (slaveList == null) {
578             return masterList;
579         }
580
581         // first we copy the master list into the hashtable
582
while (masterList.hasMoreElements()) {
583             Object JavaDoc curElement = masterList.nextElement();
584             if (!(curElement instanceof PortletBean)) {
585                 throw new JahiaException("PortletManager.mergePortletLists",
586                                          "Master portlet list contains invalid objects !",
587                                          JahiaException.ERROR_SEVERITY, JahiaException.ENGINE_ERROR);
588             }
589             PortletBean pb = (PortletBean) curElement;
590             resultList.put(new Integer JavaDoc(pb.getPortletSourceID()), pb);
591         }
592
593         // now let's merge, resolving conflicts as they arise
594
while (slaveList.hasMoreElements()) {
595             Object JavaDoc curElement = slaveList.nextElement();
596             if (!(curElement instanceof PortletBean)) {
597                 throw new JahiaException("PortletManager.mergePortletLists",
598                                          "Slave portlet list contains invalid objects !",
599                                          JahiaException.ERROR_SEVERITY, JahiaException.ENGINE_ERROR);
600             }
601             PortletBean pb = (PortletBean) curElement;
602             if (resultList.containsKey(new Integer JavaDoc(pb.getPortletSourceID()))) {
603                 // found the portlet from the master list, let's resolve the
604
// conflict.
605
PortletBean masterPB = (PortletBean) resultList.get(new Integer JavaDoc(pb.getPortletSourceID()));
606                 /** @todo for the moment we just ignore the slave data, we will
607                  * work on implementing this in the next version of the layout
608                  * system
609                  */

610             } else {
611                 // the portlet isn't already in the list, let's simply add it
612
resultList.put(new Integer JavaDoc(pb.getPortletSourceID()), pb);
613             }
614         }
615         return resultList.elements();
616     }
617
618 }
619
Popular Tags