KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > invocation > iiop > IIOPInvoker


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.invocation.iiop;
23
24 import java.net.InetAddress JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import javax.naming.Context JavaDoc;
30 import javax.naming.InitialContext JavaDoc;
31 import javax.naming.Name JavaDoc;
32 import javax.naming.NamingException JavaDoc;
33 import javax.naming.Reference JavaDoc;
34 import javax.naming.spi.ObjectFactory JavaDoc;
35
36 import org.omg.CORBA.LocalObject JavaDoc;
37 import org.omg.CORBA.Policy JavaDoc;
38 import org.omg.CORBA.SetOverrideType JavaDoc;
39 import org.omg.CORBA.UNKNOWN JavaDoc;
40 import org.omg.PortableServer.IdAssignmentPolicyValue JavaDoc;
41 import org.omg.PortableServer.IdUniquenessPolicyValue JavaDoc;
42 import org.omg.PortableServer.LifespanPolicyValue JavaDoc;
43 import org.omg.PortableServer.POA JavaDoc;
44 import org.omg.PortableServer.POAManagerPackage.AdapterInactive JavaDoc;
45 import org.omg.PortableServer.RequestProcessingPolicyValue JavaDoc;
46 import org.omg.PortableServer.Servant JavaDoc;
47 import org.omg.PortableServer.ServantLocator JavaDoc;
48 import org.omg.PortableServer.ServantLocatorPackage.CookieHolder JavaDoc;
49 import org.omg.PortableServer.ServantRetentionPolicyValue JavaDoc;
50
51 import org.jboss.iiop.CorbaORBService;
52 import org.jboss.naming.Util;
53 import org.jboss.system.ServiceMBeanSupport;
54 import org.jboss.system.Registry;
55
56 /**
57  * IIOP invoker that routs IIOP requests to CORBA servants.
58  * It implements the interface <code>ServantRegistries</code>, which
59  * gives access to four <code>ServantRegistry</code> instances:
60  * <ul>
61  * <li>a <code>ServantRegistry</code> with a single transient POA
62  * shared among all its servants;</li>
63  * <li>a <code>ServantRegistry</code> with a single persistent POA
64  * shared among all its servants;</li>
65  * <li>a <code>ServantRegistry</code> with a transient POA per servant;</li>
66  * <li>a <code>ServantRegistry</code> with persistent POA per servant.</li>
67  * </ul>
68  *
69  * CORBA servants registered with any of these
70  * <code>ServantRegistry</code> instances will receive IIOP invocations.
71  * These CORBA servants will typically be thin wrappers that merely forward
72  * to the JBoss MBean server any invocations they receive.
73  *
74  * @author <a HREF="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
75  * @version $Revision: 37459 $
76  */

77 public class IIOPInvoker
78       extends ServiceMBeanSupport
79    implements IIOPInvokerMBean, ServantRegistries, ObjectFactory JavaDoc
80 {
81
82    // Attributes -------------------------------------------------------------
83

84    /** A reference to the singleton IIOPInvoker. */
85    private static IIOPInvoker theIIOPInvoker;
86
87    /** The root POA. **/
88    private POA JavaDoc rootPOA;
89
90    /** A ServantRegistry with a transient POA shared by all servants. */
91    private ServantRegistry registryWithSharedTransientPOA;
92
93    /** The transient POA used by the ServantRegistry above. */
94    private POA JavaDoc transientPOA;
95
96    /** The transient servant map used by the ServantRegistry above. */
97    private Map JavaDoc transientServantMap;
98
99    /** A ServantRegistry with a persistent POA shared by all servants. */
100    private ServantRegistry registryWithSharedPersistentPOA;
101
102    /** The persistent POA used by the ServantRegistry above. */
103    private POA JavaDoc persistentPOA;
104
105    /** The persistent servant map used by the ServantRegistry above. */
106    private Map JavaDoc persistentServantMap;
107
108    /** A ServantRegistry with a transient POA per servant. */
109    private ServantRegistry registryWithTransientPOAPerServant;
110
111    /** The transient POA map used by the ServantRegistry above. */
112    private Map JavaDoc transientPoaMap;
113
114    /** POA policies used by the ServantRegistry above. */
115    private Policy JavaDoc[] transientPoaPolicies;
116
117    /** A ServantRegistry with a persistent POA per servant. */
118    private ServantRegistry registryWithPersistentPOAPerServant;
119
120    /** The persistent POA map used by the ServantRegistry above. */
121    private Map JavaDoc persistentPoaMap;
122
123    /** POA policies used by the ServantRegistry above. */
124    private Policy JavaDoc[] persistentPoaPolicies;
125
126
127    // ServiceMBeanSupport overrides ---------------------------------
128

129    public void createService()
130          throws Exception JavaDoc
131    {
132       theIIOPInvoker = this;
133       transientServantMap = Collections.synchronizedMap(new HashMap JavaDoc());
134       persistentServantMap = Collections.synchronizedMap(new HashMap JavaDoc());
135       transientPoaMap = Collections.synchronizedMap(new HashMap JavaDoc());
136       persistentPoaMap = Collections.synchronizedMap(new HashMap JavaDoc());
137    }
138
139    public void startService()
140          throws Exception JavaDoc
141    {
142       // Get a reference for the root POA
143
try {
144          rootPOA = (POA JavaDoc)new InitialContext JavaDoc().lookup("java:/"
145                                               + CorbaORBService.POA_NAME);
146       }
147       catch (NamingException JavaDoc e) {
148          throw new RuntimeException JavaDoc("Cannot lookup java:/"
149                                     + CorbaORBService.POA_NAME + ": " + e);
150       }
151
152       // Policies for per-servant transient POAs
153
transientPoaPolicies = new Policy JavaDoc[] {
154          rootPOA.create_lifespan_policy(
155                            LifespanPolicyValue.TRANSIENT),
156          rootPOA.create_id_assignment_policy(
157                            IdAssignmentPolicyValue.USER_ID),
158          rootPOA.create_servant_retention_policy(
159                            ServantRetentionPolicyValue.NON_RETAIN),
160          rootPOA.create_request_processing_policy(
161                            RequestProcessingPolicyValue.USE_DEFAULT_SERVANT),
162          rootPOA.create_id_uniqueness_policy(
163                            IdUniquenessPolicyValue.MULTIPLE_ID),
164       };
165
166       // Policies for per-servant persistent POAs
167
persistentPoaPolicies = new Policy JavaDoc[] {
168          rootPOA.create_lifespan_policy(
169                            LifespanPolicyValue.PERSISTENT),
170          rootPOA.create_id_assignment_policy(
171                            IdAssignmentPolicyValue.USER_ID),
172          rootPOA.create_servant_retention_policy(
173                            ServantRetentionPolicyValue.NON_RETAIN),
174          rootPOA.create_request_processing_policy(
175                            RequestProcessingPolicyValue.USE_DEFAULT_SERVANT),
176          rootPOA.create_id_uniqueness_policy(
177                            IdUniquenessPolicyValue.MULTIPLE_ID),
178       };
179
180       // Policies for this IIOPInvoker's shared transient POA
181
Policy JavaDoc[] policies = new Policy JavaDoc[] {
182             rootPOA.create_lifespan_policy(
183                         LifespanPolicyValue.TRANSIENT),
184             rootPOA.create_id_assignment_policy(
185                         IdAssignmentPolicyValue.USER_ID),
186             rootPOA.create_servant_retention_policy(
187                         ServantRetentionPolicyValue.NON_RETAIN),
188             rootPOA.create_request_processing_policy(
189                         RequestProcessingPolicyValue.USE_SERVANT_MANAGER),
190             rootPOA.create_id_uniqueness_policy(
191                         IdUniquenessPolicyValue.MULTIPLE_ID)
192       };
193
194       // Create this IIOPInvoker's shared transient POA
195
// and set its servant locator
196
transientPOA = rootPOA.create_POA("TPOA", null, policies);
197       transientPOA.set_servant_manager(new TransientServantLocator());
198
199       // Change just one policy for this IIOPInvoker's shared persistent POA
200
policies[0] = rootPOA.create_lifespan_policy(
201             LifespanPolicyValue.PERSISTENT);
202  
203       // Create this IIOPInvoker's shared persisten POA
204
// and set its servant locator
205
persistentPOA = rootPOA.create_POA("PPOA", null, policies);
206       persistentPOA.set_servant_manager(new PersistentServantLocator());
207
208       // Create this IIOPInvoker's ServantRegistry implementations
209
registryWithSharedTransientPOA =
210          new ServantRegistryWithSharedTransientPOA();
211       registryWithSharedPersistentPOA =
212          new ServantRegistryWithSharedPersistentPOA();
213       registryWithTransientPOAPerServant =
214          new ServantRegistryWithTransientPOAPerServant();
215       registryWithPersistentPOAPerServant =
216          new ServantRegistryWithPersistentPOAPerServant();
217
218       // Export this invoker
219
Registry.bind(getServiceName(), this);
220       
221       // Activate my shared POAs
222
transientPOA.the_POAManager().activate();
223       persistentPOA.the_POAManager().activate();
224       
225       Context JavaDoc context = new InitialContext JavaDoc();
226       
227       // Bind the invoker in the JNDI invoker naming space
228
Util.rebind(
229             // The context
230
context,
231             // It should look like so "invokers/<name>/iiop"
232
"invokers/" + InetAddress.getLocalHost().getHostName() + "/iiop",
233             // A reference to this invoker
234
new Reference JavaDoc(getClass().getName(),
235                           getClass().getName(),
236                           null));
237
238       getLog().debug("Bound IIOP invoker for JMX node");
239    }
240
241    public void stopService()
242          throws Exception JavaDoc
243    {
244       // Destroy my shared POAs
245
try {
246          transientPOA.the_POAManager().deactivate(
247                                             false, /* etherealize_objects */
248                                             true /* wait_for_completion */ );
249          persistentPOA.the_POAManager().deactivate(
250                                             false, /* etherealize_objects */
251                                             true /* wait_for_completion */ );
252          transientPOA.destroy(false, /* etherealize_objects */
253                               false /* wait_for_completion */ );
254          persistentPOA.destroy(false, /* etherealize_objects */
255                                false /* wait_for_completion */ );
256       }
257       catch (AdapterInactive JavaDoc adapterInactive) {
258           getLog().error("Cannot deactivate home POA", adapterInactive);
259       }
260    }
261
262    // Auxiliary static methods -----------------------------------------------
263

264    private static Policy JavaDoc[] concatPolicies(Policy JavaDoc[] policies1,
265                                           Policy JavaDoc[] policies2)
266    {
267       Policy JavaDoc[] policies = new Policy JavaDoc[policies1.length + policies2.length];
268       int j = 0;
269       for (int i = 0; i < policies1.length; i++, j++) {
270          policies[j] = policies1[i];
271       }
272       for (int i = 0; i < policies2.length; i++, j++) {
273          policies[j] = policies2[i];
274       }
275       return policies;
276    }
277
278
279    // Implementation of the interface ServantRegistries -----------------------
280

281    public ServantRegistry getServantRegistry(ServantRegistryKind kind)
282    {
283       if (kind == ServantRegistryKind.SHARED_TRANSIENT_POA) {
284          return registryWithSharedTransientPOA;
285       }
286       else if (kind == ServantRegistryKind.SHARED_PERSISTENT_POA) {
287          return registryWithSharedPersistentPOA;
288       }
289       else if (kind == ServantRegistryKind.TRANSIENT_POA_PER_SERVANT) {
290          return registryWithTransientPOAPerServant;
291       }
292       else if (kind == ServantRegistryKind.PERSISTENT_POA_PER_SERVANT) {
293          return registryWithPersistentPOAPerServant;
294       }
295       else {
296          return null;
297       }
298    }
299
300    // Implementation of the interface ObjectFactory ---------------------------
301

302    public Object JavaDoc getObjectInstance(Object JavaDoc obj, Name JavaDoc name,
303                                    Context JavaDoc nameCtx, Hashtable JavaDoc environment)
304          throws Exception JavaDoc
305    {
306       String JavaDoc s = name.toString();
307       if (getLog().isTraceEnabled())
308          getLog().trace("getObjectInstance: obj.getClass().getName=\"" +
309                         obj.getClass().getName() +
310                         "\n name=" + s);
311       if (s.equals("iiop"))
312          return theIIOPInvoker;
313       else
314          return null;
315    }
316
317    // Static nested classes that implement the interface ReferenceFactory -----
318

319    static class PoaAndPoliciesReferenceFactory
320       implements ReferenceFactory
321    {
322       private POA JavaDoc poa;
323       private String JavaDoc servantName;
324       private Policy JavaDoc[] policies;
325       private byte[] servantId;
326       
327       PoaAndPoliciesReferenceFactory(POA JavaDoc poa,
328                                      String JavaDoc servantName, Policy JavaDoc[] policies)
329       {
330          this.poa = poa;
331          this.servantName = servantName;
332          this.policies = policies;
333          servantId = ReferenceData.create(servantName);
334       }
335       
336       PoaAndPoliciesReferenceFactory(POA JavaDoc poa, Policy JavaDoc[] policies)
337       {
338          this(poa, null, policies);
339       }
340       
341       public org.omg.CORBA.Object JavaDoc createReference(String JavaDoc interfId)
342             throws Exception JavaDoc
343       {
344          org.omg.CORBA.Object JavaDoc corbaRef =
345             poa.create_reference_with_id(servantId, interfId);
346          return corbaRef._set_policy_override(policies,
347                                               SetOverrideType.ADD_OVERRIDE);
348       }
349
350       public org.omg.CORBA.Object JavaDoc createReferenceWithId(Object JavaDoc id,
351                                                         String JavaDoc interfId)
352             throws Exception JavaDoc
353       {
354          byte[] referenceData =
355             (servantName == null) ? ReferenceData.create(id)
356                                   : ReferenceData.create(servantName, id);
357          org.omg.CORBA.Object JavaDoc corbaRef =
358             poa.create_reference_with_id(referenceData, interfId);
359          return corbaRef._set_policy_override(policies,
360                                               SetOverrideType.ADD_OVERRIDE);
361       }
362
363       public POA JavaDoc getPOA()
364       {
365          return poa;
366       }
367
368    }
369
370    static class PoaReferenceFactory
371       implements ReferenceFactory
372    {
373       private POA JavaDoc poa;
374       private String JavaDoc servantName;
375       private byte[] servantId;
376       
377       
378       PoaReferenceFactory(POA JavaDoc poa, String JavaDoc servantName)
379       {
380          this.poa = poa;
381          this.servantName = servantName;
382          servantId = ReferenceData.create(servantName);
383       }
384       
385       PoaReferenceFactory(POA JavaDoc poa)
386       {
387          this(poa, null);
388       }
389       
390       public org.omg.CORBA.Object JavaDoc createReference(String JavaDoc interfId)
391             throws Exception JavaDoc
392       {
393          return poa.create_reference_with_id(servantId, interfId);
394       }
395
396       public org.omg.CORBA.Object JavaDoc createReferenceWithId(Object JavaDoc id,
397                                                         String JavaDoc interfId)
398             throws Exception JavaDoc
399       {
400          byte[] referenceData =
401             (servantName == null) ? ReferenceData.create(id)
402                                   : ReferenceData.create(servantName, id);
403          return poa.create_reference_with_id(referenceData, interfId);
404       }
405
406       public POA JavaDoc getPOA()
407       {
408          return poa;
409       }
410
411    }
412
413    // Inner classes that implement the interface ServantRegistry --------------
414

415    /** ServantRegistry with a shared transient POA */
416    class ServantRegistryWithSharedTransientPOA
417          implements ServantRegistry
418    {
419       public ReferenceFactory bind(String JavaDoc name,
420                                    Servant JavaDoc servant,
421                                    Policy JavaDoc[] policies)
422       {
423          if (servant instanceof ServantWithMBeanServer) {
424             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
425          }
426          transientServantMap.put(name, servant);
427          return new PoaAndPoliciesReferenceFactory(transientPOA,
428                                                    name, policies);
429       }
430       
431       public ReferenceFactory bind(String JavaDoc name, Servant JavaDoc servant)
432       {
433          if (servant instanceof ServantWithMBeanServer) {
434             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
435          }
436          transientServantMap.put(name, servant);
437          return new PoaReferenceFactory(transientPOA, name);
438       }
439
440       public void unbind(String JavaDoc name)
441       {
442          transientServantMap.remove(name);
443       }
444       
445    }
446
447    /** ServantRegistry with a shared persistent POA */
448    class ServantRegistryWithSharedPersistentPOA
449          implements ServantRegistry
450    {
451       public ReferenceFactory bind(String JavaDoc name,
452                                    Servant JavaDoc servant,
453                                    Policy JavaDoc[] policies)
454       {
455          if (servant instanceof ServantWithMBeanServer) {
456             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
457          }
458          persistentServantMap.put(name, servant);
459          return new PoaAndPoliciesReferenceFactory(persistentPOA,
460                                                    name, policies);
461       }
462       
463       public ReferenceFactory bind(String JavaDoc name, Servant JavaDoc servant)
464       {
465          if (servant instanceof ServantWithMBeanServer) {
466             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
467          }
468          persistentServantMap.put(name, servant);
469          return new PoaReferenceFactory(persistentPOA, name);
470       }
471
472       public void unbind(String JavaDoc name)
473       {
474          persistentServantMap.remove(name);
475       }
476       
477    }
478
479    /** ServantRegistry with a transient POA per servant */
480    class ServantRegistryWithTransientPOAPerServant
481          implements ServantRegistry
482    {
483
484       public ReferenceFactory bind(String JavaDoc name,
485                                    Servant JavaDoc servant,
486                                    Policy JavaDoc[] policies)
487             throws Exception JavaDoc
488       {
489          if (servant instanceof ServantWithMBeanServer) {
490             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
491          }
492          Policy JavaDoc[] poaPolicies = concatPolicies(transientPoaPolicies, policies);
493          POA JavaDoc poa = rootPOA.create_POA(name, null, poaPolicies);
494          transientPoaMap.put(name, poa);
495          poa.set_servant(servant);
496          poa.the_POAManager().activate();
497          return new PoaReferenceFactory(poa); // no servantName: in this case
498
// name is the POA name
499
}
500
501       public ReferenceFactory bind(String JavaDoc name, Servant JavaDoc servant)
502             throws Exception JavaDoc
503       {
504          if (servant instanceof ServantWithMBeanServer) {
505             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
506          }
507          POA JavaDoc poa = rootPOA.create_POA(name, null, transientPoaPolicies);
508          transientPoaMap.put(name, poa);
509          poa.set_servant(servant);
510          poa.the_POAManager().activate();
511          return new PoaReferenceFactory(poa); // no servantName: in this case
512
// name is the POA name
513
}
514
515       public void unbind(String JavaDoc name)
516             throws Exception JavaDoc
517       {
518          POA JavaDoc poa = (POA JavaDoc) transientPoaMap.remove(name);
519          if (poa != null) {
520             poa.the_POAManager().deactivate(false, /* etherealize_objects */
521                                             true /* wait_for_completion */ );
522             poa.destroy(false, /* etherealize_objects */
523                         false /* wait_for_completion */ );
524          }
525       }
526       
527    }
528
529    /** ServantRegistry with a persistent POA per servant */
530    class ServantRegistryWithPersistentPOAPerServant
531          implements ServantRegistry
532    {
533
534       public ReferenceFactory bind(String JavaDoc name,
535                                    Servant JavaDoc servant,
536                                    Policy JavaDoc[] policies)
537             throws Exception JavaDoc
538       {
539          if (servant instanceof ServantWithMBeanServer) {
540             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
541          }
542          Policy JavaDoc[] poaPolicies =
543             concatPolicies(persistentPoaPolicies, policies);
544          POA JavaDoc poa = rootPOA.create_POA(name, null, poaPolicies);
545          persistentPoaMap.put(name, poa);
546          poa.set_servant(servant);
547          poa.the_POAManager().activate();
548          return new PoaReferenceFactory(poa); // no servantName: in this case
549
// name is the POA name
550
}
551
552       public ReferenceFactory bind(String JavaDoc name, Servant JavaDoc servant)
553             throws Exception JavaDoc
554       {
555          if (servant instanceof ServantWithMBeanServer) {
556             ((ServantWithMBeanServer)servant).setMBeanServer(getServer());
557          }
558          POA JavaDoc poa = rootPOA.create_POA(name, null, persistentPoaPolicies);
559          persistentPoaMap.put(name, poa);
560          poa.set_servant(servant);
561          poa.the_POAManager().activate();
562          return new PoaReferenceFactory(poa); // no servantName: in this case
563
// name is the POA name
564
}
565
566       public void unbind(String JavaDoc name)
567             throws Exception JavaDoc
568       {
569          POA JavaDoc poa = (POA JavaDoc) persistentPoaMap.remove(name);
570          if (poa != null) {
571             poa.the_POAManager().deactivate(false, /* etherealize_objects */
572                                             true /* wait_for_completion */ );
573             poa.destroy(false, /* etherealize_objects */
574                         false /* wait_for_completion */ );
575          }
576       }
577
578    }
579
580    // Inner classes that implement the interface ServantLocator ---------------
581

582    /** ServantLocator for the shared transient POA */
583    class TransientServantLocator
584          extends LocalObject JavaDoc
585          implements ServantLocator JavaDoc
586    {
587
588       public Servant JavaDoc preinvoke(byte[] oid,
589                                POA JavaDoc adapter,
590                                String JavaDoc operation,
591                                CookieHolder JavaDoc the_cookie)
592       {
593          try {
594             the_cookie.value = null;
595             Object JavaDoc id = ReferenceData.extractServantId(oid);
596             return (Servant JavaDoc)transientServantMap.get(id);
597          }
598          catch (Exception JavaDoc e) {
599             getLog().trace("Unexpected exception in preinvoke:", e);
600             throw new UNKNOWN JavaDoc(e.toString());
601          }
602       }
603       
604       public void postinvoke(byte[] oid,
605                              POA JavaDoc adapter,
606                              String JavaDoc operation,
607                              Object JavaDoc the_cookie,
608                              Servant JavaDoc the_servant)
609       {
610       }
611
612    }
613
614    /** ServantLocator for the shared persistent POA */
615    class PersistentServantLocator
616          extends LocalObject JavaDoc
617          implements ServantLocator JavaDoc
618    {
619
620       public Servant JavaDoc preinvoke(byte[] oid,
621                                POA JavaDoc adapter,
622                                String JavaDoc operation,
623                                CookieHolder JavaDoc the_cookie)
624       {
625          try {
626             the_cookie.value = null;
627             Object JavaDoc id = ReferenceData.extractServantId(oid);
628             return (Servant JavaDoc)persistentServantMap.get(id);
629          }
630          catch (Exception JavaDoc e) {
631             getLog().trace("Unexpected exception in preinvoke:", e);
632             throw new UNKNOWN JavaDoc(e.toString());
633          }
634       }
635       
636       public void postinvoke(byte[] oid,
637                              POA JavaDoc adapter,
638                              String JavaDoc operation,
639                              Object JavaDoc the_cookie,
640                              Servant JavaDoc the_servant)
641       {
642       }
643
644    }
645
646 }
647
Popular Tags