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;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProperty;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.CmsUser;
036import org.opencms.i18n.CmsMessageContainer;
037import org.opencms.lock.CmsLock;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.report.A_CmsReportThread;
042import org.opencms.report.I_CmsReport;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.xml.CmsXmlException;
045import org.opencms.xml.content.CmsXmlContent;
046import org.opencms.xml.content.CmsXmlContentFactory;
047import org.opencms.xml.types.I_CmsXmlContentValue;
048
049import java.util.Iterator;
050import java.util.List;
051import java.util.Locale;
052
053import org.apache.commons.logging.Log;
054
055import org.htmlparser.util.ParserException;
056
057/**
058 * Replaces HTML tags of xmlpage resources using the corresponding settings object.
059 * <p>
060 *
061 * @since 6.1.8
062 */
063public class CmsTagReplaceThread extends A_CmsReportThread {
064
065    /** The log object for this class. */
066    private static final Log LOG = CmsLog.getLog(CmsTagReplaceThread.class);
067
068    private CmsProperty m_markerProperty;
069
070    private CmsTagReplaceSettings m_settings;
071
072    /**
073     * Creates a replace html tag Thread.<p>
074     *
075     * @param cms the current cms context.
076     *
077     * @param settings the settings needed to perform the operation.
078     */
079    public CmsTagReplaceThread(CmsObject cms, CmsTagReplaceSettings settings) {
080
081        super(cms, Messages.get().getBundle().key(Messages.GUI_TAGREPLACE_THREAD_NAME_0));
082        initHtmlReport(cms.getRequestContext().getLocale());
083        m_settings = settings;
084        m_markerProperty = new CmsProperty(
085            CmsTagReplaceSettings.PROPERTY_CONTENTOOLS_TAGREPLACE,
086            null,
087            m_settings.getPropertyValueTagReplaceID(),
088            true);
089    }
090
091    /**
092     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
093     */
094    @Override
095    public String getReportUpdate() {
096
097        return getReport().getReportUpdate();
098    }
099
100    /**
101     * @see java.lang.Runnable#run()
102     */
103    @Override
104    public void run() {
105
106        getReport().println(
107            Messages.get().container(Messages.RPT_TAGREPLACE_BEGIN_1, m_settings.getWorkPath()),
108            I_CmsReport.FORMAT_HEADLINE);
109        try {
110            // change the element locales
111            replaceTags();
112        } catch (CmsException e) {
113            getReport().println(
114                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
115                I_CmsReport.FORMAT_ERROR);
116            getReport().println(e.getMessageContainer(), I_CmsReport.FORMAT_ERROR);
117            if (LOG.isErrorEnabled()) {
118                LOG.error(e.getMessageContainer(), e);
119            }
120        } catch (Throwable f) {
121            getReport().println(
122                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
123                I_CmsReport.FORMAT_ERROR);
124            getReport().println(f);
125            if (LOG.isErrorEnabled()) {
126                LOG.error(f);
127            }
128        }
129
130        // append runtime statistics to report
131        getReport().print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_STAT_0));
132        getReport().println(
133            org.opencms.report.Messages.get().container(
134                org.opencms.report.Messages.RPT_STAT_DURATION_1,
135                getReport().formatRuntime()));
136        getReport().println(Messages.get().container(Messages.RPT_TAGREPLACE_END_0), I_CmsReport.FORMAT_HEADLINE);
137    }
138
139    /**
140     * Checks the shared property {@link CmsTagReplaceSettings#PROPERTY_CONTENTOOLS_TAGREPLACE} if
141     * it has the value of this configuration ({@link CmsTagReplaceSettings#getPropertyValueTagReplaceID()}).<p>
142     *
143     * @param resource the resource to test.
144     *
145     * @return true if the property with the value was found.
146     *
147     * @throws CmsException if reading a property fails.
148     */
149    private boolean isProcessedBefore(CmsResource resource) throws CmsException {
150
151        CmsProperty testProp = getCms().readPropertyObject(
152            resource,
153            CmsTagReplaceSettings.PROPERTY_CONTENTOOLS_TAGREPLACE,
154            false);
155        if (testProp.isNullProperty()) {
156            return false;
157        } else {
158            String testValue = testProp.getResourceValue();
159            if (CmsStringUtil.isEmptyOrWhitespaceOnly(testValue)) {
160                return false;
161            } else {
162                return testValue.equals(m_settings.getPropertyValueTagReplaceID());
163            }
164        }
165    }
166
167    private void replaceTags() throws CmsException {
168
169        I_CmsReport report = getReport();
170        report.print(Messages.get().container(Messages.RPT_TAGREPLACE_READ_RESOURCES_1, m_settings.getWorkPath()));
171        report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
172        if (LOG.isDebugEnabled()) {
173            LOG.debug(
174                Messages.get().getBundle().key(Messages.RPT_TAGREPLACE_READ_RESOURCES_1, m_settings.getWorkPath()));
175        }
176        CmsResourceFilter filter = CmsResourceFilter.ALL.addRequireType(
177            OpenCms.getResourceManager().getResourceType("xmlpage").getTypeId());
178        List resources = getCms().readResources(m_settings.getWorkPath(), filter, true);
179        if (LOG.isDebugEnabled()) {
180            LOG.debug(
181                Messages.get().getBundle().key(Messages.LOG_TAGREPLACE_READ_RESOURCES_OK_1, m_settings.getWorkPath()));
182        }
183        report.println(
184            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
185            I_CmsReport.FORMAT_OK);
186        Integer size = Integer.valueOf(resources.size());
187        Iterator itResources = resources.iterator();
188        CmsResource resource;
189        int count = 1;
190        while (itResources.hasNext()) {
191            resource = (CmsResource)itResources.next();
192            replaceTags(resource, size, Integer.valueOf(count));
193            count++;
194        }
195    }
196
197    /**
198     * Replaces all replacement mappings configured in the internal {@link CmsTagReplaceSettings}
199     * instance in the content of the given resource.<p>
200     *
201     * No modifications will be done:
202     * <ol>
203     * <li>the resource is locked by another user.</li>
204     * <li>the special marker property with the value that stands for the replacement configuration
205     * is set on the resource (shared).</li>
206     * <li>locking of the non-locked resource fails.</li>
207     * <li>Loading of the content fails.</li>
208     * <li>Unmarshalling fails.</li>
209     * <li>Unexpected exception while replacing occur.</li>
210     * <li>Marshalling of XML fails.</li>
211     * <li>Writing of the marker property fails.</li>
212     * <li>Writing of the file fails.</li>
213     * </ol>
214     * <p>
215     *
216     * @param resource denotes the content to process.
217     *
218     * @param totalJobCount for fancy report writing.
219     *
220     * @param actualJobCount for even fancier report writing.
221     *
222     * @throws CmsException if sth. goes wrong.
223     */
224    private void replaceTags(CmsResource resource, Integer totalJobCount, Integer actualJobCount) throws CmsException {
225
226        I_CmsReport report = getReport();
227        report.print(org.opencms.report.Messages.get().container(
228            org.opencms.report.Messages.RPT_SUCCESSION_2,
229            actualJobCount,
230            totalJobCount));
231        report.print(
232            Messages.get().container(
233                Messages.RPT_TAGREPLACE_PROCESS_FILE_1,
234                getCms().getRequestContext().removeSiteRoot(resource.getRootPath())));
235        report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
236
237        if (isProcessedBefore(resource)) {
238            report.print(
239                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SKIPPED_0),
240                I_CmsReport.FORMAT_OK);
241            report.println(
242                Messages.get().container(Messages.RPT_TAGREPLACE_SKIP_REASON_PROPERTY_0),
243                I_CmsReport.FORMAT_OK);
244            return;
245        }
246
247        if (LOG.isDebugEnabled()) {
248            LOG.debug(
249                Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOCK_RESOURCE_1, resource.getRootPath()));
250        }
251        try {
252            // checking the lock:
253            if (LOG.isDebugEnabled()) {
254                LOG.debug(
255                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOCK_READ_1, resource.getRootPath()));
256            }
257
258            CmsLock lock = getCms().getLock(resource);
259
260            if (LOG.isDebugEnabled()) {
261                LOG.debug(
262                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOCK_READ_1, resource.getRootPath()));
263            }
264
265            boolean myLock = !lock.isNullLock() && lock.isOwnedBy(getCms().getRequestContext().getCurrentUser());
266            if (lock.isNullLock() || myLock) {
267                if (!myLock) {
268                    if (LOG.isDebugEnabled()) {
269                        LOG.debug(
270                            Messages.get().getBundle().key(
271                                Messages.LOG_DEBUG_TAGREPLACE_LOCK_RESOURCE_1,
272                                resource.getRootPath()));
273                    }
274                    // obtaining the lock:
275                    getCms().lockResource(getCms().getRequestContext().removeSiteRoot(resource.getRootPath()));
276                    if (LOG.isDebugEnabled()) {
277                        LOG.debug(
278                            Messages.get().getBundle().key(
279                                Messages.LOG_DEBUG_TAGREPLACE_LOCK_RESOURCE_OK_1,
280                                resource.getRootPath()));
281                    }
282                }
283            } else {
284                // locked by another user:
285                if (LOG.isDebugEnabled()) {
286                    LOG.debug(
287                        Messages.get().getBundle().key(
288                            Messages.LOG_DEBUG_TAGREPLACE_RESOURCE_SKIPPED_1,
289                            resource.getRootPath()));
290                    LOG.debug(Messages.get().getBundle().key(Messages.RPT_TAGREPLACE_SKIP_REASON_LOCKED_0));
291                }
292                report.print(
293                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SKIPPED_0),
294                    I_CmsReport.FORMAT_WARNING);
295                try {
296                    CmsUser locker = getCms().readUser(lock.getUserId());
297                    report.println(
298                        Messages.get().container(Messages.RPT_TAGREPLACE_SKIP_REASON_LOCKED_1, locker.getName()),
299                        I_CmsReport.FORMAT_WARNING);
300                } catch (Throwable f) {
301                    report.println(
302                        Messages.get().container(Messages.RPT_TAGREPLACE_SKIP_REASON_ERR_LOCK_0),
303                        I_CmsReport.FORMAT_WARNING);
304                    if (LOG.isDebugEnabled()) {
305                        LOG.debug(
306                            Messages.get().getBundle().key(
307                                Messages.LOG_DEBUG_TAGREPLACE_RESOURCE_SKIPPED_1,
308                                resource.getRootPath()));
309                        LOG.debug(Messages.get().getBundle().key(Messages.RPT_TAGREPLACE_SKIP_REASON_ERR_LOCK_0));
310                    }
311                }
312                return;
313            }
314        } catch (CmsException e) {
315            if (LOG.isErrorEnabled()) {
316                LOG.error(
317                    Messages.get().getBundle().key(
318                        Messages.LOG_WARN_TAGREPLACE_LOCK_RESOURCE_FAILED_1,
319                        resource.getRootPath()),
320                    e);
321            }
322            report.print(
323                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SKIPPED_0),
324                I_CmsReport.FORMAT_WARNING);
325            report.println(
326                Messages.get().container(Messages.RPT_TAGREPLACE_SKIP_REASON_LOCKED_0),
327                I_CmsReport.FORMAT_WARNING);
328            return;
329        }
330
331        if (LOG.isDebugEnabled()) {
332            LOG.debug(
333                Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOAD_FILE_1, resource.getRootPath()));
334        }
335
336        CmsFile file = getCms().readFile(resource);
337
338        if (LOG.isDebugEnabled()) {
339            LOG.debug(
340                Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOAD_FILE_OK_1, resource.getRootPath()));
341            LOG.debug(
342                Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_UNMARSHAL_1, resource.getRootPath()));
343        }
344
345        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(getCms(), file);
346
347        if (LOG.isDebugEnabled()) {
348            LOG.debug(
349                Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_UNMARSHAL_OK_1, resource.getRootPath()));
350        }
351
352        List locales = xmlcontent.getLocales();
353        Iterator itLocales = locales.iterator();
354        List elements;
355        Iterator itElements;
356        Locale locale;
357        CmsTagReplaceParser parser = new CmsTagReplaceParser(m_settings);
358        I_CmsXmlContentValue value;
359        int count = 1;
360        while (itLocales.hasNext()) {
361            locale = (Locale)itLocales.next();
362            if (LOG.isDebugEnabled()) {
363                LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_LOCALE_1, locale.getLanguage()));
364            }
365
366            elements = xmlcontent.getValues(locale);
367            itElements = elements.iterator();
368            while (itElements.hasNext()) {
369                value = (I_CmsXmlContentValue)itElements.next();
370                String content = value.getStringValue(getCms());
371                if (LOG.isDebugEnabled()) {
372                    LOG.debug(
373                        Messages.get().getBundle().key(
374                            Messages.LOG_DEBUG_TAGREPLACE_ELEMENT_2,
375                            value.getPath(),
376                            content));
377                }
378                try {
379
380                    parser.process(content, xmlcontent.getEncoding());
381                    value.setStringValue(getCms(), parser.getResult());
382                } catch (ParserException e) {
383                    CmsMessageContainer container = Messages.get().container(
384                        Messages.ERR_TAGREPLACE_PARSE_4,
385                        new Object[] {
386                            getCms().getRequestContext().removeSiteRoot(resource.getRootPath()),
387                            locale.getLanguage(),
388                            value.getPath(),
389                            parser.getResult()});
390                    throw new CmsXmlException(container, e);
391                }
392            }
393            count++;
394        }
395
396        if (parser.isChangedContent()) {
397
398            if (LOG.isDebugEnabled()) {
399                LOG.debug(
400                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_MARSHAL_1, resource.getRootPath()));
401            }
402            byte[] content = xmlcontent.marshal();
403            if (LOG.isDebugEnabled()) {
404                LOG.debug(
405                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_MARSHAL_OK_1, resource.getRootPath()));
406            }
407
408            // write back the modified xmlcontent:
409            file.setContents(content);
410
411            if (LOG.isDebugEnabled()) {
412                LOG.debug(
413                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_WRITE_1, resource.getRootPath()));
414            }
415
416            getCms().writeFile(file);
417
418            if (LOG.isDebugEnabled()) {
419                LOG.debug(
420                    Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_WRITE_OK_1, resource.getRootPath()));
421            }
422
423            try {
424                // set the marker property:
425
426                if (LOG.isDebugEnabled()) {
427                    LOG.debug(
428                        Messages.get().getBundle().key(
429                            Messages.LOG_DEBUG_TAGREPLACE_PROPERTY_WRITE_3,
430                            new Object[] {
431                                m_markerProperty.getName(),
432                                m_markerProperty.getResourceValue(),
433                                resource.getRootPath()}));
434                }
435                getCms().writePropertyObject(
436                    getCms().getRequestContext().removeSiteRoot(resource.getRootPath()),
437                    m_markerProperty);
438                if (LOG.isDebugEnabled()) {
439                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_PROPERTY_WRITE_OK_0));
440                }
441                report.println(
442                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
443                    I_CmsReport.FORMAT_OK);
444            } catch (CmsException e) {
445                CmsMessageContainer container = Messages.get().container(
446                    Messages.LOG_ERROR_TAGREPLACE_PROPERTY_WRITE_3,
447                    new Object[] {
448                        m_markerProperty.getName(),
449                        m_markerProperty.getResourceValue(),
450                        resource.getRootPath()});
451                throw new CmsXmlException(container, e);
452            }
453
454        } else {
455            if (LOG.isDebugEnabled()) {
456                LOG.debug(
457                    Messages.get().container(Messages.LOG_DEBUG_TAGREPLACE_UNLOCK_FILE_1, resource.getRootPath()));
458            }
459            getCms().unlockResource(getCms().getRequestContext().removeSiteRoot(resource.getRootPath()));
460            if (LOG.isDebugEnabled()) {
461                LOG.debug(Messages.get().container(Messages.LOG_DEBUG_TAGREPLACE_UNLOCK_FILE_OK_0));
462            }
463            report.print(
464                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SKIPPED_0),
465                I_CmsReport.FORMAT_OK);
466            report.println(
467                Messages.get().container(Messages.RPT_TAGREPLACE_SKIP_REASON_UNMODIFIED_0),
468                I_CmsReport.FORMAT_OK);
469
470        }
471    }
472}