View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.handler.link;
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.function.Function;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.commons.lang3.builder.ToStringBuilder;
31  import org.jdom2.Attribute;
32  import org.jetbrains.annotations.NotNull;
33  import org.jetbrains.annotations.Nullable;
34  import org.osgi.annotation.versioning.ProviderType;
35  
36  import com.day.cq.wcm.api.Page;
37  import com.fasterxml.jackson.annotation.JsonIgnore;
38  import com.fasterxml.jackson.annotation.JsonInclude;
39  import com.fasterxml.jackson.annotation.JsonInclude.Include;
40  import com.fasterxml.jackson.annotation.JsonUnwrapped;
41  
42  import io.wcm.handler.commons.dom.Anchor;
43  import io.wcm.handler.link.spi.LinkType;
44  import io.wcm.handler.media.Asset;
45  import io.wcm.handler.media.Rendition;
46  import io.wcm.wcm.commons.util.ToStringStyle;
47  
48  /**
49   * Holds information about a link processed and resolved by {@link LinkHandler}.
50   */
51  @ProviderType
52  @JsonInclude(Include.NON_NULL)
53  public final class Link {
54  
55    private final @NotNull LinkType linkType;
56    private @NotNull LinkRequest linkRequest;
57    private boolean linkReferenceInvalid;
58    private Anchor anchor;
59    private Function<Link, Anchor> anchorBuilder;
60    private String url;
61    private Page targetPage;
62    private Asset targetAsset;
63    private Rendition targetRendition;
64    private List<Page> redirectPages;
65  
66    /**
67     * @param linkType Link type
68     * @param linkRequest Processed link reference
69     */
70    public Link(@NotNull LinkType linkType, @NotNull LinkRequest linkRequest) {
71      this.linkRequest = linkRequest;
72      this.linkType = linkType;
73    }
74  
75    /**
76     * @return Link type
77     */
78    @JsonUnwrapped
79    public @NotNull LinkType getLinkType() {
80      return this.linkType;
81    }
82  
83    /**
84     * @return Link request
85     */
86    @JsonIgnore
87    public @NotNull LinkRequest getLinkRequest() {
88      return this.linkRequest;
89    }
90  
91    /**
92     * @param linkRequest Link request
93     */
94    public void setLinkRequest(@NotNull LinkRequest linkRequest) {
95      this.linkRequest = linkRequest;
96    }
97  
98    /**
99     * @return true if a link reference was set, but the reference was invalid and could not be resolved
100    */
101   @JsonIgnore
102   public boolean isLinkReferenceInvalid() {
103     return this.linkReferenceInvalid;
104   }
105 
106   /**
107    * @param linkReferenceInvalid true if a link reference was set, but the reference was invalid and could not be
108    *          resolved
109    */
110   public void setLinkReferenceInvalid(boolean linkReferenceInvalid) {
111     this.linkReferenceInvalid = linkReferenceInvalid;
112   }
113 
114   /**
115    * @return Anchor element
116    */
117   @JsonIgnore
118   public @Nullable Anchor getAnchor() {
119     if (this.anchor == null && this.anchorBuilder != null) {
120       this.anchor = this.anchorBuilder.apply(this);
121       this.anchorBuilder = null;
122     }
123     return this.anchor;
124   }
125 
126   /**
127    * @return Map with all attributes of the anchor element. Returns null if anchor element is null.
128    */
129   @JsonIgnore
130   @SuppressWarnings("java:S1168")
131   public @Nullable Map<String, String> getAnchorAttributes() {
132     Anchor a = getAnchor();
133     if (a == null) {
134       return null;
135     }
136     Map<String, String> attributes = new HashMap<>();
137     for (Attribute attribute : a.getAttributes()) {
138       attributes.put(attribute.getName(), attribute.getValue());
139     }
140     return attributes;
141   }
142 
143   /**
144    * @param anchorBuilder Function that builds an anchor representation on demand
145    */
146   public void setAnchorBuilder(@NotNull Function<Link, Anchor> anchorBuilder) {
147     this.anchorBuilder = anchorBuilder;
148   }
149 
150   /**
151    * @return Link markup (only the opening anchor tag) or null if resolving was not successful.
152    */
153   @JsonIgnore
154   public @Nullable String getMarkup() {
155     Anchor a = getAnchor();
156     if (a != null) {
157       return StringUtils.removeEnd(a.toString(), "</a>");
158     }
159     else {
160       return null;
161     }
162   }
163 
164   /**
165    * @return Link URL
166    */
167   public @Nullable String getUrl() {
168     return this.url;
169   }
170 
171   /**
172    * @param url Link URL
173    */
174   public void setUrl(@Nullable String url) {
175     this.url = url;
176   }
177 
178   /**
179    * @return Target page referenced by the link (applies only for internal links)
180    */
181   @JsonIgnore
182   public @Nullable Page getTargetPage() {
183     return this.targetPage;
184   }
185 
186   /**
187    * @param targetPage Target page referenced by the link (applies only for internal links)
188    */
189   public void setTargetPage(@Nullable Page targetPage) {
190     this.targetPage = targetPage;
191   }
192 
193   /**
194    * @return Target media item (applies only for media links)
195    */
196   @JsonIgnore
197   public @Nullable Asset getTargetAsset() {
198     return this.targetAsset;
199   }
200 
201   /**
202    * @param targetAsset Target media item (applies only for media links)
203    */
204   public void setTargetAsset(@Nullable Asset targetAsset) {
205     this.targetAsset = targetAsset;
206   }
207 
208   /**
209    * @return Target media rendition (applies only for media links)
210    */
211   @JsonIgnore
212   public @Nullable Rendition getTargetRendition() {
213     return this.targetRendition;
214   }
215 
216   /**
217    * @param targetRendition Target media rendition (applies only for media links)
218    */
219   public void setTargetRendition(@Nullable Rendition targetRendition) {
220     this.targetRendition = targetRendition;
221   }
222 
223   /**
224    * During link resolution one or multiple redirect pages may get resolved and replaced by the referenced
225    * link target. This page list gives access to all redirect pages that where visited and resolved
226    * during the link resolution process.
227    * @return List of links in the "resolve history".
228    */
229   @JsonIgnore
230   public @NotNull List<Page> getRedirectPages() {
231     if (redirectPages == null) {
232       return Collections.emptyList();
233     }
234     else {
235       return Collections.unmodifiableList(redirectPages);
236     }
237   }
238 
239   /**
240    * Add page to list of redirect pages (at first position of the list).
241    * @param redirectPage Redirect page
242    */
243   public void addRedirectPage(@NotNull Page redirectPage) {
244     if (redirectPages == null) {
245       redirectPages = new LinkedList<>();
246     }
247     redirectPages.add(0, redirectPage);
248   }
249 
250   /**
251    * @return true if link is valid and was resolved successfully
252    */
253   @SuppressWarnings({ "null", "java:S2589" }) // extra null checks for backward compatibility
254   public boolean isValid() {
255     return getLinkType() != null
256         && getUrl() != null
257         && !StringUtils.equals(getUrl(), LinkHandler.INVALID_LINK);
258   }
259 
260   @Override
261   public String toString() {
262     return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE);
263   }
264 
265 }