KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > slide > webdav > util > resourcekind > AbstractResourceKind


1 /*
2  * $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/resourcekind/AbstractResourceKind.java,v 1.30.2.1 2004/10/08 16:33:22 luetzkendorf Exp $
3  * $Revision: 1.30.2.1 $
4  * $Date: 2004/10/08 16:33:22 $
5  *
6  * ====================================================================
7  *
8  * Copyright 1999-2002 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  *
22  */

23
24 package org.apache.slide.webdav.util.resourcekind;
25
26 import java.lang.reflect.Method JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Arrays JavaDoc;
29 import java.util.Collections JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.HashSet JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.List JavaDoc;
34 import java.util.Map JavaDoc;
35 import java.util.Set JavaDoc;
36
37 import org.apache.slide.common.NamespaceAccessToken;
38 import org.apache.slide.common.NamespaceConfig;
39 import org.apache.slide.content.NodeRevisionDescriptor;
40 import org.apache.slide.content.NodeRevisionDescriptors;
41 import org.apache.slide.util.Configuration;
42 import org.apache.slide.util.XMLValue;
43 import org.apache.slide.webdav.util.AclConstants;
44 import org.apache.slide.webdav.util.BindConstants;
45 import org.apache.slide.webdav.util.DaslConstants;
46 import org.apache.slide.webdav.util.DeltavConstants;
47 import org.apache.slide.webdav.util.UriHandler;
48 import org.apache.slide.webdav.util.WebdavConstants;
49 import org.jdom.Element;
50 import org.jdom.JDOMException;
51
52 /**
53  * Abstraction of a WebDAV-compliant resource kind.
54  */

55 abstract public class AbstractResourceKind implements ResourceKind, WebdavConstants, DeltavConstants, AclConstants, DaslConstants, BindConstants {
56     
57     /**
58      ** String constant for an empty (<code>""</code>) string.
59      **/

60     public static final String JavaDoc EMPTY_STRING = "";
61     
62     // supported reports
63
protected static Set JavaDoc supportedFeatures = new HashSet JavaDoc();
64     
65     // protected properties
66
protected static Set JavaDoc liveProperties = new HashSet JavaDoc();
67     
68     // protected properties
69
protected static Set JavaDoc protectedProperties = new HashSet JavaDoc();
70     
71     // computed properties
72
protected static Set JavaDoc computedProperties = new HashSet JavaDoc();
73     
74     /**
75      * A String array containing the names of the Elements supported as a
76      * value of the <code>&lt;auto-version&gt;</code> property,
77      * e.g. <code>checkout-checkin</code>.
78      */

79     protected final static String JavaDoc[] SUPPORTED_AUTO_VERSION_ELEMENTS =
80         new String JavaDoc[] {EMPTY_STRING, E_CHECKOUT, E_CHECKOUT_IGNORE_UNLOCK, E_CHECKOUT_CHECKIN, E_CHECKOUT_UNLOCKED_CHECKIN, E_LOCKED_CHECKOUT};
81     
82     /**
83      * A String array containing the names of the Elements supported as a
84      * value of the <code>&lt;checkout-fork&gt;</code> property,
85      * e.g. <code>discouraged</code>.
86      */

87     protected final static String JavaDoc[] SUPPORTED_CHECKOUT_FORK_ELEMENTS =
88         new String JavaDoc[] {EMPTY_STRING, E_DISCOURAGED, E_FORBIDDEN};
89     
90     /**
91      * A String array containing the names of the Elements supported as a
92      * value of the <code>&lt;checkin-fork&gt;</code> property,
93      * e.g. <code>discouraged</code>.
94      */

95     protected final static String JavaDoc[] SUPPORTED_CHECKIN_FORK_ELEMENTS =
96         new String JavaDoc[] {EMPTY_STRING, E_DISCOURAGED, E_FORBIDDEN};
97     
98     /**
99      * The values of {@link #SUPPORTED_AUTO_VERSION_ELEMENTS SUPPORTED_AUTO_VERSION_ELEMENTS}
100      * as a (unmodifiable) List.
101      */

102     protected final static List JavaDoc SUPPORTED_AUTO_VERSION_ELEMENTS_LIST = Collections.unmodifiableList(Arrays.asList(SUPPORTED_AUTO_VERSION_ELEMENTS));
103     
104     /**
105      * The values of {@link #SUPPORTED_CHECKOUT_FORK_ELEMENTS SUPPORTED_CHECKOUT_FORK_ELEMENTS}
106      * as a (unmodifiable) List.
107      */

108     protected final static List JavaDoc SUPPORTED_CHECKOUT_FORK_ELEMENTS_LIST = Collections.unmodifiableList(Arrays.asList(SUPPORTED_CHECKOUT_FORK_ELEMENTS));
109     
110     /**
111      * The values of {@link #SUPPORTED_CHECKIN_FORK_ELEMENTS SUPPORTED_CHECKIN_FORK_ELEMENTS}
112      * as a (unmodifiable) List.
113      */

114     protected final static List JavaDoc SUPPORTED_CHECKIN_FORK_ELEMENTS_LIST = Collections.unmodifiableList(Arrays.asList(SUPPORTED_CHECKIN_FORK_ELEMENTS));
115     
116     /**
117      * Maps the names of the properties that have a restricted set of supported
118      * Element values to the List of names of these supported Elements
119      * (e.g. {@link #SUPPORTED_AUTO_VERSION_ELEMENTS_LIST SUPPORTED_AUTO_VERSION_ELEMENTS_LIST}.
120      */

121     protected final static Map JavaDoc RESTRICTED_PROPERTY_VALUE_MAP = new HashMap JavaDoc();
122     
123     
124     // static initialization
125
static {
126         // features
127
supportedFeatures.add( F_WEBDAV );
128         supportedFeatures.add( F_SLIDE );
129         if( Configuration.useIntegratedLocking() )
130             supportedFeatures.add( F_LOCKING );
131         if( Configuration.useIntegratedSecurity() )
132             supportedFeatures.add( F_ACCESS_CONTROL );
133         if( Configuration.useSearch() )
134             supportedFeatures.add( F_SEARCHING_AND_LOCATING );
135         if( Configuration.useVersionControl() ) {
136             supportedFeatures.add( F_VERSION_CONTROL );
137             supportedFeatures.add( F_VERSION_HISTORY );
138             supportedFeatures.add( F_CHECKOUT_IN_PLACE );
139             supportedFeatures.add( F_WORKSPACE );
140             supportedFeatures.add( F_WORKING_RESOURCE );
141             supportedFeatures.add( F_LABEL );
142             supportedFeatures.add( F_UPDATE );
143         }
144         if (Configuration.useGlobalBinding()) {
145             supportedFeatures.add( F_BINDING );
146         }
147         
148         // Computed properties
149
computedProperties.add( P_ACL);
150         computedProperties.add( P_ACL_RESTRICTIONS );
151         computedProperties.add( P_ACTIVITY_CHECKOUT_SET );
152         computedProperties.add( P_ACTIVITY_VERSION_SET );
153         computedProperties.add( P_BASELINE_CONTROLLED_COLLECTION_SET );
154         computedProperties.add( P_CREATIONUSER );
155         computedProperties.add( P_CURRENT_USER_PRIVILEGE_SET);
156         computedProperties.add( P_CURRENT_WORKSPACE_SET );
157         computedProperties.add( P_ECLIPSED_SET );
158         computedProperties.add( P_GROUP_MEMBERSHIP );
159         computedProperties.add( P_INHERITED_ACL_SET );
160         computedProperties.add( P_LOCKDISCOVERY );
161         computedProperties.add( P_MODIFICATIONUSER );
162         computedProperties.add( P_OWNER);
163         computedProperties.add( P_PRINCIPAL_COLLECTION_SET);
164         computedProperties.add( P_PRIVILEGE_COLLECTION_SET);
165         computedProperties.add( P_ROOT_VERSION );
166         computedProperties.add( P_SUCCESSOR_SET );
167         computedProperties.add( P_SUPPORTEDLOCK );
168         computedProperties.add( P_SUPPORTED_LIVE_PROPERTY_SET ); //"protected" in spec but too long for some datastores
169
computedProperties.add( P_SUPPORTED_METHOD_SET ); //"protected" in spec but too long for some datastores
170
computedProperties.add( P_SUPPORTED_PRIVILEGE_SET);
171         computedProperties.add( P_SUPPORTED_REPORT_SET ); //"protected" in spec but too long for some datastores
172
computedProperties.add( P_VERSION_CONTROLLED_CONFIGURATION );
173         computedProperties.add( P_VERSION_HISTORY );
174         computedProperties.add( P_WORKSPACE ); //"protected" in spec but made computed for BIND
175
computedProperties.add( P_WORKSPACE_CHECKOUT_SET );
176         
177         // Protected properties
178
protectedProperties.addAll( computedProperties );
179         protectedProperties.add( P_ALTERNATE_URI_SET );
180         protectedProperties.add( P_AUTO_UPDATE );
181         protectedProperties.add( P_BASELINE_COLLECTION );
182         protectedProperties.add( P_BASELINE_CONTROLLED_COLLECTION );
183         protectedProperties.add( P_CHECKED_IN );
184         protectedProperties.add( P_CHECKED_OUT );
185         protectedProperties.add( P_CHECKOUT_SET ); //"computed" in spec but made protected for performance reasons
186
protectedProperties.add( P_CREATIONDATE );
187         protectedProperties.add( P_MODIFICATIONDATE );
188         protectedProperties.add( P_GETLASTMODIFIED );
189 // protectedProperties.add( P_GETCONTENTTYPE ); // so what ... let the client set the content-type via PROPPATCH
190
protectedProperties.add( P_GETCONTENTLENGTH );
191         protectedProperties.add( P_GETETAG );
192         protectedProperties.add( P_LABEL_NAME_SET );
193         protectedProperties.add( P_PARENT_SET );
194         protectedProperties.add( P_PREDECESSOR_SET );
195         protectedProperties.add( P_RESOURCE_ID );
196         protectedProperties.add( P_RESOURCETYPE );
197         protectedProperties.add( P_SOURCE );
198         protectedProperties.add( P_SUBBASELINE_SET );
199         protectedProperties.add( P_VERSION_CONTROLLED_BINDING_SET );
200         protectedProperties.add( P_VERSION_NAME );
201         protectedProperties.add( P_VERSION_SET );
202         protectedProperties.add( P_PRIVILEGE_MEMBERSHIP ); // TODO: make computed??
203

204         // Live properties
205
liveProperties.addAll( WebdavConstants.WEBDAV_PROPERTY_LIST );
206         liveProperties.addAll( AclConstants.ACL_PROPERTY_LIST );
207         liveProperties.addAll( DeltavConstants.DELTAV_PROPERTY_LIST );
208         liveProperties.addAll( BindConstants.BIND_PROPERTY_LIST );
209         
210         // restricted property values
211
RESTRICTED_PROPERTY_VALUE_MAP.put(P_AUTO_VERSION, SUPPORTED_AUTO_VERSION_ELEMENTS_LIST);
212         RESTRICTED_PROPERTY_VALUE_MAP.put(P_CHECKOUT_FORK, SUPPORTED_CHECKOUT_FORK_ELEMENTS_LIST);
213         RESTRICTED_PROPERTY_VALUE_MAP.put(P_CHECKIN_FORK, SUPPORTED_CHECKIN_FORK_ELEMENTS_LIST);
214     }
215     
216     /**
217      * Factory method.
218      */

219     static public ResourceKind getInstance() {
220         return null;
221     }
222     
223     /**
224      * Factory method.
225      */

226     static public ResourceKind determineResourceKind( NamespaceAccessToken nsaToken, NodeRevisionDescriptors nrds, NodeRevisionDescriptor nrd ) {
227         UriHandler uh = UriHandler.getUriHandler( nrds, nrd );
228         return determineResourceKind( nsaToken, uh.toString(), nrd );
229     }
230     
231     /**
232      * Factory method.
233      */

234     static public ResourceKind determineResourceKind( NamespaceAccessToken nsaToken, String JavaDoc resourcePath, NodeRevisionDescriptor nrd ) {
235         UriHandler uh = UriHandler.getUriHandler( resourcePath );
236         NamespaceConfig config = nsaToken.getNamespaceConfig();
237         
238         if( nrd == null ) {
239             return DeltavCompliantUnmappedUrlImpl.getInstance();
240         }
241         else if( uh.isHistoryUri() ) {
242             return VersionHistoryImpl.getInstance();
243         }
244         else if( uh.isVersionUri() ) {
245             return VersionImpl.getInstance();
246         }
247         else if( uh.isWorkspaceUri() ) {
248             return WorkspaceImpl.getInstance();
249         }
250         else if( uh.isWorkingresourceUri() ) {
251             return WorkingImpl.getInstance();
252         }
253         else if( nrd.exists(P_CHECKED_IN) ) {
254             return CheckedInVersionControlledImpl.getInstance();
255         }
256         else if( nrd.exists(P_CHECKED_OUT) ) {
257             return CheckedOutVersionControlledImpl.getInstance();
258         }
259         else if( config.isPrincipal(resourcePath) ) {
260             return PrincipalImpl.getInstance();
261         }
262         else if( nrd.propertyValueContains(P_RESOURCETYPE, E_COLLECTION) ) {
263             return DeltavCompliantCollectionImpl.getInstance();
264         }
265         else {
266             return VersionableImpl.getInstance();
267         }
268     }
269     
270     /**
271      *
272      */

273     protected static boolean isSupportedFeature( String JavaDoc feature ) {
274         return supportedFeatures.contains( feature );
275     }
276     
277     /**
278      *
279      */

280     protected static boolean isSupportedFeature( String JavaDoc feature, String JavaDoc[] excludedFeatures ) {
281         return supportedFeatures.contains( feature )
282             && (Arrays.binarySearch(excludedFeatures, feature) < 0);
283     }
284     
285     /**
286      * Return true if the specified property is a DAV: live property.
287      */

288     public static boolean isLiveProperty( String JavaDoc propName ) {
289         return( liveProperties.contains(propName) );
290     }
291     
292     /**
293      * Return true if the specified property is a protected DAV: live property.
294      */

295     public static boolean isProtectedProperty( String JavaDoc propName ) {
296         return( protectedProperties.contains(propName) );
297     }
298     
299     /**
300      * Return true if the specified property is a computed DAV: live property.
301      */

302     public static boolean isComputedProperty( String JavaDoc propName ) {
303         return( computedProperties.contains(propName) );
304     }
305     
306     /**
307      * Return the set of all DAV: live properties.
308      */

309     public static Set JavaDoc getAllLiveProperties() {
310         return( Collections.unmodifiableSet(liveProperties) );
311     }
312     
313     /**
314      * Return the set of all DAV: protected live properties.
315      */

316     public static Set JavaDoc getAllProtectedProperties() {
317         return( Collections.unmodifiableSet(protectedProperties) );
318     }
319     
320     /**
321      * Return the set of all DAV: computed live properties.
322      */

323     public static Set JavaDoc getAllComputedProperties() {
324         return( Collections.unmodifiableSet(computedProperties) );
325     }
326     
327     
328     
329     /**
330      * Get the set properties supported by this resource kind.
331      */

332     public Set JavaDoc getSupportedLiveProperties() {
333         return getSupportedLiveProperties( new String JavaDoc[0] );
334     }
335     
336     /**
337      * Get the set properties supported by this resource kind.
338      * @param filter Q_PROTECTED_ONLY or Q_COMPUTED_ONLY (no filtering if null)
339      * @see org.apache.slide.webdav.util.WebdavConstants
340      * @see org.apache.slide.webdav.util.DeltavConstants
341      * @see org.apache.slide.webdav.util.AclConstants
342      * @see org.apache.slide.webdav.util.DaslConstants
343      */

344     public Set JavaDoc getSupportedLiveProperties( String JavaDoc filter ) {
345         Set JavaDoc s = getSupportedLiveProperties( new String JavaDoc[0] );
346         if( Q_COMPUTED_ONLY.equals(filter) ) {
347             s.retainAll( computedProperties );
348         }
349         if( Q_PROTECTED_ONLY.equals(filter) ) {
350             s.retainAll( protectedProperties );
351         }
352         return s;
353     }
354     
355     /**
356      * Get the set properties supported by this resource kind.
357      * @param excludedFeatures array of F_* constants (no filtering if null or empty)
358      * @see org.apache.slide.webdav.util.WebdavConstants
359      * @see org.apache.slide.webdav.util.DeltavConstants
360      * @see org.apache.slide.webdav.util.AclConstants
361      * @see org.apache.slide.webdav.util.DaslConstants
362      */

363     public Set JavaDoc getSupportedLiveProperties( String JavaDoc[] excludedFeatures ) {
364         Set JavaDoc s = new HashSet JavaDoc();
365         Iterator JavaDoc it = getSuperKinds().iterator();
366         while( it.hasNext() ) {
367             ResourceKind superkind = (ResourceKind)it.next();
368             s.addAll( superkind.getSupportedLiveProperties(excludedFeatures) );
369         }
370         return s;
371     }
372     
373     /**
374      * Get the set properties supported by this resource kind.
375      * @param filter Q_PROTECTED_ONLY or Q_COMPUTED_ONLY (no filtering if null)
376      * @param excludedFeatures array of F_* constants (no filtering if null or empty)
377      * @see org.apache.slide.webdav.util.WebdavConstants
378      * @see org.apache.slide.webdav.util.DeltavConstants
379      * @see org.apache.slide.webdav.util.AclConstants
380      * @see org.apache.slide.webdav.util.DaslConstants
381      */

382     public Set JavaDoc getSupportedLiveProperties( String JavaDoc filter, String JavaDoc[] excludedFeatures ) {
383         Set JavaDoc s = getSupportedLiveProperties( excludedFeatures );
384         if( Q_COMPUTED_ONLY.equals(filter) ) {
385             s.retainAll( computedProperties );
386         }
387         if( Q_PROTECTED_ONLY.equals(filter) ) {
388             s.retainAll( protectedProperties );
389         }
390         return s;
391     }
392     
393     /**
394      * Get the set methods supported by this resource kind.
395      */

396     public Set JavaDoc getSupportedMethods() {
397         Set JavaDoc result = new HashSet JavaDoc();
398         Iterator JavaDoc it = getSuperKinds().iterator();
399         while( it.hasNext() ) {
400             ResourceKind superkind = (ResourceKind)it.next();
401             result.addAll( superkind.getSupportedMethods() );
402         }
403         return result;
404     }
405     
406     /**
407      * Return true, if the specified property is supported by this resource kind.
408      */

409     public boolean isSupportedLiveProperty( String JavaDoc prop ) {
410         return getSupportedLiveProperties().contains( prop );
411     }
412     
413     /**
414      * Return true, if the specified method is supported by this resource kind.
415      */

416     public boolean isSupportedMethod( String JavaDoc method ) {
417         return getSupportedMethods().contains( method );
418     }
419     
420     /**
421      * Get the set reports supported by this resource kind.
422      */

423     public Set JavaDoc getSupportedReports() {
424         Set JavaDoc result = new HashSet JavaDoc();
425         Iterator JavaDoc it = getSuperKinds().iterator();
426         while( it.hasNext() ) {
427             ResourceKind superkind = (ResourceKind)it.next();
428             result.addAll( superkind.getSupportedReports() );
429         }
430         return result;
431     }
432     
433     /**
434      * Some properties (e.g. <code>&lt;auto-version&gt;</code>) have a
435      * restricted set of supported values.
436      * If the value set of the given <code>property</code> is restricted and
437      * the given <code>value</code> is not contained in that set, this method
438      * returns <code>false</code>, otherwise <code>true</code>.
439      *
440      * @param propertyName the name of the property.
441      * @param value the value to check.
442      *
443      * @return <code>false</code> if the value is not allowed, otherwise
444      * <code>true</code>.
445      */

446     public boolean isSupportedPropertyValue(String JavaDoc propertyName, Object JavaDoc value) {
447         
448         boolean isSupported = true;
449         List JavaDoc listOfRestrictedValues = (List JavaDoc)RESTRICTED_PROPERTY_VALUE_MAP.get(propertyName);
450         if (listOfRestrictedValues != null) {
451             
452             if (value == null) {
453                 return false;
454             }
455             
456             // handle "" value
457
if (EMPTY_STRING.equals(value.toString())) {
458                 return listOfRestrictedValues.contains(EMPTY_STRING);
459             }
460             
461             XMLValue xmlValue = null;
462             if (value instanceof XMLValue) {
463                 xmlValue = (XMLValue)value;
464             }
465             else {
466                 try {
467                     xmlValue = new XMLValue(value.toString());
468                 }
469                 catch (JDOMException e) {
470                     return false;
471                 }
472             }
473             isSupported =
474                 (xmlValue.size() > 0) &&
475                 listOfRestrictedValues.contains(((Element)xmlValue.iterator().next()).getName());
476         }
477         
478         return isSupported;
479     }
480     
481     /**
482      *
483      */

484     public String JavaDoc toString() {
485         return plainClassName( getClass() );
486     }
487     
488     protected List JavaDoc getSuperKinds() {
489         List JavaDoc result = new ArrayList JavaDoc();
490         Class JavaDoc myclass = getClass();
491         String JavaDoc myclassName = plainClassName( myclass );
492         Class JavaDoc[] ifs = myclass.getInterfaces();
493         for( int i = 0; i < ifs.length; i++ ) {
494             Class JavaDoc myif = ifs[i];
495             String JavaDoc myifName = plainClassName( myif );
496             if( !myclassName.startsWith(myifName) )
497                 continue;
498             Class JavaDoc[] superifs = myif.getInterfaces();
499             for( int j = 0; j < superifs.length; j++ ) {
500                 Class JavaDoc superif = superifs[j];
501                 String JavaDoc superifName = plainClassName( superif );
502                 if( "ResourceKind".equals(superifName) )
503                     continue;
504                 Class JavaDoc superclass = null;
505                 ResourceKind superkind = null;
506                 try {
507                     superclass = Class.forName( superif.getName()+"Impl" );
508                     Class JavaDoc[] ptypes = new Class JavaDoc[0];
509                     Method JavaDoc facmeth = superclass.getMethod( "getInstance", ptypes );
510                     Object JavaDoc[] parms = new Object JavaDoc[0];
511                     result.add( facmeth.invoke(null, parms) );
512                 }
513                 catch( Exception JavaDoc x ) {
514                     x.printStackTrace();
515                     throw new IllegalStateException JavaDoc( x.getMessage() );
516                 }
517             }
518         }
519         
520         return result;
521     }
522     
523     private String JavaDoc plainClassName( Class JavaDoc c ) {
524         String JavaDoc n = c.getName();
525         int i = n.lastIndexOf( '.' );
526         return n.substring( i + 1 );
527     }
528     
529     /**
530      *
531      */

532     public static void main(String JavaDoc[] args) {
533         String JavaDoc rkn = args[0];
534         Class JavaDoc[] pt = new Class JavaDoc[0];
535         Object JavaDoc[] p = new Object JavaDoc[0];
536         Iterator JavaDoc i;
537         
538         if( rkn == null || rkn.length() == 0 )
539             return;
540         try {
541             Class JavaDoc rkc = Class.forName(
542                 "org.apache.slide.webdav.util.resourcekind."+rkn+"Impl");
543             ResourceKind rk =
544                 (ResourceKind)rkc.getMethod("getInstance", pt).invoke(null, p);
545             System.out.println("\nResource kind: "+rk);
546             System.out.println("\nSupported live properties:");
547             i = rk.getSupportedLiveProperties().iterator();
548             while( i.hasNext() )
549                 System.out.println("- "+i.next());
550             System.out.println("\nSupported methods:");
551             i = rk.getSupportedMethods().iterator();
552             while( i.hasNext() )
553                 System.out.println("- "+i.next());
554             System.out.println("\nSupported reports:");
555             i = rk.getSupportedReports().iterator();
556             while( i.hasNext() )
557                 System.out.println("- "+i.next());
558         }
559         catch( Exception JavaDoc x ) { x.printStackTrace(); }
560     }
561 }
562
563
Popular Tags