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 GNU
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.search.fields;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsVfsResourceNotFoundException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.search.I_CmsSearchIndex;
037import org.opencms.search.extractors.CmsExtractionResult;
038import org.opencms.search.extractors.I_CmsExtractionResult;
039import org.opencms.search.solr.CmsSolrDocumentXmlContent;
040import org.opencms.search.solr.CmsSolrIndex;
041import org.opencms.util.CmsGeoUtil;
042import org.opencms.util.CmsStringUtil;
043import org.opencms.xml.A_CmsXmlDocument;
044import org.opencms.xml.CmsXmlUtils;
045import org.opencms.xml.content.CmsGeoMappingConfiguration;
046import org.opencms.xml.content.I_CmsXmlContentHandler;
047
048import java.util.HashSet;
049import java.util.List;
050import java.util.Locale;
051import java.util.Set;
052
053import org.apache.commons.logging.Log;
054
055/**
056 * Class extracting the Geo coordinates from a content field.
057 */
058public class CmsGeoCoordinateFieldMapping implements I_CmsSearchFieldMapping {
059
060    /** The log object for this class. */
061    private static final Log LOG = CmsLog.getLog(CmsGeoCoordinateFieldMapping.class);
062
063    /** Serial version UID. */
064    private static final long serialVersionUID = 1;
065
066    /** Maximum recursion depth for following links. */
067    public static final int MAX_DEPTH = 2;
068
069    /** The geo-mapping configuration. */
070    private CmsGeoMappingConfiguration m_config;
071
072    /**
073     * Creates a new instance.
074     *
075     * @param config the configuration to use
076     */
077    public CmsGeoCoordinateFieldMapping(CmsGeoMappingConfiguration config) {
078
079        m_config = config;
080    }
081
082    /**
083     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#getDefaultValue()
084     */
085    public String getDefaultValue() {
086
087        return "0.000000,0.000000";
088    }
089
090    /**
091     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#getParam()
092     */
093    public String getParam() {
094
095        return "";
096    }
097
098    /**
099     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#getStringValue(org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List)
100     */
101    public String getStringValue(
102        CmsObject cms,
103        CmsResource res,
104        I_CmsExtractionResult extractionResult,
105        List<CmsProperty> properties,
106        List<CmsProperty> propertiesSearched) {
107
108        String result = getStringValue(0, m_config, cms, res, extractionResult);
109        return result;
110    }
111
112    /**
113     * Gets the mapped value.
114     *
115     * @param depth the current recursion depth
116     * @param mappingConfig the mapping configuration to use
117     * @param cms the CMS context
118     * @param res the resource for which to get the value
119     * @param extractionResult the extraction result of the resource
120     *
121     * @return the mapped value
122     */
123    public String getStringValue(
124        int depth,
125        CmsGeoMappingConfiguration mappingConfig,
126        CmsObject cms,
127        CmsResource res,
128        I_CmsExtractionResult extractionResult) {
129
130        for (CmsGeoMappingConfiguration.Entry entry : mappingConfig.getEntries()) {
131            try {
132                switch (entry.getType()) {
133                    case field:
134                        String value = findFirstCoordinatesValue(extractionResult, entry.getValue());
135                        String coord = CmsGeoUtil.parseCoordinates(value);
136                        if (coord != null) {
137                            return coord;
138                        }
139                        break;
140                    case link:
141                        if (depth >= MAX_DEPTH) {
142                            LOG.error(
143                                "maximum depth exceeded for linked geo-coordinate mapping in " + res.getRootPath());
144                            return null;
145                        }
146                        String xpath = CmsXmlUtils.createXpath(entry.getValue(), 1);
147                        Set<String> paths = new HashSet<>();
148                        for (Locale locale : extractionResult.getLocales()) {
149                            String path = extractionResult.getContentItems(locale).get(xpath);
150                            if (path != null) {
151                                paths.add(path);
152                            }
153                        }
154                        I_CmsSearchIndex index = OpenCms.getSearchManager().getIndex(
155                            cms.getRequestContext().getCurrentProject().isOnlineProject()
156                            ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE
157                            : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE);
158
159                        for (String path : paths) {
160                            try {
161                                CmsResource linkedResource = cms.readResource(path);
162                                A_CmsXmlDocument[] contentHolder = new A_CmsXmlDocument[] {null};
163                                CmsExtractionResult linkExtractionResult = CmsSolrDocumentXmlContent.extractXmlContent(
164                                    cms,
165                                    linkedResource,
166                                    index,
167                                    null,
168                                    new HashSet<>(),
169                                    content -> {
170                                        contentHolder[0] = content;
171                                    });
172                                if ((linkExtractionResult != null) && (contentHolder[0] != null)) {
173                                    I_CmsXmlContentHandler linkContentHandler = contentHolder[0].getContentDefinition().getContentHandler();
174                                    CmsGeoMappingConfiguration linkMappingConfig = linkContentHandler.getGeoMappingConfiguration();
175                                    if (linkMappingConfig != null) {
176                                        String linkResult = getStringValue(
177                                            depth + 1,
178                                            linkMappingConfig,
179                                            cms,
180                                            contentHolder[0].getFile(),
181                                            linkExtractionResult);
182                                        if (linkResult != null) {
183                                            return linkResult;
184                                        }
185                                    }
186                                }
187                            } catch (CmsVfsResourceNotFoundException e) {
188                                LOG.debug(e.getLocalizedMessage(), e);
189                            } catch (Exception e) {
190                                LOG.error(e.getLocalizedMessage(), e);
191                            }
192                        }
193                        break;
194                    default:
195                        break;
196                }
197            } catch (Exception e) {
198                LOG.error(e.getLocalizedMessage(), e);
199            }
200        }
201        return null;
202    }
203
204    /**
205     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#getType()
206     */
207    public CmsSearchFieldMappingType getType() {
208
209        return CmsSearchFieldMappingType.ITEM;
210    }
211
212    /**
213     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#setDefaultValue(java.lang.String)
214     */
215    public void setDefaultValue(String defaultValue) {
216
217        // not used
218    }
219
220    /**
221     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#setParam(java.lang.String)
222     */
223    public void setParam(String param) {
224
225        // not used
226    }
227
228    /**
229     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#setType(org.opencms.search.fields.CmsSearchFieldMappingType)
230     */
231    public void setType(CmsSearchFieldMappingType type) {
232
233        // not used
234    }
235
236    /**
237     * @see org.opencms.search.fields.I_CmsSearchFieldMapping#setType(java.lang.String)
238     */
239    public void setType(String type) {
240
241        // not used
242    }
243
244    /**
245     * At first, we search for a coordinates value in the best matching locale of the extraction
246     * result. If not available, search for a coordinates value in other locales. In the case of
247     * a multi-valued field, only the first coordinates value is returned. Further values are ignored.
248     *
249     * @param xpath the path to look up in the extraction result
250     * @param extractionResult the extraction result
251     * @return the coordinates value
252     */
253    private String findFirstCoordinatesValue(I_CmsExtractionResult extractionResult, String xpath) {
254
255        xpath = CmsXmlUtils.createXpath(xpath, 1);
256        String value = extractionResult.getContentItems().get(xpath);
257        if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
258            for (Locale locale : extractionResult.getLocales()) {
259                String val = extractionResult.getContentItems(locale).get(xpath);
260                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(val)) {
261                    return val;
262                }
263            }
264        }
265        return value;
266    }
267
268}