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.util.AbstractCollection;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.Map;
044import java.util.Objects;
045import java.util.Optional;
046import java.util.concurrent.ConcurrentHashMap;
047
048import org.apache.commons.logging.Log;
049
050/**
051 * Wrapper for handling links in template/formatter JSP EL.
052 */
053public class CmsJspLinkWrapper extends AbstractCollection<String> {
054
055    /** Logger instance for this class. */
056    private static final Log LOG = CmsLog.getLog(CmsJspLinkWrapper.class);
057
058    /** Stored CMS context. */
059    protected CmsObject m_cms;
060
061    /** The link literal from which this wrapper was created. */
062    protected String m_link;
063
064    /** Cached internal/external state. */
065    protected Boolean m_internal;
066
067    /** Cached link target resource. */
068    protected Optional<CmsResource> m_resource;
069
070    /** Cached links (online, perma, server). */
071    protected Map<String, String> m_stringCache = new ConcurrentHashMap<>();
072
073    /** If <code>true</code> then empty links are allowed. */
074    private boolean m_allowEmpty;
075
076    /**
077     * Creates a new link wrapper.<p>
078     *
079     * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e.
080     * either a full external URL or a site path with a query string attached.
081     *
082     * @param cms the CMS context
083     * @param link the link to wrap
084     */
085    public CmsJspLinkWrapper(CmsObject cms, String link) {
086
087        this(cms, link, false);
088    }
089
090    /**
091     * Creates a new link wrapper.<p>
092     *
093     * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e.
094     * either a full external URL or a site path with a query string attached.
095     *
096     * @param cms the CMS context
097     * @param link the link to wrap
098     * @param allowEmpty if <code>true</code> then empty links are allowed
099     */
100    public CmsJspLinkWrapper(CmsObject cms, String link, boolean allowEmpty) {
101
102        m_cms = cms;
103        m_link = link;
104        m_allowEmpty = allowEmpty;
105    }
106
107    /**
108     * @see java.lang.Object#equals(java.lang.Object)
109     */
110    @Override
111    public boolean equals(Object obj) {
112
113        if (obj == this) {
114            return true;
115        }
116        if (obj instanceof CmsJspLinkWrapper) {
117            return obj.toString().equals(toString());
118        }
119        return false;
120    }
121
122    /**
123     * Returns <code>true</code> if the link is internal.
124     *
125     * @return <code>true</code> if the link is internal
126     */
127    public boolean getIsInternal() {
128
129        if (m_internal == null) {
130            if (isEmpty()) {
131                m_internal = Boolean.FALSE;
132            } else {
133                m_internal = Boolean.valueOf(
134                    null != CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink()));
135            }
136        }
137        return m_internal.booleanValue();
138    }
139
140    /**
141     * Performs normal link substitution.
142     *
143     * @return the substituted link
144     */
145    public String getLink() {
146
147        return m_stringCache.computeIfAbsent(
148            "link",
149            k -> (!isEmpty() ? A_CmsJspValueWrapper.substituteLink(m_cms, m_link) : ""));
150    }
151
152    /**
153     * Gets the literal from which this wrapper was constructed.
154     *
155     * @return the original link literal
156     */
157    public String getLiteral() {
158
159        return m_link;
160    }
161
162    /**
163     * Performs online link substitution.
164     *
165     * @return the online link
166     */
167    public String getOnlineLink() {
168
169        return m_stringCache.computeIfAbsent(
170            "online",
171            k -> (!isEmpty() ? OpenCms.getLinkManager().getOnlineLink(m_cms, m_link) : ""));
172    }
173
174    /**
175     * Performs permalink substitution.
176     *
177     * @return the permalink
178     */
179    public String getPermaLink() {
180
181        return m_stringCache.computeIfAbsent(
182            "perma",
183            k -> (!isEmpty() ? OpenCms.getLinkManager().getPermalink(m_cms, m_link) : ""));
184    }
185
186    /**
187     * Gets the resource wrapper for the link target.
188     *
189     * @return the resource wrapper for the target
190     */
191    public CmsJspResourceWrapper getResource() {
192
193        if (m_resource == null) {
194            try {
195                String link = CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink());
196                if (link == null) {
197                    m_resource = Optional.empty();
198                } else {
199                    CmsLink linkObj = new CmsLink(/*name=*/null, CmsRelationType.HYPERLINK, link, true);
200                    linkObj.checkConsistency(m_cms);
201                    m_resource = Optional.ofNullable(linkObj.getResource());
202                }
203            } catch (Exception e) {
204                LOG.warn(e.getLocalizedMessage(), e);
205                m_resource = Optional.empty();
206            }
207        }
208        if (m_resource.isPresent()) {
209            return CmsJspResourceWrapper.wrap(m_cms, m_resource.get());
210        } else {
211            return null;
212        }
213
214    }
215
216    /**
217     * Performs server link substitution.
218     *
219     * @return the server link
220     */
221    public String getServerLink() {
222
223        return m_stringCache.computeIfAbsent(
224            "server",
225            k -> (!isEmpty() ? OpenCms.getLinkManager().getServerLink(m_cms, m_link) : ""));
226    }
227
228    /**
229     * Returns the wrapped link as a String as in {@link #toString()}.<p>
230     *
231     * @return the wrapped link as a String
232     */
233    public String getToString() {
234
235        return toString();
236    }
237
238    /**
239     * @see org.opencms.jsp.util.A_CmsJspValueWrapper#hashCode()
240     */
241    @Override
242    public int hashCode() {
243
244        if (m_link == null) {
245            return 0;
246        }
247        return toString().hashCode();
248    }
249
250    /**
251     * Returns <code>true</code> if the wrapped link has been initialized.<p>
252     *
253     * @return <code>true</code> if the wrapped link has been initialized
254     */
255    @Override
256    public boolean isEmpty() {
257
258        if (m_allowEmpty) {
259            return m_link == null;
260        }
261        return CmsStringUtil.isEmptyOrWhitespaceOnly(m_link);
262    }
263
264    /**
265     * @see java.util.AbstractCollection#iterator()
266     */
267    @Override
268    public Iterator<String> iterator() {
269
270        return isEmpty() ? Collections.emptyIterator() : Collections.singletonList(toString()).iterator();
271    }
272
273    /**
274     * @see java.util.AbstractCollection#size()
275     */
276    @Override
277    public int size() {
278
279        return isEmpty() ? 0 : 1;
280    }
281
282    /**
283     * Returns the wrapped link as a String as in {@link #getLink()}.<p>
284     *
285     * @return the wrapped link as a String
286     *
287     * @see #getLiteral()
288     */
289    @Override
290    public String toString() {
291
292        return getLink();
293    }
294}