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 GmbH & Co. KG, 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.workplace.tools.content.check;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.i18n.CmsLocaleManager;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.util.CmsStringUtil;
037import org.opencms.xml.content.CmsXmlContent;
038import org.opencms.xml.content.CmsXmlContentFactory;
039
040import java.util.ArrayList;
041import java.util.List;
042import java.util.Locale;
043import java.util.regex.Pattern;
044
045import org.apache.commons.logging.Log;
046
047/**
048 * This implementation of the I_CmsContentCheck interface implements a check for
049 * resource properties.<p>
050 *
051 * The following items can be configured and checked:
052 * <ul>
053 * <li>Property not set</li>
054 * <li>Property value contains filename</li>
055 * <li>Property value is shorter than a minimum size</li>
056 * <li>Property value contains a given value (with RegEx)</li>
057 * <li>Property value does not contain a given value (with RegEx)</li>
058 * </ul>
059 *
060 * @since 6.1.2
061 */
062public class CmsContentCheckProperty extends A_CmsContentCheck {
063
064    /** Path to the configuration file. */
065    private static final String CONFIGURATION = CmsContentCheck.VFS_PATH_PLUGIN_FOLDER
066        + "propertycheck/configuration.xml";
067
068    /** Name of the dialog parameter. */
069    private static final String DIALOG_PARAMETER = "property";
070
071    /** Path to the configuration icon. */
072    private static final String ICONPATH = "tools/contenttools/icons/big/contentcheck_property_configuration.png";
073
074    /** The log object for this class. */
075    private static final Log LOG = CmsLog.getLog(CmsContentCheckProperty.class);
076
077    /** Name of this content check. */
078    private static final String NAME = "Property Check";
079
080    /** The xpath for the empty configuration. */
081    private static final String XPATH_EMPTY = "empty";
082
083    /** The xpath for the error configuration. */
084    private static final String XPATH_ERROR = "error";
085
086    /** The xpath for the filename configuration. */
087    private static final String XPATH_FILENAME = "filename";
088
089    /** The xpath for the length configuration. */
090    private static final String XPATH_LENGTH = "length";
091
092    /** The xpath for the propertyname configuration. */
093    private static final String XPATH_PROPERTYNAME = "propertyname";
094
095    /** The xpath for the type configuration. */
096    private static final String XPATH_TYPE = "type";
097
098    /** The xpath for the value configuration. */
099    private static final String XPATH_VALUE = "value";
100
101    /** The xpath for the warning configuration. */
102    private static final String XPATH_WARNUING = "warning";
103
104    /** The active flag, signaling if this content check is active. */
105    private boolean m_active = true;
106
107    /** The CmsObject. */
108    private CmsObject m_cms;
109
110    /** List of all configured error checks. */
111    private List m_configuredErrorChecks;
112
113    /** List of all configured warning checks. */
114    private List m_configuredWarningChecks;
115
116    /** Locale to be used to extract XML content. */
117    private Locale m_locale;
118
119    /**
120     *
121     * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#executeContentCheck(org.opencms.file.CmsObject, org.opencms.workplace.tools.content.check.CmsContentCheckResource)
122     */
123    @Override
124    public CmsContentCheckResource executeContentCheck(CmsObject cms, CmsContentCheckResource testResource)
125    throws CmsException {
126
127        getConfiguration();
128        if (LOG.isDebugEnabled()) {
129            LOG.debug(
130                Messages.get().getBundle().key(
131                    Messages.LOG_DEBUG_PROPERTY_CONFIGURED_ERRORS_2,
132                    new Object[] {testResource.getResourceName(), m_configuredErrorChecks}));
133        }
134        // check for errors
135        List errors = processProperties(m_configuredErrorChecks, testResource);
136        if (errors.size() > 0) {
137            testResource.addErrors(errors);
138        }
139        if (LOG.isDebugEnabled()) {
140            LOG.debug(
141                Messages.get().getBundle().key(
142                    Messages.LOG_DEBUG_PROPERTY_CONFIGURED_WARNINGS_2,
143                    testResource.getResourceName(),
144                    m_configuredErrorChecks));
145        }
146        // check for warnings
147        List warnings = processProperties(m_configuredWarningChecks, testResource);
148        if (warnings.size() > 0) {
149            testResource.addWarnings(warnings);
150        }
151        return testResource;
152    }
153
154    /**
155     *
156     * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#getDialogParameterName()
157     */
158    @Override
159    public String getDialogParameterName() {
160
161        return DIALOG_PARAMETER;
162    }
163
164    /**
165     * @see org.opencms.workplace.tools.I_CmsToolHandler#getHelpText()
166     */
167    @Override
168    public String getHelpText() {
169
170        return Messages.get().getBundle().key(Messages.GUI_CHECKCONTENT_CONFIGURATION_PROPERTY_HELP_0);
171    }
172
173    /**
174     * @see org.opencms.workplace.tools.I_CmsToolHandler#getIconPath()
175     */
176    @Override
177    public String getIconPath() {
178
179        return ICONPATH;
180    }
181
182    /**
183     * @see org.opencms.workplace.tools.I_CmsToolHandler#getLink()
184     */
185    @Override
186    public String getLink() {
187
188        return "/system/workplace/views/admin/admin-editor.jsp?resource=/system/workplace/admin/contenttools/check/plugin/propertycheck/configuration.xml";
189    }
190
191    /**
192     * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#getMessageBundles()
193     */
194    @Override
195    public List getMessageBundles() {
196
197        List messages = new ArrayList();
198        messages.add(org.opencms.workplace.tools.content.check.Messages.get().getBundleName());
199        return messages;
200    }
201
202    /**
203     * @see org.opencms.workplace.tools.I_CmsToolHandler#getName()
204     */
205    @Override
206    public String getName() {
207
208        return NAME;
209    }
210
211    /**
212     * @see org.opencms.workplace.tools.I_CmsToolHandler#getPath()
213     */
214    @Override
215    public String getPath() {
216
217        return "/contenttools/checkconfig/checkproperty";
218    }
219
220    /**
221     * @see org.opencms.workplace.tools.I_CmsToolHandler#getPosition()
222     */
223    @Override
224    public float getPosition() {
225
226        return 1;
227    }
228
229    /**
230     * @see org.opencms.workplace.tools.I_CmsToolHandler#getShortName()
231     */
232    @Override
233    public String getShortName() {
234
235        return NAME;
236    }
237
238    /**
239     * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#init(org.opencms.file.CmsObject)
240     */
241    @Override
242    public void init(CmsObject cms) {
243
244        m_cms = cms;
245        m_locale = CmsLocaleManager.getLocale("en");
246    }
247
248    /**
249     * Gets the active flag.<p>
250     *
251     * @return true if this content check is active, false otherwise.
252     */
253    @Override
254    public boolean isActive() {
255
256        return m_active;
257    }
258
259    /**
260     * Sets the active flag.<p>
261     *
262     * This method is required to build the widget dialog frontend.
263     *
264     * @param value true if this content check is set to be active, false otherwise.
265     */
266    @Override
267    public void setActive(boolean value) {
268
269        m_active = value;
270    }
271
272    /**
273     * Gets the configuration of the property check.<p>
274     *
275     *@throws CmsException if an error occurs reading the configuration
276     */
277    private void getConfiguration() throws CmsException {
278
279        if ((m_configuredErrorChecks == null) || (m_configuredWarningChecks == null)) {
280            // get the configuration file
281            CmsResource res = m_cms.readResource(CONFIGURATION);
282            if (LOG.isDebugEnabled()) {
283                LOG.debug(
284                    Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_CONFIG_FILENAME_1, res.getRootPath()));
285            }
286            CmsFile file = m_cms.readFile(res);
287            if (LOG.isDebugEnabled()) {
288                LOG.debug(
289                    Messages.get().getBundle().key(
290                        Messages.LOG_DEBUG_PROPERTY_CONFIG_FILE_1,
291                        new String(file.getContents())));
292            }
293            CmsXmlContent configuration = CmsXmlContentFactory.unmarshal(m_cms, file);
294
295            // now extract the configured error checks from it
296            m_configuredErrorChecks = getConfiguredChecks(configuration, XPATH_ERROR);
297            m_configuredWarningChecks = getConfiguredChecks(configuration, XPATH_WARNUING);
298        }
299    }
300
301    /**
302     * Reads the configuration for a given xpath and stored all results in a list.<p>
303     *
304     * @param configuration the configuration to read from
305     * @param xpath the xpath prefix
306     * @return list of CmsContentCheckProperetyObject objects
307     */
308    private List getConfiguredChecks(CmsXmlContent configuration, String xpath) {
309
310        List checks = new ArrayList();
311
312        if (LOG.isDebugEnabled()) {
313            LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_CONFIG_XPATH_2, xpath, m_locale));
314        }
315
316        int size = configuration.getIndexCount(xpath, m_locale);
317        for (int i = 1; i <= size; i++) {
318            // extract the values from the configuration
319            String propertyname = configuration.getValue(
320                xpath + "[" + i + "]/" + XPATH_PROPERTYNAME,
321                m_locale).getStringValue(m_cms);
322            String type = configuration.getValue(xpath + "[" + i + "]/" + XPATH_TYPE, m_locale).getStringValue(m_cms);
323            String empty = configuration.getValue(xpath + "[" + i + "]/" + XPATH_EMPTY, m_locale).getStringValue(m_cms);
324            String filename = configuration.getValue(xpath + "[" + i + "]/" + XPATH_FILENAME, m_locale).getStringValue(
325                m_cms);
326            String length = configuration.getValue(xpath + "[" + i + "]/" + XPATH_LENGTH, m_locale).getStringValue(
327                m_cms);
328            int values = configuration.getIndexCount(xpath + "[" + i + "]/" + XPATH_VALUE, m_locale);
329
330            //String value = configuration.getValue(xpath + "[" + i + "]/" + XPATH_VALUE, m_locale).getStringValue(m_cms);
331
332            // store them in the CmsContentCheckProperetyObject object for further processing
333            CmsContentCheckPropertyObject propObject = new CmsContentCheckPropertyObject();
334
335            if (CmsStringUtil.isNotEmpty(propertyname)) {
336                propObject.setPropertyname(propertyname);
337            }
338            if (CmsStringUtil.isNotEmpty(type)) {
339                propObject.setType(type);
340            }
341            if (CmsStringUtil.isNotEmpty(empty)) {
342                propObject.setEmpty(empty.equals("true"));
343            }
344            if (CmsStringUtil.isNotEmpty(filename)) {
345                propObject.setFilename(filename.equals("true"));
346            }
347            if (CmsStringUtil.isNotEmpty(length)) {
348                propObject.setLength(Integer.valueOf(length).intValue());
349            }
350            if (values > 0) {
351                List valueList = new ArrayList();
352                for (int j = 1; j <= values; j++) {
353                    String value = configuration.getValue(
354                        xpath + "[" + i + "]/" + XPATH_VALUE + "[" + j + "]",
355                        m_locale).getStringValue(m_cms);
356                    if (CmsStringUtil.isNotEmpty(value)) {
357                        valueList.add(value);
358                    }
359                }
360                propObject.setValue(valueList);
361            }
362
363            if (LOG.isDebugEnabled()) {
364                LOG.debug(
365                    Messages.get().getBundle().key(
366                        Messages.LOG_DEBUG_PROPERTY_CONFIG_PROPERTY_3,
367                        Integer.valueOf(i),
368                        Integer.valueOf(size),
369                        propObject));
370            }
371
372            checks.add(propObject);
373        }
374        return checks;
375    }
376
377    /**
378     * Processes a list of CmsContentCheckProperetyObject and runs all available tests on them.<p>
379     *
380     * All errors or warnings found are collected in a list returned to the calling method.
381     *
382     * @param properties list of CmsContentCheckProperetyObject to process
383     * @param testResource the CmsContentCheckResource to run all tests on
384     * @return list of Strings containing either errors or warinings
385     */
386    private List processProperties(List properties, CmsContentCheckResource testResource) {
387
388        List results = new ArrayList();
389
390        if (LOG.isDebugEnabled()) {
391            LOG.debug(
392                Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_RESOURCE_1, testResource.getResourceName()));
393        }
394
395        //loop through all property tests
396        for (int i = 0; i < properties.size(); i++) {
397            try {
398                CmsContentCheckPropertyObject propObject = (CmsContentCheckPropertyObject)properties.get(i);
399
400                if (LOG.isDebugEnabled()) {
401                    LOG.debug(
402                        Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_PROPERTY_1, propObject.toString()));
403                }
404
405                // check if this test must be done for thies kind of resource
406                if ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_BOTH))
407                    || ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_FILE)
408                        && (testResource.getResource().isFile())))
409                    || ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_FOLDER)
410                        && (testResource.getResource().isFolder())))
411
412                ) {
413
414                    // read the property
415                    String prop = m_cms.readPropertyObject(
416                        testResource.getResource(),
417                        propObject.getPropertyname(),
418                        false).getValue();
419
420                    if (LOG.isDebugEnabled()) {
421                        LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_VALUE_1, prop));
422                    }
423
424                    if (LOG.isDebugEnabled()) {
425                        LOG.debug(
426                            Messages.get().getBundle().key(
427                                Messages.LOG_DEBUG_PROPERTY_ISEMPTYCHECK_1,
428                                Boolean.valueOf(propObject.isEmpty())));
429                    }
430
431                    // test if the property is empty
432                    if (propObject.isEmpty() && CmsStringUtil.isEmpty(prop)) {
433                        results.add(
434                            Messages.get().getBundle().key(
435                                Messages.ERR_CHECK_NO_PROPERTYNAME_1,
436                                propObject.getPropertyname()));
437                        if (LOG.isDebugEnabled()) {
438                            LOG.debug(
439                                Messages.get().getBundle().key(
440                                    Messages.ERR_CHECK_NO_PROPERTYNAME_1,
441                                    propObject.getPropertyname()));
442                        }
443                    }
444
445                    if (LOG.isDebugEnabled()) {
446                        LOG.debug(
447                            Messages.get().getBundle().key(
448                                Messages.LOG_DEBUG_PROPERTY_ISFILENAME_1,
449                                Boolean.valueOf(propObject.isFilename())));
450                    }
451
452                    // test if the property does not start with the filename
453                    if (!CmsStringUtil.isEmpty(prop)) {
454                        if (propObject.isFilename()
455                            && testResource.getResource().getName().toLowerCase().startsWith(prop.toLowerCase())) {
456                            results.add(
457                                Messages.get().getBundle().key(
458                                    Messages.ERR_CHECK_CONTAINS_FILENAME_2,
459                                    propObject.getPropertyname(),
460                                    prop));
461                            if (LOG.isDebugEnabled()) {
462                                LOG.debug(
463                                    Messages.get().getBundle().key(
464                                        Messages.ERR_CHECK_CONTAINS_FILENAME_2,
465                                        propObject.getPropertyname(),
466                                        prop));
467                            }
468                        }
469
470                        if (LOG.isDebugEnabled()) {
471                            LOG.debug(
472                                Messages.get().getBundle().key(
473                                    Messages.LOG_DEBUG_PROPERTY_CHECKLENGTH_2,
474                                    Integer.valueOf(propObject.getLength()),
475                                    Integer.valueOf(prop.length())));
476                        }
477                        // test if the minmal property length is valid
478                        if (propObject.getLength() > -1) {
479                            if (prop.length() < propObject.getLength()) {
480                                results.add(
481                                    Messages.get().getBundle().key(
482                                        Messages.ERR_CHECK_TOO_SHORT_3,
483                                        propObject.getPropertyname(),
484                                        prop,
485                                        Integer.valueOf(prop.length())));
486                                if (LOG.isDebugEnabled()) {
487                                    LOG.debug(
488                                        Messages.get().getBundle().key(
489                                            Messages.ERR_CHECK_TOO_SHORT_3,
490                                            propObject.getPropertyname(),
491                                            prop,
492                                            Integer.valueOf(prop.length())));
493                                }
494                            }
495                        }
496
497                        // test if the value matches a regex
498                        if (propObject.getValue().size() > 0) {
499                            for (int j = 0; j < propObject.getValue().size(); j++) {
500
501                                String regex = (String)propObject.getValue().get(j);
502
503                                boolean matchResult = true;
504                                if (regex.charAt(0) == '!') {
505                                    // negate the pattern
506                                    matchResult = false;
507                                    regex = regex.substring(1);
508                                }
509                                String matchValue = prop;
510                                boolean match = Pattern.matches(regex, matchValue);
511
512                                if (LOG.isDebugEnabled()) {
513                                    LOG.debug(
514                                        Messages.get().getBundle().key(
515                                            Messages.LOG_DEBUG_PROPERTY_MATCHPATTERN_2,
516                                            regex,
517                                            matchValue));
518                                }
519
520                                if (matchResult != match) {
521                                    results.add(
522                                        Messages.get().getBundle().key(
523                                            Messages.ERR_CHECK_MATCH_3,
524                                            propObject.getPropertyname(),
525                                            prop,
526                                            propObject.getValue().get(j)));
527                                    if (LOG.isDebugEnabled()) {
528                                        LOG.debug(
529                                            Messages.get().getBundle().key(
530                                                Messages.ERR_CHECK_MATCH_3,
531                                                propObject.getPropertyname(),
532                                                prop,
533                                                propObject.getValue().get(j)));
534                                    }
535                                }
536                            }
537                        }
538                    }
539                }
540
541            } catch (CmsException e) {
542                LOG.error(
543                    Messages.get().getBundle().key(
544                        Messages.LOG_ERROR_PROCESSING_PROPERTIES_2,
545                        testResource.getResourceName(),
546                        e));
547            }
548        }
549
550        return results;
551    }
552}