001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNUAbstractCollection<String>
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp.util;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.jsp.CmsJspResourceWrapper;
033import org.opencms.main.CmsLog;
034import org.opencms.main.OpenCms;
035import org.opencms.relations.CmsLink;
036import org.opencms.relations.CmsRelationType;
037import org.opencms.util.CmsStringUtil;
038import org.opencms.xml.types.CmsXmlVarLinkValue;
039
040import java.net.URI;
041import java.net.URISyntaxException;
042import java.util.AbstractCollection;
043import java.util.Collections;
044import java.util.Iterator;
045import java.util.Map;
046import java.util.Optional;
047import java.util.concurrent.ConcurrentHashMap;
048
049import org.apache.commons.logging.Log;
050
051/**
052 * Wrapper for handling links in template/formatter JSP EL.
053 */
054public class CmsJspLinkWrapper extends AbstractCollection<String> {
055
056    /** Logger instance for this class. */
057    private static final Log LOG = CmsLog.getLog(CmsJspLinkWrapper.class);
058
059    /** Stored CMS context. */
060    protected CmsObject m_cms;
061
062    /** Cached internal/external state. */
063    protected Boolean m_internal;
064
065    /** The link literal from which this wrapper was created. */
066    protected String m_link;
067
068    /** Cached link target resource. */
069    protected Optional<CmsResource> m_resource;
070
071    /** Cached links (online, perma, server). */
072    protected Map<String, String> m_stringCache = new ConcurrentHashMap<>();
073
074    /** If <code>true</code> then empty links are allowed. */
075    private boolean m_allowEmpty;
076
077    /**
078     * Creates a new link wrapper for a specific resource.
079     *
080     * @param cms the CMS context
081     * @param resource the resource to link to
082     */
083    public CmsJspLinkWrapper(CmsObject cms, CmsResource resource) {
084
085        m_cms = cms;
086        m_link = cms.getSitePath(resource);
087        m_allowEmpty = false;
088        m_internal = Boolean.TRUE;
089
090    }
091
092    /**
093     * Creates a new link wrapper.<p>
094     *
095     * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e.
096     * either a full external URL or a site path with a query string attached.
097     *
098     * @param cms the CMS context
099     * @param link the link to wrap
100     */
101    public CmsJspLinkWrapper(CmsObject cms, String link) {
102
103        this(cms, link, false);
104    }
105
106    /**
107     * Creates a new link wrapper.<p>
108     *
109     * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e.
110     * either a full external URL or a site path with a query string attached.
111     *
112     * @param cms the CMS context
113     * @param link the link to wrap
114     * @param allowEmpty if <code>true</code> then empty links are allowed
115     */
116    public CmsJspLinkWrapper(CmsObject cms, String link, boolean allowEmpty) {
117
118        m_cms = cms;
119        m_link = link;
120        m_allowEmpty = allowEmpty;
121    }
122
123    /**
124     * @see java.lang.Object#equals(java.lang.Object)
125     */
126    @Override
127    public boolean equals(Object obj) {
128
129        if (obj == this) {
130            return true;
131        }
132        if (obj instanceof CmsJspLinkWrapper) {
133            return obj.toString().equals(toString());
134        }
135        return false;
136    }
137
138    /**
139     * Returns <code>true</code> if the link is internal.
140     *
141     * @return <code>true</code> if the link is internal
142     */
143    public boolean getIsInternal() {
144
145        if (m_internal == null) {
146            if (isEmpty()) {
147                m_internal = Boolean.FALSE;
148            } else {
149                m_internal = Boolean.valueOf(
150                    null != CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink()));
151            }
152        }
153        return m_internal.booleanValue();
154    }
155
156    /**
157     * Performs normal link substitution.
158     *
159     * @return the substituted link
160     */
161    public String getLink() {
162
163        return m_stringCache.computeIfAbsent(
164            "link",
165            k -> (!isEmpty() ? A_CmsJspValueWrapper.substituteLink(m_cms, m_link) : ""));
166    }
167
168    /**
169     * Gets the literal from which this wrapper was constructed.
170     *
171     * @return the original link literal
172     */
173    public String getLiteral() {
174
175        return m_link;
176    }
177
178    /**
179     * Performs online link substitution.
180     *
181     * @return the online link
182     */
183    public String getOnlineLink() {
184
185        return m_stringCache.computeIfAbsent(
186            "online",
187            k -> (!isEmpty() ? OpenCms.getLinkManager().getOnlineLink(m_cms, m_link) : ""));
188    }
189
190    /**
191     * Performs permalink substitution.
192     *
193     * @return the permalink
194     */
195    public String getPermaLink() {
196
197        return m_stringCache.computeIfAbsent(
198            "perma",
199            k -> (!isEmpty() ? OpenCms.getLinkManager().getPermalink(m_cms, m_link) : ""));
200    }
201
202    /**
203     * Gets the resource wrapper for the link target.
204     *
205     * @return the resource wrapper for the target
206     */
207    public CmsJspResourceWrapper getResource() {
208
209        if (m_resource == null) {
210            try {
211                String link = CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink());
212                if (link == null) {
213                    m_resource = Optional.empty();
214                } else {
215                    CmsLink linkObj = new CmsLink(/*name=*/null, CmsRelationType.HYPERLINK, link, true);
216                    linkObj.checkConsistency(m_cms);
217                    m_resource = Optional.ofNullable(linkObj.getResource());
218                }
219            } catch (Exception e) {
220                LOG.warn(e.getLocalizedMessage(), e);
221                m_resource = Optional.empty();
222            }
223        }
224        if (m_resource.isPresent()) {
225            return CmsJspResourceWrapper.wrap(m_cms, m_resource.get());
226        } else {
227            return null;
228        }
229
230    }
231
232    /**
233     * Performs server link substitution.
234     *
235     * @return the server link
236     */
237    public String getServerLink() {
238
239        return m_stringCache.computeIfAbsent(
240            "server",
241            k -> (!isEmpty() ? OpenCms.getLinkManager().getServerLink(m_cms, m_link) : ""));
242    }
243
244    /**
245     * Returns the wrapped link as a String as in {@link #toString()}.<p>
246     *
247     * @return the wrapped link as a String
248     */
249    public String getToString() {
250
251        return toString();
252    }
253
254    /**
255     * Converts the wrapped string to an URI object and returns it.
256     *
257     * <p>If the wrapped string cannont be converted, returns null.
258     *
259     * @return the URI object for the wrapped string, or null if conversion fails
260     */
261    public URI getToURI() {
262
263        return toURI();
264    }
265
266    /**
267     * @see org.opencms.jsp.util.A_CmsJspValueWrapper#hashCode()
268     */
269    @Override
270    public int hashCode() {
271
272        if (m_link == null) {
273            return 0;
274        }
275        return toString().hashCode();
276    }
277
278    /**
279     * Returns <code>true</code> if the wrapped link has been initialized.<p>
280     *
281     * @return <code>true</code> if the wrapped link has been initialized
282     */
283    @Override
284    public boolean isEmpty() {
285
286        if (m_allowEmpty) {
287            return m_link == null;
288        }
289        return CmsStringUtil.isEmptyOrWhitespaceOnly(m_link);
290    }
291
292    /**
293     * @see java.util.AbstractCollection#iterator()
294     */
295    @Override
296    public Iterator<String> iterator() {
297
298        return isEmpty() ? Collections.emptyIterator() : Collections.singletonList(toString()).iterator();
299    }
300
301    /**
302     * @see java.util.AbstractCollection#size()
303     */
304    @Override
305    public int size() {
306
307        return isEmpty() ? 0 : 1;
308    }
309
310    /**
311     * Returns the wrapped link as a String as in {@link #getLink()}.<p>
312     *
313     * @return the wrapped link as a String
314     *
315     * @see #getLiteral()
316     */
317    @Override
318    public String toString() {
319
320        return getLink();
321    }
322
323    /**
324     * Converts the wrapped string to an URI object and returns it.
325     *
326     * <p>If the wrapped string cannont be converted, returns null.
327     *
328     * @return the URI object for the wrapped string, or null if conversion fails
329     */
330    public URI toURI() {
331
332        if (m_link == null) {
333            return null;
334        }
335        try {
336            return new URI(m_link);
337        } catch (URISyntaxException e) {
338            return null;
339        }
340    }
341}