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.file.CmsObject;
033import org.opencms.main.CmsException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.monitor.CmsMemoryMonitor;
037
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.Iterator;
041import java.util.List;
042
043import org.apache.commons.collections.Buffer;
044import org.apache.commons.collections.BufferUtils;
045import org.apache.commons.collections.buffer.TypedBuffer;
046import org.apache.commons.collections.buffer.UnboundedFifoBuffer;
047import org.apache.commons.logging.Log;
048
049/**
050 * This queue contains all not jet started publish jobs.<p>
051 *
052 * @since 6.5.5
053 */
054public class CmsPublishQueue {
055
056    /** The log object for this class. */
057    private static final Log LOG = CmsLog.getLog(CmsPublishHistory.class);
058
059    /** The publish engine. */
060    protected final CmsPublishEngine m_publishEngine;
061
062    /**
063     * Default constructor, for an empty queue.<p>
064     *
065     * @param publishEngine the publish engine instance
066     */
067    protected CmsPublishQueue(final CmsPublishEngine publishEngine) {
068
069        m_publishEngine = publishEngine;
070    }
071
072    /**
073     * Creates the buffer used as publish queue.<p>
074     *
075     * @return the queue buffer
076     */
077    public static Buffer getQueue() {
078
079        return BufferUtils.synchronizedBuffer(TypedBuffer.decorate(new UnboundedFifoBuffer() {
080
081            /** The serialization version id constant. */
082            private static final long serialVersionUID = 606444342980861724L;
083
084            /**
085             * Called when the queue is full to remove the oldest element.<p>
086             *
087             * @see org.apache.commons.collections.buffer.BoundedFifoBuffer#remove()
088             */
089            @Override
090            public Object remove() {
091
092                CmsPublishJobInfoBean publishJob = (CmsPublishJobInfoBean)super.remove();
093                return publishJob;
094            }
095        }, CmsPublishJobInfoBean.class));
096    }
097
098    /**
099     * Aborts the given publish job.<p>
100     *
101     * @param publishJob the publish job to abort
102     *
103     * @return <code>true</code> if the publish job was found
104     */
105    protected boolean abortPublishJob(CmsPublishJobInfoBean publishJob) {
106
107        if (OpenCms.getMemoryMonitor().getCachedPublishJob(publishJob.getPublishHistoryId().toString()) != null) {
108            // remove publish job from cache
109            OpenCms.getMemoryMonitor().uncachePublishJob(publishJob);
110            return true;
111        } else {
112            return false;
113        }
114    }
115
116    /**
117     * Pushes a new publish job with the given information in publish queue.<p>
118     *
119     * If possible, the publish job starts immediately.<p>
120     *
121     * @param publishJob the publish job to enqueue
122     *
123     * @throws CmsException if something goes wrong
124     */
125    protected void add(CmsPublishJobInfoBean publishJob) throws CmsException {
126
127        // set the queue status in the publish job
128        publishJob.enqueue();
129
130        // add job to database if necessary
131        if (OpenCms.getMemoryMonitor().requiresPersistency()) {
132            CmsDbContext dbc = m_publishEngine.getDbContext(null);
133            try {
134                // this operation may in rare circumstances fail with a DB exception
135                // if this is the case the publish job must NOT be in the queue
136                m_publishEngine.getDriverManager().createPublishJob(dbc, publishJob);
137            } catch (CmsException e) {
138                dbc.rollback();
139                LOG.error(e.getLocalizedMessage(), e);
140                throw e;
141            } finally {
142                dbc.clear();
143            }
144        }
145
146        // add publish job to cache
147        OpenCms.getMemoryMonitor().cachePublishJob(publishJob);
148    }
149
150    /**
151     * Returns an unmodifiable list representation of this queue.<p>
152     *
153     * @return a list of {@link CmsPublishJobEnqueued} objects
154     */
155    protected List<CmsPublishJobEnqueued> asList() {
156
157        List<CmsPublishJobInfoBean> cachedPublishJobs = OpenCms.getMemoryMonitor().getAllCachedPublishJobs();
158        List<CmsPublishJobEnqueued> result = new ArrayList<CmsPublishJobEnqueued>(cachedPublishJobs.size());
159        Iterator<CmsPublishJobInfoBean> it = cachedPublishJobs.iterator();
160        while (it.hasNext()) {
161            CmsPublishJobInfoBean publishJob = it.next();
162            result.add(new CmsPublishJobEnqueued(publishJob));
163        }
164        return Collections.unmodifiableList(result);
165    }
166
167    /**
168     * Checks if the given job is already in the queue, this does only check for the identical job.<p>
169     *
170     * @param publishJob the publish job to check for
171     *
172     * @return true if the given job is already in the queue
173     */
174    protected boolean contains(CmsPublishJobInfoBean publishJob) {
175
176        List<CmsPublishJobInfoBean> l = OpenCms.getMemoryMonitor().getAllCachedPublishJobs();
177        if (l != null) {
178            for (int i = 0; i < l.size(); i++) {
179                CmsPublishJobInfoBean b = l.get(i);
180                if (b == publishJob) {
181                    return true;
182                }
183            }
184        }
185        return false;
186    }
187
188    /**
189     * Initializes the internal FIFO queue with publish jobs from the database.<p>
190     *
191     * @param adminCms an admin cms object
192     * @param revive <code>true</code> if the publish queue should be revived from the database
193     */
194    protected void initialize(CmsObject adminCms, boolean revive) {
195
196        CmsDriverManager driverManager = m_publishEngine.getDriverManager();
197
198        try {
199            OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISH_QUEUE);
200            if (revive) {
201                // read all pending publish jobs from the database
202                CmsDbContext dbc = m_publishEngine.getDbContext(null);
203                List<CmsPublishJobInfoBean> publishJobs = null;
204                try {
205                    publishJobs = driverManager.readPublishJobs(dbc, 0L, 0L);
206                } catch (Exception e) {
207                    dbc.rollback();
208                } finally {
209                    dbc.clear();
210                    dbc = null;
211                }
212                if (publishJobs != null) {
213                    for (Iterator<CmsPublishJobInfoBean> i = publishJobs.iterator(); i.hasNext();) {
214                        CmsPublishJobInfoBean job = i.next();
215                        dbc = m_publishEngine.getDbContext(null);
216                        if (!job.isStarted()) {
217                            // add jobs not already started to queue again
218                            try {
219                                job.revive(adminCms, driverManager.readPublishList(dbc, job.getPublishHistoryId()));
220                                m_publishEngine.lockPublishList(job);
221                                OpenCms.getMemoryMonitor().cachePublishJob(job);
222                            } catch (CmsException exc) {
223                                // skip job
224                                dbc.rollback();
225                                if (LOG.isErrorEnabled()) {
226                                    LOG.error(
227                                        Messages.get().getBundle().key(
228                                            Messages.ERR_PUBLISH_JOB_INVALID_1,
229                                            job.getPublishHistoryId()),
230                                        exc);
231                                }
232                                m_publishEngine.getDriverManager().deletePublishJob(dbc, job.getPublishHistoryId());
233                            } finally {
234                                dbc.clear();
235                            }
236                        } else {
237                            try {
238                                // remove locks, set finish info and move job to history
239                                job.revive(adminCms, driverManager.readPublishList(dbc, job.getPublishHistoryId()));
240                                m_publishEngine.unlockPublishList(job);
241                                new CmsPublishJobEnqueued(job).m_publishJob.finish();
242                                m_publishEngine.getPublishHistory().add(job);
243                            } catch (CmsException exc) {
244                                dbc.rollback();
245                                LOG.error(exc.getLocalizedMessage(), exc);
246                            } finally {
247                                dbc.clear();
248                            }
249                        }
250                    }
251                }
252            }
253        } catch (CmsException exc) {
254            if (LOG.isErrorEnabled()) {
255                LOG.error(exc.getLocalizedMessage(), exc);
256            }
257        }
258    }
259
260    /**
261     * Checks if the queue is empty.<p>
262     *
263     * @return <code>true</code> if the queue is empty
264     */
265    protected boolean isEmpty() {
266
267        return ((OpenCms.getMemoryMonitor() == null)
268            || (OpenCms.getMemoryMonitor().getFirstCachedPublishJob() == null));
269    }
270
271    /**
272     * Returns the next publish job to be published, removing it
273     * from the queue, or <code>null</code> if the queue is empty.<p>
274     *
275     * @return the next publish job to be published
276     */
277    protected CmsPublishJobInfoBean next() {
278
279        CmsPublishJobInfoBean publishJob = OpenCms.getMemoryMonitor().getFirstCachedPublishJob();
280        if (publishJob != null) {
281            OpenCms.getMemoryMonitor().uncachePublishJob(publishJob);
282        }
283        return publishJob;
284    }
285
286    /**
287     * Removes the given job from the list.<p>
288     *
289     * @param publishJob the publish job to remove
290     *
291     * @throws CmsException if something goes wrong
292     */
293    protected void remove(CmsPublishJobInfoBean publishJob) throws CmsException {
294
295        try {
296            // signalizes that job will be removed
297            m_publishEngine.publishJobRemoved(publishJob);
298        } finally {
299            // remove publish job from cache
300            OpenCms.getMemoryMonitor().uncachePublishJob(publishJob);
301        }
302
303        // remove job from database if necessary
304        if (OpenCms.getMemoryMonitor().requiresPersistency()) {
305            CmsDbContext dbc = m_publishEngine.getDbContext(null);
306            try {
307                m_publishEngine.getDriverManager().deletePublishJob(dbc, publishJob.getPublishHistoryId());
308            } catch (CmsException e) {
309                dbc.rollback();
310                LOG.error(e.getLocalizedMessage(), e);
311                throw e;
312            } finally {
313                dbc.clear();
314            }
315        }
316    }
317
318    /**
319     * Updates the given job in the list.<p>
320     *
321     * @param publishJob the publish job to
322     */
323    protected void update(CmsPublishJobInfoBean publishJob) {
324
325        if (OpenCms.getMemoryMonitor().requiresPersistency()) {
326            CmsDbContext dbc = m_publishEngine.getDbContext(null);
327            try {
328                m_publishEngine.getDriverManager().writePublishJob(dbc, publishJob);
329            } catch (CmsException e) {
330                dbc.rollback();
331                if (LOG.isErrorEnabled()) {
332                    LOG.error(e.getLocalizedMessage(), e);
333                }
334            } finally {
335                dbc.clear();
336            }
337        }
338    }
339}