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