KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > pentaho > repository > runtime > RuntimeElement


1 /*
2  * Copyright 2006 Pentaho Corporation. All rights reserved.
3  * This software was developed by Pentaho Corporation and is provided under the terms
4  * of the Mozilla Public License, Version 1.1, or any later version. You may not use
5  * this file except in compliance with the license. If you need a copy of the license,
6  * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Original Code is the Pentaho
7  * BI Platform. The Initial Developer is Pentaho Corporation.
8  *
9  * Software distributed under the Mozilla Public License is distributed on an "AS IS"
10  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
11  * the license for the specific language governing your rights and limitations.
12  *
13  * @created Jun 15, 2005
14  * @author Marc Batchelor
15  *
16  */

17
18 package org.pentaho.repository.runtime;
19
20 import java.math.BigDecimal JavaDoc;
21 import java.util.Collection JavaDoc;
22 import java.util.Date JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Set JavaDoc;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.pentaho.core.repository.IRuntimeElement;
30 import org.pentaho.core.session.IPentahoSession;
31 import org.pentaho.core.system.PentahoBase;
32 import org.pentaho.core.util.XmlHelper;
33 import org.pentaho.messages.Messages;
34 import org.pentaho.repository.HibernateUtil;
35 import org.pentaho.core.repository.RepositoryException;
36
37 public class RuntimeElement extends PentahoBase implements IRuntimeElement {
38     public static final int ClassVersionNumber = 3;
39
40     private static final long serialVersionUID = 5024690844237335928L;
41
42     private static Log logger = LogFactory.getLog(RuntimeElement.class);
43
44     private String JavaDoc instanceId;
45
46     private String JavaDoc parentId;
47
48     private String JavaDoc solutionId;
49
50     private String JavaDoc parentType;
51
52     private int revision;
53
54     private Map JavaDoc typesMap = new HashMap JavaDoc(); // The total list of properties and
55

56     // their types
57

58     private Map JavaDoc paramMapSS = new HashMap JavaDoc(); // ShortString Map ( VARCHAR(254) )
59

60     private Map JavaDoc paramMapLS = new HashMap JavaDoc(); // LongString Map ( CLOB )
61

62     private Map JavaDoc paramMapBD = new HashMap JavaDoc(); // BigDecimal Map
63

64     private Map JavaDoc paramMapDT = new HashMap JavaDoc(); // Date Map
65

66     private Map JavaDoc paramMapLong = new HashMap JavaDoc(); // Long Map
67

68     private Map JavaDoc paramMapCPLX = new HashMap JavaDoc(); // Complex Map (Serialized as a
69

70     private Date JavaDoc createdDate = new Date JavaDoc(); // Created Date
71

72     // Blob
73

74     private static final int MAXSSLENGH = 254;
75
76     private static final ThreadLocal JavaDoc allowableReadAttributeNames = new ThreadLocal JavaDoc();
77
78     private boolean loaded;
79
80     private boolean readOnly;
81
82     // TODO: Implement check on every set and get to make sure that the
83
// attribute is allowed to be read/written
84

85     /**
86      * Constructor for Hibernate
87      */

88     protected RuntimeElement() {
89
90     }
91
92     /**
93      * Constructor
94      *
95      * @param instId
96      * The Instance Id
97      */

98     public RuntimeElement(String JavaDoc instId) {
99         instanceId = instId;
100     }
101
102     /**
103      * Constructor
104      *
105      * @param instId
106      * The Instance Id
107      * @param parId
108      * The Parent Id
109      * @param parType
110      * The Parent Type
111      */

112     public RuntimeElement(String JavaDoc instId, String JavaDoc parId, String JavaDoc parType) {
113         instanceId = instId;
114         parentId = parId;
115         parentType = parType;
116     }
117
118     /**
119      * Constructor
120      *
121      * @param instId
122      * The Instance Id
123      * @param parId
124      * The Parent Id
125      * @param parType
126      * The Parent Type
127      * @param solnId
128      * The Solution Id
129      */

130     public RuntimeElement(String JavaDoc instId, String JavaDoc parId, String JavaDoc parType, String JavaDoc solnId) {
131         instanceId = instId;
132         parentId = parId;
133         parentType = parType;
134         solutionId = solnId;
135     }
136
137     public List JavaDoc getMessages() {
138         return null;
139     }
140
141     protected void setPentahoSession(IPentahoSession sess) {
142         genLogIdFromSession(sess);
143     }
144
145     /**
146      * @return Returns the parentId.
147      */

148     public String JavaDoc getParentId() {
149         return parentId;
150     }
151
152     /**
153      * @param parentId
154      * The parentId to set.
155      */

156     public void setParentId(String JavaDoc parentId) {
157         this.updateOk();
158         this.parentId = parentId;
159     }
160
161     /**
162      * @return Returns the parentType.
163      */

164     public String JavaDoc getParentType() {
165         return parentType;
166     }
167
168     /**
169      * @param parentType
170      * The parentType to set.
171      */

172     public void setParentType(String JavaDoc parentType) {
173         this.updateOk();
174         this.parentType = parentType;
175     }
176
177     /**
178      * @return Returns the instanceId.
179      */

180     public String JavaDoc getInstanceId() {
181         return instanceId;
182     }
183
184     /**
185      * @param instId
186      * The instanceId to set.
187      */

188     public void setInstanceId(String JavaDoc instId) {
189         this.updateOk();
190         this.instanceId = instId;
191     }
192
193     /**
194      * @return Returns the solutionId.
195      */

196     public String JavaDoc getSolutionId() {
197         return solutionId;
198     }
199
200     /**
201      * @param solutionId
202      * The solutionId to set.
203      */

204     public void setSolutionId(String JavaDoc solutionId) {
205         this.updateOk();
206         this.solutionId = solutionId;
207     }
208
209     /**
210      * Auto-handled revision mechanism.
211      *
212      * @return The current revision
213      */

214     public int getRevision() {
215         return revision;
216     }
217
218     /**
219      * Sets the revision of the class
220      *
221      * @param rev
222      * New revision to set.
223      */

224     protected void setRevision(int rev) {
225         revision = rev;
226     }
227
228     /**
229      * Uses the instanceId to distinguish equality. The instanceId will never be
230      * null, won't change, and is the primary key. Therefore, it's the perfect
231      * candidate for equals() and hashcode.
232      */

233     public boolean equals(Object JavaDoc other) {
234         if (this == other) {
235             return true;
236         }
237         if (!(other instanceof RuntimeElement)) {
238             return false;
239         }
240         final RuntimeElement otherRE = (RuntimeElement) other;
241         return this.getInstanceId().equals(otherRE.getInstanceId());
242     }
243
244     public int hashCode() {
245         return this.getInstanceId().hashCode();
246     }
247
248     protected Map JavaDoc getParamMapSS() {
249         return paramMapSS;
250     }
251
252     protected Map JavaDoc getParamMapLS() {
253         return paramMapLS;
254     }
255
256     protected Map JavaDoc getParamMapDT() {
257         return paramMapDT;
258     }
259
260     protected Map JavaDoc getParamMapBD() {
261         return paramMapBD;
262     }
263
264     protected Map JavaDoc getParamMapLong() {
265         return paramMapLong;
266     }
267
268     protected Map JavaDoc getParamMapCPLX() {
269         return paramMapCPLX;
270     }
271
272     protected void setParamMapSS(Map JavaDoc ss) {
273         paramMapSS = ss;
274     }
275
276     protected void setParamMapLS(Map JavaDoc ls) {
277         paramMapLS = ls;
278     }
279
280     protected void setParamMapDT(Map JavaDoc dt) {
281         paramMapDT = dt;
282     }
283
284     protected void setParamMapBD(Map JavaDoc bd) {
285         paramMapBD = bd;
286     }
287
288     protected void setParamMapLong(Map JavaDoc lng) {
289         paramMapLong = lng;
290     }
291
292     protected void setParamMapCPLX(Map JavaDoc cplx) {
293         paramMapCPLX = cplx;
294     }
295
296     
297     public Date JavaDoc getCreateDate() {
298       return createdDate;
299     }
300     
301     public void setCreateDate(Date JavaDoc value) {
302       createdDate = value;
303     }
304     
305     /**
306      * Gets a property from the paramMap as a string with no default value.
307      *
308      * @param key
309      * The key into the map.
310      * @return The property.
311      */

312     public String JavaDoc getStringProperty(String JavaDoc key) {
313         return getStringProperty(key, null);
314     }
315
316     /**
317      * Gets a property from the paramMap as a string, using a default value if
318      * it doesn't exist in the map.
319      *
320      * @param key
321      * The key into the map.
322      * @param defaultValue
323      * Default value returned if the key isn't already in the map.
324      * @return The property.
325      */

326     public String JavaDoc getStringProperty(String JavaDoc key, String JavaDoc defaultValue) {
327         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getString", key)); //$NON-NLS-1$ //$NON-NLS-2$
328
Object JavaDoc prop = getParamMapSS().get(key);
329         if (prop == null) {
330             prop = getParamMapLS().get(key);
331         }
332         return (prop != null) ? prop.toString() : defaultValue;
333     }
334
335     protected void checkType(String JavaDoc key, String JavaDoc type, boolean setIt) {
336         Map JavaDoc localTypesMap = getTypesMap();
337         String JavaDoc curType = (String JavaDoc) localTypesMap.get(key);
338         if (curType != null) {
339             if (!curType.equals(type)) {
340                 throw new RepositoryException(Messages.getErrorString("RTREPO.ERROR_0001_INVALIDTYPE", curType, type)); //$NON-NLS-1$
341
}
342         }
343         if (setIt) {
344             localTypesMap.put(key, type);
345         }
346     }
347
348     /**
349      * Sets a property into the paramMap. Special implementation note - Null
350      * values aren't supported in the Map. So, if a null value is passed in,
351      * this implementation will remove the entry from the map.
352      *
353      * @param key
354      * The key into the map.
355      * @param value
356      * The value to set.
357      */

358     public void setStringProperty(String JavaDoc key, String JavaDoc value) {
359         this.updateOk();
360         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setString", key)); //$NON-NLS-1$ //$NON-NLS-2$
361
checkType(key, value.getClass().getName(), true);
362         Map JavaDoc theMapSS = getParamMapSS();
363         Map JavaDoc theMapLS = getParamMapLS();
364         if (value != null) {
365             if (value.length() > MAXSSLENGH) {
366                 theMapSS.remove(key); // Make sure it's not in the short map
367
// first.
368
theMapLS.put(key, new StringBuffer JavaDoc(value));
369             } else {
370                 theMapLS.remove(key);
371                 theMapSS.put(key, value);
372             }
373         } else {
374             theMapSS.remove(key);
375             theMapLS.remove(key);
376         }
377     }
378
379     /**
380      * Gets a BigDecimal property from the paramMap.
381      *
382      * @param key
383      * Key in the paramMap.
384      * @return BigDecimal property
385      */

386     public BigDecimal JavaDoc getBigDecimalProperty(String JavaDoc key) {
387         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getBigDecimal", key)); //$NON-NLS-1$ //$NON-NLS-2$
388
return getBigDecimalProperty(key, null);
389     }
390
391     /**
392      * Gets a property from the paramMap as a BigDecimal, using a default value
393      * if it doesn't exist in the map.
394      *
395      * @param key
396      * Key in the paramMap.
397      * @param defaultValue
398      * Detault value if the property doesn't exist in the paramMap.
399      * @return Returns the property from the paramMap.
400      */

401     public BigDecimal JavaDoc getBigDecimalProperty(String JavaDoc key, BigDecimal JavaDoc defaultValue) {
402         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getBigDecimal", key)); //$NON-NLS-1$ //$NON-NLS-2$
403
Object JavaDoc prop = getParamMapBD().get(key);
404         return (prop != null) ? new BigDecimal JavaDoc((String JavaDoc) prop) : defaultValue;
405     }
406
407     /**
408      * Sets the BigDecimal property in the paramMap. Special implementation note -
409      * Null values aren't supported in the Map. So, if a null value is passed
410      * in, this implementation will remove the entry from the map.
411      *
412      * @param key
413      * Key in the paramMap.
414      * @param value
415      * The property value to set.
416      */

417     public void setBigDecimalProperty(String JavaDoc key, BigDecimal JavaDoc value) {
418         this.updateOk();
419         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setBigDecimal", key)); //$NON-NLS-1$ //$NON-NLS-2$
420
checkType(key, value.getClass().getName(), true);
421         Map JavaDoc theMap = getParamMapBD();
422         if (value != null) {
423             theMap.put(key, value.toString());
424         } else {
425             theMap.remove(key);
426         }
427     }
428
429     /**
430      * Gets a property from the paramMap as a Date, with no default value.
431      *
432      * @param key
433      * Key in the paramMap
434      * @return The property in the map.
435      */

436     public Date JavaDoc getDateProperty(String JavaDoc key) {
437         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getDate", key)); //$NON-NLS-1$ //$NON-NLS-2$
438
return getDateProperty(key, null);
439     }
440
441     /**
442      * Gets a property from the paramMap as a Date using a default value if it
443      * doesn't exist in the map
444      *
445      * @param key
446      * Key in the paramMap
447      * @param defaultValue
448      * The default value if the property doesn't exist in the
449      * paramMap.
450      * @return The property in the map.
451      */

452     public Date JavaDoc getDateProperty(String JavaDoc key, Date JavaDoc defaultValue) {
453         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getDate", key)); //$NON-NLS-1$ //$NON-NLS-2$
454
Object JavaDoc prop = getParamMapDT().get(key);
455         return (prop != null) ? (Date JavaDoc) prop : defaultValue;
456     }
457
458     /**
459      * Sets a date property in the paramMap. If null comes in, it removes the
460      * value from the map. Special implementation note - Null values aren't
461      * supported in the Map. So, if a null value is passed in, this
462      * implementation will remove the entry from the map.
463      *
464      * @param key
465      * Key in the paramMap
466      * @param value
467      * The property value to set.
468      */

469     public void setDateProperty(String JavaDoc key, Date JavaDoc value) {
470         this.updateOk();
471         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setDate", key)); //$NON-NLS-1$ //$NON-NLS-2$
472
checkType(key, value.getClass().getName(), true);
473         Map JavaDoc theMap = getParamMapDT();
474         if (value != null) {
475             theMap.put(key, value);
476         } else {
477             theMap.remove(key);
478         }
479     }
480
481     /**
482      * Gets a property from the paramMap as a Long using a default value if it
483      * doesn't exist in the map
484      *
485      * @param key
486      * Key in the paramMap
487      * @param defaultValue
488      * The default value if the property doesn't exist in the
489      * paramMap.
490      * @return The property in the map.
491      */

492     public Long JavaDoc getLongProperty(String JavaDoc key, Long JavaDoc defaultValue) {
493         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getLong", key)); //$NON-NLS-1$ //$NON-NLS-2$
494
Object JavaDoc prop = getParamMapLong().get(key);
495         return (prop != null) ? (Long JavaDoc) prop : defaultValue;
496     }
497
498     /**
499      * Gets a property from the paramMap as a long using a default value if it
500      * doesn't exist in the map
501      *
502      * @param key
503      * Key in the paramMap
504      * @param defaultValue
505      * The default value if the property doesn't exist in the
506      * paramMap.
507      * @return The property in the map.
508      */

509     public long getLongProperty(String JavaDoc key, long defaultValue) {
510         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getLong", key)); //$NON-NLS-1$ //$NON-NLS-2$
511
Object JavaDoc prop = getParamMapLong().get(key);
512         return (prop != null) ? ((Long JavaDoc) prop).longValue() : defaultValue;
513     }
514
515     /**
516      * Sets a long property in the paramMap. If null comes in, it removes the
517      * value from the map. Special implementation note - Null values aren't
518      * supported in the Map. So, if a null value is passed in, this
519      * implementation will remove the entry from the map.
520      *
521      * @param key
522      * Key in the paramMap
523      * @param value
524      * The property value to set.
525      */

526     public void setLongProperty(String JavaDoc key, Long JavaDoc value) {
527         this.updateOk();
528         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setLong", key)); //$NON-NLS-1$ //$NON-NLS-2$
529
checkType(key, value.getClass().getName(), true);
530         Map JavaDoc theMap = getParamMapLong();
531         if (value != null) {
532             theMap.put(key, value);
533         } else {
534             theMap.remove(key);
535         }
536     }
537
538     /**
539      * Sets a long property in the paramMap.
540      *
541      * @param key
542      * Key in the paramMap
543      * @param value
544      * The property value to set.
545      */

546     public void setLongProperty(String JavaDoc key, long value) {
547         this.updateOk();
548         setLongProperty(key, new Long JavaDoc(value));
549     }
550
551     /**
552      * Gets a list property from the paramMap.
553      *
554      * @param key
555      * Key in the map
556      * @return The list property in the paramMap.
557      */

558     public List JavaDoc getListProperty(String JavaDoc key) {
559         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getList", key)); //$NON-NLS-1$ //$NON-NLS-2$
560
Object JavaDoc prop = getParamMapCPLX().get(key);
561         return (List JavaDoc) prop;
562     }
563
564     /**
565      * Gets a map property from the paramMap.
566      *
567      * @param key
568      * The key in the map
569      * @return The map value in the paramMap.
570      */

571     public Map JavaDoc getMapProperty(String JavaDoc key) {
572         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "getMap", key)); //$NON-NLS-1$ //$NON-NLS-2$
573
Object JavaDoc prop = getParamMapCPLX().get(key);
574         return (Map JavaDoc) prop;
575     }
576
577     /**
578      * Sets a list property in the paramMap. Special implementation note - Null
579      * values aren't supported in the Map. So, if a null value is passed in,
580      * this implementation will remove the entry from the map.
581      *
582      * @param key
583      * The key in the map.
584      * @param value
585      * The list property to set.
586      */

587     public void setListProperty(String JavaDoc key, List JavaDoc value) {
588         this.updateOk();
589         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setList", key)); //$NON-NLS-1$ //$NON-NLS-2$
590
checkType(key, value.getClass().getName(), true);
591         Map JavaDoc theMap = getParamMapCPLX();
592         if (value != null) {
593             theMap.put(key, value);
594         } else {
595             theMap.remove(key);
596         }
597     }
598
599     /**
600      * Sets a map property in the paramMap. Special implementation note - Null
601      * values aren't supported in the Map. So, if a null value is passed in,
602      * this implementation will remove the entry from the map.
603      *
604      * @param key
605      * The key in the map.
606      * @param value
607      * The map property to set.
608      */

609     public void setMapProperty(String JavaDoc key, Map JavaDoc value) {
610         this.updateOk();
611         trace(Messages.getString("RTREPO.DEBUG_PROPERTY_GETSET", "setMap", key)); //$NON-NLS-1$ //$NON-NLS-2$
612
checkType(key, value.getClass().getName(), true);
613         Map JavaDoc theMap = getParamMapCPLX();
614         if (value != null) {
615             theMap.put(key, value);
616         } else {
617             theMap.remove(key);
618         }
619     }
620
621     /**
622      * Returns an XML representation of the RuntimeElement. Mainly for
623      * Debug/Test Cases to make sure that what goes in is what comes out during
624      * tests.
625      *
626      * @return Returns an XML representation of the RuntimeElement
627      */

628     public String JavaDoc toXML() {
629         StringBuffer JavaDoc rtn = new StringBuffer JavaDoc();
630         rtn.append("<runtime-element>\r"); //$NON-NLS-1$
631
rtn.append(getXMLString(getInstanceId(), "instance-id", " ")); //$NON-NLS-1$ //$NON-NLS-2$
632
rtn.append(getXMLString(Integer.toString(getRevision()), "revision", " ")); //$NON-NLS-1$ //$NON-NLS-2$
633
rtn.append(getXMLString(getParentId(), "parent-id", " ")); //$NON-NLS-1$ //$NON-NLS-2$
634
rtn.append(getXMLString(getParentType(), "parent-type", " ")); //$NON-NLS-1$ //$NON-NLS-2$
635
rtn.append(getXMLString(getSolutionId(), "solution-id", " ")); //$NON-NLS-1$ //$NON-NLS-2$
636
rtn.append(getMapXML(this.getParamMapSS(), "small-string-map")); //$NON-NLS-1$
637
rtn.append(getMapXML(this.getParamMapLS(), "large-string-map")); //$NON-NLS-1$
638
rtn.append(getMapXML(this.getParamMapDT(), "date-map")); //$NON-NLS-1$
639
rtn.append(getMapXML(this.getParamMapBD(), "big-decimal-map")); //$NON-NLS-1$
640
rtn.append(getMapXML(this.getParamMapLong(), "long-map")); //$NON-NLS-1$
641
rtn.append(getMapXML(this.getParamMapCPLX(), "complex-map")); //$NON-NLS-1$
642
rtn.append("</runtime-element>\r"); //$NON-NLS-1$
643
return rtn.toString();
644     }
645
646     private String JavaDoc getXMLString(String JavaDoc str, String JavaDoc tag, String JavaDoc indent) {
647         return indent + "<" + tag + "><![CDATA[" + str + "]]></" + tag + ">\r"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
648
}
649
650     private String JavaDoc getMapXML(Map JavaDoc theMap, String JavaDoc tag) {
651         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
652         sb.append(" <").append(tag).append(">\r"); //$NON-NLS-1$ //$NON-NLS-2$
653
sb.append(XmlHelper.mapToXML(theMap, " ")); //$NON-NLS-1$
654
sb.append(" </").append(tag).append(">\r"); //$NON-NLS-1$ //$NON-NLS-2$
655
return sb.toString();
656     }
657
658     /* ILogger Needs */
659     public Log getLogger() {
660         return logger;
661     }
662
663     public void setAllowableAttributeNames(Collection JavaDoc allowedReadNames) {
664         allowableReadAttributeNames.set(allowedReadNames);
665     }
666
667     /**
668      * @return Returns the typesMap.
669      */

670     protected Map JavaDoc getTypesMap() {
671         return typesMap;
672     }
673
674     /**
675      * @param typesMap
676      * The typesMap to set.
677      */

678     protected void setTypesMap(Map JavaDoc typesMap) {
679         this.typesMap = typesMap;
680     }
681
682     public Set JavaDoc getParameterNames() {
683         return getTypesMap().keySet();
684     }
685
686     public String JavaDoc getParameterType(String JavaDoc parameterName) {
687         return (String JavaDoc) getTypesMap().get(parameterName);
688     }
689
690     public void setLoaded(boolean value) {
691         this.loaded = value;
692     }
693
694     public boolean getLoaded() {
695         return this.loaded;
696     }
697
698     private void updateOk() {
699         if (!loaded) {
700             return;
701         }
702         if (readOnly) {
703             throw new IllegalStateException JavaDoc(Messages.getErrorString("RTELEMENT.ERROR_0001_INVALIDUPDATE")); //$NON-NLS-1$
704
}
705     }
706
707     public boolean getReadOnly() {
708         return readOnly;
709     }
710
711     public void setReadOnly(boolean value) {
712         this.readOnly = value;
713     }
714
715     public void forceSave() {
716         try {
717             HibernateUtil.commitTransaction();
718             HibernateUtil.flushSession();
719         } finally {
720             HibernateUtil.beginTransaction();
721         }
722     }
723 }
724
Popular Tags