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.publish;
029
030import org.opencms.db.CmsDbContext;
031import org.opencms.db.CmsDriverManager;
032import org.opencms.db.CmsPublishList;
033import org.opencms.db.CmsUserSettings;
034import org.opencms.db.I_CmsDbContextFactory;
035import org.opencms.file.CmsDataAccessException;
036import org.opencms.file.CmsObject;
037import org.opencms.file.CmsRequestContext;
038import org.opencms.file.CmsResource;
039import org.opencms.file.CmsUser;
040import org.opencms.lock.CmsLockType;
041import org.opencms.main.CmsBroadcast.ContentMode;
042import org.opencms.main.CmsEvent;
043import org.opencms.main.CmsException;
044import org.opencms.main.CmsInitException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.I_CmsEventListener;
047import org.opencms.main.OpenCms;
048import org.opencms.monitor.CmsMemoryMonitor;
049import org.opencms.report.I_CmsReport;
050import org.opencms.security.CmsAuthentificationException;
051import org.opencms.security.CmsRole;
052import org.opencms.util.CmsUUID;
053
054import java.util.HashMap;
055import java.util.Iterator;
056import java.util.List;
057import java.util.Map;
058
059import org.apache.commons.logging.Log;
060
061/**
062 * This class is responsible for the publish process.<p>
063 *
064 * @since 6.5.5
065 */
066public final class CmsPublishEngine {
067
068    /** The log object for this class. */
069    private static final Log LOG = CmsLog.getLog(CmsPublishEngine.class);
070
071    /** The id of the admin user. */
072    private CmsUUID m_adminUserId;
073
074    /** The current running publish job. */
075    private CmsPublishThread m_currentPublishThread;
076
077    /** The runtime info factory used during publishing. */
078    private final I_CmsDbContextFactory m_dbContextFactory;
079
080    /** The driver manager instance. */
081    private CmsDriverManager m_driverManager;
082
083    /** The engine state. */
084    private CmsPublishEngineState m_engineState;
085
086    /** The publish listeners. */
087    private final CmsPublishListenerCollection m_listeners;
088
089    /** The publish history list with already published jobs. */
090    private final CmsPublishHistory m_publishHistory;
091
092    /** The queue with still waiting publish job. */
093    private final CmsPublishQueue m_publishQueue;
094
095    /** The amount of time the system will wait for a running publish job during shutdown. */
096    private int m_publishQueueShutdowntime;
097
098    /** Is set during shutdown. */
099    private boolean m_shuttingDown;
100
101    /**
102     * Default constructor.<p>
103     *
104     * @param dbContextFactory the initialized OpenCms runtime info factory
105     *
106     * @throws CmsInitException if the configured path to store the publish reports is not accessible
107     */
108    public CmsPublishEngine(I_CmsDbContextFactory dbContextFactory)
109    throws CmsInitException {
110
111        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) {
112            // OpenCms is already initialized
113            throw new CmsInitException(
114                org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_ALREADY_INITIALIZED_0));
115        }
116        if (dbContextFactory == null) {
117            throw new CmsInitException(
118                org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
119        }
120        // initialize the db context factory
121        m_dbContextFactory = dbContextFactory;
122        // initialize publish queue
123        m_publishQueue = new CmsPublishQueue(this);
124        // initialize publish history
125        m_publishHistory = new CmsPublishHistory(this);
126        // initialize event handling
127        m_listeners = new CmsPublishListenerCollection(this);
128        // set engine state to normal processing
129        m_engineState = CmsPublishEngineState.ENGINE_STARTED;
130        if (CmsLog.INIT.isInfoEnabled()) {
131            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_PUBLISH_ENGINE_READY_0));
132        }
133    }
134
135    /**
136     * Abandons the given publish thread.<p>
137     */
138    public void abandonThread() {
139
140        if (!m_currentPublishThread.isAlive()) {
141            // thread is dead
142            if (LOG.isDebugEnabled()) {
143                LOG.debug(Messages.get().getBundle().key(Messages.LOG_PUBLISH_ENGINE_DEAD_JOB_0));
144            }
145        } else {
146            // thread is not dead, and we suppose it hangs :(
147            if (LOG.isWarnEnabled()) {
148                LOG.warn(
149                    Messages.get().getBundle().key(
150                        Messages.LOG_THREADSTORE_PUBLISH_THREAD_INTERRUPT_2,
151                        m_currentPublishThread.getName(),
152                        m_currentPublishThread.getUUID()));
153            }
154            m_currentPublishThread.interrupt();
155        }
156        // just throw it away
157        m_currentPublishThread = null;
158        // and try again
159        checkCurrentPublishJobThread();
160    }
161
162    /**
163     * Controls the publish process.<p>
164     */
165    public synchronized void checkCurrentPublishJobThread() {
166
167        // give the finishing publish thread enough time to clean up
168        try {
169            Thread.sleep(200);
170        } catch (InterruptedException e) {
171            // ignore
172        }
173
174        // create a publish thread only if engine is started
175        if (m_engineState != CmsPublishEngineState.ENGINE_STARTED) {
176            return;
177        }
178
179        if (LOG.isDebugEnabled()) {
180            LOG.debug(Messages.get().getBundle().key(Messages.LOG_PUBLISH_ENGINE_RUNNING_0));
181        }
182
183        // check the driver manager
184        if ((m_driverManager == null) || (m_dbContextFactory == null)) {
185            LOG.error(Messages.get().getBundle().key(Messages.ERR_PUBLISH_ENGINE_NOT_INITIALIZED_0));
186            // without these there is nothing we can do
187            return;
188        }
189
190        // there is no running publish job
191        if (m_currentPublishThread == null) {
192            // but something is waiting in the queue
193            if (!m_publishQueue.isEmpty()) {
194                // start the next waiting publish job
195                CmsPublishJobInfoBean publishJob = m_publishQueue.next();
196                m_currentPublishThread = new CmsPublishThread(this, publishJob);
197                m_currentPublishThread.start();
198            } else {
199                // nothing to do
200                if (LOG.isDebugEnabled()) {
201                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_PUBLISH_ENGINE_NO_RUNNING_JOB_0));
202                }
203            }
204            return;
205        }
206        if (m_currentPublishThread.isAlive()) {
207            // normal running
208            // wait until it is finished
209            if (LOG.isDebugEnabled()) {
210                LOG.debug(Messages.get().getBundle().key(Messages.LOG_PUBLISH_ENGINE_WAITING_0));
211            }
212        } else {
213            // clean up the dead thread
214            abandonThread();
215        }
216    }
217
218    /**
219     * Enqueues a new publish job with the given information in publish queue.<p>
220     *
221     * All resources should already be locked.<p>
222     *
223     * If possible, the publish job starts immediately.<p>
224     *
225     * @param cms the cms context to publish for
226     * @param publishList the resources to publish
227     * @param report the report to write to
228     *
229     * @throws CmsException if something goes wrong while cloning the cms context
230     */
231    public void enqueuePublishJob(CmsObject cms, CmsPublishList publishList, I_CmsReport report) throws CmsException {
232
233        // check the driver manager
234        if ((m_driverManager == null) || (m_dbContextFactory == null)) {
235            // the resources are unlocked in the driver manager
236            throw new CmsPublishException(Messages.get().container(Messages.ERR_PUBLISH_ENGINE_NOT_INITIALIZED_0));
237        }
238        // prevent new jobs if the engine is disabled
239        if (m_shuttingDown || (!isEnabled() && !OpenCms.getRoleManager().hasRole(cms, CmsRole.ROOT_ADMIN))) {
240            // the resources are unlocked in the driver manager
241            throw new CmsPublishException(Messages.get().container(Messages.ERR_PUBLISH_ENGINE_DISABLED_0));
242        }
243
244        // create the publish job
245        CmsPublishJobInfoBean publishJob = new CmsPublishJobInfoBean(cms, publishList, report);
246        try {
247            // enqueue it and
248            m_publishQueue.add(publishJob);
249            // notify all listeners
250            m_listeners.fireEnqueued(new CmsPublishJobBase(publishJob));
251        } catch (Throwable t) {
252            // we really really need to catch everything here, or else the queue status is broken
253            if (m_publishQueue.contains(publishJob)) {
254                m_publishQueue.remove(publishJob);
255            }
256            // throw the exception again
257            throw new CmsException(
258                Messages.get().container(Messages.ERR_PUBLISH_ENGINE_QUEUE_1, publishJob.getPublishHistoryId()),
259                t);
260        }
261        // try to start the publish job immediately
262        checkCurrentPublishJobThread();
263    }
264
265    /**
266     * Returns a publish job based on its publish history id.<p>
267     *
268     * The returned publish job may be an enqueued, running or finished publish job.<p>
269     *
270     * @param publishHistoryId the publish history id to search for
271     *
272     * @return the publish job with the given publish history id, or <code>null</code>
273     */
274    public CmsPublishJobBase getJobByPublishHistoryId(CmsUUID publishHistoryId) {
275
276        // try current running job
277        if ((m_currentPublishThread != null)
278            && m_currentPublishThread.getPublishJob().getPublishHistoryId().equals(publishHistoryId)) {
279            return new CmsPublishJobRunning(m_currentPublishThread.getPublishJob());
280        }
281        // try enqueued jobs
282        Iterator<CmsPublishJobEnqueued> itEnqueuedJobs = getPublishQueue().asList().iterator();
283        while (itEnqueuedJobs.hasNext()) {
284            CmsPublishJobEnqueued enqueuedJob = itEnqueuedJobs.next();
285            if (enqueuedJob.getPublishList().getPublishHistoryId().equals(publishHistoryId)) {
286                return enqueuedJob;
287            }
288        }
289        // try finished jobs
290        Iterator<CmsPublishJobFinished> itFinishedJobs = getPublishHistory().asList().iterator();
291        while (itFinishedJobs.hasNext()) {
292            CmsPublishJobFinished finishedJob = itFinishedJobs.next();
293            if (finishedJob.getPublishHistoryId().equals(publishHistoryId)) {
294                return finishedJob;
295            }
296        }
297        return null;
298    }
299
300    /**
301     * Sets the driver manager instance.<p>
302     *
303     * @param driverManager the driver manager instance
304     */
305    public void setDriverManager(CmsDriverManager driverManager) {
306
307        m_driverManager = driverManager;
308        CmsDbContext dbc = m_dbContextFactory.getDbContext();
309        try {
310            m_adminUserId = m_driverManager.readUser(dbc, OpenCms.getDefaultUsers().getUserAdmin()).getId();
311        } catch (CmsException e) {
312            dbc.rollback();
313            LOG.error(e.getLocalizedMessage(), e);
314        } finally {
315            dbc.clear();
316        }
317    }
318
319    /**
320     * Shuts down all this static export manager.<p>
321     *
322     * NOTE: this method may or may NOT be called (i.e. kill -9 in the stop script), if a system is stopped.<p>
323     *
324     * This is required since there may still be a thread running when the system is being shut down.<p>
325     */
326    public synchronized void shutDown() {
327
328        if (CmsLog.INIT.isInfoEnabled()) {
329            CmsLog.INIT.info(
330                org.opencms.main.Messages.get().getBundle().key(
331                    org.opencms.main.Messages.INIT_SHUTDOWN_START_1,
332                    this.getClass().getName()));
333        }
334
335        // prevent new publish jobs are accepted
336        m_shuttingDown = true;
337
338        // if a job is currently running,
339        // wait the specified amount of time,
340        // then write an abort message to the report
341        if (m_currentPublishThread != null) {
342
343            // if a shutdown time is defined, wait  if a publish process is running
344            if (m_publishQueueShutdowntime > 0) {
345                synchronized (this) {
346                    try {
347                        Thread.sleep(m_publishQueueShutdowntime * 1000);
348                    } catch (InterruptedException exc) {
349                        // ignore
350                    }
351                }
352            }
353
354            if (m_currentPublishThread != null) {
355                CmsPublishJobInfoBean publishJob = m_currentPublishThread.getPublishJob();
356                try {
357                    abortPublishJob(m_adminUserId, new CmsPublishJobEnqueued(publishJob), false);
358                } catch (CmsException e) {
359                    if (LOG.isErrorEnabled()) {
360                        LOG.error(e.getLocalizedMessage(), e);
361                    }
362                }
363            }
364        }
365
366        // write the log
367        CmsDbContext dbc = getDbContext(null);
368        try {
369            m_driverManager.updateLog(dbc);
370        } catch (CmsDataAccessException e) {
371            if (LOG.isErrorEnabled()) {
372                LOG.error(e.getLocalizedMessage(), e);
373            }
374        } finally {
375            dbc.clear();
376        }
377
378        if (CmsLog.INIT.isInfoEnabled()) {
379            CmsLog.INIT.info(
380                org.opencms.staticexport.Messages.get().getBundle().key(
381                    org.opencms.staticexport.Messages.INIT_SHUTDOWN_1,
382                    this.getClass().getName()));
383        }
384    }
385
386    /**
387     * Aborts the given publish job.<p>
388     *
389     * @param userId the id of user that wants to abort the given publish job
390     * @param publishJob the publish job to abort
391     * @param removeJob indicates if the job will be removed or added to history
392     *
393     * @throws CmsException if there is some problem during unlocking the resources
394     * @throws CmsPublishException if the publish job can not be aborted
395     */
396    protected void abortPublishJob(CmsUUID userId, CmsPublishJobEnqueued publishJob, boolean removeJob)
397    throws CmsException, CmsPublishException {
398
399        // abort event should be raised before the job is removed implicitly
400        m_listeners.fireAbort(userId, publishJob);
401
402        if ((m_currentPublishThread == null)
403            || !publishJob.m_publishJob.equals(m_currentPublishThread.getPublishJob())) {
404            // engine is currently publishing another job or is not publishing
405            if (!m_publishQueue.abortPublishJob(publishJob.m_publishJob)) {
406                // job not found
407                throw new CmsPublishException(
408                    Messages.get().container(Messages.ERR_PUBLISH_ENGINE_MISSING_PUBLISH_JOB_0));
409            }
410        } else if (!m_shuttingDown) {
411            // engine is currently publishing the job to abort
412            m_currentPublishThread.abort();
413        } else if (m_shuttingDown && (m_currentPublishThread != null)) {
414            // aborting the current job during shut down
415            I_CmsReport report = m_currentPublishThread.getReport();
416            report.println();
417            report.println();
418            report.println(
419                Messages.get().container(Messages.RPT_PUBLISH_JOB_ABORT_SHUTDOWN_0),
420                I_CmsReport.FORMAT_ERROR);
421            report.println();
422        }
423
424        // unlock all resources
425        if (publishJob.getPublishList() != null) {
426            unlockPublishList(publishJob.m_publishJob);
427        }
428        // keep job if requested
429        if (!removeJob) {
430            // set finish info
431            publishJob.m_publishJob.finish();
432            getPublishHistory().add(publishJob.m_publishJob);
433        } else {
434            getPublishQueue().remove(publishJob.m_publishJob);
435        }
436    }
437
438    /**
439     * Adds a publish listener to listen on publish events.<p>
440     *
441     * @param listener the publish listener to add
442     */
443    protected void addPublishListener(I_CmsPublishEventListener listener) {
444
445        m_listeners.add(listener);
446    }
447
448    /**
449     * Disables the publish engine, i.e. publish jobs are not accepted.<p>
450     */
451    protected void disableEngine() {
452
453        m_engineState = CmsPublishEngineState.ENGINE_DISABLED;
454    }
455
456    /**
457     * Enables the publish engine, i.e. publish jobs are accepted.<p>
458     */
459    protected void enableEngine() {
460
461        m_engineState = CmsPublishEngineState.ENGINE_STARTED;
462        // start publish job if jobs waiting
463        if ((m_currentPublishThread == null) && !m_publishQueue.isEmpty()) {
464            checkCurrentPublishJobThread();
465        }
466    }
467
468    /**
469     * Returns the current running publish job.<p>
470     *
471     * @return the current running publish job
472     */
473    protected CmsPublishThread getCurrentPublishJob() {
474
475        return m_currentPublishThread;
476    }
477
478    /**
479     * Returns the a new db context object.<p>
480     *
481     * @param ctx optional request context, can be <code>null</code>
482     *
483     * @return the a new db context object
484     */
485    protected CmsDbContext getDbContext(CmsRequestContext ctx) {
486
487        return m_dbContextFactory.getDbContext(ctx);
488    }
489
490    /**
491     * Returns the driver manager instance.<p>
492     *
493     * @return the driver manager instance
494     */
495    protected CmsDriverManager getDriverManager() {
496
497        return m_driverManager;
498    }
499
500    /**
501     * Returns the publish history list with already publish job.<p>
502     *
503     * @return the publish history list with already publish job
504     */
505    protected CmsPublishHistory getPublishHistory() {
506
507        return m_publishHistory;
508    }
509
510    /**
511     * Returns the queue with still waiting publish job.<p>
512     *
513     * @return the queue with still waiting publish job
514     */
515    protected CmsPublishQueue getPublishQueue() {
516
517        return m_publishQueue;
518    }
519
520    /**
521     * Returns the content of the publish report assigned to the given publish job.<p>
522     *
523     * @param publishJob the published job
524     * @return the content of the assigned publish report
525     *
526     * @throws CmsException if something goes wrong
527     */
528    protected byte[] getReportContents(CmsPublishJobFinished publishJob) throws CmsException {
529
530        byte[] result = null;
531        CmsDbContext dbc = m_dbContextFactory.getDbContext();
532        try {
533            result = m_driverManager.readPublishReportContents(dbc, publishJob.getPublishHistoryId());
534        } catch (CmsException e) {
535            dbc.rollback();
536            LOG.error(e.getLocalizedMessage(), e);
537            throw e;
538        } finally {
539            dbc.clear();
540        }
541        return result;
542    }
543
544    /**
545     * Returns the user identified by the given id.<p>
546     *
547     * @param userId the id of the user to retrieve
548     *
549     * @return the user identified by the given id
550     */
551    protected CmsUser getUser(CmsUUID userId) {
552
553        CmsDbContext dbc = m_dbContextFactory.getDbContext();
554        try {
555            return m_driverManager.readUser(dbc, userId);
556        } catch (CmsException e) {
557            dbc.rollback();
558            LOG.error(e.getLocalizedMessage(), e);
559        } finally {
560            dbc.clear();
561        }
562        return null;
563    }
564
565    /**
566     * Initializes the publish engine.<p>
567     *
568     * @param adminCms the admin cms
569     * @param publishQueuePersistance flag if the queue is persisted
570     * @param publishQueueShutdowntime amount of time to wait for a publish job during shutdown
571     *
572     * @throws CmsException if something goes wrong
573     */
574    protected void initialize(CmsObject adminCms, boolean publishQueuePersistance, int publishQueueShutdowntime)
575    throws CmsException {
576
577        // check the driver manager
578        if ((m_driverManager == null) || (m_dbContextFactory == null)) {
579            throw new CmsPublishException(Messages.get().container(Messages.ERR_PUBLISH_ENGINE_NOT_INITIALIZED_0));
580        }
581
582        m_publishQueueShutdowntime = publishQueueShutdowntime;
583
584        // initially the engine is stopped, must be restartet after full system initialization
585        m_engineState = CmsPublishEngineState.ENGINE_STOPPED;
586        // read the publish history from the repository
587        m_publishHistory.initialize();
588        // read the queue from the repository
589        m_publishQueue.initialize(adminCms, publishQueuePersistance);
590    }
591
592    /**
593     * Returns the working state, that is if no publish job
594     * is waiting to be processed and there is no current running
595     * publish job.<p>
596     *
597     * @return the working state
598     */
599    protected boolean isRunning() {
600
601        return (((m_engineState == CmsPublishEngineState.ENGINE_STARTED) && !m_publishQueue.isEmpty())
602            || (m_currentPublishThread != null));
603    }
604
605    /**
606     * Sets publish locks of resources in a publish list.<p>
607     *
608     * @param publishJob the publish job
609     * @throws CmsException if something goes wrong
610     */
611    protected void lockPublishList(CmsPublishJobInfoBean publishJob) throws CmsException {
612
613        CmsPublishList publishList = publishJob.getPublishList();
614        // lock them
615        CmsDbContext dbc = getDbContext(publishJob.getCmsObject().getRequestContext());
616        try {
617            Iterator<CmsResource> itResources = publishList.getAllResources().iterator();
618            while (itResources.hasNext()) {
619                CmsResource resource = itResources.next();
620                m_driverManager.lockResource(dbc, resource, CmsLockType.PUBLISH);
621            }
622        } catch (CmsException e) {
623            dbc.rollback();
624            LOG.error(e.getLocalizedMessage(), e);
625            throw e;
626        } finally {
627            dbc.clear();
628        }
629    }
630
631    /**
632     * Signalizes that the publish thread finishes.<p>
633     *
634     * @param publishJob the finished publish job
635     */
636    protected void publishJobFinished(CmsPublishJobInfoBean publishJob) {
637
638        // in order to avoid not removable publish locks, unlock all assigned resources again
639        try {
640            unlockPublishList(publishJob);
641        } catch (Throwable t) {
642            // log failure, most likely a database problem
643            LOG.error(t.getLocalizedMessage(), t);
644        }
645
646        // trigger the old event mechanism
647        CmsDbContext dbc = m_dbContextFactory.getDbContext(publishJob.getCmsObject().getRequestContext());
648        try {
649            // fire an event that a project has been published
650            Map<String, Object> eventData = new HashMap<String, Object>();
651            eventData.put(I_CmsEventListener.KEY_REPORT, publishJob.getPublishReport());
652            eventData.put(
653                I_CmsEventListener.KEY_PUBLISHID,
654                publishJob.getPublishList().getPublishHistoryId().toString());
655            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
656            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
657            CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
658            OpenCms.fireCmsEvent(afterPublishEvent);
659        } catch (Throwable t) {
660            if (dbc != null) {
661                dbc.rollback();
662            }
663            LOG.error(t.getLocalizedMessage(), t);
664            // catch every thing including runtime exceptions
665            publishJob.getPublishReport().println(t);
666        } finally {
667            if (dbc != null) {
668                try {
669                    dbc.clear();
670                } catch (Throwable t) {
671                    // ignore
672                }
673                dbc = null;
674            }
675        }
676        try {
677            // fire the publish finish event
678            m_listeners.fireFinish(new CmsPublishJobRunning(publishJob));
679        } catch (Throwable t) {
680            // log failure, most likely a database problem
681            LOG.error(t.getLocalizedMessage(), t);
682        }
683        try {
684            // finish the job
685            publishJob.finish();
686        } catch (Throwable t) {
687            // log failure, most likely a database problem
688            LOG.error(t.getLocalizedMessage(), t);
689        }
690        try {
691            // put the publish job into the history list
692            m_publishHistory.add(publishJob);
693        } catch (Throwable t) {
694            // log failure, most likely a database problem
695            LOG.error(t.getLocalizedMessage(), t);
696        }
697        if (Thread.currentThread() == m_currentPublishThread) {
698            // wipe the dead thread, only if this thread has not been abandoned
699            m_currentPublishThread = null;
700        }
701        // clear the published resources cache
702        OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISHED_RESOURCES);
703        // try to start a new publish job
704        checkCurrentPublishJobThread();
705    }
706
707    /**
708     * A publish job has been permanently removed from the history.<p>
709     *
710     * @param publishJob the removed publish job
711     */
712    protected void publishJobRemoved(CmsPublishJobInfoBean publishJob) {
713
714        // a publish job has been removed
715        m_listeners.fireRemove(new CmsPublishJobFinished(publishJob));
716    }
717
718    /**
719     * Signalizes that the publish thread starts.<p>
720     *
721     * @param publishJob the started publish job
722     */
723    protected void publishJobStarted(CmsPublishJobInfoBean publishJob) {
724
725        // update the job
726        m_publishQueue.update(publishJob);
727
728        // fire the publish start event
729        m_listeners.fireStart(new CmsPublishJobEnqueued(publishJob));
730    }
731
732    /**
733     * Removes the given publish listener.<p>
734     *
735     * @param listener the publish listener to remove
736     */
737    protected void removePublishListener(I_CmsPublishEventListener listener) {
738
739        m_listeners.remove(listener);
740    }
741
742    /**
743     * Sends a message to the given user, if publish notification is enabled or an error is shown in the message.<p>
744     *
745     * @param toUserId the id of the user to send the message to
746     * @param message the message to send
747     * @param hasErrors flag to determine if the message to send shows an error
748     */
749    protected void sendMessage(CmsUUID toUserId, String message, boolean hasErrors) {
750
751        CmsDbContext dbc = m_dbContextFactory.getDbContext();
752        try {
753            CmsUser toUser = m_driverManager.readUser(dbc, toUserId);
754            CmsUserSettings settings = new CmsUserSettings(toUser);
755            if (settings.getShowPublishNotification() || hasErrors) {
756                // only show message if publish notification is enabled or the message shows an error
757                OpenCms.getSessionManager().sendBroadcast(null, message, toUser, ContentMode.plain);
758            }
759        } catch (CmsException e) {
760            dbc.rollback();
761            LOG.error(e.getLocalizedMessage(), e);
762        } finally {
763            dbc.clear();
764        }
765    }
766
767    /**
768     * Starts the publish engine, i.e. publish jobs are accepted and processed.<p>
769     */
770    protected void startEngine() {
771
772        if (m_engineState != CmsPublishEngineState.ENGINE_STARTED) {
773            m_engineState = CmsPublishEngineState.ENGINE_STARTED;
774            // start publish job if jobs waiting
775            if ((m_currentPublishThread == null) && !m_publishQueue.isEmpty()) {
776                checkCurrentPublishJobThread();
777            }
778        }
779    }
780
781    /**
782     * Stops the publish engine, i.e. publish jobs are still accepted but not published.<p>
783     */
784    protected void stopEngine() {
785
786        m_engineState = CmsPublishEngineState.ENGINE_STOPPED;
787    }
788
789    /**
790     * Removes all publish locks of resources in a publish list of a publish job.<p>
791     *
792     * @param publishJob the publish job
793     * @throws CmsException if something goes wrong
794     */
795    protected void unlockPublishList(CmsPublishJobInfoBean publishJob) throws CmsException {
796
797        CmsPublishList publishList = publishJob.getPublishList();
798        List<CmsResource> allResources = publishList.getAllResources();
799        // unlock them
800        CmsDbContext dbc = getDbContext(publishJob.getCmsObject().getRequestContext());
801        try {
802            Iterator<CmsResource> itResources = allResources.iterator();
803            while (itResources.hasNext()) {
804                CmsResource resource = itResources.next();
805                m_driverManager.unlockResource(dbc, resource, true, true);
806            }
807        } catch (CmsException e) {
808            dbc.rollback();
809            LOG.error(e.getLocalizedMessage(), e);
810            throw e;
811        } finally {
812            dbc.clear();
813        }
814    }
815
816    /**
817     * Returns <code>true</code> if the login manager allows login.<p>
818     *
819     * @return if enabled
820     */
821    private boolean isEnabled() {
822
823        try {
824            if ((m_engineState == CmsPublishEngineState.ENGINE_STOPPED)
825                || (m_engineState == CmsPublishEngineState.ENGINE_STARTED)) {
826                OpenCms.getLoginManager().checkLoginAllowed();
827                return true;
828            } else {
829                return false;
830            }
831        } catch (CmsAuthentificationException e) {
832            return false;
833        }
834    }
835}