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 tools.gsf.properties;
17  
18  import COM.FutureTense.Interfaces.ICS;
19  import COM.FutureTense.Interfaces.ISyncHash;
20  import COM.FutureTense.Util.ftErrors;
21  import com.fatwire.assetapi.common.AssetAccessException;
22  import com.fatwire.assetapi.common.SiteAccessException;
23  import com.fatwire.assetapi.data.AssetData;
24  import com.fatwire.assetapi.data.AssetDataManager;
25  import com.fatwire.assetapi.data.AssetId;
26  import com.fatwire.assetapi.data.AttributeData;
27  import com.fatwire.assetapi.data.MutableAssetData;
28  import com.fatwire.assetapi.query.OpTypeEnum;
29  import com.fatwire.assetapi.query.Query;
30  import com.fatwire.assetapi.site.SiteInfo;
31  import com.fatwire.assetapi.site.SiteManager;
32  import tools.gsf.facade.assetapi.AttributeDataUtils;
33  import tools.gsf.facade.assetapi.QueryBuilder;
34  import tools.gsf.facade.runtag.render.LogDep;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  import tools.gsf.runtime.CSRuntimeException;
38  
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collection;
42  import java.util.Collections;
43  import java.util.HashSet;
44  
45  /**
46   * Class representing properties stored as an asset. Can be basic or flex. Property name field must be a core
47   * asset field ("name" is usually chosen). Other fields can be set as needed.
48   * <p>
49   * Adding a new property adds it to the current site, as defined by the pubid session variable.
50   *
51   * @author Tony Field
52   * @since 2011-09-02
53   */
54  public final class AssetApiPropertyDao implements PropertyDao {
55      private static final Logger LOG = LoggerFactory.getLogger("tools.gsf.properties.AssetApiPropertyDao");
56      private static final int TIMEOUT_MINUTES = 60 * 24; // one day
57      private static final int MAX_SIZE = 1000000; // a million
58      private static final String ID = "id";
59      private static final String PUBLIST = "Publist";
60  
61      private final ISyncHash _props;
62      private final AssetDataManager assetDataManager;
63      private final SiteManager siteManager;
64      private final String assetType;
65      private final String assetFlexDefinition;
66      private final String propertyNameAttr;
67      private final String propertyDescriptionAttr;
68      private final String propertyValueAttr;
69      private final ICS _ics;
70  
71  
72      /**
73       * Property dao backed by a basic or flex asset.
74       *
75       * @param adm           asset data manager
76       * @param siteManager   site manager
77       * @param type          asset type to be used
78       * @param flexDefName   flex definition, if using a flex asset; null if using a basic asset
79       * @param propNameAttr  attribute name holding property name. Must be a core asset field (i.e. in the main row of the asset) so that lookups can be done properly. Must be a string attribute type.
80       * @param propDescAttr  attribute name holding the description of the property Must be a string attribute type.
81       * @param propValueAttr attribute name holding the property value. Must be a string attribute type.
82       * @param ics           ics context
83       */
84      public AssetApiPropertyDao(AssetDataManager adm, SiteManager siteManager, String type, String flexDefName, String propNameAttr, String propDescAttr, String propValueAttr, ICS ics) {
85          this.assetDataManager = adm;
86          this.siteManager = siteManager;
87          this.assetType = type;
88          this.assetFlexDefinition = flexDefName;
89          this.propertyNameAttr = propNameAttr;
90          this.propertyDescriptionAttr = propDescAttr;
91          this.propertyValueAttr = propValueAttr;
92          this._props = ics.GetSynchronizedHash(AssetApiPropertyDao.class.getName(), true, TIMEOUT_MINUTES, MAX_SIZE, true, true, Collections.singletonList(ics.GetProperty("cs.dsn") + assetType));
93          this._ics = ics;
94      }
95  
96      public synchronized Property getProperty(String name) {
97          PropertyHolder ph = (PropertyHolder) _props.get(name);
98          if (ph == null) {
99              ph = _readProperty(name);
100             _props.put(name, ph);
101         }
102         if (ph.getId() != null) {
103             LogDep.logDep(_ics, ph.getId()); // can't rely on asset API to do this for us since we're caching
104         }
105         return ph.getProp();
106     }
107 
108     @SuppressWarnings("unchecked")
109     public synchronized Collection<String> getPropertyNames() {
110         ArrayList<String> keys = new ArrayList<>();
111         for (Object key : _props.keySet()) {
112             String sKey = (String) key;
113             PropertyHolder ph = (PropertyHolder) _props.get(sKey);
114             if (ph != PropertyHolder.EMPTY_HOLDER) {
115                 keys.add(sKey);
116             }
117         }
118         return keys;
119     }
120 
121     private PropertyHolder _readProperty(String name) {
122         Query loadQuery = new QueryBuilder(assetType, assetFlexDefinition).attributes(ID, propertyNameAttr, propertyDescriptionAttr, propertyValueAttr)
123                 .condition("status", OpTypeEnum.NOT_EQUALS, "VO")
124                 .condition(propertyNameAttr, OpTypeEnum.EQUALS, name)
125                 .setBasicSearch(true) // note must be a core field for lookup to work
126                 .setFixedList(true)  // we are being precise
127                 .toQuery();
128         try {
129             for (AssetData propData : assetDataManager.read(loadQuery)) {
130                 AttributeData oName = propData.getAttributeData(propertyNameAttr);
131                 AttributeData oDesc = propData.getAttributeData(propertyDescriptionAttr);
132                 AttributeData oVal = propData.getAttributeData(propertyValueAttr);
133                 AssetId id = propData.getAssetId();
134                 if (oName == null) {
135                     throw new IllegalStateException("Property name attribute configured in PropertyDao not found in asset: " + propertyNameAttr);
136                 }
137                 if (oDesc == null) {
138                     throw new IllegalStateException("Property description attribute configured in PropertyDao not found in asset" + propertyDescriptionAttr);
139                 }
140                 if (oVal == null) {
141                     throw new IllegalStateException("Property value attribute configured in PropertyDao not found in asset" + propertyValueAttr);
142                 }
143                 LOG.debug("Loaded property {}", name);
144                 return new PropertyHolder(name, AttributeDataUtils.asString(oDesc), AttributeDataUtils.asString(oVal), id);
145             }
146         } catch (AssetAccessException e) {
147             LOG.error("Failure reading property: {}", name, e);
148             return AssetApiPropertyDao.PropertyHolder.EMPTY_HOLDER;
149         }
150         LOG.debug("Property not found: {}", name);
151         return AssetApiPropertyDao.PropertyHolder.EMPTY_HOLDER;
152     }
153 
154     /**
155      * Convenience method to set (or re-set) a property value
156      *
157      * @param name        property name
158      * @param description property description (optional)
159      * @param value       value as a string
160      */
161     public synchronized void setProperty(String name, String description, String value) {
162         if (name == null) {
163             throw new IllegalArgumentException("Cannot set a null property name");
164         }
165 
166         String pubid = _ics.GetSSVar("pubid");
167         String site = _lookupSiteName(pubid);
168 
169         PropertyHolder holder = _readProperty(name);
170         if (holder == PropertyHolder.EMPTY_HOLDER) {
171             // add
172             try {
173                 MutableAssetData data = assetDataManager.newAssetData(assetType, assetFlexDefinition);
174                 data.getAttributeData(propertyNameAttr).setData(name);
175                 data.getAttributeData(propertyDescriptionAttr).setData(description);
176                 data.getAttributeData(propertyValueAttr).setData(value);
177                 _appendToPublist(data.getAttributeData(PUBLIST), site);
178                 assetDataManager.insert(Collections.singletonList(data));
179                 holder = new PropertyHolder(name, description, value, data.getAssetId());
180             } catch (AssetAccessException e) {
181                 throw new CSRuntimeException("Could not add new property " + name, ftErrors.exceptionerr, e);
182             }
183         } else {
184             // replace
185             try {
186                 ArrayList<AssetData> sAssets = new ArrayList<>();
187                 for (MutableAssetData data : assetDataManager.readForUpdate(Collections.singletonList(holder.getId()))) {
188                     // sorry can't reset 'name'
189                     data.getAttributeData(propertyDescriptionAttr).setData(description);
190                     data.getAttributeData(propertyValueAttr).setData(value);
191                     _appendToPublist(data.getAttributeData(PUBLIST), site);
192                     sAssets.add(data);
193                     holder = new PropertyHolder(name, description, value, holder.getId());
194                 }
195                 assetDataManager.update(sAssets); // there can only be one since we are loading by id
196             } catch (AssetAccessException e) {
197                 throw new CSRuntimeException("Could not update property " + name, ftErrors.exceptionerr, e);
198             }
199         }
200         // cache or replace in cache
201         _props.put(name, holder);
202     }
203 
204     /**
205      * Set (or re-set) a property value
206      *
207      * @param property property object with name and value
208      */
209     public synchronized void setProperty(Property property) {
210         if (property == null) {
211             throw new IllegalArgumentException("Can't set a null property object");
212         }
213         setProperty(property.getName(), property.getDescription(), property.asString());
214     }
215 
216     @SuppressWarnings("unchecked")
217     private void _appendToPublist(AttributeData data, String... siteName) {
218         if (siteName != null) {
219             HashSet<String> pubs = new HashSet<>();
220             pubs.addAll(Arrays.asList(siteName));
221             pubs.addAll(data.getDataAsList());
222             data.setDataAsList(new ArrayList<>(pubs));
223         }
224     }
225 
226     private String _lookupSiteName(String pubid) {
227         if (pubid != null) {
228             long id = Long.valueOf(pubid);
229             try {
230                 for (SiteInfo si : siteManager.list()) {
231                     if (si.getId() == id) {
232                         return si.getName();
233                     }
234                 }
235             } catch (SiteAccessException e) {
236                 throw new CSRuntimeException("Could not determine name of current site: " + e, ftErrors.exceptionerr, e);
237             }
238         }
239         return null;
240     }
241 
242     @SuppressWarnings({"unchecked", "rawtypes"})
243     public synchronized void addToSite(String name, String... site) {
244         if (name == null) {
245             throw new IllegalArgumentException("Invalid property name null");
246         }
247         PropertyHolder holder = _readProperty(name);
248         if (holder == PropertyHolder.EMPTY_HOLDER) {
249             throw new IllegalArgumentException("Could not locate property " + name);
250         }
251 
252         try {
253             ArrayList<AssetData> sAssets = new ArrayList<>();
254             for (MutableAssetData data : assetDataManager.readForUpdate(Collections.singletonList(holder.getId()))) {
255                 AttributeData publistData = data.getAttributeData(PUBLIST);
256                 _appendToPublist(publistData, site);
257                 sAssets.add(data);
258             }
259             assetDataManager.update(sAssets);
260         } catch (AssetAccessException e) {
261             throw new CSRuntimeException("Failure adding property " + name + " to sites " + Arrays.asList(site), ftErrors.exceptionerr, e);
262         }
263     }
264 
265     private static class PropertyHolder {
266 
267         private static final PropertyHolder EMPTY_HOLDER = new PropertyHolder();
268         private final Property prop;
269         private final AssetId id;
270 
271         /**
272          * Default constructor for empty object.
273          */
274         private PropertyHolder() {
275             prop = null;
276             id = null;
277         }
278 
279         /**
280          * Deep copy constructor, to be used when adding, to prevent messy cache issues
281          *
282          * @param name        property name
283          * @param description property description
284          * @param value       property value
285          * @param id          property id
286          */
287         private PropertyHolder(String name, String description, String value, AssetId id) {
288             if (name == null) {
289                 throw new IllegalArgumentException("Null name not allowed");
290             }
291             if (value == null) {
292                 throw new IllegalArgumentException("Null value not allowed");
293             }
294             this.id = id;
295             this.prop = new PropertyImpl(id.getType(), name, description, value);
296         }
297 
298         private Property getProp() {
299             return prop;
300         }
301 
302         private AssetId getId() {
303             return id;
304         }
305     }
306 }