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.wra.navigation;
17  
18  import java.util.Collection;
19  import java.util.Date;
20  import java.util.List;
21  
22  import COM.FutureTense.Interfaces.ICS;
23  import COM.FutureTense.Interfaces.Utilities;
24  
25  import com.fatwire.assetapi.data.AssetData;
26  import com.fatwire.assetapi.data.AssetId;
27  import com.fatwire.assetapi.site.SiteInfo;
28  import com.fatwire.gst.foundation.controller.AssetIdWithSite;
29  import com.fatwire.gst.foundation.facade.assetapi.AssetAccessTemplate;
30  import com.fatwire.gst.foundation.facade.assetapi.AssetClosure;
31  import com.fatwire.gst.foundation.facade.assetapi.AssetDataUtils;
32  import com.fatwire.gst.foundation.facade.assetapi.AttributeDataUtils;
33  import com.fatwire.gst.foundation.facade.assetapi.asset.DateFilterClosure;
34  import com.fatwire.gst.foundation.facade.assetapi.asset.PreviewContext;
35  import com.fatwire.gst.foundation.facade.runtag.render.LogDep;
36  import com.fatwire.gst.foundation.facade.runtag.siteplan.ListPages;
37  import com.fatwire.gst.foundation.wra.Alias;
38  import com.fatwire.gst.foundation.wra.AliasCoreFieldDao;
39  import com.fatwire.gst.foundation.wra.AssetApiAliasCoreFieldDao;
40  import com.fatwire.gst.foundation.wra.AssetApiWraCoreFieldDao;
41  import com.fatwire.gst.foundation.wra.VanityAsset;
42  import com.fatwire.gst.foundation.wra.WebReferenceableAsset;
43  import com.fatwire.gst.foundation.wra.WraCoreFieldDao;
44  import com.fatwire.gst.foundation.wra.WraUriBuilder;
45  import com.fatwire.mda.DimensionFilterInstance;
46  import com.openmarket.xcelerate.asset.AssetIdImpl;
47  
48  import org.apache.commons.lang.StringUtils;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  
52  import static com.fatwire.gst.foundation.facade.runtag.asset.FilterAssetsByDate.isValidOnDate;
53  
54  /**
55   * Used to retrieve the Navigation Bar data. See the description of
56   * {@link #getSitePlan(String)} for more details.
57   * 
58   * @author David Chesebro
59   * @author Dolf Dijkstra
60   * @since Jun 17, 2010
61   */
62  public class NavigationHelper {
63      /**
64       * ICS context
65       */
66      protected final ICS ics;
67  
68      final AssetAccessTemplate assetTemplate;
69      /**
70       * Local instance of the WraCoreFieldDao, pre-instantiated and ready to go
71       */
72      protected final WraCoreFieldDao wraDao;
73  
74      /**
75       * Local instance of the AliasCoreFieldDao.
76       */
77      protected final AliasCoreFieldDao aliasDao;
78      /**
79       * Log file
80       */
81      protected final Log LOG = LogFactory.getLog(NavigationHelper.class);
82      /**
83       * Effective date for the purposes of startdate/enddate comparisons for an
84       * asset.
85       */
86      protected final Date assetEffectiveDate;
87  
88      /**
89       * Constructor. Initializes assetEffectiveDate to null.
90       * 
91       * @param ics object
92       */
93      public NavigationHelper(final ICS ics) {
94          this.ics = ics;
95          this.wraDao = new AssetApiWraCoreFieldDao(ics);
96  
97          this.aliasDao = new AssetApiAliasCoreFieldDao(ics, wraDao);
98          this.assetEffectiveDate = null;
99          assetTemplate = new AssetAccessTemplate(ics);
100     }
101 
102     /**
103      * Constructor with all the dependencies listed. Initializes
104      * assetEffectiveDate to null.
105      * 
106      * @param ics
107      * @param assetTemplate
108      * @param wraDao
109      * @param aliasDao
110      */
111     public NavigationHelper(final ICS ics, AssetAccessTemplate assetTemplate, WraCoreFieldDao wraDao,
112             AliasCoreFieldDao aliasDao) {
113         this.ics = ics;
114         this.wraDao = wraDao;
115         this.aliasDao = aliasDao;
116         this.assetTemplate = assetTemplate;
117         this.assetEffectiveDate = null;
118     }
119 
120     /**
121      * Name of the page subtype indicating that this page is NOT rendered on the
122      * site but is instead merely used to group navigation components on the
123      * site.
124      */
125     public static final String NAVBAR_NAME = "GSTNavName";
126 
127     /**
128      * Name of the page subtype indicating that this page is a Link, meaning
129      * that the content is in the unnamed association
130      */
131     public static final String NAVBAR_LINK = "GSTNavLink";
132 
133     /**
134      * @param name the name of the Page asset
135      * @return NavNode for the Page with the name
136      */
137     public NavNode getSitePlanByPage(final String name) {
138         return getSitePlanByPage(1, name);
139     }
140 
141     /**
142      * Retrieves the NavNode for the given Page with the provided name.
143      * 
144      * @param depth the maximum depth to retrieve, -1 for no limit.
145      * @param name the name of the Page asset
146      * @return NavNode for the Page with the name
147      */
148     public NavNode getSitePlanByPage(final int depth, final String name) {
149         final String sitename = ics.GetVar("site");
150         if (StringUtils.isBlank(sitename)) {
151             throw new IllegalStateException(
152                     "site is not a ics variable. This function needs this variable to be aviable and contain the name of the site.");
153         }
154 
155         return getSitePlanByPage(depth, name, sitename);
156     }
157 
158     /**
159      * Retrieves the NavNode for the given Page with the provided name.
160      * 
161      * @param depth the maximum depth to retrieve, -1 for no limit.
162      * @param name the name of the Page asset
163      * @param dimensionFilter in order to translate the output.
164      * @return NavNode for the Page with the name
165      */
166     public NavNode getSitePlanByPage(final int depth, final String name, final DimensionFilterInstance dimensionFilter) {
167         final String sitename = ics.GetVar("site");
168         if (StringUtils.isBlank(sitename)) {
169             throw new IllegalStateException(
170                     "site is not a ics variable. This function needs this variable to be aviable and contain the name of the site.");
171         }
172 
173         return getSitePlanByPage(depth, name, sitename, dimensionFilter);
174     }
175 
176     /**
177      * Retrieves the NavNode for the given Page with the provided name.
178      * 
179      * @param depth the maximum depth to retrieve, -1 for no limit.
180      * @param name the name of the Page asset
181      * @param sitename the name of the site you want the navigation for.
182      * @return NavNode for the Page with the name
183      */
184     public NavNode getSitePlanByPage(final int depth, final String name, final String sitename) {
185         return getSitePlanByPage(depth, name, sitename, null);
186     }
187 
188     /**
189      * Retrieves the NavNode for the given Page with the provided name.
190      * 
191      * @param depth the maximum depth to retrieve, -1 for no limit.
192      * @param name the name of the Page asset
193      * @param sitename the name of the site you want the navigation for.
194      * @param dimensionFilter in order to translate the output.
195      * @return NavNode for the Page with the name
196      */
197     public NavNode getSitePlanByPage(final int depth, final String name, final String sitename,
198             final DimensionFilterInstance dimensionFilter) {
199 
200         final SiteInfo site = assetTemplate.readSiteInfo(sitename);
201         if (site == null) {
202             throw new RuntimeException("Site with name '" + sitename + "' not found.");
203         }
204         final AssetId pageid = assetTemplate.findByName(ics, "Page", name, site.getId());
205         if (pageid == null) {
206             return null;
207         }
208         return getSitePlan(depth, pageid, dimensionFilter);
209     }
210 
211     /**
212      * Retrieves the NavNode for the given Page with the provided id.
213      * 
214      * The NavNode contains all the attributes necessary to create a nav bar.
215      * <p/>
216      * Links are not populated for Navigation Placeholders, but it is often very
217      * convenient to pass a navigation placeholder into this function in order
218      * to return all children under a specific placeholder.
219      * <p/>
220      * StartDate and EndDate are checked and invalid pages aren't added. If a
221      * Page asset is not valid, its children are not even examined.
222      * 
223      * 
224      * @param pageid the assetid of the Page asset.
225      * @return the NavNode for this page
226      */
227     public NavNode getSitePlan(final String pageid) {
228         return getSitePlan(new AssetIdImpl("Page", Long.parseLong(pageid)));
229     }
230 
231     /**
232      * Get the NavNode for the current page with unlimited depth.
233      * 
234      * @param pageid
235      * @return the NavNode associated with this pageid.
236      */
237     public NavNode getSitePlan(final AssetId pageid) {
238         return getSitePlan(-1, pageid, 0, null);
239     }
240 
241     /**
242      * Retrieves the NavNode for the given Page with the provided id.
243      * 
244      * @param depth the maximum depth to retrieve, -1 for no limit.
245      * @param pageid the AssetId for the page
246      * @return the NavNode for this page
247      */
248     public NavNode getSitePlan(final int depth, final AssetId pageid) {
249         return getSitePlan(depth, pageid, 0, null);
250     }
251 
252     /**
253      * Retrieves the NavNode for the given Page with the provided id.
254      * 
255      * @param depth the maximum depth to retrieve, -1 for no limit.
256      * @param pageid the AssetId for the page
257      * @param dimensionFilter in order to translate the output.
258      * @return the NavNode for this page
259      */
260     public NavNode getSitePlan(final int depth, final AssetId pageid, final DimensionFilterInstance dimensionFilter) {
261         LOG.debug("Dimension filter " + dimensionFilter + " provided for site plan lookup");
262         return getSitePlan(depth, pageid, 0, dimensionFilter);
263     }
264 
265     /**
266      * Called from public {@link #getSitePlan(int, AssetId)}. See that
267      * function's description for details
268      * 
269      * @param depth the depth of the tree to retrieve, -1 for unlimited depth.
270      * @param pageId id of the page asset
271      * @param level starting level number when traversing the site plan tree
272      * @return NavNode of the site plan tree
273      */
274     private NavNode getSitePlan(final int depth, final AssetId pageId, final int level,
275             final DimensionFilterInstance dimensionFilter) {
276         // check the start/end date of the page asset
277         LogDep.logDep(ics, pageId);
278         if (!isValidOnDate(ics, pageId, assetEffectiveDate)) {
279             // the input object is not valid. Abort
280             if (LOG.isDebugEnabled()) {
281                 LOG.debug("Input asset " + pageId + " is not effective on " + assetEffectiveDate);
282             }
283             return null;
284         }
285 
286         // determine if it's a wra, a placeholder or an alias
287 
288         final AssetData pageData = AssetDataUtils.getAssetData(ics, pageId, "subtype", "name");
289         final String subtype = AttributeDataUtils.asString(pageData.getAttributeData("subtype"));
290         final String name = AttributeDataUtils.asString(pageData.getAttributeData("name"));
291         final boolean isNavigationPlaceholder = NAVBAR_NAME.equals(subtype);
292         final NavNode node = new NavNode();
293 
294         node.setPage(pageId);
295         node.setLevel(level);
296         node.setPagesubtype(subtype);
297         node.setPagename(name);
298 
299         if (isNavigationPlaceholder) {
300             // no link if it's just a placeholder
301         } else {
302             // not a GSTNavLink, probably 11g page
303             final AssetClosure closure = new NodeWraAssetClosure(node);
304             final DateFilterClosure dateFilterClosure = new DateFilterClosure(PreviewContext.getPreviewDate(ics,
305                     assetEffectiveDate), closure);
306 
307             // retrieve the unnamed association(s) based on date filter
308             if (dimensionFilter == null) {
309                 assetTemplate.readAssociatedAssets(pageId, "-", dateFilterClosure);
310             } else {
311 
312                 final Collection<AssetId> associatedWraList = assetTemplate.readAssociatedAssetIds(pageId, "-");
313                 for (final AssetId child : dimensionFilter.filterAssets(associatedWraList)) {
314                     assetTemplate.readAsset(child, dateFilterClosure);
315                 }
316             }
317 
318         }
319 
320         if (depth < 0 || depth > level) {
321             // get the children in the Site Plan
322             final List<AssetId> childrenIDs = ListPages.getChildPages(ics, pageId.getId());
323             for (final AssetId aid : childrenIDs) {
324                 // note recursing here
325                 final NavNode kidInfo = getSitePlan(depth, aid, level + 1, dimensionFilter);
326                 if (kidInfo != null && kidInfo.getPage() != null) {
327                     node.addChild(kidInfo);
328                 }
329             }
330         }
331         return node;
332     }
333 
334     /**
335      * Constant containing the asset type of the GST Alias asset.
336      */
337     public final String GST_ALIAS_TYPE = Alias.ALIAS_ASSET_TYPE_NAME;
338 
339     /**
340      * Return true if the asset type is a GSTAlias asset type. May be overridden
341      * if customers are attempting to retrofit this class for alias-like
342      * functionality that is not implemented by the GSTAlias asset type.
343      * 
344      * @param id asset for which a link is required
345      * @return true if the asset is an alias, false if it is a web-referenceable
346      *         asset
347      */
348     protected boolean isGstAlias(final AssetId id) {
349         return GST_ALIAS_TYPE.equals(id.getType());
350     }
351 
352     /**
353      * Get the URL for the alias.
354      * 
355      * For external links, the targeturl attribute is rendered.
356      * 
357      * For Aliases that refer to another WRA, the alias is allowed to override
358      * any WRA fields. For instance, the path, and the template can be
359      * overridden by an alias for a WRA (though the template in the Alias had
360      * better be typeless, or a template of the same name must exist in the
361      * WRA's asset type or there will be a problem).
362      * 
363      * @param alias Alias bean, which of course is also a WRA.
364      * @return url
365      */
366     protected String getUrlForAlias(final Alias alias) {
367         if (alias.getTargetUrl() != null) {
368             return alias.getTargetUrl();
369         } else {
370             return getUrlForWra(alias);
371         }
372     }
373 
374     /**
375      * Get the URL to use for the web-referenceable asset.
376      * 
377      * @param wra WebReferenceableAsset bean
378      * @return url
379      */
380     protected String getUrlForWra(final VanityAsset wra) {
381         if (wra.getTemplate() == null || wra.getTemplate().length() == 0) {
382             LOG.warn("Asset " + wra + " does not have a valid template set.");
383             return null;
384         }
385         String wrapper = ics.GetProperty("com.fatwire.gst.foundation.url.wrapathassembler.dispatcher",
386                 "ServletRequest.properties", true);
387         if (!Utilities.goodString(wrapper)) {
388             wrapper = "GST/Dispatcher";
389         }
390         return new WraUriBuilder(wra, wrapper).toURI(ics);
391 
392     }
393 
394     protected void decorateAsWra(final AssetId id, final NavNode node) {
395 
396         final WebReferenceableAsset wra = wraDao.getWra(id);
397         final String url = getUrlForWra(wra);
398         final String linktext = wra.getLinkText();
399         node.setWra(wra);
400         if (url != null) {
401             node.setUrl(url);
402         }
403         if (linktext != null) {
404             node.setLinktext(linktext);
405         }
406     }
407 
408     protected void decorateAsAlias(final AssetId id, final NavNode node) {
409         final Alias alias = aliasDao.getAlias(id);
410         final String url = getUrlForAlias(alias);
411         final String linktext = alias.getLinkText();
412         node.setWra(alias);
413         if (url != null) {
414             node.setUrl(url);
415         }
416         if (linktext != null) {
417             node.setLinktext(linktext);
418         }
419 
420     }
421 
422     /**
423      * Locate the page that contains the specified Web-Referenceable Asset.
424      * <p/>
425      * A WRA is supposed to just be placed on one page (in the unnamed
426      * association block), and this method locates it. If it is not found, 0L is
427      * returned.
428      * <p/>
429      * If multiple matches are found, a warning is logged and the first one is
430      * returned.
431      * 
432      * @param site_name name of the site to search within
433      * @param wraAssetId the asset id of the web-referenceable asset
434      * @return page asset ID or 0L.
435      */
436     public long findP(final String site_name, final AssetId wraAssetId) {
437         return wraDao.findP(new AssetIdWithSite(wraAssetId.getType(), wraAssetId.getId(), site_name));
438     }
439 
440     class NodeWraAssetClosure implements AssetClosure {
441 
442         private final NavNode node;
443 
444         public NodeWraAssetClosure(final NavNode node) {
445             this.node = node;
446         }
447 
448         @Override
449         public boolean work(final AssetData asset) {
450             final AssetId id = asset.getAssetId();
451             node.setId(id);
452             if (isGstAlias(id)) {
453                 decorateAsAlias(id, node);
454             } else {
455                 decorateAsWra(id, node);
456             }
457             return false; // needs to return only one node
458         }
459 
460     }
461 
462 }