View Javadoc

1   /*
2    * Copyright 2010 FatWire Corporation. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.fatwire.gst.foundation.tagging.db;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.Date;
22  import java.util.HashSet;
23  import java.util.List;
24  
25  import COM.FutureTense.Cache.CacheHelper;
26  import COM.FutureTense.Cache.CacheManager;
27  import COM.FutureTense.Interfaces.ICS;
28  
29  import com.fatwire.assetapi.data.AssetData;
30  import com.fatwire.assetapi.data.AssetId;
31  import com.fatwire.cs.core.db.PreparedStmt;
32  import com.fatwire.cs.core.db.StatementParam;
33  import com.fatwire.gst.foundation.facade.assetapi.AssetAccessTemplate;
34  import com.fatwire.gst.foundation.facade.assetapi.AssetMapper;
35  import com.fatwire.gst.foundation.facade.assetapi.AttributeDataUtils;
36  import com.fatwire.gst.foundation.facade.assetapi.DirectSqlAccessTools;
37  import com.fatwire.gst.foundation.facade.cm.AddRow;
38  import com.fatwire.gst.foundation.facade.logging.LogUtil;
39  import com.fatwire.gst.foundation.facade.runtag.asset.FilterAssetsByDate;
40  import com.fatwire.gst.foundation.facade.runtag.render.LogDep;
41  import com.fatwire.gst.foundation.facade.sql.Row;
42  import com.fatwire.gst.foundation.facade.sql.SqlHelper;
43  import com.fatwire.gst.foundation.facade.sql.table.TableColumn;
44  import com.fatwire.gst.foundation.facade.sql.table.TableCreator;
45  import com.fatwire.gst.foundation.facade.sql.table.TableDef;
46  import com.fatwire.gst.foundation.tagging.AssetTaggingService;
47  import com.fatwire.gst.foundation.tagging.Tag;
48  import com.openmarket.xcelerate.asset.AssetIdImpl;
49  
50  import org.apache.commons.lang.StringUtils;
51  import org.apache.commons.logging.Log;
52  
53  import static com.fatwire.gst.foundation.tagging.TagUtils.asTag;
54  import static com.fatwire.gst.foundation.tagging.TagUtils.convertTagToCacheDepString;
55  
56  /**
57   * Database-backed implementation of AsseTaggingService
58   * 
59   * @author Tony Field
60   * @since Jul 28, 2010
61   */
62  public final class TableTaggingServiceImpl implements AssetTaggingService {
63  
64      private static final Log LOG = LogUtil.getLog(TableTaggingServiceImpl.class);
65  
66      public static String TAGREGISTRY_TABLE = "GSTTagRegistry";
67      public static String TABLE_ACL_LIST = ""; // no ACLs because events are
68      // anonymous
69  
70      private final ICS ics;
71      private final DirectSqlAccessTools directSqlAccessTools;
72  
73      public TableTaggingServiceImpl(ICS ics) {
74          this.ics = ics;
75          this.directSqlAccessTools = new DirectSqlAccessTools(ics);
76      }
77  
78      public void install() {
79          TableDef def = new TableDef(TAGREGISTRY_TABLE, TABLE_ACL_LIST, "obj");
80          // todo: low priority: define the PK properly (this will work for now)
81  
82          def.addColumn("tag", TableColumn.Type.ccvarchar).setLength(255).setNullable(false);
83          def.addColumn("assettype", TableColumn.Type.ccvarchar).setLength(255).setNullable(false);
84          def.addColumn("assetid", TableColumn.Type.ccbigint).setNullable(false);
85          def.addColumn("startdate", TableColumn.Type.ccdatetime).setNullable(true);
86          def.addColumn("enddate", TableColumn.Type.ccdatetime).setNullable(true);
87  
88          new TableCreator(ics).createTable(def);
89      }
90  
91      public boolean isInstalled() {
92          return SqlHelper.tableExists(ics, TAGREGISTRY_TABLE);
93      }
94  
95      public void recordCacheDependency(Tag tag) {
96          CacheManager.RecordItem(ics, convertTagToCacheDepString(tag));
97      }
98  
99      public void clearCacheForTag(Collection<Tag> tags) {
100         List<String> ids = new ArrayList<String>();
101         for (Tag tag : tags) {
102             ids.add(convertTagToCacheDepString(tag));
103         }
104         CacheManager cm = new CacheManager(ics);
105         cm.setPagesByID(ics, ids.toArray(new String[ids.size()]));
106         cm.flushCSEngine(ics, CacheHelper._both);
107         cm.flushSSEngines(ics);
108     }
109 
110     public void addAsset(AssetId id) {
111         // Only add assets that are tagged.
112 
113         TaggedAsset asset = loadTaggedAsset(id);
114         if (isTagged(asset)) {
115             if (LOG.isTraceEnabled()) {
116                 LOG.trace("Adding tagged asset to tag registry: " + asset);
117             }
118             for (Tag tag : asset.getTags()) {
119 
120                 AddRow r = new AddRow(TAGREGISTRY_TABLE);
121 
122                 r.set("id", ics.genID(true));
123                 r.set("tag", tag.getTag());
124                 r.set("assettype", id.getType());
125                 r.set("assetid", id.getId());
126                 r.set("startdate", asset.getStartDate());
127                 r.set("enddate", asset.getEndDate());
128                 r.execute(ics);
129             }
130         }
131     }
132 
133     public void updateAsset(AssetId id) {
134         // todo: low priority: optimize
135         deleteAsset(id);
136         addAsset(id);
137     }
138 
139     public void deleteAsset(AssetId id) {
140         if (LOG.isTraceEnabled()) {
141             LOG.trace("Attempting to remove asset from tag registry:" + id);
142         }
143         SqlHelper.execute(
144                 ics,
145                 TAGREGISTRY_TABLE,
146                 "DELETE FROM " + TAGREGISTRY_TABLE + " WHERE assettype = '" + id.getType() + "' and assetid = "
147                         + id.getId());
148         if (LOG.isDebugEnabled()) {
149             LOG.debug("Deleted tagged asset " + id + " from tag registry (or asset was never there in the first place)");
150         }
151     }
152 
153     public Collection<Tag> getTags(AssetId id) {
154         // this method records all the compositional dependencies that we need
155         // for this method (this is critical)
156         return loadTaggedAsset(id).getTags();
157     }
158 
159     public Collection<Tag> getTags(Collection<AssetId> ids) {
160         HashSet<Tag> tags = new HashSet<Tag>();
161         // todo: medium priority: IMPORTANT: This should be optimized so that we
162         // don't kill the db
163         if (ids.size() > 5)
164             LOG.warn("Fetching tags serially for " + ids.size()
165                     + " assets.  TableTaggingServiceImpl isn't yet optimized to handle this very nicely");
166         for (AssetId id : ids) {
167             tags.addAll(getTags(id));
168         }
169         return tags;
170     }
171 
172     /**
173      * Retrieve the tags for the tagged asset. This method records a
174      * compositional dependency on both the input asset AND the tags themselves.
175      * 
176      * @param id asset id
177      * @return tagged asset
178      */
179     private TaggedAsset loadTaggedAsset(AssetId id) {
180         LogDep.logDep(ics, id);
181 
182         // Temporarily disable usage of asset APIs in this use case due to a bug
183         // in which asset listeners
184         // cause a deadlock when the asset API is used.
185 
186         final TaggedAsset ret;
187         final String gsttagAttrVal;
188         if (directSqlAccessTools.isFlex(id)) {
189             PreparedStmt basicFields = new PreparedStmt("SELECT id,startdate,enddate" + " FROM " + id.getType()
190                     + " WHERE id = ?", Collections.singletonList(id.getType()));
191             basicFields.setElement(0, id.getType(), "id");
192 
193             StatementParam param = basicFields.newParam();
194             param.setLong(0, id.getId());
195             Row row = SqlHelper.selectSingle(ics, basicFields, param);
196 
197             Date start = StringUtils.isBlank(row.getString("startdate")) ? null : row.getDate("startdate");
198             Date end = StringUtils.isBlank(row.getString("enddate")) ? null : row.getDate("enddate");
199             ret = new TaggedAsset(id, start, end);
200             // todo: medium: optimize as this is very inefficient for flex
201             // assets
202             gsttagAttrVal = directSqlAccessTools.getFlexAttributeValue(id, "gsttag");
203 
204         } else {
205 
206             PreparedStmt basicFields = new PreparedStmt("SELECT * FROM " + id.getType() + " WHERE ID = ?",
207                     Collections.singletonList(id.getType()));
208             basicFields.setElement(0, id.getType(), "id");
209 
210             StatementParam param = basicFields.newParam();
211             param.setLong(0, id.getId());
212             Row row = SqlHelper.selectSingle(ics, basicFields, param);
213 
214             Date start = StringUtils.isBlank(row.getString("startdate")) ? null : row.getDate("startdate");
215             Date end = StringUtils.isBlank(row.getString("enddate")) ? null : row.getDate("enddate");
216             ret = new TaggedAsset(id, start, end);
217             String s = "";
218             try {
219                 if (row.isField("gsttag")) {
220                     s = row.getString("gsttag");
221                 }
222             } catch (Exception e) {
223                 LOG.trace("Could not get gsttag data from basic asset.  Maybe this is just because "
224                         + "there is no gsttag column - which is just fine.", e);
225             }
226             gsttagAttrVal = s;
227         }
228 
229         if (StringUtils.isNotBlank(gsttagAttrVal)) {
230             for (String tag : gsttagAttrVal.split(",")) {
231                 Tag oTag = asTag(tag);
232                 recordCacheDependency(oTag);
233                 ret.addTag(oTag);
234             }
235         }
236 
237         // End temporary deadlock workaround
238 
239         if (LOG.isTraceEnabled())
240             LOG.trace("Loaded tagged asset " + ret);
241         return ret;
242     }
243 
244     private AssetMapper<TaggedAsset> mapper = new AssetMapper<TaggedAsset>() {
245 
246         public TaggedAsset map(AssetData data) {
247             Date startDate = AttributeDataUtils.asDate(data.getAttributeData("startdate"));
248             Date endDate = AttributeDataUtils.asDate(data.getAttributeData("enddate"));
249             TaggedAsset ret = new TaggedAsset(data.getAssetId(), startDate, endDate);
250             for (String tag : AttributeDataUtils.getAndSplitString(data.getAttributeData("gsttag"), ",")) {
251                 Tag oTag = asTag(tag);
252 
253                 ret.addTag(oTag);
254             }
255 
256             return ret;
257         }
258 
259     };
260 
261     /**
262      * Retrieve the tags for the tagged asset. This method records a
263      * compositional dependency on both the input asset AND the tags themselves.
264      * 
265      * @param id asset id
266      * @return tagged asset
267      */
268 
269     @SuppressWarnings("unused")
270     private TaggedAsset loadTaggedAssetNew(AssetId id) {
271         LogDep.logDep(ics, id);
272         AssetAccessTemplate aat = new AssetAccessTemplate(ics);
273         TaggedAsset ret = aat.readAsset(id, mapper, "startdate", "enddate", "gsttag");
274 
275         for (Tag tag : ret.getTags()) {
276             recordCacheDependency(tag);
277         }
278         if (LOG.isTraceEnabled())
279             LOG.trace("Loaded tagged asset " + ret);
280         return ret;
281     }
282 
283     public Collection<AssetId> lookupTaggedAssets(Tag tag) {
284         recordCacheDependency(tag);
285         final StatementParam param = REGISTRY_SELECT.newParam();
286         param.setString(0, tag.getTag());
287         List<AssetId> ids = new ArrayList<AssetId>();
288         for (final Row asset : SqlHelper.select(ics, REGISTRY_SELECT, param)) {
289             AssetId id = new AssetIdImpl(asset.getString("assettype"), asset.getLong("assetid"));
290             LogDep.logDep(ics, id);
291             if (FilterAssetsByDate.isValidOnDate(ics, id, null)) {
292                 ids.add(id);
293             } else {
294                 if (LOG.isDebugEnabled())
295                     LOG.debug("Asset " + id + " tagged with " + tag + " is not active based on startdate/enddate");
296             }
297         }
298         return ids;
299     }
300 
301     public boolean isTagged(AssetId id) {
302         TaggedAsset ta = loadTaggedAsset(id);
303         return isTagged(ta);
304     }
305 
306     public boolean isTagged(TaggedAsset ta) {
307         if (ta == null)
308             return false;
309         try {
310             if (ta.getTags().size() > 0) {
311                 if (LOG.isTraceEnabled()) {
312                     LOG.trace("isTagged loaded the asset and found that " + ta.getId() + " is a tagged asset.");
313                 }
314                 return true;
315             } else {
316                 if (LOG.isTraceEnabled()) {
317                     LOG.trace("isTagged loaded the asset and found that " + ta.getId() + " is not a tagged asset.");
318                 }
319                 return false;
320             }
321         } catch (RuntimeException e) {
322             if (LOG.isTraceEnabled()) {
323                 LOG.trace(
324                         "isTagged found that " + ta.getId() + " is not a tagged asset.  We found an exception: " + e.toString(),
325                         e);
326             }
327             return false;
328         }
329     }
330 
331     private static final PreparedStmt REGISTRY_SELECT = new PreparedStmt(
332             "SELECT tag, assettype, assetid, startdate, enddate " + "FROM " + TAGREGISTRY_TABLE
333                     + " WHERE tag=? ORDER BY startdate,enddate", Collections.singletonList(TAGREGISTRY_TABLE));
334 
335     static {
336         REGISTRY_SELECT.setElement(0, TAGREGISTRY_TABLE, "tag");
337     }
338 
339     private static class TaggedAsset {
340         final AssetId id;
341         final Date startDate;
342         final Date endDate;
343         final Collection<Tag> tags;
344 
345         TaggedAsset(AssetId id, Date startDate, Date endDate) {
346             this.id = id;
347             this.startDate = startDate;
348             this.endDate = endDate;
349             tags = new ArrayList<Tag>();
350         }
351 
352         public AssetId getId() {
353             return id;
354         }
355 
356         public Date getStartDate() {
357             return startDate;
358         }
359 
360         public Date getEndDate() {
361             return endDate;
362         }
363 
364         public Collection<Tag> getTags() {
365             return tags;
366         }
367 
368         private void addTag(Tag tag) {
369             tags.add(tag);
370         }
371 
372         public String toString() {
373             return id.toString() + "|startdate:" + startDate + "|enddate:" + endDate + "|tags:" + tags;
374         }
375     }
376 }