You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2023/04/18 23:56:29 UTC
[sling-org-apache-sling-event-api] branch master updated (d151fdf -> 3c014ec)
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
from d151fdf SLING-11709 - Set up Jira autolinks to all Sling Github projects
new 757c549 SLING-11831 - Allow setting job properties for custom job state
new e014be0 SLING-11831 - Allow setting job properties for custom job state
new 3c014ec Merge pull request #3 from andrewmkhoury/SLING-11831
The 32 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/sling/event/jobs/consumer/JobExecutionContext.java | 7 +++++++
.../java/org/apache/sling/event/jobs/consumer/package-info.java | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
[sling-org-apache-sling-event-api] 05/32: SLING-6739 : removed unnecessary properties
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 4d0bcce29f380a61dbb26898bf7068b6263718c1
Author: Stefan Egli <st...@apache.org>
AuthorDate: Tue Apr 11 16:08:16 2017 +0000
SLING-6739 : removed unnecessary properties
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1790997 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 6 ------
1 file changed, 6 deletions(-)
diff --git a/pom.xml b/pom.xml
index efa00b7..ff65800 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,12 +44,6 @@
<properties>
<site.jira.version.id>12315369</site.jira.version.id>
- <exam.version>4.4.0</exam.version>
- <url.version>2.4.5</url.version>
- <bundle.build.dir>${basedir}/target</bundle.build.dir>
- <bundle.file.name>${bundle.build.dir}/${project.build.finalName}.jar</bundle.file.name>
- <min.port>37000</min.port>
- <max.port>37999</max.port>
</properties>
<build>
[sling-org-apache-sling-event-api] 03/32: SLING-6739 : pom.xml cleanup to reflect its status as the new sling.event.api bundle - eg no dependencies needed, no ITs etc
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit f429950539894b04da2ed5cd6ee2d8b15826699f
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Mar 29 09:50:50 2017 +0000
SLING-6739 : pom.xml cleanup to reflect its status as the new sling.event.api bundle - eg no dependencies needed, no ITs etc
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1789318 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 269 +---------------------------------------------------------------
1 file changed, 2 insertions(+), 267 deletions(-)
diff --git a/pom.xml b/pom.xml
index 385da15..efa00b7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,9 +31,9 @@
<packaging>bundle</packaging>
<version>0.0.1-SNAPSHOT</version>
- <name>Apache Sling Event Support</name>
+ <name>Apache Sling Event API</name>
<description>
- Support for eventing.
+ Support API for eventing.
</description>
<scm>
@@ -52,7 +52,6 @@
<max.port>37999</max.port>
</properties>
-
<build>
<plugins>
<plugin>
@@ -61,21 +60,6 @@
<extensions>true</extensions>
<configuration>
<instructions>
- <Import-Package>
- javax.servlet;javax.servlet.http;resolution:=optional,
- org.apache.felix.inventory;resolution:=optional,
- *
- </Import-Package>
- <DynamicImport-Package>
- javax.servlet,
- javax.servlet.http,
- org.apache.felix.inventory
- </DynamicImport-Package>
- <Embed-Dependency>
- jackrabbit-jcr-commons;inline="org/apache/jackrabbit/util/ISO9075.*|org/apache/jackrabbit/util/ISO8601.*|org/apache/jackrabbit/util/XMLChar.*",
- org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/PropertiesUtil.*",
- quartz;inline="org/quartz/CronExpression.*|org/quartz/ValueSet.*"
- </Embed-Dependency>
<_plugin>org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir=${project.build.outputDirectory};</_plugin>
</instructions>
</configuration>
@@ -90,11 +74,6 @@
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>derby.log</exclude>
- </excludes>
- </configuration>
</plugin>
<!-- plain unit tests -->
<plugin>
@@ -105,82 +84,9 @@
</excludes>
</configuration>
</plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <executions>
- <execution>
- <id>reserve-network-port</id>
- <goals>
- <goal>reserve-network-port</goal>
- </goals>
- <phase>pre-integration-test</phase>
- <configuration>
- <portNames>
- <portName>http.port</portName>
- </portNames>
- <minPortNumber>${min.port}</minPortNumber>
- <maxPortNumber>${max.port}</maxPortNumber>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <!-- integration tests run with pax-exam -->
- <plugin>
- <artifactId>maven-failsafe-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>integration-test</goal>
- <goal>verify</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <systemProperties>
- <property>
- <name>project.bundle.file</name>
- <value>${bundle.file.name}</value>
- </property>
- <property>
- <name>bundle.build.dir</name>
- <value>${bundle.build.dir}</value>
- </property>
- <property>
- <name>org.osgi.service.http.port</name>
- <value>${http.port}</value>
- </property>
- </systemProperties>
- <argLine>
- -Xmx2048m -XX:MaxPermSize=512m
- </argLine>
- <includes>
- <include>**/it/*</include>
- </includes>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-clean-plugin</artifactId>
- <configuration>
- <filesets>
- <fileset>
- <directory>${basedir}</directory>
- <includes>
- <include>derby.log</include>
- </includes>
- </fileset>
- <fileset>
- <directory>jackrabbit</directory>
- </fileset>
- </filesets>
- </configuration>
- </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
- <configuration>
- <excludePackageNames>org.apache.sling.event.impl:org.apache.sling.event.impl.jobs:org.apache.sling.event.impl.jobs.config:org.apache.sling.event.impl.jobs.console:org.apache.sling.event.impl.jobs.jmx:org.apache.sling.event.impl.jobs.notifications:org.apache.sling.event.impl.jobs.queues:org.apache.sling.event.impl.jobs.scheduling:org.apache.sling.event.impl.jobs.stats:org.apache.sling.event.impl.jobs.tasks:org.apache.sling.event.impl.support</excludePackageNames>
- </configuration>
</plugin>
</plugins>
</build>
@@ -197,175 +103,4 @@
</properties>
</profile>
</profiles>
- <dependencies>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.discovery.api</artifactId>
- <version>1.0.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.inventory</artifactId>
- <version>1.0.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>osgi.core</artifactId>
- <version>6.0.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.service.event</artifactId>
- <version>1.3.1</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr.annotations</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.settings</artifactId>
- <version>1.0.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.api</artifactId>
- <version>2.11.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.commons.osgi</artifactId>
- <version>2.1.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.commons.scheduler</artifactId>
- <version>2.4.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.commons.threads</artifactId>
- <version>3.1.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.discovery.commons</artifactId>
- <version>1.0.12</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.serviceusermapper</artifactId>
- <version>1.2.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.jackrabbit</groupId>
- <artifactId>jackrabbit-jcr-commons</artifactId>
- <version>2.11.2</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.quartz-scheduler</groupId>
- <artifactId>quartz</artifactId>
- <version>2.2.0</version>
- <scope>provided</scope>
- </dependency>
- <!-- Webconsole -->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- </dependency>
- <!-- Testing -->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.service.cm</artifactId>
- <version>1.5.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.10.19</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.testing.tools</artifactId>
- <version>1.0.6</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.testing.sling-mock</artifactId>
- <version>1.6.0</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.ops4j.pax.exam</groupId>
- <artifactId>pax-exam-container-forked</artifactId>
- <version>${exam.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.ops4j.pax.exam</groupId>
- <artifactId>pax-exam-junit4</artifactId>
- <version>${exam.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.ops4j.pax.exam</groupId>
- <artifactId>pax-exam-link-mvn</artifactId>
- <version>${exam.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.ops4j.pax.exam</groupId>
- <artifactId>pax-exam-cm</artifactId>
- <version>${exam.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.ops4j.pax.url</groupId>
- <artifactId>pax-url-aether</artifactId>
- <version>${url.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.framework</artifactId>
- <version>5.4.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>javax.inject</groupId>
- <artifactId>javax.inject</artifactId>
- <version>1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
</project>
[sling-org-apache-sling-event-api] 19/32: trivial: added license header to *.md files
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 02ba7a5ac67a004367e55ce5ff28848ff1c50619
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Sep 20 11:09:02 2018 +0200
trivial: added license header to *.md files
---
CODE_OF_CONDUCT.md | 18 ++++++++++++++++++
CONTRIBUTING.md | 18 ++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 52f21cb..0fa18e5 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,3 +1,21 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
Apache Software Foundation Code of Conduct
====
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ca36072..ac82a1a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,21 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
Contributing
====
[sling-org-apache-sling-event-api] 30/32: SLING-11831 - Allow setting job properties for custom job state
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 757c54903f261ff88f5bf5e1ab79f24c4d4d8d77
Author: Andrew Khoury <ak...@adobe.com>
AuthorDate: Mon Apr 17 16:46:43 2023 -0700
SLING-11831 - Allow setting job properties for custom job state
---
.../org/apache/sling/event/jobs/consumer/JobExecutionContext.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/main/java/org/apache/sling/event/jobs/consumer/JobExecutionContext.java b/src/main/java/org/apache/sling/event/jobs/consumer/JobExecutionContext.java
index 9370922..5f04d9a 100644
--- a/src/main/java/org/apache/sling/event/jobs/consumer/JobExecutionContext.java
+++ b/src/main/java/org/apache/sling/event/jobs/consumer/JobExecutionContext.java
@@ -85,6 +85,13 @@ public interface JobExecutionContext {
*/
void updateProgress(long eta);
+ /**
+ * Sets a property on the job. This property can be retrieved using {@link org.apache.sling.event.jobs.Job#getProperty(String)}.
+ * @param name The property name
+ * @param value The property value
+ */
+ void setProperty(String name, Object value);
+
/**
* Log a message.
* A job consumer can use this method during job processing to add additional information
[sling-org-apache-sling-event-api] 02/32: SLING-6739 : split of sling.event to sling.event.api - initial adjustment in pom.xml, removed impl, resources and test
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit a6c45c22512751bf04814c5b5495649548d787ae
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Mar 29 08:44:03 2017 +0000
SLING-6739 : split of sling.event to sling.event.api - initial adjustment in pom.xml, removed impl, resources and test
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1789298 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 18 +-
.../sling/event/impl/EnvironmentComponent.java | 82 ---
.../sling/event/impl/EventingThreadPool.java | 129 ----
.../sling/event/impl/jobs/InternalJobState.java | 42 --
.../sling/event/impl/jobs/JobBuilderImpl.java | 70 --
.../sling/event/impl/jobs/JobConsumerManager.java | 511 --------------
.../apache/sling/event/impl/jobs/JobHandler.java | 284 --------
.../org/apache/sling/event/impl/jobs/JobImpl.java | 411 -----------
.../sling/event/impl/jobs/JobManagerImpl.java | 759 ---------------------
.../sling/event/impl/jobs/JobTopicTraverser.java | 176 -----
.../org/apache/sling/event/impl/jobs/Utility.java | 281 --------
.../jobs/config/ConfigurationChangeListener.java | 33 -
.../impl/jobs/config/ConfigurationConstants.java | 48 --
.../jobs/config/InternalQueueConfiguration.java | 402 -----------
.../impl/jobs/config/JobManagerConfiguration.java | 661 ------------------
.../impl/jobs/config/MainQueueConfiguration.java | 115 ----
.../jobs/config/QueueConfigurationManager.java | 169 -----
.../impl/jobs/config/TopologyCapabilities.java | 324 ---------
.../event/impl/jobs/config/TopologyHandler.java | 114 ----
.../event/impl/jobs/console/InventoryPlugin.java | 471 -------------
.../event/impl/jobs/console/WebConsolePlugin.java | 453 ------------
.../event/impl/jobs/jmx/AbstractJobStatistics.java | 100 ---
.../event/impl/jobs/jmx/AllJobStatisticsMBean.java | 57 --
.../sling/event/impl/jobs/jmx/EmptyStatistics.java | 79 ---
.../sling/event/impl/jobs/jmx/QueueMBeanImpl.java | 50 --
.../event/impl/jobs/jmx/QueueStatusEvent.java | 51 --
.../sling/event/impl/jobs/jmx/QueuesMBeanImpl.java | 185 -----
.../impl/jobs/notifications/NewJobSender.java | 123 ----
.../jobs/notifications/NotificationUtility.java | 77 ---
.../impl/jobs/queues/JobExecutionContextImpl.java | 124 ----
.../impl/jobs/queues/JobExecutionResultImpl.java | 97 ---
.../sling/event/impl/jobs/queues/JobQueueImpl.java | 708 -------------------
.../event/impl/jobs/queues/QueueJobCache.java | 344 ----------
.../sling/event/impl/jobs/queues/QueueManager.java | 447 ------------
.../event/impl/jobs/queues/QueueServices.java | 49 --
.../event/impl/jobs/queues/ResultBuilderImpl.java | 57 --
.../jobs/scheduling/JobScheduleBuilderImpl.java | 120 ----
.../impl/jobs/scheduling/JobSchedulerImpl.java | 566 ---------------
.../impl/jobs/scheduling/ScheduledJobHandler.java | 545 ---------------
.../impl/jobs/scheduling/ScheduledJobInfoImpl.java | 193 ------
.../event/impl/jobs/stats/StatisticsImpl.java | 319 ---------
.../event/impl/jobs/stats/StatisticsManager.java | 182 -----
.../event/impl/jobs/stats/TopicStatisticsImpl.java | 169 -----
.../event/impl/jobs/tasks/CheckTopologyTask.java | 333 ---------
.../sling/event/impl/jobs/tasks/CleanUpTask.java | 275 --------
.../impl/jobs/tasks/FindUnfinishedJobsTask.java | 137 ----
.../event/impl/jobs/tasks/HistoryCleanUpTask.java | 255 -------
.../sling/event/impl/jobs/tasks/UpgradeTask.java | 279 --------
.../event/impl/support/BatchResourceRemover.java | 55 --
.../sling/event/impl/support/Environment.java | 35 -
.../event/impl/support/ExactTopicMatcher.java | 44 --
.../event/impl/support/PackageTopicMatcher.java | 51 --
.../sling/event/impl/support/ResourceHelper.java | 426 ------------
.../sling/event/impl/support/ScheduleInfoImpl.java | 421 ------------
.../impl/support/SubPackagesTopicMatcher.java | 52 --
.../sling/event/impl/support/TopicMatcher.java | 28 -
.../event/impl/support/TopicMatcherHelper.java | 69 --
src/main/resources/SLING-INF/nodetypes/event.cnd | 42 --
.../java/org/apache/sling/event/impl/Barrier.java | 57 --
.../java/org/apache/sling/event/impl/TestUtil.java | 53 --
.../jobs/InstanceDescriptionComparatorTest.java | 199 ------
.../event/impl/jobs/JobConsumerManagerTest.java | 198 ------
.../apache/sling/event/impl/jobs/JobsImplTest.java | 62 --
.../sling/event/impl/jobs/StatisticsImplTest.java | 268 --------
.../apache/sling/event/impl/jobs/UtilityTest.java | 80 ---
.../config/InternalQueueConfigurationTest.java | 195 ------
.../jobs/config/JobManagerConfigurationTest.java | 259 -------
.../impl/jobs/config/TopologyCapabilitiesTest.java | 71 --
.../impl/jobs/jmx/AllJobStatisticsMBeanTest.java | 69 --
.../sling/event/impl/jobs/jmx/DummyStatistics.java | 84 ---
.../event/impl/jobs/jmx/QueuesMBeanImplTest.java | 134 ----
.../impl/jobs/tasks/HistoryCleanUpTaskTest.java | 106 ---
.../sling/event/it/AbstractJobHandlingTest.java | 462 -------------
.../java/org/apache/sling/event/it/ChaosTest.java | 402 -----------
.../apache/sling/event/it/ClassloadingTest.java | 211 ------
.../org/apache/sling/event/it/HistoryTest.java | 141 ----
.../org/apache/sling/event/it/JobHandlingTest.java | 436 ------------
.../apache/sling/event/it/OrderedQueueTest.java | 173 -----
.../apache/sling/event/it/RoundRobinQueueTest.java | 172 -----
.../org/apache/sling/event/it/SchedulingTest.java | 87 ---
.../org/apache/sling/event/it/TimedJobsTest.java | 86 ---
.../apache/sling/event/it/TopicMatchingTest.java | 168 -----
.../apache/sling/event/it/UnorderedQueueTest.java | 172 -----
83 files changed, 6 insertions(+), 17036 deletions(-)
diff --git a/pom.xml b/pom.xml
index 4c12105..385da15 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,13 +23,13 @@
<parent>
<groupId>org.apache.sling</groupId>
<artifactId>sling</artifactId>
- <version>30</version>
+ <version>29</version>
<relativePath />
</parent>
- <artifactId>org.apache.sling.event</artifactId>
+ <artifactId>org.apache.sling.event.api</artifactId>
<packaging>bundle</packaging>
- <version>4.2.3-SNAPSHOT</version>
+ <version>0.0.1-SNAPSHOT</version>
<name>Apache Sling Event Support</name>
<description>
@@ -37,9 +37,9 @@
</description>
<scm>
- <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event</connection>
- <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event</developerConnection>
- <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event</url>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/api</url>
</scm>
<properties>
@@ -71,12 +71,6 @@
javax.servlet.http,
org.apache.felix.inventory
</DynamicImport-Package>
- <Sling-Nodetypes>
- SLING-INF/nodetypes/event.cnd
- </Sling-Nodetypes>
- <Sling-Namespaces>
- slingevent=http://sling.apache.org/jcr/event/1.0
- </Sling-Namespaces>
<Embed-Dependency>
jackrabbit-jcr-commons;inline="org/apache/jackrabbit/util/ISO9075.*|org/apache/jackrabbit/util/ISO8601.*|org/apache/jackrabbit/util/XMLChar.*",
org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/PropertiesUtil.*",
diff --git a/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java b/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java
deleted file mode 100644
index b3f7929..0000000
--- a/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.settings.SlingSettingsService;
-
-/**
- * Environment component. This component provides "global settings"
- * to all services, like the application id and the thread pool.
- * @since 3.0
- *
- * This component needs to be immediate to set the global variables
- * (application id and thread pool).
- */
-@Component(immediate=true)
-@Service(value=EnvironmentComponent.class)
-public class EnvironmentComponent {
-
- /**
- * Our thread pool.
- */
- @Reference(referenceInterface=EventingThreadPool.class)
- private ThreadPool threadPool;
-
- /** Sling settings service. */
- @Reference
- private SlingSettingsService settingsService;
-
- /**
- * Activate this component.
- */
- @Activate
- protected void activate() {
- // Set the application id and the thread pool
- Environment.APPLICATION_ID = this.settingsService.getSlingId();
- Environment.THREAD_POOL = this.threadPool;
- }
-
- /**
- * Deactivate this component.
- */
- @Deactivate
- protected void deactivate() {
- // Unset the thread pool
- if ( Environment.THREAD_POOL == this.threadPool ) {
- Environment.THREAD_POOL = null;
- }
- }
-
- protected void bindThreadPool(final EventingThreadPool etp) {
- this.threadPool = etp;
- }
-
- protected void unbindThreadPool(final EventingThreadPool etp) {
- if ( this.threadPool == etp ) {
- this.threadPool = null;
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java b/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java
deleted file mode 100644
index 0821866..0000000
--- a/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl;
-
-import java.util.Map;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Modified;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.commons.threads.ModifiableThreadPoolConfig;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.commons.threads.ThreadPoolConfig;
-import org.apache.sling.commons.threads.ThreadPoolConfig.ThreadPriority;
-import org.apache.sling.commons.threads.ThreadPoolManager;
-
-
-/**
- * The configurable eventing thread pool.
- */
-@Component(label="Apache Sling Job Thread Pool",
- description="This is the thread pool used by the Apache Sling job handling. The "
- + "threads from this pool are merely used for executing jobs. By limiting this pool, it is "
- + "possible to limit the maximum number of parallel processed jobs - regardless of the queue "
- + "configuration.",
- metatype=true)
-@Service(value=EventingThreadPool.class)
-public class EventingThreadPool implements ThreadPool {
-
- @Reference
- private ThreadPoolManager threadPoolManager;
-
- /** The real thread pool used. */
- private org.apache.sling.commons.threads.ThreadPool threadPool;
-
- private static final int DEFAULT_POOL_SIZE = 35;
-
- @Property(intValue=DEFAULT_POOL_SIZE,
- label="Pool Size",
- description="The size of the thread pool. This pool is used to execute jobs and therefore "
- + "limits the maximum number of jobs executed in parallel.")
- private static final String PROPERTY_POOL_SIZE = "minPoolSize";
-
- public EventingThreadPool() {
- // default constructor
- }
-
- public EventingThreadPool(final ThreadPoolManager tpm, final int poolSize) {
- this.threadPoolManager = tpm;
- this.configure(poolSize);
- }
-
- public void release() {
- this.deactivate();
- }
-
- /**
- * Activate this component.
- */
- @Activate
- @Modified
- protected void activate(final Map<String, Object> props) {
- final int maxPoolSize = PropertiesUtil.toInteger(props.get(PROPERTY_POOL_SIZE), DEFAULT_POOL_SIZE);
- this.configure(maxPoolSize);
- }
-
- private void configure(final int maxPoolSize) {
- final ModifiableThreadPoolConfig config = new ModifiableThreadPoolConfig();
- config.setMinPoolSize(maxPoolSize);
- config.setMaxPoolSize(config.getMinPoolSize());
- config.setQueueSize(-1); // unlimited
- config.setShutdownGraceful(true);
- config.setPriority(ThreadPriority.NORM);
- config.setDaemon(true);
- this.threadPool = threadPoolManager.create(config, "Apache Sling Job Thread Pool");
- }
-
- /**
- * Deactivate this component.
- */
- @Deactivate
- protected void deactivate() {
- this.threadPoolManager.release(this.threadPool);
- }
-
- /**
- * @see org.apache.sling.commons.threads.ThreadPool#execute(java.lang.Runnable)
- */
- @Override
- public void execute(final Runnable runnable) {
- threadPool.execute(runnable);
- }
-
- /**
- * @see org.apache.sling.commons.threads.ThreadPool#getConfiguration()
- */
- @Override
- public ThreadPoolConfig getConfiguration() {
- return threadPool.getConfiguration();
- }
-
- /**
- * @see org.apache.sling.commons.threads.ThreadPool#getName()
- */
- @Override
- public String getName() {
- return threadPool.getName();
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java b/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java
deleted file mode 100644
index 6fe1994..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.consumer.JobExecutor;
-
-/**
- * The state of the job after it has been processed by a {@link JobExecutor}.
- */
-public enum InternalJobState {
-
- SUCCEEDED(NotificationConstants.TOPIC_JOB_FINISHED), // processing finished successfully
- FAILED(NotificationConstants.TOPIC_JOB_FAILED), // processing failed, can be retried
- CANCELLED(NotificationConstants.TOPIC_JOB_CANCELLED); // processing failed permanently
-
- private final String topic;
-
- InternalJobState(final String topic) {
- this.topic = topic;
- }
-
- public String getTopic() {
- return this.topic;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
deleted file mode 100644
index a662066..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.sling.event.impl.jobs.scheduling.JobScheduleBuilderImpl;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.JobBuilder;
-
-/**
- * Fluent builder API
- */
-public class JobBuilderImpl implements JobBuilder {
-
- private final String topic;
-
- private final JobManagerImpl jobManager;
-
- private Map<String, Object> properties;
-
- public JobBuilderImpl(final JobManagerImpl manager, final String topic) {
- this.jobManager = manager;
- this.topic = topic;
- }
-
-
- @Override
- public JobBuilder properties(final Map<String, Object> props) {
- this.properties = props;
- return this;
- }
-
- @Override
- public Job add() {
- return this.add(null);
- }
-
- @Override
- public Job add(final List<String> errors) {
- return this.jobManager.addJob(this.topic, this.properties, errors);
- }
-
- @Override
- public ScheduleBuilder schedule() {
- return new JobScheduleBuilderImpl(
- this.topic,
- this.properties,
- UUID.randomUUID().toString(),
- this.jobManager.getJobScheduler());
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java b/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java
deleted file mode 100644
index 08660e8..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Modified;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.PropertyUnbounded;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.References;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.discovery.PropertyProvider;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.impl.support.TopicMatcher;
-import org.apache.sling.event.impl.support.TopicMatcherHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.consumer.JobConsumer;
-import org.apache.sling.event.jobs.consumer.JobConsumer.JobResult;
-import org.apache.sling.event.jobs.consumer.JobExecutionContext;
-import org.apache.sling.event.jobs.consumer.JobExecutionResult;
-import org.apache.sling.event.jobs.consumer.JobExecutor;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This component manages/keeps track of all job consumer services.
- */
-@Component(label="Apache Sling Job Consumer Manager",
- description="The consumer manager controls the job consumer (= processors). "
- + "It can be used to temporarily disable job processing on the current instance. Other instances "
- + "in a cluster are not affected.",
- metatype=true)
-@Service(value=JobConsumerManager.class)
-@References({
- @Reference(referenceInterface=JobConsumer.class,
- cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
- policy=ReferencePolicy.DYNAMIC),
- @Reference(referenceInterface=JobExecutor.class,
- cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
- policy=ReferencePolicy.DYNAMIC)
-})
-@Property(name="org.apache.sling.installer.configuration.persist", boolValue=false,
- label="Distribute config",
- description="If this is disabled, the configuration is not persisted on save in the cluster and is "
- + "only used on the current instance. This option should always be disabled!")
-public class JobConsumerManager {
-
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Property(unbounded=PropertyUnbounded.ARRAY, value = "*",
- label="Topic Whitelist",
- description="This is a list of topics which currently should be "
- + "processed by this instance. Leaving it empty, all job consumers are disabled. Putting a '*' as "
- + "one entry, enables all job consumers. Adding separate topics enables job consumers for exactly "
- + "this topic.")
- private static final String PROPERTY_WHITELIST = "job.consumermanager.whitelist";
-
- @Property(unbounded=PropertyUnbounded.ARRAY,
- label="Topic Blacklist",
- description="This is a list of topics which currently shouldn't be "
- + "processed by this instance. Leaving it empty, all job consumers are enabled. Putting a '*' as "
- + "one entry, disables all job consumers. Adding separate topics disables job consumers for exactly "
- + "this topic.")
- private static final String PROPERTY_BLACKLIST = "job.consumermanager.blacklist";
-
- /** The map with the consumers, keyed by topic, sorted by service ranking. */
- private final Map<String, List<ConsumerInfo>> topicToConsumerMap = new HashMap<String, List<ConsumerInfo>>();
-
- /** ServiceRegistration for propagation. */
- private ServiceRegistration propagationService;
-
- private String topics;
-
- private TopicMatcher[] whitelistMatchers;
-
- private TopicMatcher[] blacklistMatchers;
-
- private volatile long changeCount;
-
- private BundleContext bundleContext;
-
- private final Map<String, Object[]> listenerMap = new HashMap<String, Object[]>();
-
- private Dictionary<String, Object> getRegistrationProperties() {
- final Dictionary<String, Object> serviceProps = new Hashtable<String, Object>();
- serviceProps.put(PropertyProvider.PROPERTY_PROPERTIES, TopologyCapabilities.PROPERTY_TOPICS);
- // we add a changing property to the service registration
- // to make sure a modification event is really sent
- synchronized ( this ) {
- serviceProps.put("changeCount", this.changeCount++);
- }
- return serviceProps;
- }
-
- @Activate
- protected void activate(final BundleContext bc, final Map<String, Object> props) {
- this.bundleContext = bc;
- this.modified(bc, props);
- }
-
- @Modified
- protected void modified(final BundleContext bc, final Map<String, Object> props) {
- final boolean wasEnabled = this.propagationService != null;
- this.whitelistMatchers = TopicMatcherHelper.buildMatchers(PropertiesUtil.toStringArray(props.get(PROPERTY_WHITELIST)));
- this.blacklistMatchers = TopicMatcherHelper.buildMatchers(PropertiesUtil.toStringArray(props.get(PROPERTY_BLACKLIST)));
-
- final boolean enable = this.whitelistMatchers != null && this.blacklistMatchers != TopicMatcherHelper.MATCH_ALL;
- if ( wasEnabled != enable ) {
- synchronized ( this.topicToConsumerMap ) {
- this.calculateTopics(enable);
- }
- if ( enable ) {
- logger.debug("Registering property provider with: {}", this.topics);
- this.propagationService = bc.registerService(PropertyProvider.class.getName(),
- new PropertyProvider() {
-
- @Override
- public String getProperty(final String name) {
- if ( TopologyCapabilities.PROPERTY_TOPICS.equals(name) ) {
- return topics;
- }
- return null;
- }
- }, this.getRegistrationProperties());
- } else {
- logger.debug("Unregistering property provider with");
- this.propagationService.unregister();
- this.propagationService = null;
- }
- } else if ( enable ) {
- // update properties
- synchronized ( this.topicToConsumerMap ) {
- this.calculateTopics(true);
- }
- logger.debug("Updating property provider with: {}", this.topics);
- this.propagationService.setProperties(this.getRegistrationProperties());
- }
- }
-
- @Deactivate
- protected void deactivate() {
- if ( this.propagationService != null ) {
- this.propagationService.unregister();
- this.propagationService = null;
- }
- this.bundleContext = null;
- synchronized ( this.topicToConsumerMap ) {
- this.topicToConsumerMap.clear();
- this.listenerMap.clear();
- }
- }
-
- /**
- * Get the executor for the topic.
- * @param topic The job topic
- * @return A consumer or <code>null</code>
- */
- public JobExecutor getExecutor(final String topic) {
- synchronized ( this.topicToConsumerMap ) {
- final List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
- if ( consumers != null ) {
- return consumers.get(0).getExecutor(this.bundleContext);
- }
- int pos = topic.lastIndexOf('/');
- if ( pos > 0 ) {
- final String category = topic.substring(0, pos + 1).concat("*");
- final List<ConsumerInfo> categoryConsumers = this.topicToConsumerMap.get(category);
- if ( categoryConsumers != null ) {
- return categoryConsumers.get(0).getExecutor(this.bundleContext);
- }
-
- // search deep consumers (since 1.2 of the consumer package)
- do {
- final String subCategory = topic.substring(0, pos + 1).concat("**");
- final List<ConsumerInfo> subCategoryConsumers = this.topicToConsumerMap.get(subCategory);
- if ( subCategoryConsumers != null ) {
- return subCategoryConsumers.get(0).getExecutor(this.bundleContext);
- }
- pos = topic.lastIndexOf('/', pos - 1);
- } while ( pos > 0 );
- }
- }
- return null;
- }
-
- public void registerListener(final String key, final JobExecutor consumer, final JobExecutionContext handler) {
- synchronized ( this.topicToConsumerMap ) {
- this.listenerMap.put(key, new Object[] {consumer, handler});
- }
- }
-
- public void unregisterListener(final String key) {
- synchronized ( this.topicToConsumerMap ) {
- this.listenerMap.remove(key);
- }
- }
-
- /**
- * Return the topics information of this instance.
- */
- public String getTopics() {
- return this.topics;
- }
-
- /**
- * Bind a new consumer
- * @param serviceReference The service reference to the consumer.
- */
- protected void bindJobConsumer(final ServiceReference serviceReference) {
- this.bindService(serviceReference, true);
- }
-
- /**
- * Unbind a consumer
- * @param serviceReference The service reference to the consumer.
- */
- protected void unbindJobConsumer(final ServiceReference serviceReference) {
- this.unbindService(serviceReference, true);
- }
-
- /**
- * Bind a new executor
- * @param serviceReference The service reference to the executor.
- */
- protected void bindJobExecutor(final ServiceReference serviceReference) {
- this.bindService(serviceReference, false);
- }
-
- /**
- * Unbind a executor
- * @param serviceReference The service reference to the executor.
- */
- protected void unbindJobExecutor(final ServiceReference serviceReference) {
- this.unbindService(serviceReference, false);
- }
-
- /**
- * Bind a consumer or executor
- * @param serviceReference The service reference to the consumer or executor.
- * @param isConsumer Indicating whether this is a JobConsumer or JobExecutor
- */
- private void bindService(final ServiceReference serviceReference, final boolean isConsumer) {
- final String[] topics = PropertiesUtil.toStringArray(serviceReference.getProperty(JobConsumer.PROPERTY_TOPICS));
- if ( topics != null && topics.length > 0 ) {
- final ConsumerInfo info = new ConsumerInfo(serviceReference, isConsumer);
- boolean changed = false;
- synchronized ( this.topicToConsumerMap ) {
- for(final String t : topics) {
- if ( t != null ) {
- final String topic = t.trim();
- if ( topic.length() > 0 ) {
- List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
- if ( consumers == null ) {
- consumers = new ArrayList<JobConsumerManager.ConsumerInfo>();
- this.topicToConsumerMap.put(topic, consumers);
- changed = true;
- }
- consumers.add(info);
- Collections.sort(consumers);
- }
- }
- }
- if ( changed ) {
- this.calculateTopics(this.propagationService != null);
- }
- }
- if ( changed && this.propagationService != null ) {
- logger.debug("Updating property provider with: {}", this.topics);
- this.propagationService.setProperties(this.getRegistrationProperties());
- }
- }
- }
-
- /**
- * Unbind a consumer or executor
- * @param serviceReference The service reference to the consumer or executor.
- * @param isConsumer Indicating whether this is a JobConsumer or JobExecutor
- */
- private void unbindService(final ServiceReference serviceReference, final boolean isConsumer) {
- final String[] topics = PropertiesUtil.toStringArray(serviceReference.getProperty(JobConsumer.PROPERTY_TOPICS));
- if ( topics != null && topics.length > 0 ) {
- final ConsumerInfo info = new ConsumerInfo(serviceReference, isConsumer);
- boolean changed = false;
- synchronized ( this.topicToConsumerMap ) {
- for(final String t : topics) {
- if ( t != null ) {
- final String topic = t.trim();
- if ( topic.length() > 0 ) {
- final List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
- if ( consumers != null ) { // sanity check
- for(final ConsumerInfo oldConsumer : consumers) {
- if ( oldConsumer.equals(info) && oldConsumer.executor != null ) {
- // notify listener
- for(final Object[] listenerObjects : this.listenerMap.values()) {
- if ( listenerObjects[0] == oldConsumer.executor ) {
- final JobExecutionContext context = (JobExecutionContext)listenerObjects[1];
- context.asyncProcessingFinished(context.result().failed());
- break;
- }
- }
- }
- }
- consumers.remove(info);
- if ( consumers.size() == 0 ) {
- this.topicToConsumerMap.remove(topic);
- changed = true;
- }
- }
- }
- }
- }
- if ( changed ) {
- this.calculateTopics(this.propagationService != null);
- }
- }
- if ( changed && this.propagationService != null ) {
- logger.debug("Updating property provider with: {}", this.topics);
- this.propagationService.setProperties(this.getRegistrationProperties());
- }
- }
- }
-
- private boolean match(final String topic, final TopicMatcher[] matchers) {
- for(final TopicMatcher m : matchers) {
- if ( m.match(topic) != null ) {
- return true;
- }
- }
- return false;
- }
-
- private void calculateTopics(final boolean enabled) {
- if ( enabled ) {
- // create a sorted list - this ensures that the property value
- // is always the same for the same topics.
- final List<String> topicList = new ArrayList<String>();
- for(final String topic : this.topicToConsumerMap.keySet() ) {
- // check whitelist
- if ( this.match(topic, this.whitelistMatchers) ) {
- // and blacklist
- if ( this.blacklistMatchers == null || !this.match(topic, this.blacklistMatchers) ) {
- topicList.add(topic);
- }
- }
- }
- Collections.sort(topicList);
-
- final StringBuilder sb = new StringBuilder();
- boolean first = true;
- for(final String topic : topicList ) {
- if ( first ) {
- first = false;
- } else {
- sb.append(',');
- }
- sb.append(topic);
- }
- this.topics = sb.toString();
- } else {
- this.topics = null;
- }
- }
-
- /**
- * Internal class caching some consumer infos like service id and ranking.
- */
- private final static class ConsumerInfo implements Comparable<ConsumerInfo> {
-
- public final ServiceReference serviceReference;
- private final boolean isConsumer;
- public JobExecutor executor;
- public final int ranking;
- public final long serviceId;
-
- public ConsumerInfo(final ServiceReference serviceReference, final boolean isConsumer) {
- this.serviceReference = serviceReference;
- this.isConsumer = isConsumer;
- final Object sr = serviceReference.getProperty(Constants.SERVICE_RANKING);
- if ( sr == null || !(sr instanceof Integer)) {
- this.ranking = 0;
- } else {
- this.ranking = (Integer)sr;
- }
- this.serviceId = (Long)serviceReference.getProperty(Constants.SERVICE_ID);
- }
-
- @Override
- public int compareTo(final ConsumerInfo o) {
- if ( this.ranking < o.ranking ) {
- return 1;
- } else if (this.ranking > o.ranking ) {
- return -1;
- }
- // If ranks are equal, then sort by service id in descending order.
- return (this.serviceId < o.serviceId) ? -1 : 1;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if ( obj instanceof ConsumerInfo ) {
- return ((ConsumerInfo)obj).serviceId == this.serviceId;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return serviceReference.hashCode();
- }
-
- public JobExecutor getExecutor(final BundleContext bundleContext) {
- if ( executor == null ) {
- if ( this.isConsumer ) {
- executor = new JobConsumerWrapper((JobConsumer) bundleContext.getService(this.serviceReference));
- } else {
- executor = (JobExecutor) bundleContext.getService(this.serviceReference);
- }
- }
- return executor;
- }
- }
-
- private final static class JobConsumerWrapper implements JobExecutor {
-
- private final JobConsumer consumer;
-
- public JobConsumerWrapper(final JobConsumer consumer) {
- this.consumer = consumer;
- }
-
- @Override
- public JobExecutionResult process(final Job job, final JobExecutionContext context) {
- final JobConsumer.AsyncHandler asyncHandler =
- new JobConsumer.AsyncHandler() {
-
- final Object asyncLock = new Object();
- final AtomicBoolean asyncDone = new AtomicBoolean(false);
-
- private void check(final JobExecutionResult result) {
- synchronized ( asyncLock ) {
- if ( !asyncDone.get() ) {
- asyncDone.set(true);
- context.asyncProcessingFinished(result);
- } else {
- throw new IllegalStateException("Job is already marked as processed");
- }
- }
- }
-
- @Override
- public void ok() {
- this.check(context.result().succeeded());
- }
-
- @Override
- public void failed() {
- this.check(context.result().failed());
- }
-
- @Override
- public void cancel() {
- this.check(context.result().cancelled());
- }
- };
- ((JobImpl)job).setProperty(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER, asyncHandler);
- final JobConsumer.JobResult result = this.consumer.process(job);
- if ( result == JobResult.ASYNC ) {
- return null;
- } else if ( result == JobResult.FAILED) {
- return context.result().failed();
- } else if ( result == JobResult.OK) {
- return context.result().succeeded();
- }
- return context.result().cancelled();
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java
deleted file mode 100644
index 6337b6c..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.sling.api.resource.ModifiableValueMap;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.consumer.JobExecutor;
-
-
-/**
- * This object adds actions to a {@link JobImpl}.
- */
-public class JobHandler {
-
- private final JobImpl job;
-
- public volatile long started = -1;
-
- private volatile boolean isStopped = false;
-
- private final JobManagerConfiguration configuration;
-
- private final JobExecutor consumer;
-
- public JobHandler(final JobImpl job,
- final JobExecutor consumer,
- final JobManagerConfiguration configuration) {
- this.job = job;
- this.consumer = consumer;
- this.configuration = configuration;
- }
-
- public JobImpl getJob() {
- return this.job;
- }
-
- public JobExecutor getConsumer() {
- return this.consumer;
- }
-
- public boolean startProcessing(final Queue queue) {
- this.isStopped = false;
- return this.persistJobProperties(this.job.prepare(queue));
- }
-
- /**
- * Reschedule the job
- * Update the retry count and remove the started time.
- * @return <code>true</code> if rescheduling was successful, <code>false</code> otherwise.
- */
- public boolean reschedule() {
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource jobResource = resolver.getResource(job.getResourcePath());
- if ( jobResource != null ) {
- final ModifiableValueMap mvm = jobResource.adaptTo(ModifiableValueMap.class);
- mvm.put(Job.PROPERTY_JOB_RETRY_COUNT, job.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class));
- if ( job.getProperty(Job.PROPERTY_RESULT_MESSAGE) != null ) {
- mvm.put(Job.PROPERTY_RESULT_MESSAGE, job.getProperty(Job.PROPERTY_RESULT_MESSAGE));
- }
- mvm.remove(Job.PROPERTY_JOB_STARTED_TIME);
- mvm.put(JobImpl.PROPERTY_JOB_QUEUED, Calendar.getInstance());
- try {
- resolver.commit();
- return true;
- } catch ( final PersistenceException pe ) {
- this.configuration.getMainLogger().debug("Unable to update reschedule properties for job " + job.getId(), pe);
- }
- }
- } finally {
- resolver.close();
- }
-
- return false;
- }
-
- /**
- * Finish a job.
- * @param state The state of the processing
- * @param keepJobInHistory whether to keep the job in the job history.
- * @param duration the duration of the processing.
- */
- public void finished(final Job.JobState state,
- final boolean keepJobInHistory,
- final Long duration) {
- final boolean isSuccess = (state == Job.JobState.SUCCEEDED);
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource jobResource = resolver.getResource(job.getResourcePath());
- if ( jobResource != null ) {
- try {
- String newPath = null;
- if ( keepJobInHistory ) {
- final ValueMap vm = ResourceHelper.getValueMap(jobResource);
- newPath = this.configuration.getStoragePath(job.getTopic(), job.getId(), isSuccess);
- final Map<String, Object> props = new HashMap<String, Object>(vm);
- props.put(JobImpl.PROPERTY_FINISHED_STATE, state.name());
- if ( isSuccess ) {
- // we set the finish date to start date + duration
- final Date finishDate = new Date();
- finishDate.setTime(job.getProcessingStarted().getTime().getTime() + duration);
- final Calendar finishCal = Calendar.getInstance();
- finishCal.setTime(finishDate);
- props.put(JobImpl.PROPERTY_FINISHED_DATE, finishCal);
- } else {
- // current time is good enough
- props.put(JobImpl.PROPERTY_FINISHED_DATE, Calendar.getInstance());
- }
- if ( job.getProperty(Job.PROPERTY_RESULT_MESSAGE) != null ) {
- props.put(Job.PROPERTY_RESULT_MESSAGE, job.getProperty(Job.PROPERTY_RESULT_MESSAGE));
- }
- ResourceHelper.getOrCreateResource(resolver, newPath, props);
- }
- resolver.delete(jobResource);
- resolver.commit();
-
- if ( keepJobInHistory && configuration.getMainLogger().isDebugEnabled() ) {
- if ( isSuccess ) {
- configuration.getMainLogger().debug("Kept successful job {} at {}", Utility.toString(job), newPath);
- } else {
- configuration.getMainLogger().debug("Moved cancelled job {} to {}", Utility.toString(job), newPath);
- }
- }
- } catch ( final PersistenceException pe ) {
- this.configuration.getMainLogger().warn("Unable to finish job " + job.getId(), pe);
- } catch (final InstantiationException ie) {
- // something happened with the resource in the meantime
- this.configuration.getMainLogger().debug("Unable to instantiate job", ie);
- }
- }
- } finally {
- resolver.close();
- }
- }
-
- /**
- * Reassign to a new instance.
- */
- public void reassign() {
- final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(job.getTopic());
- // Sanity check if queue configuration has changed
- final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
- final String targetId = (caps == null ? null : caps.detectTarget(job.getTopic(), job.getProperties(), queueInfo));
-
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource jobResource = resolver.getResource(job.getResourcePath());
- if ( jobResource != null ) {
- try {
- final ValueMap vm = ResourceHelper.getValueMap(jobResource);
- final String newPath = this.configuration.getUniquePath(targetId, job.getTopic(), job.getId(), job.getProperties());
-
- final Map<String, Object> props = new HashMap<String, Object>(vm);
- props.remove(Job.PROPERTY_JOB_QUEUE_NAME);
- if ( targetId == null ) {
- props.remove(Job.PROPERTY_JOB_TARGET_INSTANCE);
- } else {
- props.put(Job.PROPERTY_JOB_TARGET_INSTANCE, targetId);
- }
- props.remove(Job.PROPERTY_JOB_STARTED_TIME);
-
- try {
- ResourceHelper.getOrCreateResource(resolver, newPath, props);
- resolver.delete(jobResource);
- resolver.commit();
- } catch ( final PersistenceException pe ) {
- this.configuration.getMainLogger().warn("Unable to reassign job " + job.getId(), pe);
- }
- } catch (final InstantiationException ie) {
- // something happened with the resource in the meantime
- this.configuration.getMainLogger().debug("Unable to instantiate job", ie);
- }
- }
- } finally {
- resolver.close();
- }
- }
-
- /**
- * Update the property of a job in the resource tree
- * @param propNames the property names to update
- * @return {@code true} if the update was successful.
- */
- public boolean persistJobProperties(final String... propNames) {
- if ( propNames != null ) {
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource jobResource = resolver.getResource(job.getResourcePath());
- if ( jobResource != null ) {
- final ModifiableValueMap mvm = jobResource.adaptTo(ModifiableValueMap.class);
- for(final String propName : propNames) {
- final Object val = job.getProperty(propName);
- if ( val != null ) {
- if ( val.getClass().isEnum() ) {
- mvm.put(propName, val.toString());
- } else {
- mvm.put(propName, val);
- }
- } else {
- mvm.remove(propName);
- }
- }
- resolver.commit();
-
- return true;
- } else {
- this.configuration.getMainLogger().debug("No job resource found at {}", job.getResourcePath());
- }
- } catch ( final PersistenceException ignore ) {
- this.configuration.getMainLogger().debug("Unable to persist properties", ignore);
- } finally {
- resolver.close();
- }
- return false;
- }
- return true;
- }
-
- public boolean isStopped() {
- return this.isStopped;
- }
-
- public void stop() {
- this.isStopped = true;
- }
-
- public void addToRetryList() {
- this.configuration.addJobToRetryList(this.job);
-
- }
-
- public boolean removeFromRetryList() {
- return this.configuration.removeJobFromRetryList(this.job);
- }
-
- @Override
- public int hashCode() {
- return this.job.getId().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if ( ! (obj instanceof JobHandler) ) {
- return false;
- }
- return this.job.getId().equals(((JobHandler)obj).job.getId());
- }
-
- @Override
- public String toString() {
- return "JobHandler(" + this.job.getId() + ")";
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java
deleted file mode 100644
index 5514ebf..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.text.MessageFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.api.wrappers.ValueMapDecorator;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.Queue;
-
-/**
- * This object encapsulates all information about a job.
- */
-public class JobImpl implements Job, Comparable<JobImpl> {
-
- /** Internal job property containing the resource path. */
- public static final String PROPERTY_RESOURCE_PATH = "slingevent:path";
-
- /** Internal job property containing optional delay override. */
- public static final String PROPERTY_DELAY_OVERRIDE = ":slingevent:delayOverride";
-
- /**
- * Internal job property specifying when the job was put into the queue.
- */
- public static final String PROPERTY_JOB_QUEUED = "event.job.queued.time";
-
- /**
- * This property contains the finished state of a job once it's marked as finished.
- * The value is either "CANCELLED" or "SUCCEEDED".
- * This property is read-only and can't be specified when the job is created.
- */
- public static final String PROPERTY_FINISHED_STATE = "slingevent:finishedState";
-
- private final ValueMap properties;
-
- private final String topic;
-
- private final String path;
-
- private final String jobId;
-
- private final List<Exception> readErrorList;
-
- private final long counter;
-
- /**
- * Create a new job instance
- *
- * @param topic The job topic
- * @param name The unique job name (optional)
- * @param jobId The unique (internal) job id
- * @param properties Non-null map of properties, at least containing {@link #PROPERTY_RESOURCE_PATH}
- */
- @SuppressWarnings("unchecked")
- public JobImpl(final String topic,
- final String jobId,
- final Map<String, Object> properties) {
- this.topic = topic;
- this.jobId = jobId;
- this.path = (String)properties.remove(PROPERTY_RESOURCE_PATH);
- this.readErrorList = (List<Exception>) properties.remove(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
-
- this.properties = new ValueMapDecorator(properties);
- this.properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, jobId);
- final int lastPos = jobId.lastIndexOf('_');
- this.counter = Long.valueOf(jobId.substring(lastPos + 1));
- }
-
- /**
- * Get the full resource path.
- */
- public String getResourcePath() {
- return this.path;
- }
-
- /**
- * Did we have read errors?
- */
- public boolean hasReadErrors() {
- return this.readErrorList != null;
- }
-
- /**
- * Is the error recoverable?
- */
- public boolean isReadErrorRecoverable() {
- boolean result = true;
- if ( this.readErrorList != null ) {
- for(final Exception e : this.readErrorList) {
- if ( e instanceof RuntimeException ) {
- result = false;
- break;
- }
- }
- }
- return result;
- }
-
- /**
- * Get all properties
- */
- public Map<String, Object> getProperties() {
- return this.properties;
- }
-
- /**
- * Update the information for a retry
- */
- public void retry() {
- final int retries = this.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class);
- this.properties.put(Job.PROPERTY_JOB_RETRY_COUNT, retries + 1);
- this.properties.remove(Job.PROPERTY_JOB_STARTED_TIME);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getTopic()
- */
- @Override
- public String getTopic() {
- return this.topic;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getId()
- */
- @Override
- public String getId() {
- return this.jobId;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String)
- */
- @Override
- public Object getProperty(final String name) {
- return this.properties.get(name);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String, java.lang.Class)
- */
- @Override
- public <T> T getProperty(final String name, final Class<T> type) {
- return this.properties.get(name, type);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String, java.lang.Object)
- */
- @Override
- public <T> T getProperty(final String name, final T defaultValue) {
- return this.properties.get(name, defaultValue);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getPropertyNames()
- */
- @Override
- public Set<String> getPropertyNames() {
- return this.properties.keySet();
- }
-
- @Override
- public int getRetryCount() {
- return this.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class);
- }
-
- @Override
- public int getNumberOfRetries() {
- return this.getProperty(Job.PROPERTY_JOB_RETRIES, Integer.class);
- }
-
- @Override
- public String getQueueName() {
- return this.getProperty(Job.PROPERTY_JOB_QUEUE_NAME, String.class);
- }
-
- @Override
- public String getTargetInstance() {
- return this.getProperty(Job.PROPERTY_JOB_TARGET_INSTANCE, String.class);
- }
-
- @Override
- public Calendar getProcessingStarted() {
- return this.getProperty(Job.PROPERTY_JOB_STARTED_TIME, Calendar.class);
- }
-
- @Override
- public Calendar getCreated() {
- return this.getProperty(Job.PROPERTY_JOB_CREATED, Calendar.class);
- }
-
- @Override
- public String getCreatedInstance() {
- return this.getProperty(Job.PROPERTY_JOB_CREATED_INSTANCE, String.class);
- }
-
- /**
- * Update information about the queue.
- */
- public void updateQueueInfo(final Queue queue) {
- this.properties.put(Job.PROPERTY_JOB_QUEUE_NAME, queue.getName());
- this.properties.put(Job.PROPERTY_JOB_RETRIES, queue.getConfiguration().getMaxRetries());
- }
-
- public void setProperty(final String name, final Object value) {
- if ( value == null ) {
- this.properties.remove(name);
- } else {
- this.properties.put(name, value);
- }
- }
-
- /**
- * Prepare a new job execution
- */
- public String[] prepare(final Queue queue) {
- this.updateQueueInfo(queue);
- this.properties.remove(JobImpl.PROPERTY_DELAY_OVERRIDE);
- this.properties.remove(Job.PROPERTY_JOB_PROGRESS_LOG);
- this.properties.remove(Job.PROPERTY_JOB_PROGRESS_ETA);
- this.properties.remove(Job.PROPERTY_JOB_PROGRESS_STEPS);
- this.properties.remove(Job.PROPERTY_JOB_PROGRESS_STEP);
- this.properties.remove(Job.PROPERTY_RESULT_MESSAGE);
- this.properties.put(Job.PROPERTY_JOB_STARTED_TIME, Calendar.getInstance());
- return new String[] {Job.PROPERTY_JOB_QUEUE_NAME, Job.PROPERTY_JOB_RETRIES,
- Job.PROPERTY_JOB_PROGRESS_LOG, Job.PROPERTY_JOB_PROGRESS_ETA, PROPERTY_JOB_PROGRESS_STEPS,
- PROPERTY_JOB_PROGRESS_STEP, Job.PROPERTY_RESULT_MESSAGE, Job.PROPERTY_JOB_STARTED_TIME};
- }
-
- public String[] startProgress(final int steps, final long eta) {
- if ( steps > 0 ) {
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, steps);
- }
- if ( eta > 0 ) {
- final Date finishDate = new Date(System.currentTimeMillis() + eta * 1000);
- final Calendar finishCal = Calendar.getInstance();
- finishCal.setTime(finishDate);
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, finishCal);
- }
- return new String[] {Job.PROPERTY_JOB_PROGRESS_ETA, PROPERTY_JOB_PROGRESS_STEPS};
- }
-
- public String[] setProgress(final int step) {
- final int steps = this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, -1);
- if ( steps > 0 && step > 0 ) {
- int current = this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEP, 0);
- current += step;
- if ( current > steps ) {
- current = steps;
- }
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_STEP, current);
-
- final Calendar now = Calendar.getInstance();
- final long elapsed = now.getTimeInMillis() - this.getProcessingStarted().getTimeInMillis();
-
- final long eta = elapsed * steps / step;
- now.setTimeInMillis(eta);
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, now);
- return new String[] {Job.PROPERTY_JOB_PROGRESS_STEP, Job.PROPERTY_JOB_PROGRESS_ETA};
- }
- return null;
- }
-
- public String update(final long eta) {
- if ( eta > 0 ) {
- final Date finishDate = new Date(System.currentTimeMillis() + eta * 1000);
- final Calendar finishCal = Calendar.getInstance();
- finishCal.setTime(finishDate);
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, eta);
- } else {
- this.properties.remove(Job.PROPERTY_JOB_PROGRESS_ETA);
- }
- return Job.PROPERTY_JOB_PROGRESS_ETA;
- }
-
- public String log(final String message, final Object... args) {
- final String logEntry = MessageFormat.format(message, args);
- final String[] entries = this.getProperty(Job.PROPERTY_JOB_PROGRESS_LOG, String[].class);
- if ( entries == null ) {
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_LOG, new String[] {logEntry});
- } else {
- final String[] newEntries = new String[entries.length + 1];
- System.arraycopy(entries, 0, newEntries, 0, entries.length);
- newEntries[entries.length] = logEntry;
- this.setProperty(Job.PROPERTY_JOB_PROGRESS_LOG, newEntries);
- }
- return Job.PROPERTY_JOB_PROGRESS_LOG;
- }
-
- @Override
- public JobState getJobState() {
- final String enumValue = this.getProperty(JobImpl.PROPERTY_FINISHED_STATE, String.class);
- if ( enumValue == null ) {
- if ( this.getProcessingStarted() != null ) {
- return JobState.ACTIVE;
- }
- return JobState.QUEUED;
- }
- return JobState.valueOf(enumValue);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getFinishedDate()
- */
- @Override
- public Calendar getFinishedDate() {
- return this.getProperty(Job.PROPERTY_FINISHED_DATE, Calendar.class);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getResultMessage()
- */
- @Override
- public String getResultMessage() {
- return this.getProperty(Job.PROPERTY_RESULT_MESSAGE, String.class);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProgressLog()
- */
- @Override
- public String[] getProgressLog() {
- return this.getProperty(Job.PROPERTY_JOB_PROGRESS_LOG, String[].class);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProgressStepCount()
- */
- @Override
- public int getProgressStepCount() {
- return this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, -1);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getFinishedProgressStep()
- */
- @Override
- public int getFinishedProgressStep() {
- return this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEP, 0);
- }
-
- /**
- * @see org.apache.sling.event.jobs.Job#getProgressETA()
- */
- @Override
- public Calendar getProgressETA() {
- return this.getProperty(Job.PROPERTY_JOB_PROGRESS_ETA, Calendar.class);
- }
-
- @Override
- public int compareTo(final JobImpl o) {
- int result = this.getCreated().compareTo(o.getCreated());
- if ( result == 0 ) {
- if ( this.counter < o.counter ) {
- result = -1;
- } else if ( this.counter > o.counter ) {
- result = 1;
- } else {
- result = this.jobId.compareTo(o.jobId);
- }
- }
- return result;
- }
-
- @Override
- public int hashCode() {
- return this.jobId.hashCode();
- }
-
- @Override
- public boolean equals(final Object obj) {
- if ( obj == this ) {
- return true;
- }
- if ( obj instanceof JobImpl ) {
- return this.jobId.equals(((JobImpl)obj).jobId);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return "JobImpl [properties=" + properties + ", topic=" + topic
- + ", path=" + path + ", jobId=" + jobId + "]";
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
deleted file mode 100644
index b76fda4..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
+++ /dev/null
@@ -1,759 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.jackrabbit.util.ISO9075;
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.QuerySyntaxException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChangeListener;
-import org.apache.sling.commons.scheduler.Scheduler;
-import org.apache.sling.commons.threads.ThreadPoolManager;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.impl.jobs.notifications.NotificationUtility;
-import org.apache.sling.event.impl.jobs.queues.JobQueueImpl;
-import org.apache.sling.event.impl.jobs.queues.QueueManager;
-import org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl;
-import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
-import org.apache.sling.event.impl.jobs.tasks.CleanUpTask;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.Job.JobState;
-import org.apache.sling.event.jobs.JobBuilder;
-import org.apache.sling.event.jobs.JobManager;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-import org.apache.sling.event.jobs.Statistics;
-import org.apache.sling.event.jobs.TopicStatistics;
-import org.apache.sling.event.jobs.jmx.QueuesMBean;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Implementation of the job manager.
- */
-@Component(immediate=true)
-@Service(value={JobManager.class, EventHandler.class, Runnable.class})
-@Properties({
- @Property(name="scheduler.period", longValue=60),
- @Property(name="scheduler.concurrent", boolValue=false),
- @Property(name=EventConstants.EVENT_TOPIC,
- value={ResourceHelper.BUNDLE_EVENT_STARTED,
- ResourceHelper.BUNDLE_EVENT_UPDATED})
-})
-public class JobManagerImpl
- implements JobManager, EventHandler, Runnable {
-
- /** Default logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Reference
- private EventAdmin eventAdmin;
-
- @Reference
- private Scheduler scheduler;
-
- @Reference
- private JobConsumerManager jobConsumerManager;
-
- @Reference
- private QueuesMBean queuesMBean;
-
- @Reference
- private ThreadPoolManager threadPoolManager;
-
- /** The job manager configuration. */
- @Reference
- private JobManagerConfiguration configuration;
-
- @Reference
- private StatisticsManager statisticsManager;
-
- @Reference
- private QueueManager qManager;
-
- private volatile CleanUpTask maintenanceTask;
-
- /** Job Scheduler. */
- private org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl jobScheduler;
-
- private volatile ServiceRegistration<ResourceChangeListener> changeListenerReg;
-
- /**
- * Activate this component.
- * @param props Configuration properties
- */
- @Activate
- protected void activate(final BundleContext ctx, final Map<String, Object> props) throws LoginException {
- this.jobScheduler = new org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl(this.configuration, this.scheduler, this);
- this.maintenanceTask = new CleanUpTask(this.configuration, this.jobScheduler);
-
- final Dictionary<String, Object> regProps = new Hashtable<>();
- regProps.put(ResourceChangeListener.PATHS, this.configuration.getScheduledJobsPath(false));
- regProps.put(ResourceChangeListener.CHANGES, new String[] {
- ResourceChange.ChangeType.ADDED.name(),
- ResourceChange.ChangeType.CHANGED.name(),
- ResourceChange.ChangeType.REMOVED.name()
- });
- regProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
- regProps.put(Constants.SERVICE_DESCRIPTION, "Resource change listener for scheduled jobs");
- this.changeListenerReg = ctx.registerService(ResourceChangeListener.class, this.jobScheduler, regProps);
- logger.info("Apache Sling Job Manager started on instance {}", Environment.APPLICATION_ID);
- }
-
- /**
- * Deactivate this component.
- */
- @Deactivate
- protected void deactivate() {
- logger.debug("Apache Sling Job Manager stopping on instance {}", Environment.APPLICATION_ID);
-
- if ( this.changeListenerReg != null ) {
- this.changeListenerReg.unregister();
- this.changeListenerReg = null;
- }
-
- this.jobScheduler.deactivate();
-
- this.maintenanceTask = null;
- logger.info("Apache Sling Job Manager stopped on instance {}", Environment.APPLICATION_ID);
- }
-
- /**
- * This method is invoked periodically by the scheduler.
- * In the default configuration every minute
- * @see java.lang.Runnable#run()
- */
- @Override
- public void run() {
- // invoke maintenance task
- final CleanUpTask task = this.maintenanceTask;
- if ( task != null ) {
- task.run();
- }
- }
-
- /**
- * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
- */
- @Override
- public void handleEvent(final Event event) {
- this.jobScheduler.handleEvent(event);
- }
-
- /**
- * Return our internal statistics object.
- *
- * @see org.apache.sling.event.jobs.JobManager#getStatistics()
- */
- @Override
- public synchronized Statistics getStatistics() {
- return this.statisticsManager.getGlobalStatistics();
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getTopicStatistics()
- */
- @Override
- public Iterable<TopicStatistics> getTopicStatistics() {
- return this.statisticsManager.getTopicStatistics().values();
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getQueue(java.lang.String)
- */
- @Override
- public Queue getQueue(final String name) {
- return qManager.getQueue(ResourceHelper.filterQueueName(name));
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getQueues()
- */
- @Override
- public Iterable<Queue> getQueues() {
- return qManager.getQueues();
- }
-
- /**
- * Remove a job.
- * If the job is already in the storage area, it's removed forever.
- * Otherwise it's moved to the storage area.
- */
- private boolean internalRemoveJobById(final String jobId, final boolean forceRemove) {
- logger.debug("Trying to remove job {}", jobId);
- boolean result = true;
- JobImpl job = (JobImpl)this.getJobById(jobId);
- if ( job != null ) {
- if ( logger.isDebugEnabled() ) {
- logger.debug("Found removal job: {}", Utility.toString(job));
- }
- final JobImpl retryJob = (JobImpl)this.configuration.getJobFromRetryList(jobId);
- if ( retryJob != null ) {
- job = retryJob;
- }
- // currently running?
- if ( !forceRemove && job.getProcessingStarted() != null ) {
- if ( logger.isDebugEnabled() ) {
- logger.debug("Unable to remove job - job is started: {}", Utility.toString(job));
- }
- result = false;
- } else {
- final boolean isHistoryJob = this.configuration.isStoragePath(job.getResourcePath());
- // if history job, simply remove - otherwise move to history!
- if ( isHistoryJob ) {
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource jobResource = resolver.getResource(job.getResourcePath());
- if ( jobResource != null ) {
- resolver.delete(jobResource);
- resolver.commit();
- logger.debug("Removed job with id: {}", jobId);
- } else {
- logger.debug("Unable to remove job with id - resource already removed: {}", jobId);
- }
- NotificationUtility.sendNotification(this.eventAdmin, NotificationConstants.TOPIC_JOB_REMOVED, job, null);
- } catch ( final PersistenceException pe) {
- logger.warn("Unable to remove job at " + job.getResourcePath(), pe);
- result = false;
- } finally {
- resolver.close();
- }
- } else {
- final JobHandler jh = new JobHandler(job, null, this.configuration);
- jh.finished(Job.JobState.DROPPED, true, null);
- }
- this.configuration.getAuditLogger().debug("REMOVE OK : {}", jobId);
- }
- } else {
- logger.debug("Job for removal does not exist (anymore): {}", jobId);
- }
- return result;
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#addJob(java.lang.String, java.util.Map)
- */
- @Override
- public Job addJob(String topic, Map<String, Object> properties) {
- return this.addJob(topic, properties, null);
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getJobById(java.lang.String)
- */
- @Override
- public Job getJobById(final String id) {
- logger.debug("Getting job by id: {}", id);
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- final StringBuilder buf = new StringBuilder(64);
- try {
-
- buf.append("/jcr:root");
- buf.append(this.configuration.getJobsBasePathWithSlash());
- buf.append("/element(*,");
- buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
- buf.append(")[@");
- buf.append(ResourceHelper.PROPERTY_JOB_ID);
- buf.append(" = '");
- buf.append(id);
- buf.append("']");
- if ( logger.isDebugEnabled() ) {
- logger.debug("Exceuting query: {}", buf.toString());
- }
- final Iterator<Resource> result = resolver.findResources(buf.toString(), "xpath");
-
- while ( result.hasNext() ) {
- final Resource jobResource = result.next();
- // sanity check for the path
- if ( this.configuration.isJob(jobResource.getPath()) ) {
- final JobImpl job = Utility.readJob(logger, jobResource);
- if ( job != null ) {
- if ( logger.isDebugEnabled() ) {
- logger.debug("Found job with id {} = {}", id, Utility.toString(job));
- }
- return job;
- }
- }
- }
- } catch (final QuerySyntaxException qse) {
- logger.warn("Query syntax wrong " + buf.toString(), qse);
- } finally {
- resolver.close();
- }
- logger.debug("Job not found with id: {}", id);
- return null;
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getJob(java.lang.String, java.util.Map)
- */
- @SuppressWarnings("unchecked")
- @Override
- public Job getJob(final String topic, final Map<String, Object> template) {
- final Iterable<Job> iter;
- if ( template == null ) {
- iter = this.findJobs(QueryType.ALL, topic, 1, (Map<String, Object>[])null);
- } else {
- iter = this.findJobs(QueryType.ALL, topic, 1, template);
- }
- final Iterator<Job> i = iter.iterator();
- if ( i.hasNext() ) {
- return i.next();
- }
- return null;
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#removeJobById(java.lang.String)
- */
- @Override
- public boolean removeJobById(final String jobId) {
- return this.internalRemoveJobById(jobId, true);
- }
-
- private enum Operation {
- LESS,
- LESS_OR_EQUALS,
- EQUALS,
- GREATER_OR_EQUALS,
- GREATER
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#findJobs(org.apache.sling.event.jobs.JobManager.QueryType, java.lang.String, long, java.util.Map[])
- */
- @Override
- public Collection<Job> findJobs(final QueryType type,
- final String topic,
- final long limit,
- final Map<String, Object>... templates) {
- final boolean isHistoryQuery = type == QueryType.HISTORY
- || type == QueryType.SUCCEEDED
- || type == QueryType.CANCELLED
- || type == QueryType.DROPPED
- || type == QueryType.ERROR
- || type == QueryType.GIVEN_UP
- || type == QueryType.STOPPED;
- final List<Job> result = new ArrayList<Job>();
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- final StringBuilder buf = new StringBuilder(64);
- try {
-
- buf.append("/jcr:root");
- buf.append(this.configuration.getJobsBasePathWithSlash());
- buf.append("/element(*,");
- buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
- buf.append(")[@");
- buf.append(ISO9075.encode(ResourceHelper.PROPERTY_JOB_TOPIC));
- if (topic != null) {
- buf.append(" = '");
- buf.append(topic);
- buf.append("'");
- }
-
- // restricting on the type - history or unfinished
- if ( isHistoryQuery ) {
- buf.append(" and @");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- if ( type == QueryType.SUCCEEDED || type == QueryType.DROPPED || type == QueryType.ERROR || type == QueryType.GIVEN_UP || type == QueryType.STOPPED ) {
- buf.append(" = '");
- buf.append(type.name());
- buf.append("'");
- } else if ( type == QueryType.CANCELLED ) {
- buf.append(" and (@");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- buf.append(" = '");
- buf.append(QueryType.DROPPED.name());
- buf.append("' or @");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- buf.append(" = '");
- buf.append(QueryType.ERROR.name());
- buf.append("' or @");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- buf.append(" = '");
- buf.append(QueryType.GIVEN_UP.name());
- buf.append("' or @");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- buf.append(" = '");
- buf.append(QueryType.STOPPED.name());
- buf.append("')");
- }
- } else {
- buf.append(" and not(@");
- buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
- buf.append(")");
- if ( type == QueryType.ACTIVE ) {
- buf.append(" and @");
- buf.append(ISO9075.encode(Job.PROPERTY_JOB_STARTED_TIME));
- } else if ( type == QueryType.QUEUED ) {
- buf.append(" and not(@");
- buf.append(ISO9075.encode(Job.PROPERTY_JOB_STARTED_TIME));
- buf.append(")");
- }
- }
-
- if ( templates != null && templates.length > 0 ) {
- int index = 0;
- for (final Map<String,Object> template : templates) {
- // skip empty templates
- if ( template.size() == 0 ) {
- continue;
- }
- if ( index == 0 ) {
- buf.append(" and (");
- } else {
- buf.append(" or ");
- }
- buf.append('(');
- final Iterator<Map.Entry<String, Object>> i = template.entrySet().iterator();
- boolean first = true;
- while ( i.hasNext() ) {
- final Map.Entry<String, Object> current = i.next();
- final String key = ISO9075.encode(current.getKey());
- final char firstChar = key.length() > 0 ? key.charAt(0) : 0;
- final String propName;
- final Operation op;
- if ( firstChar == '=' ) {
- propName = key.substring(1);
- op = Operation.EQUALS;
- } else if ( firstChar == '<' ) {
- final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
- if ( secondChar == '=' ) {
- op = Operation.LESS_OR_EQUALS;
- propName = key.substring(2);
- } else {
- op = Operation.LESS;
- propName = key.substring(1);
- }
- } else if ( firstChar == '>' ) {
- final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
- if ( secondChar == '=' ) {
- op = Operation.GREATER_OR_EQUALS;
- propName = key.substring(2);
- } else {
- op = Operation.GREATER;
- propName = key.substring(1);
- }
- } else {
- propName = key;
- op = Operation.EQUALS;
- }
-
- if ( first ) {
- first = false;
- buf.append('@');
- } else {
- buf.append(" and @");
- }
- buf.append(propName);
- buf.append(' ');
- switch ( op ) {
- case EQUALS : buf.append('=');break;
- case LESS : buf.append('<'); break;
- case LESS_OR_EQUALS : buf.append("<="); break;
- case GREATER : buf.append('>'); break;
- case GREATER_OR_EQUALS : buf.append(">="); break;
- }
- buf.append(" '");
- buf.append(current.getValue());
- buf.append("'");
- }
- buf.append(')');
- index++;
- }
- if ( index > 0 ) {
- buf.append(')');
- }
- }
- buf.append("] order by @");
- if ( isHistoryQuery ) {
- buf.append(JobImpl.PROPERTY_FINISHED_DATE);
- buf.append(" descending");
- } else {
- buf.append(Job.PROPERTY_JOB_CREATED);
- buf.append(" ascending");
- }
- final Iterator<Resource> iter = resolver.findResources(buf.toString(), "xpath");
- long count = 0;
-
- while ( iter.hasNext() && (limit < 1 || count < limit) ) {
- final Resource jobResource = iter.next();
- // sanity check for the path
- if ( this.configuration.isJob(jobResource.getPath()) ) {
- final JobImpl job = Utility.readJob(logger, jobResource);
- if ( job != null ) {
- count++;
- result.add(job);
- }
- }
- }
- } catch (final QuerySyntaxException qse) {
- logger.warn("Query syntax wrong " + buf.toString(), qse);
- } finally {
- resolver.close();
- }
- return result;
- }
-
- /**
- * Persist the job in the resource tree
- * @param jobTopic The required job topic
- * @param jobName The optional job name
- * @param passedJobProperties The optional job properties
- * @return The persisted job or <code>null</code>.
- */
- private Job addJobInternal(final String jobTopic,
- final Map<String, Object> jobProperties,
- final List<String> errors) {
- final QueueInfo info = this.configuration.getQueueConfigurationManager().getQueueInfo(jobTopic);
-
- final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
- info.targetId = (caps == null ? null : caps.detectTarget(jobTopic, jobProperties, info));
-
- if ( logger.isDebugEnabled() ) {
- if ( info.targetId != null ) {
- logger.debug("Persisting job {} into queue {}, target={}", new Object[] {Utility.toString(jobTopic, jobProperties), info.queueName, info.targetId});
- } else {
- logger.debug("Persisting job {} into queue {}", Utility.toString(jobTopic, jobProperties), info.queueName);
- }
- }
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final JobImpl job = this.writeJob(resolver,
- jobTopic,
- jobProperties,
- info);
- if ( info.targetId != null ) {
- this.configuration.getAuditLogger().debug("ASSIGN OK {} : {}",
- info.targetId, job.getId());
- } else {
- this.configuration.getAuditLogger().debug("UNASSIGN OK : {}",
- job.getId());
- }
- return job;
- } catch (final PersistenceException re ) {
- // something went wrong, so let's log it
- this.logger.error("Exception during persisting new job '" + Utility.toString(jobTopic, jobProperties) + "'", re);
- } finally {
- resolver.close();
- }
- if ( errors != null ) {
- errors.add("Unable to persist new job.");
- }
-
- return null;
- }
-
- /**
- * Write a job to the resource tree.
- * @param resolver The resolver resolver
- * @param event The event
- * @param info The queue information (queue name etc.)
- * @throws PersistenceException
- */
- private JobImpl writeJob(final ResourceResolver resolver,
- final String jobTopic,
- final Map<String, Object> jobProperties,
- final QueueInfo info)
- throws PersistenceException {
- final String jobId = this.configuration.getUniqueId(jobTopic);
- final String path = this.configuration.getUniquePath(info.targetId, jobTopic, jobId, jobProperties);
-
- // create properties
- final Map<String, Object> properties = new HashMap<String, Object>();
-
- if ( jobProperties != null ) {
- for(final Map.Entry<String, Object> entry : jobProperties.entrySet() ) {
- final String propName = entry.getKey();
- if ( !ResourceHelper.ignoreProperty(propName) ) {
- properties.put(propName, entry.getValue());
- }
- }
- }
-
- properties.put(ResourceHelper.PROPERTY_JOB_ID, jobId);
- properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
- properties.put(Job.PROPERTY_JOB_QUEUE_NAME, info.queueConfiguration.getName());
- properties.put(Job.PROPERTY_JOB_RETRY_COUNT, 0);
- properties.put(Job.PROPERTY_JOB_RETRIES, info.queueConfiguration.getMaxRetries());
-
- properties.put(Job.PROPERTY_JOB_CREATED, Calendar.getInstance());
- properties.put(JobImpl.PROPERTY_JOB_QUEUED, Calendar.getInstance());
- properties.put(Job.PROPERTY_JOB_CREATED_INSTANCE, Environment.APPLICATION_ID);
- if ( info.targetId != null ) {
- properties.put(Job.PROPERTY_JOB_TARGET_INSTANCE, info.targetId);
- } else {
- properties.remove(Job.PROPERTY_JOB_TARGET_INSTANCE);
- }
-
- // create path and resource
- properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, ResourceHelper.RESOURCE_TYPE_JOB);
- if ( logger.isDebugEnabled() ) {
- logger.debug("Storing new job {} at {}", Utility.toString(jobTopic, properties), path);
- }
- ResourceHelper.getOrCreateResource(resolver,
- path,
- properties);
-
- // update property types - priority, add path and create job
- properties.put(JobImpl.PROPERTY_RESOURCE_PATH, path);
- return new JobImpl(jobTopic, jobId, properties);
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#stopJobById(java.lang.String)
- */
- @Override
- public void stopJobById(final String jobId) {
- this.stopJobById(jobId, true);
- }
-
- private void stopJobById(final String jobId, final boolean forward) {
- final JobImpl job = (JobImpl)this.getJobById(jobId);
- if ( job != null && !this.configuration.isStoragePath(job.getResourcePath()) ) {
- // get the queue configuration
- final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(job.getTopic());
- final JobQueueImpl queue = (JobQueueImpl)this.qManager.getQueue(queueInfo.queueName);
-
- boolean stopped = false;
- if ( queue != null ) {
- stopped = queue.stopJob(job);
- }
- if ( forward && !stopped ) {
- // mark the job as stopped
- final JobHandler jh = new JobHandler(job, null, this.configuration);
- jh.finished(JobState.STOPPED, true, null);
- }
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#createJob(java.lang.String)
- */
- @Override
- public JobBuilder createJob(final String topic) {
- return new JobBuilderImpl(this, topic);
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getScheduledJobs()
- */
- @Override
- public Collection<ScheduledJobInfo> getScheduledJobs() {
- return this.jobScheduler.getScheduledJobs(null, -1, (Map<String, Object>[])null);
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#getScheduledJobs()
- */
- @Override
- public Collection<ScheduledJobInfo> getScheduledJobs(final String topic,
- final long limit,
- final Map<String, Object>... templates) {
- return this.jobScheduler.getScheduledJobs(topic, limit, templates);
- }
-
- /**
- * Internal method to add a job
- */
- public Job addJob(final String topic,
- final Map<String, Object> properties,
- final List<String> errors) {
- final String errorMessage = Utility.checkJob(topic, properties);
- if ( errorMessage != null ) {
- logger.warn("{}", errorMessage);
- if ( errors != null ) {
- errors.add(errorMessage);
- }
- this.configuration.getAuditLogger().debug("ADD FAILED topic={}, properties={} : {}",
- new Object[] {topic,
- properties,
- errorMessage});
- return null;
- }
- final List<String> errorList = new ArrayList<String>();
- Job result = this.addJobInternal(topic, properties, errorList);
- if ( errors != null ) {
- errors.addAll(errorList);
- }
- if ( result == null ) {
- this.configuration.getAuditLogger().debug("ADD FAILED topic={}, properties={} : {}",
- new Object[] {topic,
- properties,
- errorList});
- } else {
- this.configuration.getAuditLogger().debug("ADD OK topic={}, properties={} : {}",
- new Object[] {topic,
- properties,
- result.getId()});
- }
-
- return result;
- }
-
- /**
- * @see org.apache.sling.event.jobs.JobManager#retryJobById(java.lang.String)
- */
- @Override
- public Job retryJobById(final String jobId) {
- final JobImpl job = (JobImpl)this.getJobById(jobId);
- if ( job != null && this.configuration.isStoragePath(job.getResourcePath()) ) {
- this.internalRemoveJobById(jobId, true);
- return this.addJob(job.getTopic(), job.getProperties());
- }
- return null;
- }
-
- public JobSchedulerImpl getJobScheduler() {
- return this.jobScheduler;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java b/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java
deleted file mode 100644
index 1981e77..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.sling.api.resource.Resource;
-import org.slf4j.Logger;
-
-/**
- * The job topic traverser is an utility class to traverse all jobs
- * of a specific topic in order of creation.
- *
- * The traverser can be used with two different callbacks,
- * the resource callback is called with a resource object,
- * the job callback with a job object created from the
- * resource.
- */
-public class JobTopicTraverser {
-
- /**
- * Callback called for each found job.
- */
- public interface JobCallback {
-
- /**
- * Callback handle for a job.
- * If the callback signals to stop traversing, the current minute is still
- * processed completely (to ensure correct ordering of jobs).
- * @param job The job to handle
- * @return <code>true</code> If processing should continue, <code>false</code> otherwise.
- */
- boolean handle(final JobImpl job);
- }
-
- /**
- * Callback called for each found resource.
- */
- public interface ResourceCallback {
-
- /**
- * Callback handle for a resource.
- * The callback is called in sorted order on a minute base, all resources within a minute
- * are not necessarily called in correct time order!
- * If the callback signals to stop traversing, the traversal is stopped
- * immediately.
- * @param rsrc The resource to handle
- * @return <code>true</code> If processing should continue, <code>false</code> otherwise.
- */
- boolean handle(final Resource rsrc);
- }
-
- /**
- * Traverse the topic and call the callback for each found job.
- *
- * Once the callback notifies to stop traversing by returning false, the current minute
- * will be processed completely (to ensure correct ordering of jobs) and then the
- * traversal stops.
- *
- * @param logger The logger to use for debug logging
- * @param topicResource The topic resource
- * @param handler The callback
- */
- public static void traverse(final Logger logger,
- final Resource topicResource,
- final JobCallback handler) {
- traverse(logger, topicResource, handler, null);
- }
-
- /**
- * Traverse the topic and call the callback for each found resource.
- *
- * Once the callback notifies to stop traversing by returning false, the
- * traversal stops.
- *
- * @param logger The logger to use for debug logging
- * @param topicResource The topic resource
- * @param handler The callback
- */
- public static void traverse(final Logger logger,
- final Resource topicResource,
- final ResourceCallback handler) {
- traverse(logger, topicResource, null, handler);
- }
-
- /**
- * Internal method for traversal
- * @param logger The logger to use for debug logging
- * @param topicResource The topic resource
- * @param jobHandler The job callback
- * @param resourceHandler The resource callback
- */
- private static void traverse(final Logger logger,
- final Resource topicResource,
- final JobCallback jobHandler,
- final ResourceCallback resourceHandler) {
- logger.debug("Processing topic {}", topicResource.getName().replace('.', '/'));
- // now years
- for(final Resource yearResource: Utility.getSortedChildren(logger, "year", topicResource)) {
- logger.debug("Processing year {}", yearResource.getName());
-
- // now months
- for(final Resource monthResource: Utility.getSortedChildren(logger, "month", yearResource)) {
- logger.debug("Processing month {}", monthResource.getName());
-
- // now days
- for(final Resource dayResource: Utility.getSortedChildren(logger, "day", monthResource)) {
- logger.debug("Processing day {}", dayResource.getName());
-
- // now hours
- for(final Resource hourResource: Utility.getSortedChildren(logger, "hour", dayResource)) {
- logger.debug("Processing hour {}", hourResource.getName());
-
- // now minutes
- for(final Resource minuteResource: Utility.getSortedChildren(logger, "minute", hourResource)) {
- logger.debug("Processing minute {}", minuteResource.getName());
-
- // now jobs
- final List<JobImpl> jobs = new ArrayList<JobImpl>();
- // we use an iterator to skip removed entries
- // see SLING-4073
- final Iterator<Resource> jobIter = minuteResource.listChildren();
- while ( jobIter.hasNext() ) {
- final Resource jobResource = jobIter.next();
- if ( resourceHandler != null ) {
- if ( !resourceHandler.handle(jobResource) ) {
- return;
- }
- } else {
- final JobImpl job = Utility.readJob(logger, jobResource);
- if ( job != null ) {
- logger.debug("Found job {}", jobResource.getName());
- jobs.add(job);
- }
- }
- }
-
- if ( jobHandler != null ) {
- Collections.sort(jobs);
-
- boolean stop = false;
- for(final JobImpl job : jobs) {
- if ( !jobHandler.handle(job) ) {
- stop = true;
- }
- }
- if ( stop ) {
- return;
- }
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/Utility.java b/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
deleted file mode 100644
index c93fe51..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.consumer.JobConsumer;
-import org.osgi.service.event.Event;
-import org.slf4j.Logger;
-
-public abstract class Utility {
-
- public static volatile boolean LOG_DEPRECATION_WARNINGS = true;
-
- /**
- * Check if the job topic is a valid OSGI event name (see 113.3.1 of the OSGI spec)
- * @return <code>null</code> if the topic is syntactically correct otherwise an error description is returned
- */
- public static String checkJobTopic(final Object jobTopic) {
- String message = null;
- if ( jobTopic != null ) {
- if ( jobTopic instanceof String ) {
- try {
- new Event((String)jobTopic, (Dictionary<String, Object>)null);
- } catch (final IllegalArgumentException iae) {
- message = String.format("Discarding job - job has an illegal job topic '%s'",jobTopic);
- }
-
- } else {
- message = "Discarding job - job topic is not of type string";
- }
- } else {
- message = "Discarding job - job topic is missing";
- }
- return message;
- }
-
- /**
- * Check the job.
- * @return <code>null</code> if the topic topic is correct and all properties are serializable,
- * otherwise an error description is returned
- */
- public static String checkJob(final Object jobTopic, final Map<String, Object> properties) {
- final String msg = checkJobTopic(jobTopic);
- if ( msg == null ) {
- if ( properties != null ) {
- for(final Object val : properties.values()) {
- if ( val != null && !(val instanceof Serializable) ) {
- return "Discarding job - properties must be serializable: " + jobTopic + " : " + properties;
- }
- }
- }
- }
- return msg;
- }
-
- /**
- * Create an event from a job
- * @param job The job
- * @return New event object.
- */
- public static Event toEvent(final Job job) {
- final Map<String, Object> eventProps = new HashMap<String, Object>();
- eventProps.putAll(((JobImpl)job).getProperties());
- eventProps.put(ResourceHelper.PROPERTY_JOB_ID, job.getId());
- eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER);
- return new Event(job.getTopic(), eventProps);
- }
-
- /**
- * Append properties to the string builder
- */
- private static void appendProperties(final StringBuilder sb,
- final Map<String, Object> properties) {
- if ( properties != null ) {
- sb.append(", properties=");
- boolean first = true;
- for(final String propName : properties.keySet()) {
- if ( propName.equals(ResourceHelper.PROPERTY_JOB_ID)
- || propName.equals(ResourceHelper.PROPERTY_JOB_TOPIC) ) {
- continue;
- }
- if ( first ) {
- first = false;
- } else {
- sb.append(",");
- }
- sb.append(propName);
- sb.append('=');
- final Object value = properties.get(propName);
- // the toString() method of Calendar is very verbose
- // therefore we do a toString for these objects based
- // on a date
- if ( value instanceof Calendar ) {
- sb.append(value.getClass().getName());
- sb.append('(');
- sb.append(((Calendar)value).getTime());
- sb.append(')');
- } else {
- sb.append(value);
- }
- }
- }
- }
-
- /**
- * Improved toString method for a job.
- * This method prints out the job topic and all of the properties.
- */
- public static String toString(final String jobTopic,
- final Map<String, Object> properties) {
- final StringBuilder sb = new StringBuilder("Sling Job ");
- sb.append("[topic=");
- sb.append(jobTopic);
- appendProperties(sb, properties);
-
- sb.append("]");
- return sb.toString();
- }
-
- /**
- * Improved toString method for a job.
- * This method prints out the job topic and all of the properties.
- */
- public static String toString(final Job job) {
- if ( job != null ) {
- final StringBuilder sb = new StringBuilder("Sling Job ");
- sb.append("[topic=");
- sb.append(job.getTopic());
- sb.append(", id=");
- sb.append(job.getId());
- appendProperties(sb, ((JobImpl)job).getProperties());
- sb.append("]");
- return sb.toString();
- }
- return "<null>";
- }
-
- /**
- * Read a job
- */
- public static JobImpl readJob(final Logger logger, final Resource resource) {
- JobImpl job = null;
- if ( resource != null ) {
- try {
- final ValueMap vm = ResourceHelper.getValueMap(resource);
-
- // check job topic and job id
- final String errorMessage = Utility.checkJobTopic(vm.get(ResourceHelper.PROPERTY_JOB_TOPIC));
- final String jobId = vm.get(ResourceHelper.PROPERTY_JOB_ID, String.class);
- if ( errorMessage == null && jobId != null ) {
- final String topic = vm.get(ResourceHelper.PROPERTY_JOB_TOPIC, String.class);
- final Map<String, Object> jobProperties = ResourceHelper.cloneValueMap(vm);
-
- jobProperties.put(JobImpl.PROPERTY_RESOURCE_PATH, resource.getPath());
- // convert to integers (JCR supports only long...)
- jobProperties.put(Job.PROPERTY_JOB_RETRIES, vm.get(Job.PROPERTY_JOB_RETRIES, Integer.class));
- jobProperties.put(Job.PROPERTY_JOB_RETRY_COUNT, vm.get(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class));
- if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS) != null ) {
- jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEPS, vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS, Integer.class));
- }
- if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEP) != null ) {
- jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEP, vm.get(Job.PROPERTY_JOB_PROGRESS_STEP, Integer.class));
- }
- @SuppressWarnings("unchecked")
- final List<Exception> readErrorList = (List<Exception>) jobProperties.get(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
- if ( readErrorList != null ) {
- for(final Exception e : readErrorList) {
- logger.warn("Unable to read job from " + resource.getPath(), e);
- }
- }
- job = new JobImpl(topic,
- jobId,
- jobProperties);
- } else {
- if ( errorMessage != null ) {
- logger.warn("{} : {}", errorMessage, resource.getPath());
- } else if ( jobId == null ) {
- logger.warn("Discarding job - no job id found : {}", resource.getPath());
- }
- // remove the job as the topic is invalid anyway
- try {
- resource.getResourceResolver().delete(resource);
- resource.getResourceResolver().commit();
- } catch ( final PersistenceException ignore) {
- logger.debug("Unable to remove job resource.", ignore);
- }
- }
- } catch (final InstantiationException ie) {
- // something happened with the resource in the meantime
- logger.debug("Unable to instantiate resource.", ie);
- } catch (final RuntimeException re) {
- logger.debug("Unable to read resource.", re);
- }
-
- }
- return job;
- }
-
- private static final Comparator<Resource> RESOURCE_COMPARATOR = new Comparator<Resource>() {
-
- @Override
- public int compare(final Resource o1, final Resource o2) {
- Integer value1 = null;
- try {
- value1 = Integer.valueOf(o1.getName());
- } catch ( final NumberFormatException nfe) {
- // ignore
- }
- Integer value2 = null;
- try {
- value2 = Integer.valueOf(o2.getName());
- } catch ( final NumberFormatException nfe) {
- // ignore
- }
- if ( value1 != null && value2 != null ) {
- return value1.compareTo(value2);
- }
- return o1.getName().compareTo(o2.getName());
- }
- };
-
- /**
- * Helper method to read all children of a resource and sort them by name
- * @param type The type of resources (for debugging)
- * @param rsrc The parent resource
- * @return Sorted list of children.
- */
- public static List<Resource> getSortedChildren(final Logger logger, final String type, final Resource rsrc) {
- final List<Resource> children = new ArrayList<Resource>();
- final Iterator<Resource> monthIter = rsrc.listChildren();
- while ( monthIter.hasNext() ) {
- final Resource monthResource = monthIter.next();
- children.add(monthResource);
- logger.debug("Found {} : {}", type, monthResource.getName());
- }
- Collections.sort(children, RESOURCE_COMPARATOR);
- return children;
- }
-
- /**
- * Log a deprecation warning on level info into the log
- * @param logger The logger to use
- * @param message The message.
- */
- public static void logDeprecated(final Logger logger, final String message) {
- if ( LOG_DEPRECATION_WARNINGS && logger.isInfoEnabled() ) {
- logger.info("DEPRECATION-WARNING: " + message, new Exception(message));
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java
deleted file mode 100644
index 71b6fe6..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-/**
- * Listener interface to get topology / queue changes.
- * Components interested in configuration changes can subscribe
- * themselves using the {@link JobManagerConfiguration}.
- */
-public interface ConfigurationChangeListener {
-
- /**
- * Notify about a configuration change.
- * @param active {@code true} if job processing is active, otherwise {@code false}
- */
- void configurationChanged(final boolean active);
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java
deleted file mode 100644
index 18f4886..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-/**
- * Constants for the queue configuration.
- */
-public abstract class ConfigurationConstants {
-
- public static final int NUMBER_OF_PROCESSORS = Runtime.getRuntime().availableProcessors();
-
- public static final String DEFAULT_TYPE = "UNORDERED";
- public static final String DEFAULT_PRIORITY = "NORM";
- public static final int DEFAULT_RETRIES = 10;
- public static final long DEFAULT_RETRY_DELAY = 2000;
- public static final int DEFAULT_MAX_PARALLEL = 15;
- public static final boolean DEFAULT_KEEP_JOBS = false;
- public static final int DEFAULT_THREAD_POOL_SIZE = 0;
- public static final boolean DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE = false;
-
- public static final String PROP_NAME = "queue.name";
- public static final String PROP_TYPE = "queue.type";
- public static final String PROP_TOPICS = "queue.topics";
- public static final String PROP_MAX_PARALLEL = "queue.maxparallel";
- public static final String PROP_RETRIES = "queue.retries";
- public static final String PROP_RETRY_DELAY = "queue.retrydelay";
- public static final String PROP_PRIORITY = "queue.priority";
- public static final String PROP_KEEP_JOBS = "queue.keepJobs";
- public static final String PROP_THREAD_POOL_SIZE = "queue.threadPoolSize";
- public static final String PROP_PREFER_RUN_ON_CREATION_INSTANCE = "queue.preferRunOnCreationInstance";
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java
deleted file mode 100644
index c8457a2..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.ConfigurationPolicy;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.PropertyOption;
-import org.apache.felix.scr.annotations.PropertyUnbounded;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.event.impl.support.TopicMatcher;
-import org.apache.sling.event.impl.support.TopicMatcherHelper;
-import org.apache.sling.event.jobs.QueueConfiguration;
-import org.osgi.framework.Constants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Component(metatype=true,
- name="org.apache.sling.event.jobs.QueueConfiguration",
- label="Apache Sling Job Queue Configuration",
- description="The configuration of a job processing queue.",
- configurationFactory=true, policy=ConfigurationPolicy.REQUIRE)
-@Service(value={InternalQueueConfiguration.class})
-@Properties({
- @Property(name=ConfigurationConstants.PROP_NAME,
- label="Name",
- description="The name of the queue. If matching is used the token {0} can be used to substitute the real value."),
- @Property(name=ConfigurationConstants.PROP_TOPICS,
- unbounded=PropertyUnbounded.ARRAY,
- label="Topics",
- description="This value is required and lists the topics processed by "
- + "this queue. The value is a list of strings. If a string ends with a dot, "
- + "all topics in exactly this package match. If the string ends with a star, "
- + "all topics in this package and all subpackages match. If the string neither "
- + "ends with a dot nor with a star, this is assumed to define an exact topic."),
- @Property(name=ConfigurationConstants.PROP_TYPE,
- value=ConfigurationConstants.DEFAULT_TYPE,
- options={@PropertyOption(name="UNORDERED",value="Parallel"),
- @PropertyOption(name="ORDERED",value="Ordered"),
- @PropertyOption(name="TOPIC_ROUND_ROBIN",value="Topic Round Robin")},
- label="Type",
- description="The queue type."),
- @Property(name=ConfigurationConstants.PROP_MAX_PARALLEL,
- doubleValue=ConfigurationConstants.DEFAULT_MAX_PARALLEL,
- label="Maximum Parallel Jobs",
- description="The maximum number of parallel jobs started for this queue. "
- + "A value of -1 is substituted with the number of available processors. "
- + "Positive integer values specify number of processors to use. Can be greater than number of processors. "
- + "A decimal number between 0.0 and 1.0 is treated as a fraction of available processors. "
- + "For example 0.5 means half of the available processors. For ordered queue types this value is ignored (always enforced to be 1)."),
- @Property(name=ConfigurationConstants.PROP_RETRIES,
- intValue=ConfigurationConstants.DEFAULT_RETRIES,
- label="Maximum Retries",
- description="The maximum number of times a failed job slated "
- + "for retries is actually retried. If a job has been retried this number of "
- + "times and still fails, it is not rescheduled and assumed to have failed. The "
- + "default value is 10."),
- @Property(name=ConfigurationConstants.PROP_RETRY_DELAY,
- longValue=ConfigurationConstants.DEFAULT_RETRY_DELAY,
- label="Retry Delay",
- description="The number of milliseconds to sleep between two "
- + "consecutive retries of a job which failed and was set to be retried. The "
- + "default value is 2 seconds. This value is only relevant if there is a single "
- + "failed job in the queue. If there are multiple failed jobs, each job is "
- + "retried in turn without an intervening delay."),
- @Property(name=ConfigurationConstants.PROP_PRIORITY,
- value=ConfigurationConstants.DEFAULT_PRIORITY,
- options={@PropertyOption(name="NORM",value="Norm"),
- @PropertyOption(name="MIN",value="Min"),
- @PropertyOption(name="MAX",value="Max")},
- label="Priority",
- description="The priority for the threads used by this queue. Default is norm."),
- @Property(name=ConfigurationConstants.PROP_KEEP_JOBS,
- boolValue=ConfigurationConstants.DEFAULT_KEEP_JOBS,
- label="Keep History",
- description="If this option is enabled, successful finished jobs are kept "
- + "to provide a complete history."),
- @Property(name=ConfigurationConstants.PROP_PREFER_RUN_ON_CREATION_INSTANCE,
- boolValue=ConfigurationConstants.DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE,
- label="Prefer Creation Instance",
- description="If this option is enabled, the jobs are tried to "
- + "be run on the instance where the job was created."),
- @Property(name=ConfigurationConstants.PROP_THREAD_POOL_SIZE,
- intValue=ConfigurationConstants.DEFAULT_THREAD_POOL_SIZE,
- label="Thread Pool Size",
- description="Optional configuration value for a thread pool to be used by "
- + "this queue. If this is value has a positive number of threads configuration, this queue uses "
- + "an own thread pool with the configured number of threads."),
- @Property(name=Constants.SERVICE_RANKING,
- intValue=0,
- propertyPrivate=false,
- label="Ranking",
- description="Integer value defining the ranking of this queue configuration. "
- + "If more than one queue matches a job topic, the one with the highest ranking is used."),
- @Property(name="webconsole.configurationFactory.nameHint", value="Queue: {" + ConfigurationConstants.PROP_NAME + "}")
-})
-public class InternalQueueConfiguration
- implements QueueConfiguration, Comparable<InternalQueueConfiguration> {
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** The name of the queue. */
- private String name;
-
- /** The queue type. */
- private Type type;
-
- /** Number of retries. */
- private int retries;
-
- /** Retry delay. */
- private long retryDelay;
-
- /** Thread priority. */
- private ThreadPriority priority;
-
- /** The maximum number of parallel processes (for non ordered queues) */
- private int maxParallelProcesses;
-
- /** The ordering. */
- private int serviceRanking;
-
- /** The matchers for topics. */
- private TopicMatcher[] matchers;
-
- /** The configured topics. */
- private String[] topics;
-
- /** Keep jobs. */
- private boolean keepJobs;
-
- /** Valid flag. */
- private boolean valid = false;
-
- /** Optional thread pool size. */
- private int ownThreadPoolSize;
-
- /** Prefer creation instance. */
- private boolean preferCreationInstance;
-
- private String pid;
-
- /**
- * Create a new configuration from a config
- */
- public static InternalQueueConfiguration fromConfiguration(final Map<String, Object> params) {
- final InternalQueueConfiguration c = new InternalQueueConfiguration();
- c.activate(params);
- return c;
- }
-
- public InternalQueueConfiguration() {
- // nothing to do, see activate
- }
-
- /**
- * Create a new queue configuration
- */
- @Activate
- protected void activate(final Map<String, Object> params) {
- this.name = PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_NAME), null);
- try {
- this.priority = ThreadPriority.valueOf(PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_PRIORITY), ConfigurationConstants.DEFAULT_PRIORITY));
- } catch ( final IllegalArgumentException iae) {
- logger.warn("Invalid value for queue priority. Using default instead of : {}", params.get(ConfigurationConstants.PROP_PRIORITY));
- this.priority = ThreadPriority.valueOf(ConfigurationConstants.DEFAULT_PRIORITY);
- }
- try {
- this.type = Type.valueOf(PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_TYPE), ConfigurationConstants.DEFAULT_TYPE));
- } catch ( final IllegalArgumentException iae) {
- logger.error("Invalid value for queue type configuration: {}", params.get(ConfigurationConstants.PROP_TYPE));
- this.type = null;
- }
- this.retries = PropertiesUtil.toInteger(params.get(ConfigurationConstants.PROP_RETRIES), ConfigurationConstants.DEFAULT_RETRIES);
- this.retryDelay = PropertiesUtil.toLong(params.get(ConfigurationConstants.PROP_RETRY_DELAY), ConfigurationConstants.DEFAULT_RETRY_DELAY);
-
- // Float values are treated as percentage. int values are treated as number of cores, -1 == all available
- // Note: the value is based on the core count at startup. It will not change dynamically if core count changes.
- int cores = ConfigurationConstants.NUMBER_OF_PROCESSORS;
- final double inMaxParallel = PropertiesUtil.toDouble(params.get(ConfigurationConstants.PROP_MAX_PARALLEL),
- ConfigurationConstants.DEFAULT_MAX_PARALLEL);
- logger.debug("Max parallel for queue {} is {}", this.name, inMaxParallel);
- if ((inMaxParallel == Math.floor(inMaxParallel)) && !Double.isInfinite(inMaxParallel)) {
- // integral type
- if ((int) inMaxParallel == 0) {
- logger.warn("Max threads property for {} set to zero.", this.name);
- }
- this.maxParallelProcesses = (inMaxParallel <= -1 ? cores : (int) inMaxParallel);
- } else {
- // percentage (rounded)
- if ((inMaxParallel > 0.0) && (inMaxParallel < 1.0)) {
- this.maxParallelProcesses = (int) Math.round(cores * inMaxParallel);
- } else {
- logger.warn("Invalid queue max parallel value for queue {}. Using {}", this.name, cores);
- this.maxParallelProcesses = cores;
- }
- }
- logger.debug("Thread pool size for {} was set to {}", this.name, this.maxParallelProcesses);
-
- // ignore parallel setting for ordered queues
- if ( this.type == Type.ORDERED ) {
- this.maxParallelProcesses = 1;
- }
- final String[] topicsParam = PropertiesUtil.toStringArray(params.get(ConfigurationConstants.PROP_TOPICS));
- this.matchers = TopicMatcherHelper.buildMatchers(topicsParam);
- if ( this.matchers == null ) {
- this.topics = null;
- } else {
- this.topics = topicsParam;
- }
- this.keepJobs = PropertiesUtil.toBoolean(params.get(ConfigurationConstants.PROP_KEEP_JOBS), ConfigurationConstants.DEFAULT_KEEP_JOBS);
- this.serviceRanking = PropertiesUtil.toInteger(params.get(Constants.SERVICE_RANKING), 0);
- this.ownThreadPoolSize = PropertiesUtil.toInteger(params.get(ConfigurationConstants.PROP_THREAD_POOL_SIZE), ConfigurationConstants.DEFAULT_THREAD_POOL_SIZE);
- this.preferCreationInstance = PropertiesUtil.toBoolean(params.get(ConfigurationConstants.PROP_PREFER_RUN_ON_CREATION_INSTANCE), ConfigurationConstants.DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE);
- this.pid = (String)params.get(Constants.SERVICE_PID);
- this.valid = this.checkIsValid();
- }
-
- /**
- * Check if this configuration is valid,
- * If it is invalid, it is ignored.
- */
- private boolean checkIsValid() {
- if ( type == null ) {
- return false;
- }
- boolean hasMatchers = false;
- if ( this.matchers != null ) {
- for(final TopicMatcher m : this.matchers ) {
- if ( m != null ) {
- hasMatchers = true;
- break;
- }
- }
- }
- if ( !hasMatchers ) {
- return false;
- }
- if ( name == null || name.length() == 0 ) {
- return false;
- }
- if ( retries < -1 ) {
- return false;
- }
- if ( maxParallelProcesses < 1 ) {
- return false;
- }
- return true;
- }
-
- public boolean isValid() {
- return this.valid;
- }
-
- /**
- * Check if the queue processes the event.
- * @param topic The topic of the event
- * @return The queue name or <code>null</code>
- */
- public String match(final String topic) {
- if ( this.matchers != null ) {
- for(final TopicMatcher m : this.matchers ) {
- if ( m != null ) {
- final String rep = m.match(topic);
- if ( rep != null ) {
- return this.name.replace("{0}", rep);
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Return the name of the queue.
- */
- public String getName() {
- return this.name;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getRetryDelayInMs()
- */
- @Override
- public long getRetryDelayInMs() {
- return this.retryDelay;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getMaxRetries()
- */
- @Override
- public int getMaxRetries() {
- return this.retries;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getType()
- */
- @Override
- public Type getType() {
- return this.type;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getMaxParallel()
- */
- @Override
- public int getMaxParallel() {
- return this.maxParallelProcesses;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getTopics()
- */
- @Override
- public String[] getTopics() {
- return this.topics;
- }
-
- /**
- * @see org.apache.sling.event.jobs.QueueConfiguration#getRanking()
- */
- @Override
- public int getRanking() {
- return this.serviceRanking;
- }
-
- public String getPid() {
- return this.pid;
- }
-
- @Override
- public boolean isKeepJobs() {
- return this.keepJobs;
- }
-
- @Override
- public int getOwnThreadPoolSize() {
- return this.ownThreadPoolSize;
- }
-
- @Override
- public boolean isPreferRunOnCreationInstance() {
- return this.preferCreationInstance;
- }
-
- @Override
- public String toString() {
- return "Queue-Configuration(" + this.hashCode() + ") : {" +
- "name=" + this.name +
- ", type=" + this.type +
- ", topics=" + (this.matchers == null ? "[]" : Arrays.toString(this.matchers)) +
- ", maxParallelProcesses=" + this.maxParallelProcesses +
- ", retries=" + this.retries +
- ", retryDelayInMs=" + this.retryDelay +
- ", keepJobs=" + this.keepJobs +
- ", preferRunOnCreationInstance=" + this.preferCreationInstance +
- ", ownThreadPoolSize=" + this.ownThreadPoolSize +
- ", serviceRanking=" + this.serviceRanking +
- ", pid=" + this.pid +
- ", isValid=" + this.isValid() + "}";
- }
-
- @Override
- public int compareTo(final InternalQueueConfiguration other) {
- if ( this.serviceRanking < other.serviceRanking ) {
- return 1;
- } else if ( this.serviceRanking > other.serviceRanking ) {
- return -1;
- }
- return 0;
- }
-
- @Override
- public ThreadPriority getThreadPriority() {
- return this.priority;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java
deleted file mode 100644
index 3cc8b72..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.sql.Date;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Modified;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.commons.scheduler.Scheduler;
-import org.apache.sling.discovery.TopologyEvent;
-import org.apache.sling.discovery.TopologyEvent.Type;
-import org.apache.sling.discovery.TopologyEventListener;
-import org.apache.sling.discovery.commons.InitDelayingTopologyEventListener;
-import org.apache.sling.event.impl.EnvironmentComponent;
-import org.apache.sling.event.impl.jobs.Utility;
-import org.apache.sling.event.impl.jobs.tasks.CheckTopologyTask;
-import org.apache.sling.event.impl.jobs.tasks.FindUnfinishedJobsTask;
-import org.apache.sling.event.impl.jobs.tasks.UpgradeTask;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.serviceusermapping.ServiceUserMapped;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Configuration of the job handling
- *
- */
-@Component(immediate=true, metatype=true,
- label="Apache Sling Job Manager",
- description="This is the central service of the job handling.",
- name="org.apache.sling.event.impl.jobs.jcr.PersistenceHandler")
-@Service(value={JobManagerConfiguration.class})
-@Properties({
- @Property(name=JobManagerConfiguration.PROPERTY_DISABLE_DISTRIBUTION,
- boolValue=JobManagerConfiguration.DEFAULT_DISABLE_DISTRIBUTION,
- label="Disable Distribution",
- description="If the distribution is disabled, all jobs will be processed on the leader only! "
- + "Please use this switch with care."),
- @Property(name=JobManagerConfiguration.PROPERTY_LOG_DEPRECATION_WARNINGS,
- boolValue=JobManagerConfiguration.DEFAULT_LOG_DEPRECATION_WARNINGS,
- label="Deprecation Warnings",
- description="If this switch is enabled, deprecation warnings will be logged with the INFO level."),
- @Property(name=JobManagerConfiguration.PROPERTY_STARTUP_DELAY,
- longValue=JobManagerConfiguration.DEFAULT_STARTUP_DELAY,
- label="Startup Delay",
- description="Specify amount in seconds that job manager waits on startup before starting with job handling. "
- + "This can be used to allow enough time to restart a cluster before jobs are eventually reassigned."),
- @Property(name=JobManagerConfiguration.PROPERTY_REPOSITORY_PATH,
- value=JobManagerConfiguration.DEFAULT_REPOSITORY_PATH, propertyPrivate=true),
- @Property(name=JobManagerConfiguration.PROPERTY_SCHEDULED_JOBS_PATH,
- value=JobManagerConfiguration.DEFAULT_SCHEDULED_JOBS_PATH, propertyPrivate=true),
- @Property(name=JobManagerConfiguration.PROPERTY_BACKGROUND_LOAD_DELAY,
- longValue=JobManagerConfiguration.DEFAULT_BACKGROUND_LOAD_DELAY, propertyPrivate=true),
-})
-public class JobManagerConfiguration {
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger("org.apache.sling.event.impl.jobs");
-
- /** Audit Logger. */
- private final Logger auditLogger = LoggerFactory.getLogger("org.apache.sling.event.jobs.audit");
-
- /** Default resource path for jobs. */
- public static final String DEFAULT_REPOSITORY_PATH = "/var/eventing/jobs";
-
- /** Default background load delay. */
- public static final long DEFAULT_BACKGROUND_LOAD_DELAY = 10;
-
- /** Default startup delay. */
- public static final long DEFAULT_STARTUP_DELAY = 30;
-
- /** Default for disabling the distribution. */
- public static final boolean DEFAULT_DISABLE_DISTRIBUTION = false;
-
- /** Default resource path for scheduled jobs. */
- public static final String DEFAULT_SCHEDULED_JOBS_PATH = "/var/eventing/scheduled-jobs";
-
- /** The path where all jobs are stored. */
- public static final String PROPERTY_REPOSITORY_PATH = "repository.path";
-
- /** The background loader waits this time of seconds after startup before loading events from the repository. (in secs) */
- public static final String PROPERTY_BACKGROUND_LOAD_DELAY = "load.delay";
-
- /** The entire job handling waits time amount of seconds until it starts - to allow avoiding reassign on restart of a cluster */
- public static final String PROPERTY_STARTUP_DELAY = "startup.delay";
-
- /** Configuration switch for distributing the jobs. */
- public static final String PROPERTY_DISABLE_DISTRIBUTION = "job.consumermanager.disableDistribution";
-
- /** Configuration property for the scheduled jobs path. */
- public static final String PROPERTY_SCHEDULED_JOBS_PATH = "job.scheduled.jobs.path";
-
- /** Default value for background loading. */
- public static final boolean DEFAULT_BACKGROUND_LOAD_SEARCH = true;
-
- /** Configuration property for deprecation warnings. */
- public static final String PROPERTY_LOG_DEPRECATION_WARNINGS = "job.log.deprecation";
-
- /** Default value for deprecation warnings. */
- public static final boolean DEFAULT_LOG_DEPRECATION_WARNINGS = true;
-
- /** The jobs base path with a slash. */
- private String jobsBasePathWithSlash;
-
- /** The base path for assigned jobs. */
- private String assignedJobsPath;
-
- /** The base path for unassigned jobs. */
- private String unassignedJobsPath;
-
- /** The base path for assigned jobs to the current instance. */
- private String localJobsPath;
-
- /** The base path for assigned jobs to the current instance - ending with a slash. */
- private String localJobsPathWithSlash;
-
- private String previousVersionAnonPath;
-
- private String previousVersionIdentifiedPath;
-
- private volatile long backgroundLoadDelay;
-
- private volatile long startupDelay;
-
- private volatile InitDelayingTopologyEventListener startupDelayListener;
-
- private volatile boolean disabledDistribution;
-
- private String storedCancelledJobsPath;
-
- private String storedSuccessfulJobsPath;
-
- /** The resource path where scheduled jobs are stored. */
- private String scheduledJobsPath;
-
- /** The resource path where scheduled jobs are stored - ending with a slash. */
- private String scheduledJobsPathWithSlash;
-
- /** List of topology awares. */
- private final List<ConfigurationChangeListener> listeners = new ArrayList<ConfigurationChangeListener>();
-
- /** The environment component. */
- @Reference
- private EnvironmentComponent environment;
-
- @Reference
- private ResourceResolverFactory resourceResolverFactory;
-
- @Reference
- private QueueConfigurationManager queueConfigManager;
-
- @Reference
- private Scheduler scheduler;
-
- @Reference
- private ServiceUserMapped serviceUserMapped;
-
- /** Is this still active? */
- private final AtomicBoolean active = new AtomicBoolean(false);
-
- /** The topology capabilities. */
- private volatile TopologyCapabilities topologyCapabilities;
-
- /**
- * Activate this component.
- * @param props Configuration properties
- * @throws RuntimeException If the default paths can't be created
- */
- @Activate
- protected void activate(final Map<String, Object> props) {
- this.update(props);
- this.jobsBasePathWithSlash = PropertiesUtil.toString(props.get(PROPERTY_REPOSITORY_PATH),
- DEFAULT_REPOSITORY_PATH) + '/';
-
- // create initial resources
- this.assignedJobsPath = this.jobsBasePathWithSlash + "assigned";
- this.unassignedJobsPath = this.jobsBasePathWithSlash + "unassigned";
-
- this.localJobsPath = this.assignedJobsPath.concat("/").concat(Environment.APPLICATION_ID);
- this.localJobsPathWithSlash = this.localJobsPath.concat("/");
-
- this.previousVersionAnonPath = this.jobsBasePathWithSlash + "anon";
- this.previousVersionIdentifiedPath = this.jobsBasePathWithSlash + "identified";
-
- this.storedCancelledJobsPath = this.jobsBasePathWithSlash + "cancelled";
- this.storedSuccessfulJobsPath = this.jobsBasePathWithSlash + "finished";
-
- this.scheduledJobsPath = PropertiesUtil.toString(props.get(PROPERTY_SCHEDULED_JOBS_PATH),
- DEFAULT_SCHEDULED_JOBS_PATH);
- this.scheduledJobsPathWithSlash = this.scheduledJobsPath + "/";
-
- // create initial resources
- final ResourceResolver resolver = this.createResourceResolver();
- try {
- ResourceHelper.getOrCreateBasePath(resolver, this.getLocalJobsPath());
- ResourceHelper.getOrCreateBasePath(resolver, this.getUnassignedJobsPath());
- } catch ( final PersistenceException pe ) {
- logger.error("Unable to create default paths: " + pe.getMessage(), pe);
- throw new RuntimeException(pe);
- } finally {
- resolver.close();
- }
- this.active.set(true);
-
- // SLING-5560 : use an InitDelayingTopologyEventListener
- if (this.startupDelay > 0) {
- logger.debug("activate: job manager will start in {} sec. ({})", this.startupDelay, PROPERTY_STARTUP_DELAY);
- this.startupDelayListener = new InitDelayingTopologyEventListener(startupDelay, new TopologyEventListener() {
-
- @Override
- public void handleTopologyEvent(TopologyEvent event) {
- doHandleTopologyEvent(event);
- }
- }, this.scheduler, logger);
- } else {
- logger.debug("activate: job manager will start without delay. ({}:{})", PROPERTY_STARTUP_DELAY, this.startupDelay);
- }
- }
-
- /**
- * Update with a new configuration
- */
- @Modified
- protected void update(final Map<String, Object> props) {
- this.disabledDistribution = PropertiesUtil.toBoolean(props.get(PROPERTY_DISABLE_DISTRIBUTION), DEFAULT_DISABLE_DISTRIBUTION);
- this.backgroundLoadDelay = PropertiesUtil.toLong(props.get(PROPERTY_BACKGROUND_LOAD_DELAY), DEFAULT_BACKGROUND_LOAD_DELAY);
- // SLING-5560: note that currently you can't change the startupDelay to have
- // an immediate effect - it will only have an effect on next activation.
- // (as 'startup delay runnable' is already scheduled in activate)
- this.startupDelay = PropertiesUtil.toLong(props.get(PROPERTY_STARTUP_DELAY), DEFAULT_STARTUP_DELAY);
- Utility.LOG_DEPRECATION_WARNINGS = PropertiesUtil.toBoolean(props.get(PROPERTY_LOG_DEPRECATION_WARNINGS), DEFAULT_LOG_DEPRECATION_WARNINGS);
- }
-
- /**
- * Deactivate
- */
- @Deactivate
- protected void deactivate() {
- this.active.set(false);
- if ( this.startupDelayListener != null) {
- this.startupDelayListener.dispose();
- this.startupDelayListener = null;
- }
- this.stopProcessing();
- }
-
- /**
- * Is this component still active?
- * @return Active?
- */
- public boolean isActive() {
- return this.active.get();
- }
-
- /**
- * Create a new resource resolver for reading and writing the resource tree.
- * The resolver needs to be closed by the client.
- * @return A resource resolver or {@code null} if the component is already deactivated.
- * @throws RuntimeException if the resolver can't be created.
- */
- public ResourceResolver createResourceResolver() {
- ResourceResolver resolver = null;
- final ResourceResolverFactory factory = this.resourceResolverFactory;
- if ( factory != null ) {
- try {
- resolver = this.resourceResolverFactory.getServiceResourceResolver(null);
- } catch ( final LoginException le) {
- logger.error("Unable to create new resource resolver: " + le.getMessage(), le);
- throw new RuntimeException(le);
- }
- }
- return resolver;
- }
-
- /**
- * Get the current topology capabilities.
- * @return The capabilities or {@code null}
- */
- public TopologyCapabilities getTopologyCapabilities() {
- return this.topologyCapabilities;
- }
-
- public QueueConfigurationManager getQueueConfigurationManager() {
- return this.queueConfigManager;
- }
-
- /**
- * Get main logger.
- * @return The main logger.
- */
- public Logger getMainLogger() {
- return this.logger;
- }
-
- /**
- * Get the resource path for all assigned jobs.
- * @return The path - does not end with a slash.
- */
- public String getAssginedJobsPath() {
- return this.assignedJobsPath;
- }
-
- /**
- * Get the resource path for all unassigned jobs.
- * @return The path - does not end with a slash.
- */
- public String getUnassignedJobsPath() {
- return this.unassignedJobsPath;
- }
-
- /**
- * Get the resource path for all jobs assigned to the current instance
- * @return The path - does not end with a slash
- */
- public String getLocalJobsPath() {
- return this.localJobsPath;
- }
-
- /** Counter for jobs without an id. */
- private final AtomicLong jobCounter = new AtomicLong(0);
-
- /**
- * Create a unique job path (folder and name) for the job.
- */
- public String getUniquePath(final String targetId,
- final String topic,
- final String jobId,
- final Map<String, Object> jobProperties) {
- final String topicName = topic.replace('/', '.');
- final StringBuilder sb = new StringBuilder();
- if ( targetId != null ) {
- sb.append(this.assignedJobsPath);
- sb.append('/');
- sb.append(targetId);
- } else {
- sb.append(this.unassignedJobsPath);
- }
- sb.append('/');
- sb.append(topicName);
- sb.append('/');
- sb.append(jobId);
-
- return sb.toString();
- }
-
- /**
- * Get the unique job id
- */
- public String getUniqueId(final String jobTopic) {
- final Calendar now = Calendar.getInstance();
- final StringBuilder sb = new StringBuilder();
- sb.append(now.get(Calendar.YEAR));
- sb.append('/');
- sb.append(now.get(Calendar.MONTH) + 1);
- sb.append('/');
- sb.append(now.get(Calendar.DAY_OF_MONTH));
- sb.append('/');
- sb.append(now.get(Calendar.HOUR_OF_DAY));
- sb.append('/');
- sb.append(now.get(Calendar.MINUTE));
- sb.append('/');
- sb.append(Environment.APPLICATION_ID);
- sb.append('_');
- sb.append(jobCounter.getAndIncrement());
-
- return sb.toString();
- }
-
- public boolean isLocalJob(final String jobPath) {
- return jobPath != null && jobPath.startsWith(this.localJobsPathWithSlash);
- }
-
- public boolean isJob(final String jobPath) {
- return jobPath.startsWith(this.jobsBasePathWithSlash);
- }
-
- public String getJobsBasePathWithSlash() {
- return this.jobsBasePathWithSlash;
- }
-
- public String getPreviousVersionAnonPath() {
- return this.previousVersionAnonPath;
- }
-
- public String getPreviousVersionIdentifiedPath() {
- return this.previousVersionIdentifiedPath;
- }
-
- public boolean disableDistribution() {
- return this.disabledDistribution;
- }
-
- public String getStoredCancelledJobsPath() {
- return this.storedCancelledJobsPath;
- }
-
- public String getStoredSuccessfulJobsPath() {
- return this.storedSuccessfulJobsPath;
- }
-
- /**
- * Get the storage path for finished jobs.
- * @param topic Topic of the finished job
- * @param jobId The job id of the finished job.
- * @param isSuccess Whether processing was successful or not
- * @return The complete storage path
- */
- public String getStoragePath(final String topic, final String jobId, final boolean isSuccess) {
- final String topicName = topic.replace('/', '.');
- final StringBuilder sb = new StringBuilder();
- if ( isSuccess ) {
- sb.append(this.storedSuccessfulJobsPath);
- } else {
- sb.append(this.storedCancelledJobsPath);
- }
- sb.append('/');
- sb.append(topicName);
- sb.append('/');
- sb.append(jobId);
-
- return sb.toString();
-
- }
-
- /**
- * Check whether this is a storage path.
- */
- public boolean isStoragePath(final String path) {
- return path.startsWith(this.storedCancelledJobsPath) || path.startsWith(this.storedSuccessfulJobsPath);
- }
-
- /**
- * Get the scheduled jobs path
- * @param slash If {@code false} the path is returned, if {@code true} the path appended with a slash is returned.
- * @return The path for the scheduled jobs
- */
- public String getScheduledJobsPath(final boolean slash) {
- return (slash ? this.scheduledJobsPathWithSlash : this.scheduledJobsPath);
- }
-
- /**
- * Stop processing
- * @param deactivate Whether to deactivate the capabilities
- */
- private void stopProcessing() {
- logger.debug("Stopping job processing...");
- final TopologyCapabilities caps = this.topologyCapabilities;
-
- if ( caps != null ) {
- // deactivate old capabilities - this stops all background processes
- caps.deactivate();
- this.topologyCapabilities = null;
-
- // stop all listeners
- this.notifiyListeners();
- }
- logger.debug("Job processing stopped");
- }
-
- /**
- * Start processing
- * @param eventType The event type
- * @param newCaps The new capabilities
- */
- private void startProcessing(final Type eventType, final TopologyCapabilities newCaps) {
- logger.debug("Starting job processing...");
- // create new capabilities and update view
- this.topologyCapabilities = newCaps;
-
- // before we propagate the new topology we do some maintenance
- if ( eventType == Type.TOPOLOGY_INIT ) {
- final UpgradeTask task = new UpgradeTask(this);
- task.run();
-
- final FindUnfinishedJobsTask rt = new FindUnfinishedJobsTask(this);
- rt.run();
-
- final CheckTopologyTask mt = new CheckTopologyTask(this);
- mt.fullRun();
-
- notifiyListeners();
- } else {
- // and run checker again in some seconds (if leader)
- // notify listeners afterwards
- final Scheduler local = this.scheduler;
- if ( local != null ) {
- final Runnable r = new Runnable() {
-
- @Override
- public void run() {
- if ( newCaps == topologyCapabilities && newCaps.isActive()) {
- // start listeners
- notifiyListeners();
- if ( newCaps.isLeader() && newCaps.isActive() ) {
- final CheckTopologyTask mt = new CheckTopologyTask(JobManagerConfiguration.this);
- mt.fullRun();
- }
- }
- }
- };
- if ( !local.schedule(r, local.AT(new Date(System.currentTimeMillis() + this.backgroundLoadDelay * 1000))) ) {
- // if for whatever reason scheduling doesn't work, let's run now
- r.run();
- }
- }
- }
- logger.debug("Job processing started");
- }
-
- /**
- * Notify all listeners
- */
- private void notifiyListeners() {
- synchronized ( this.listeners ) {
- final TopologyCapabilities caps = this.topologyCapabilities;
- for(final ConfigurationChangeListener l : this.listeners) {
- l.configurationChanged(caps != null);
- }
- }
- }
-
- /**
- * This method is invoked asynchronously from the TopologyHandler.
- * Therefore this method can't be invoked concurrently
- * @see org.apache.sling.discovery.TopologyEventListener#handleTopologyEvent(org.apache.sling.discovery.TopologyEvent)
- */
- public void handleTopologyEvent(TopologyEvent event) {
- if ( this.startupDelayListener != null ) {
- // with startup.delay > 0
- this.startupDelayListener.handleTopologyEvent(event);
- } else {
- // classic (startup.delay <= 0)
- this.logger.debug("Received topology event {}", event);
- doHandleTopologyEvent(event);
- }
- }
-
- void doHandleTopologyEvent(final TopologyEvent event) {
-
- // check if there is a change of properties which doesn't affect us
- // but we need to use the new view !
- boolean stopProcessing = true;
- if ( event.getType() == Type.PROPERTIES_CHANGED ) {
- final Map<String, String> newAllInstances = TopologyCapabilities.getAllInstancesMap(event.getNewView());
- if ( this.topologyCapabilities != null && this.topologyCapabilities.isSame(newAllInstances) ) {
- logger.debug("No changes in capabilities - updating topology capabilities with new view");
- stopProcessing = false;
- }
- }
-
- final TopologyEvent.Type eventType = event.getType();
-
- if ( eventType == Type.TOPOLOGY_CHANGING ) {
- this.stopProcessing();
-
- } else if ( eventType == Type.TOPOLOGY_INIT
- || event.getType() == Type.TOPOLOGY_CHANGED
- || event.getType() == Type.PROPERTIES_CHANGED ) {
-
- if ( stopProcessing ) {
- this.stopProcessing();
- }
-
- this.startProcessing(eventType, new TopologyCapabilities(event.getNewView(), this));
- }
- }
-
- /**
- * Add a topology aware listener
- * @param service Listener to notify about changes.
- */
- public void addListener(final ConfigurationChangeListener service) {
- synchronized ( this.listeners ) {
- this.listeners.add(service);
- service.configurationChanged(this.topologyCapabilities != null);
- }
- }
-
- /**
- * Remove a topology aware listener
- * @param service Listener to notify about changes.
- */
- public void removeListener(final ConfigurationChangeListener service) {
- synchronized ( this.listeners ) {
- this.listeners.remove(service);
- }
- }
-
- private final Map<String, Job> retryList = new HashMap<String, Job>();
-
- public void addJobToRetryList(final Job job) {
- synchronized ( retryList ) {
- retryList.put(job.getId(), job);
- }
- }
-
- public List<Job> clearJobRetryList() {
- final List<Job> result = new ArrayList<Job>();
- synchronized ( this.retryList ) {
- result.addAll(retryList.values());
- retryList.clear();
- }
- return result;
- }
-
- public boolean removeJobFromRetryList(final Job job) {
- synchronized ( retryList ) {
- return retryList.remove(job.getId()) != null;
- }
- }
-
- public Job getJobFromRetryList(final String jobId) {
- synchronized ( retryList ) {
- return retryList.get(jobId);
- }
- }
-
- /**
- * The audit logger is logging actions for auditing.
- * @return The logger
- */
- public Logger getAuditLogger() {
- return this.auditLogger;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java
deleted file mode 100644
index 9287e6b..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Modified;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.PropertyOption;
-import org.apache.felix.scr.annotations.Service;
-import org.osgi.framework.Constants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * This is the configuration for the main queue.
- *
- */
-@Component(label="Apache Sling Job Default Queue",
- description="The configuration of the default job queue.",
- name="org.apache.sling.event.impl.jobs.DefaultJobManager",
- metatype=true)
-@Service(value=MainQueueConfiguration.class)
-@Properties({
- @Property(name=ConfigurationConstants.PROP_PRIORITY,
- value=ConfigurationConstants.DEFAULT_PRIORITY,
- options={@PropertyOption(name="NORM",value="Norm"),
- @PropertyOption(name="MIN",value="Min"),
- @PropertyOption(name="MAX",value="Max")},
- label="Priority",
- description="The priority for the threads used by this queue. Default is norm."),
- @Property(name=ConfigurationConstants.PROP_RETRIES,
- intValue=ConfigurationConstants.DEFAULT_RETRIES,
- label="Maximum Retries",
- description="The maximum number of times a failed job slated "
- + "for retries is actually retried. If a job has been retried this number of "
- + "times and still fails, it is not rescheduled and assumed to have failed. The "
- + "default value is 10."),
- @Property(name=ConfigurationConstants.PROP_RETRY_DELAY,
- longValue=ConfigurationConstants.DEFAULT_RETRY_DELAY,
- label="Retry Delay",
- description="The number of milliseconds to sleep between two "
- + "consecutive retries of a job which failed and was set to be retried. The "
- + "default value is 2 seconds. This value is only relevant if there is a single "
- + "failed job in the queue. If there are multiple failed jobs, each job is "
- + "retried in turn without an intervening delay."),
- @Property(name=ConfigurationConstants.PROP_MAX_PARALLEL,
- intValue=ConfigurationConstants.DEFAULT_MAX_PARALLEL,
- label="Maximum Parallel Jobs",
- description="The maximum number of parallel jobs started for this queue. "
- + "A value of -1 is substituted with the number of available processors."),
-})
-public class MainQueueConfiguration {
-
- public static final String MAIN_QUEUE_NAME = "<main queue>";
-
- /** Default logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- private InternalQueueConfiguration mainConfiguration;
-
- /**
- * Activate this component.
- * @param props Configuration properties
- */
- @Activate
- protected void activate(final Map<String, Object> props) {
- this.update(props);
- }
-
- /**
- * Configure this component.
- * @param props Configuration properties
- */
- @Modified
- protected void update(final Map<String, Object> props) {
- // create a new dictionary with the missing info and do some sanity puts
- final Map<String, Object> queueProps = new HashMap<String, Object>(props);
- queueProps.put(ConfigurationConstants.PROP_TOPICS, "*");
- queueProps.put(ConfigurationConstants.PROP_NAME, MAIN_QUEUE_NAME);
- queueProps.put(ConfigurationConstants.PROP_TYPE, InternalQueueConfiguration.Type.UNORDERED);
- queueProps.put(Constants.SERVICE_PID, "org.apache.sling.event.impl.jobs.DefaultJobManager");
- logger.debug("properties for queue {}: {}", MAIN_QUEUE_NAME, queueProps);
- this.mainConfiguration = InternalQueueConfiguration.fromConfiguration(queueProps);
- }
-
- /**
- * Return the main queue configuration object.
- * @return The main queue configuration object.
- */
- public InternalQueueConfiguration getMainConfiguration() {
- return this.mainConfiguration;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java b/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java
deleted file mode 100644
index ce2b79a..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.event.impl.support.ResourceHelper;
-
-
-/**
- * The queue manager manages queue configurations.
- */
-@Component
-@Service(value=QueueConfigurationManager.class)
-@Reference(referenceInterface=InternalQueueConfiguration.class, policy=ReferencePolicy.DYNAMIC,
- cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
- bind="bindConfig", unbind="unbindConfig", updated="updateConfig")
-public class QueueConfigurationManager {
-
- /** Empty configuration array. */
- private static final InternalQueueConfiguration[] EMPTY_CONFIGS = new InternalQueueConfiguration[0];
-
- /** Configurations - ordered by service ranking. */
- private volatile InternalQueueConfiguration[] orderedConfigs = EMPTY_CONFIGS;
-
- /** All configurations. */
- private final List<InternalQueueConfiguration> configurations = new ArrayList<InternalQueueConfiguration>();
-
- /** The main queue configuration. */
- @Reference
- private MainQueueConfiguration mainQueueConfiguration;
-
- /**
- * Add a new queue configuration.
- * @param config A new queue configuration.
- */
- protected void bindConfig(final InternalQueueConfiguration config) {
- synchronized ( configurations ) {
- configurations.add(config);
- this.createConfigurationCache();
- }
- }
-
- /**
- * Remove a queue configuration.
- * @param config The queue configuration.
- */
- protected void unbindConfig(final InternalQueueConfiguration config) {
- synchronized ( configurations ) {
- configurations.remove(config);
- this.createConfigurationCache();
- }
- }
-
- /**
- * Update a queue configuration.
- * @param config The queue configuration.
- */
- protected void updateConfig(final InternalQueueConfiguration config) {
- // InternalQueueConfiguration does not implement modified atm,
- // but we handle this case anyway
- synchronized ( configurations ) {
- this.createConfigurationCache();
- }
- }
-
- /**
- * Create the configurations cache used by clients.
- */
- private void createConfigurationCache() {
- if ( this.configurations.isEmpty() ) {
- this.orderedConfigs = EMPTY_CONFIGS;
- } else {
- Collections.sort(configurations);
- orderedConfigs = configurations.toArray(new InternalQueueConfiguration[configurations.size()]);
- }
- }
-
- /**
- * Return all configurations.
- * @return An array with all queue configurations except the main queue. Array might be empty.
- */
- public InternalQueueConfiguration[] getConfigurations() {
- return orderedConfigs;
- }
-
- /**
- * Get the configuration for the main queue.
- * @return The configuration for the main queue.
- */
- public InternalQueueConfiguration getMainQueueConfiguration() {
- return this.mainQueueConfiguration.getMainConfiguration();
- }
-
- public static final class QueueInfo {
- public InternalQueueConfiguration queueConfiguration;
- public String queueName;
- public String targetId;
-
- @Override
- public String toString() {
- return queueName;
- }
-
- @Override
- public int hashCode() {
- return queueName.hashCode();
- }
-
- @Override
- public boolean equals(final Object obj) {
- if ( obj == this ) {
- return true;
- }
- if ( obj instanceof QueueInfo ) {
- return ((QueueInfo)obj).queueName.equals(this.queueName);
- }
- return false;
- }
- }
-
- /**
- * Find the queue configuration for the job.
- * This method only returns a configuration if one matches.
- */
- public QueueInfo getQueueInfo(final String topic) {
- final InternalQueueConfiguration[] configurations = this.getConfigurations();
- for(final InternalQueueConfiguration config : configurations) {
- if ( config.isValid() ) {
- final String qn = config.match(topic);
- if ( qn != null ) {
- final QueueInfo result = new QueueInfo();
- result.queueConfiguration = config;
- result.queueName = ResourceHelper.filterName(qn);
-
- return result;
- }
- }
- }
- final QueueInfo result = new QueueInfo();
- result.queueConfiguration = this.mainQueueConfiguration.getMainConfiguration();
- result.queueName = result.queueConfiguration.getName();
-
- return result;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java
deleted file mode 100644
index 75532fb..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.sling.discovery.InstanceDescription;
-import org.apache.sling.discovery.TopologyView;
-import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.event.jobs.QueueConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The capabilities of a topology.
- */
-public class TopologyCapabilities {
-
- public static final String PROPERTY_TOPICS = "org.apache.sling.event.jobs.consumer.topics";
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** Map: key: topic, value: sling IDs */
- private final Map<String, List<InstanceDescription>> instanceCapabilities;
-
- /** Round robin map. */
- private final Map<String, Integer> roundRobinMap = new HashMap<String, Integer>();
-
- /** Instance map. */
- private final Map<String, InstanceDescription> instanceMap = new HashMap<String, InstanceDescription>();
-
- /** Is this the leader of the cluster? */
- private final boolean isLeader;
-
- /** Is this still active? */
- private volatile boolean active = true;
-
- /** All instances. */
- private final Map<String, String> allInstances;
-
- /** Instance comparator. */
- private final InstanceDescriptionComparator instanceComparator;
-
- /** JobManagerConfiguration. */
- private final JobManagerConfiguration jobManagerConfiguration;
-
- /** Topology view. */
- private final TopologyView view;
-
- public static final class InstanceDescriptionComparator implements Comparator<InstanceDescription> {
-
- private final String localClusterId;
-
-
- public InstanceDescriptionComparator(final String clusterId) {
- this.localClusterId = clusterId;
- }
-
- @Override
- public int compare(final InstanceDescription o1, final InstanceDescription o2) {
- if ( o1.getSlingId().equals(o2.getSlingId()) ) {
- return 0;
- }
- final boolean o1IsLocalCluster = localClusterId.equals(o1.getClusterView().getId());
- final boolean o2IsLocalCluster = localClusterId.equals(o2.getClusterView().getId());
- if ( o1IsLocalCluster && !o2IsLocalCluster ) {
- return -1;
- }
- if ( !o1IsLocalCluster && o2IsLocalCluster ) {
- return 1;
- }
- if ( o1IsLocalCluster ) {
- if ( o1.isLeader() && !o2.isLeader() ) {
- return -1;
- } else if ( o2.isLeader() && !o1.isLeader() ) {
- return 1;
- }
- }
- return o1.getSlingId().compareTo(o2.getSlingId());
- }
- }
-
- public static Map<String, String> getAllInstancesMap(final TopologyView view) {
- final Map<String, String> allInstances = new TreeMap<String, String>();
-
- for(final InstanceDescription desc : view.getInstances() ) {
- final String topics = desc.getProperty(PROPERTY_TOPICS);
- if ( topics != null && topics.length() > 0 ) {
- allInstances.put(desc.getSlingId(), topics);
- } else {
- allInstances.put(desc.getSlingId(), "");
- }
- }
- return allInstances;
- }
-
- /**
- * Create a new instance
- * @param view The new view
- * @param config The current job manager configuration.
- */
- public TopologyCapabilities(final TopologyView view,
- final JobManagerConfiguration config) {
- this.jobManagerConfiguration = config;
- this.instanceComparator = new InstanceDescriptionComparator(view.getLocalInstance().getClusterView().getId());
- this.isLeader = view.getLocalInstance().isLeader();
- this.allInstances = getAllInstancesMap(view);
- final Map<String, List<InstanceDescription>> newCaps = new HashMap<String, List<InstanceDescription>>();
- for(final InstanceDescription desc : view.getInstances() ) {
- final String topics = desc.getProperty(PROPERTY_TOPICS);
- if ( topics != null && topics.length() > 0 ) {
- this.logger.debug("Detected capabilities of {} : {}", desc.getSlingId(), topics);
- for(final String topic : topics.split(",") ) {
- List<InstanceDescription> list = newCaps.get(topic);
- if ( list == null ) {
- list = new ArrayList<InstanceDescription>();
- newCaps.put(topic, list);
- }
- list.add(desc);
- Collections.sort(list, this.instanceComparator);
- }
- }
- this.instanceMap.put(desc.getSlingId(), desc);
- }
- this.instanceCapabilities = newCaps;
- this.view = view;
- }
-
- /**
- * Is this capabilities the same as represented by the provided instance map?
- * @param newAllInstancesMap The instance map
- * @return {@code true} if they represent the same state.
- */
- public boolean isSame(final Map<String, String> newAllInstancesMap) {
- return this.allInstances.equals(newAllInstancesMap);
- }
-
- /**
- * Deactivate this object.
- */
- public void deactivate() {
- this.active = false;
- }
-
- /**
- * Is this object still active?
- * If it is not active anymore it should not be used!
- * @return {@code true} if still active.
- */
- public boolean isActive() {
- return this.active && this.jobManagerConfiguration.isActive() && this.view.isCurrent();
- }
-
- /**
- * Is this instance still active?
- * @param instanceId The instance id
- * @return {@code true} if the instance is active.
- */
- public boolean isActive(final String instanceId) {
- return this.allInstances.containsKey(instanceId);
- }
- /**
- * Is the current instance the leader?
- */
- public boolean isLeader() {
- return this.isLeader;
- }
-
- /**
- * Add instances to the list if not already included
- */
- private void addAll(final List<InstanceDescription> potentialTargets, final List<InstanceDescription> newTargets) {
- if ( newTargets != null ) {
- for(final InstanceDescription desc : newTargets) {
- boolean found = false;
- for(final InstanceDescription existingDesc : potentialTargets) {
- if ( desc.getSlingId().equals(existingDesc.getSlingId()) ) {
- found = true;
- break;
- }
- }
- if ( !found ) {
- potentialTargets.add(desc);
- }
- }
- }
- }
-
- /**
- * Return the potential targets (Sling IDs) sorted by ID
- * @return A list of instance descriptions. The list might be empty.
- */
- public List<InstanceDescription> getPotentialTargets(final String jobTopic) {
- // calculate potential targets
- final List<InstanceDescription> potentialTargets = new ArrayList<InstanceDescription>();
-
- // first: topic targets - directly handling the topic
- addAll(potentialTargets, this.instanceCapabilities.get(jobTopic));
-
- // second: category targets - handling the topic category
- int pos = jobTopic.lastIndexOf('/');
- if ( pos > 0 ) {
- final String category = jobTopic.substring(0, pos + 1).concat("*");
- addAll(potentialTargets, this.instanceCapabilities.get(category));
-
- // search deep consumers (since 1.2 of the consumer package)
- do {
- final String subCategory = jobTopic.substring(0, pos + 1).concat("**");
- addAll(potentialTargets, this.instanceCapabilities.get(subCategory));
-
- pos = jobTopic.lastIndexOf('/', pos - 1);
- } while ( pos > 0 );
- }
- Collections.sort(potentialTargets, this.instanceComparator);
-
- return potentialTargets;
- }
-
- /**
- * Detect the target instance.
- */
- public String detectTarget(final String jobTopic, final Map<String, Object> jobProperties,
- final QueueInfo queueInfo) {
- final List<InstanceDescription> potentialTargets = this.getPotentialTargets(jobTopic);
- logger.debug("Potential targets for {} : {}", jobTopic, potentialTargets);
- String createdOn = null;
- if ( jobProperties != null ) {
- createdOn = (String) jobProperties.get(org.apache.sling.event.jobs.Job.PROPERTY_JOB_CREATED_INSTANCE);
- }
- if ( createdOn == null ) {
- createdOn = Environment.APPLICATION_ID;
- }
- final InstanceDescription createdOnInstance = this.instanceMap.get(createdOn);
-
- if ( potentialTargets != null && potentialTargets.size() > 0 ) {
- if ( createdOnInstance != null ) {
- // create a list with local targets first.
- final List<InstanceDescription> localTargets = new ArrayList<InstanceDescription>();
- for(final InstanceDescription desc : potentialTargets) {
- if ( desc.getClusterView().getId().equals(createdOnInstance.getClusterView().getId()) ) {
- if ( !this.jobManagerConfiguration.disableDistribution() || desc.isLeader() ) {
- localTargets.add(desc);
- }
- }
- }
- if ( localTargets.size() > 0 ) {
- potentialTargets.clear();
- potentialTargets.addAll(localTargets);
- logger.debug("Potential targets filtered for {} : {}", jobTopic, potentialTargets);
- }
- }
- // check prefer run on creation instance
- if ( queueInfo.queueConfiguration.isPreferRunOnCreationInstance() ) {
- InstanceDescription creationDesc = null;
- for(final InstanceDescription desc : potentialTargets) {
- if ( desc.getSlingId().equals(createdOn) ) {
- creationDesc = desc;
- break;
- }
- }
- if ( creationDesc != null ) {
- potentialTargets.clear();
- potentialTargets.add(creationDesc);
- logger.debug("Potential targets reduced to creation instance for {} : {}", jobTopic, potentialTargets);
- }
- }
- if ( queueInfo.queueConfiguration.getType() == QueueConfiguration.Type.ORDERED ) {
- // for ordered queues we always pick the first as we have to pick the same target on each cluster view
- // on all instances (TODO - we could try to do some round robin of the whole queue)
- final String result = potentialTargets.get(0).getSlingId();
- logger.debug("Target for {} : {}", jobTopic, result);
-
- return result;
- }
- // TODO - this is a simple round robin which is not based on the actual load
- // of the instances
- Integer index = this.roundRobinMap.get(jobTopic);
- if ( index == null ) {
- index = 0;
- }
- if ( index >= potentialTargets.size() ) {
- index = 0;
- }
- this.roundRobinMap.put(jobTopic, index + 1);
- final String result = potentialTargets.get(index).getSlingId();
- logger.debug("Target for {} : {}", jobTopic, result);
- return result;
- }
-
- return null;
- }
-
- /**
- * Get the instance capabilities.
- * @return The map of instance capabilities.
- */
- public Map<String, List<InstanceDescription>> getInstanceCapabilities() {
- return this.instanceCapabilities;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java
deleted file mode 100644
index 2c49ca2..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.config;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.discovery.TopologyEvent;
-import org.apache.sling.discovery.TopologyEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This topology handler is handling the topology change events asynchronously
- * and processes them by queuing them.
- */
-@Component
-@Service(value = TopologyEventListener.class)
-public class TopologyHandler implements TopologyEventListener, Runnable {
-
- /** The logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
-
- @Reference
- private JobManagerConfiguration configuration;
-
- /** A local queue for async handling of the events */
- private final BlockingQueue<QueueItem> queue = new LinkedBlockingQueue<QueueItem>();
-
- /** Active flag. */
- private final AtomicBoolean isActive = new AtomicBoolean(false);
-
- @Activate
- protected void activate() {
- this.isActive.set(true);
- final Thread thread = new Thread(this, "Apache Sling Job Topology Listener Thread");
- thread.setDaemon(true);
-
- thread.start();
- }
-
- @Deactivate
- protected void deactivate() {
- this.isActive.set(false);
- this.queue.clear();
- try {
- this.queue.put(new QueueItem());
- } catch ( final InterruptedException ie) {
- logger.warn("Thread got interrupted.", ie);
- Thread.currentThread().interrupt();
- }
- }
-
- @Override
- public void handleTopologyEvent(final TopologyEvent event) {
- final QueueItem item = new QueueItem();
- item.event = event;
- try {
- this.queue.put(item);
- } catch ( final InterruptedException ie) {
- logger.warn("Thread got interrupted.", ie);
- Thread.currentThread().interrupt();
- }
- }
-
- @Override
- public void run() {
- while ( isActive.get() ) {
- QueueItem item = null;
- try {
- item = this.queue.take();
- } catch ( final InterruptedException ie) {
- logger.warn("Thread got interrupted.", ie);
- Thread.currentThread().interrupt();
- isActive.set(false);
- }
- if ( isActive.get() && item != null && item.event != null ) {
- final JobManagerConfiguration config = this.configuration;
- if ( config != null ) {
- config.handleTopologyEvent(item.event);
- }
- }
- }
- }
-
- /**
- * We need a holder class to be able to put something into the queue to stop it.
- */
- public static final class QueueItem {
- public TopologyEvent event;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java b/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java
deleted file mode 100644
index 619136c..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.console;
-
-import java.io.PrintWriter;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.felix.inventory.Format;
-import org.apache.felix.inventory.InventoryPrinter;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.discovery.InstanceDescription;
-import org.apache.sling.event.impl.jobs.JobConsumerManager;
-import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.jobs.JobManager;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.QueueConfiguration;
-import org.apache.sling.event.jobs.ScheduleInfo;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-import org.apache.sling.event.jobs.Statistics;
-import org.apache.sling.event.jobs.TopicStatistics;
-
-/**
- * This is a inventory plugin displaying the active queues, some statistics
- * and the configurations.
- * @since 3.2
- */
-@Component
-@Service(value={InventoryPrinter.class})
-@Properties({
- @Property(name=InventoryPrinter.NAME, value="slingjobs"),
- @Property(name=InventoryPrinter.TITLE, value="Sling Jobs"),
- @Property(name=InventoryPrinter.FORMAT, value={"TEXT", "JSON"}),
- @Property(name=InventoryPrinter.WEBCONSOLE, boolValue=false)
-})
-public class InventoryPlugin implements InventoryPrinter {
-
- @Reference
- private JobManager jobManager;
-
- @Reference
- private JobManagerConfiguration configuration;
-
- @Reference
- private JobConsumerManager jobConsumerManager;
-
- /**
- * Format an array.
- */
- private String formatArrayAsText(final String[] array) {
- if ( array == null || array.length == 0 ) {
- return "";
- }
- return Arrays.toString(array);
- }
-
- private String formatType(final QueueConfiguration.Type type) {
- switch ( type ) {
- case ORDERED : return "Ordered";
- case TOPIC_ROUND_ROBIN : return "Topic Round Robin";
- case UNORDERED : return "Parallel";
- }
- return type.toString();
- }
-
- /** Default date format used. */
- private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS yyyy-MMM-dd");
-
- /**
- * Format a date
- */
- private synchronized String formatDate(final long time) {
- if ( time == -1 ) {
- return "-";
- }
- final Date d = new Date(time);
- return dateFormat.format(d);
- }
-
- /**
- * Format time (= duration)
- */
- private String formatTime(final long time) {
- if ( time == 0 ) {
- return "-";
- }
- if ( time < 1000 ) {
- return time + " ms";
- } else if ( time < 1000 * 60 ) {
- return time / 1000 + " secs";
- }
- final long min = time / 1000 / 60;
- final long secs = (time - min * 1000 * 60);
- return min + " min " + secs / 1000 + " secs";
- }
-
- /**
- * @see org.apache.felix.inventory.InventoryPrinter#print(java.io.PrintWriter, org.apache.felix.inventory.Format, boolean)
- */
- @Override
- public void print(final PrintWriter pw, final Format format, final boolean isZip) {
- if ( format.equals(Format.TEXT) ) {
- printText(pw);
- } else if ( format.equals(Format.JSON) ) {
- printJson(pw);
- }
- }
-
- private void printText(final PrintWriter pw) {
- pw.println("Apache Sling Job Handling");
- pw.println("-------------------------");
-
- String topics = this.jobConsumerManager.getTopics();
- if ( topics == null ) {
- topics = "";
- }
-
- Statistics s = this.jobManager.getStatistics();
- pw.println("Overall Statistics");
- pw.printf("Start Time : %s%n", formatDate(s.getStartTime()));
- pw.printf("Local topic consumers: %s%n", topics);
- pw.printf("Last Activated : %s%n", formatDate(s.getLastActivatedJobTime()));
- pw.printf("Last Finished : %s%n", formatDate(s.getLastFinishedJobTime()));
- pw.printf("Queued Jobs : %s%n", s.getNumberOfQueuedJobs());
- pw.printf("Active Jobs : %s%n", s.getNumberOfActiveJobs());
- pw.printf("Jobs : %s%n", s.getNumberOfJobs());
- pw.printf("Finished Jobs : %s%n", s.getNumberOfFinishedJobs());
- pw.printf("Failed Jobs : %s%n", s.getNumberOfFailedJobs());
- pw.printf("Cancelled Jobs : %s%n", s.getNumberOfCancelledJobs());
- pw.printf("Processed Jobs : %s%n", s.getNumberOfProcessedJobs());
- pw.printf("Average Processing Time : %s%n", formatTime(s.getAverageProcessingTime()));
- pw.printf("Average Waiting Time : %s%n", formatTime(s.getAverageWaitingTime()));
- pw.println();
-
- pw.println("Topology Capabilities");
- final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
- if ( cap == null ) {
- pw.print("No topology information available !");
- } else {
- final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
- for(final Map.Entry<String, List<InstanceDescription>> entry : instanceCaps.entrySet()) {
- final StringBuilder sb = new StringBuilder();
- for(final InstanceDescription id : entry.getValue()) {
- if ( sb.length() > 0 ) {
- sb.append(", ");
- }
- if ( id.isLocal() ) {
- sb.append("local");
- } else {
- sb.append(id.getSlingId());
- }
- }
- pw.printf("%s : %s%n", entry.getKey(), sb.toString());
- }
- }
- pw.println();
-
- pw.println("Scheduled Jobs");
- final Collection<ScheduledJobInfo> infos = this.jobManager.getScheduledJobs();
- if ( infos.size() == 0 ) {
- pw.print("No jobs currently scheduled");
- } else {
- for(final ScheduledJobInfo info : infos) {
- pw.println("Schedule");
- pw.printf("Job Topic< : %s%n", info.getJobTopic());
- pw.print("Schedules : ");
- boolean first = true;
- for(final ScheduleInfo si : info.getSchedules() ) {
- if ( !first ) {
- pw.print(", ");
- }
- first = false;
- switch ( si.getType() ) {
- case YEARLY : pw.printf("YEARLY %s %s : %s:%s", si.getMonthOfYear(), si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case MONTHLY : pw.printf("MONTHLY %s : %s:%s", si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case WEEKLY : pw.printf("WEEKLY %s : %s:%s", si.getDayOfWeek(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case DAILY : pw.printf("DAILY %s:%s", si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case HOURLY : pw.printf("HOURLY %s", si.getMinuteOfHour());
- break;
- case CRON : pw.printf("CRON %s", si.getExpression());
- break;
- default : pw.printf("AT %s", si.getAt());
- }
- }
- pw.println();
- pw.println();
- }
- }
- pw.println();
-
- boolean isEmpty = true;
- for(final Queue q : this.jobManager.getQueues()) {
- isEmpty = false;
- pw.printf("Active JobQueue: %s %s%n", q.getName(),
- q.isSuspended() ? "(SUSPENDED)" : "");
-
- s = q.getStatistics();
- final QueueConfiguration c = q.getConfiguration();
- pw.println("Statistics");
- pw.printf("Start Time : %s%n", formatDate(s.getStartTime()));
- pw.printf("Last Activated : %s%n", formatDate(s.getLastActivatedJobTime()));
- pw.printf("Last Finished : %s%n", formatDate(s.getLastFinishedJobTime()));
- pw.printf("Queued Jobs : %s%n", s.getNumberOfQueuedJobs());
- pw.printf("Active Jobs : %s%n", s.getNumberOfActiveJobs());
- pw.printf("Jobs : %s%n", s.getNumberOfJobs());
- pw.printf("Finished Jobs : %s%n", s.getNumberOfFinishedJobs());
- pw.printf("Failed Jobs : %s%n", s.getNumberOfFailedJobs());
- pw.printf("Cancelled Jobs : %s%n", s.getNumberOfCancelledJobs());
- pw.printf("Processed Jobs : %s%n", s.getNumberOfProcessedJobs());
- pw.printf("Average Processing Time : %s%n", formatTime(s.getAverageProcessingTime()));
- pw.printf("Average Waiting Time : %s%n", formatTime(s.getAverageWaitingTime()));
- pw.printf("Status Info : %s%n", q.getStateInfo());
- pw.println("Configuration");
- pw.printf("Type : %s%n", formatType(c.getType()));
- pw.printf("Topics : %s%n", formatArrayAsText(c.getTopics()));
- pw.printf("Max Parallel : %s%n", c.getMaxParallel());
- pw.printf("Max Retries : %s%n", c.getMaxRetries());
- pw.printf("Retry Delay : %s ms%n", c.getRetryDelayInMs());
- pw.printf("Priority : %s%n", c.getThreadPriority());
- pw.println();
- }
- if ( isEmpty ) {
- pw.println("No active queues.");
- pw.println();
- }
-
- for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
- pw.printf("Topic Statistics - %s%n", ts.getTopic());
- pw.printf("Last Activated : %s%n", formatDate(ts.getLastActivatedJobTime()));
- pw.printf("Last Finished : %s%n", formatDate(ts.getLastFinishedJobTime()));
- pw.printf("Finished Jobs : %s%n", ts.getNumberOfFinishedJobs());
- pw.printf("Failed Jobs : %s%n", ts.getNumberOfFailedJobs());
- pw.printf("Cancelled Jobs : %s%n", ts.getNumberOfCancelledJobs());
- pw.printf("Processed Jobs : %s%n", ts.getNumberOfProcessedJobs());
- pw.printf("Average Processing Time : %s%n", formatTime(ts.getAverageProcessingTime()));
- pw.printf("Average Waiting Time : %s%n", formatTime(ts.getAverageWaitingTime()));
- pw.println();
- }
-
- pw.println("Apache Sling Job Handling - Job Queue Configurations");
- pw.println("----------------------------------------------------");
- this.printQueueConfiguration(pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
- final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
- for(final InternalQueueConfiguration c : configs ) {
- this.printQueueConfiguration(pw, c);
- }
- }
-
- private void printQueueConfiguration(final PrintWriter pw, final InternalQueueConfiguration c) {
- pw.printf("Job Queue Configuration: %s%n",
- c.getName());
- pw.printf("Valid : %s%n", c.isValid());
- pw.printf("Type : %s%n", formatType(c.getType()));
- pw.printf("Topics : %s%n", formatArrayAsText(c.getTopics()));
- pw.printf("Max Parallel : %s%n", c.getMaxParallel());
- pw.printf("Max Retries : %s%n", c.getMaxRetries());
- pw.printf("Retry Delay : %s ms%n", c.getRetryDelayInMs());
- pw.printf("Priority : %s%n", c.getThreadPriority());
- pw.printf("Ranking : %s%n", c.getRanking());
-
- pw.println();
- }
-
- private void printJson(final PrintWriter pw) {
- pw.println("{");
- Statistics s = this.jobManager.getStatistics();
- pw.println(" \"statistics\" : {");
- pw.printf(" \"startTime\" : %s,%n", s.getStartTime());
- pw.printf(" \"startTimeText\" : \"%s\",%n", formatDate(s.getStartTime()));
- pw.printf(" \"lastActivatedJobTime\" : %s,%n", s.getLastActivatedJobTime());
- pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(s.getLastActivatedJobTime()));
- pw.printf(" \"lastFinishedJobTime\" : %s,%n", s.getLastFinishedJobTime());
- pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(s.getLastFinishedJobTime()));
- pw.printf(" \"numberOfQueuedJobs\" : %s,%n", s.getNumberOfQueuedJobs());
- pw.printf(" \"numberOfActiveJobs\" : %s,%n", s.getNumberOfActiveJobs());
- pw.printf(" \"numberOfJobs\" : %s,%n", s.getNumberOfJobs());
- pw.printf(" \"numberOfFinishedJobs\" : %s,%n", s.getNumberOfFinishedJobs());
- pw.printf(" \"numberOfFailedJobs\" : %s,%n", s.getNumberOfFailedJobs());
- pw.printf(" \"numberOfCancelledJobs\" : %s,%n", s.getNumberOfCancelledJobs());
- pw.printf(" \"numberOfProcessedJobs\" : %s,%n", s.getNumberOfProcessedJobs());
- pw.printf(" \"averageProcessingTime\" : %s,%n", s.getAverageProcessingTime());
- pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(s.getAverageProcessingTime()));
- pw.printf(" \"averageWaitingTime\" : %s,%n", s.getAverageWaitingTime());
- pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(s.getAverageWaitingTime()));
- pw.print(" }");
-
- final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
- if ( cap != null ) {
- pw.println(",");
- pw.println(" \"capabilities\" : [");
- final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
- final Iterator<Map.Entry<String, List<InstanceDescription>>> iter = instanceCaps.entrySet().iterator();
- while ( iter.hasNext() ) {
- final Map.Entry<String, List<InstanceDescription>> entry = iter.next();
- final List<String> instances = new ArrayList<String>();
- for(final InstanceDescription id : entry.getValue()) {
- if ( id.isLocal() ) {
- instances.add("local");
- } else {
- instances.add(id.getSlingId());
- }
- }
- pw.println(" {");
- pw.printf(" \"topic\" : \"%s\",%n", entry.getKey());
- pw.printf(" \"instances\" : %s%n", formatArrayAsJson(instances.toArray(new String[instances.size()])));
- if ( iter.hasNext() ) {
- pw.println(" },");
- } else {
- pw.println(" }");
- }
- }
- pw.print(" ]");
- }
-
- boolean first = true;
- for(final Queue q : this.jobManager.getQueues()) {
- pw.println(",");
- if ( first ) {
- pw.println(" \"queues\" : [");
- first = false;
- }
- pw.println(" {");
- pw.printf(" \"name\" : \"%s\",%n", q.getName());
- pw.printf(" \"suspended\" : %s,%n", q.isSuspended());
-
- s = q.getStatistics();
- pw.println(" \"statistics\" : {");
- pw.printf(" \"startTime\" : %s,%n", s.getStartTime());
- pw.printf(" \"startTimeText\" : \"%s\",%n", formatDate(s.getStartTime()));
- pw.printf(" \"lastActivatedJobTime\" : %s,%n", s.getLastActivatedJobTime());
- pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(s.getLastActivatedJobTime()));
- pw.printf(" \"lastFinishedJobTime\" : %s,%n", s.getLastFinishedJobTime());
- pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(s.getLastFinishedJobTime()));
- pw.printf(" \"numberOfQueuedJobs\" : %s,%n", s.getNumberOfQueuedJobs());
- pw.printf(" \"numberOfActiveJobs\" : %s,%n", s.getNumberOfActiveJobs());
- pw.printf(" \"numberOfJobs\" : %s,%n", s.getNumberOfJobs());
- pw.printf(" \"numberOfFinishedJobs\" : %s,%n", s.getNumberOfFinishedJobs());
- pw.printf(" \"numberOfFailedJobs\" : %s,%n", s.getNumberOfFailedJobs());
- pw.printf(" \"numberOfCancelledJobs\" : %s,%n", s.getNumberOfCancelledJobs());
- pw.printf(" \"numberOfProcessedJobs\" : %s,%n", s.getNumberOfProcessedJobs());
- pw.printf(" \"averageProcessingTime\" : %s,%n", s.getAverageProcessingTime());
- pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(s.getAverageProcessingTime()));
- pw.printf(" \"averageWaitingTime\" : %s,%n", s.getAverageWaitingTime());
- pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(s.getAverageWaitingTime()));
- pw.print(" },");
-
- final QueueConfiguration c = q.getConfiguration();
- pw.printf(" \"stateInfo\" : \"%s\",%n", q.getStateInfo());
- pw.println(" \"configuration\" : {");
- pw.printf(" \"type\" : \"%s\",%n", c.getType());
- pw.printf(" \"topics\" : \"%s\",%n", formatArrayAsJson(c.getTopics()));
- pw.printf(" \"maxParallel\" : %s,%n", c.getMaxParallel());
- pw.printf(" \"maxRetries\" : %s,%n", c.getMaxRetries());
- pw.printf(" \"retryDelayInMs\" : %s,%n", c.getRetryDelayInMs());
- pw.printf(" \"priority\" : \"%s\"%n", c.getThreadPriority());
- pw.println(" }");
- pw.print(" }");
- }
- if ( !first ) {
- pw.print(" ]");
- }
-
- first = true;
- for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
- pw.println(",");
- if ( first ) {
- pw.println(" \"topicStatistics\" : [");
- first = false;
- }
- pw.println(" {");
- pw.printf(" \"topic\" : \"%s\",%n", ts.getTopic());
- pw.printf(" \"lastActivatedJobTime\" : %s,%n", ts.getLastActivatedJobTime());
- pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(ts.getLastActivatedJobTime()));
- pw.printf(" \"lastFinishedJobTime\" : %s,%n", ts.getLastFinishedJobTime());
- pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(ts.getLastFinishedJobTime()));
- pw.printf(" \"numberOfFinishedJobs\" : %s,%n", ts.getNumberOfFinishedJobs());
- pw.printf(" \"numberOfFailedJobs\" : %s,%n", ts.getNumberOfFailedJobs());
- pw.printf(" \"numberOfCancelledJobs\" : %s,%n", ts.getNumberOfCancelledJobs());
- pw.printf(" \"numberOfProcessedJobs\" : %s,%n", ts.getNumberOfProcessedJobs());
- pw.printf(" \"averageProcessingTime\" : %s,%n", ts.getAverageProcessingTime());
- pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(ts.getAverageProcessingTime()));
- pw.printf(" \"averageWaitingTime\" : %s,%n", ts.getAverageWaitingTime());
- pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(ts.getAverageWaitingTime()));
- pw.print(" }");
- }
- if ( !first ) {
- pw.print(" ]");
- }
-
- pw.println(",");
- pw.println(" \"configurations\" : [");
- this.printQueueConfigurationJson(pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
- final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
- for(final InternalQueueConfiguration c : configs ) {
- pw.println(",");
- this.printQueueConfigurationJson(pw, c);
- }
- pw.println();
- pw.println(" ]");
- pw.println("}");
- }
-
- private void printQueueConfigurationJson(final PrintWriter pw, final InternalQueueConfiguration c) {
- pw.println(" {");
- pw.printf(" \"name\" : \"%s\",%n", c.getName());
- pw.printf(" \"valid\" : %s,%n", c.isValid());
- pw.printf(" \"type\" : \"%s\",%n", c.getType());
- pw.printf(" \"topics\" : %s,%n", formatArrayAsJson(c.getTopics()));
- pw.printf(" \"maxParallel\" : %s,%n", c.getMaxParallel());
- pw.printf(" \"maxRetries\" : %s,%n", c.getMaxRetries());
- pw.printf(" \"retryDelayInMs\" : %s,%n", c.getRetryDelayInMs());
- pw.printf(" \"priority\" : \"%s\",%n", c.getThreadPriority());
- pw.printf(" \"ranking\" : %s%n", c.getRanking());
- pw.print(" }");
- }
-
- /**
- * Format an array.
- */
- private String formatArrayAsJson(final String[] array) {
- if ( array == null || array.length == 0 ) {
- return "[]";
- }
- final StringBuilder sb = new StringBuilder("[");
- boolean first = true;
- for(final String s : array) {
- if ( !first ) {
- sb.append(", ");
- }
- first = false;
- sb.append("\"");
- sb.append(s);
- sb.append("\"");
- }
- sb.append("]");
- return sb.toString();
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java b/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
deleted file mode 100644
index ffe94b5..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.console;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.URLEncoder;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.request.ResponseUtil;
-import org.apache.sling.discovery.InstanceDescription;
-import org.apache.sling.event.impl.jobs.JobConsumerManager;
-import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.JobManager;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.QueueConfiguration;
-import org.apache.sling.event.jobs.ScheduleInfo;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-import org.apache.sling.event.jobs.Statistics;
-import org.apache.sling.event.jobs.TopicStatistics;
-import org.apache.sling.event.jobs.consumer.JobConsumer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This is a web console plugin displaying the active queues, some statistics
- * and the configurations.
- * @since 3.0
- */
-@Component
-@Service(value={javax.servlet.Servlet.class, JobConsumer.class})
-@Properties({
- @Property(name="felix.webconsole.label", value="slingevent"),
- @Property(name="felix.webconsole.title", value="Jobs"),
- @Property(name="felix.webconsole.category", value="Sling"),
- @Property(name=JobConsumer.PROPERTY_TOPICS, value={"sling/webconsole/test"})
-})
-public class WebConsolePlugin extends HttpServlet implements JobConsumer {
-
- private static final String SLING_WEBCONSOLE_TEST_JOB_TOPIC = "sling/webconsole/test";
-
- private static final long serialVersionUID = -6983227434841706385L;
-
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Reference
- private JobManager jobManager;
-
- @Reference
- private JobManagerConfiguration configuration;
-
- @Reference
- private JobConsumerManager jobConsumerManager;
-
- private static final String PAR_QUEUE = "queue";
-
- private Queue getQueue(final HttpServletRequest req) {
- final String name = req.getParameter(PAR_QUEUE);
- if ( name != null ) {
- for(final Queue q : this.jobManager.getQueues()) {
- if ( name.equals(q.getName()) ) {
- return q;
- }
- }
- }
- return null;
- }
-
- private String getQueueErrorMessage(final HttpServletRequest req, final String command) {
- final String name = req.getParameter(PAR_QUEUE);
- if ( name == null || name.length() == 0 ) {
- return "Queue parameter missing for opertation " + command;
- }
- return "Queue with name '" + name + "' not found for operation " + command;
- }
-
- @Override
- protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
- throws ServletException, IOException {
- String msg = null;
- final String cmd = req.getParameter("action");
- if ( "suspend".equals(cmd) ) {
- final Queue q = this.getQueue(req);
- if ( q != null ) {
- q.suspend();
- } else {
- msg = this.getQueueErrorMessage(req, "suspend");
- }
- } else if ( "resume".equals(cmd) ) {
- final Queue q = this.getQueue(req);
- if ( q != null ) {
- q.resume();
- } else {
- msg = this.getQueueErrorMessage(req, "resume");
- }
- } else if ( "reset".equals(cmd) ) {
- if ( req.getParameter(PAR_QUEUE) == null || req.getParameter(PAR_QUEUE).length() == 0 ) {
- this.jobManager.getStatistics().reset();
- } else {
- final Queue q = this.getQueue(req);
- if ( q != null ) {
- q.getStatistics().reset();
- } else {
- msg = this.getQueueErrorMessage(req, "reset");
- }
- }
- } else if ( "test".equals(cmd) ) {
- this.startTestJob();
- } else if ( "dropall".equals(cmd) ) {
- final Queue q = this.getQueue(req);
- if ( q != null ) {
- q.removeAll();
- } else {
- msg = this.getQueueErrorMessage(req, "drop all");
- }
- } else {
- msg = "Unknown command";
- }
- final String path = req.getContextPath() + req.getServletPath() + req.getPathInfo();
- final String redirectTo;
- if ( msg == null ) {
- redirectTo = path;
- } else {
- redirectTo = path + "?message=" + URLEncoder.encode(msg, "UTF-8");
- }
- resp.sendRedirect(resp.encodeRedirectURL(redirectTo));
- }
-
- private void startTestJob() {
- logger.info("Adding test job: {}", SLING_WEBCONSOLE_TEST_JOB_TOPIC);
- this.jobManager.addJob(SLING_WEBCONSOLE_TEST_JOB_TOPIC, null);
- }
-
- @Override
- protected void doGet(final HttpServletRequest req, final HttpServletResponse res)
- throws ServletException, IOException {
- final String msg = req.getParameter("message");
- final PrintWriter pw = res.getWriter();
-
- pw.println("<form method='POST' name='eventingcmd'>" +
- "<input type='hidden' name='action' value=''/>"+
- "<input type='hidden' name='queue' value=''/>" +
- "</form>");
- pw.println("<script type='text/javascript'>");
- pw.println("function eventingsubmit(action, queue) {" +
- " document.forms['eventingcmd'].action.value = action;" +
- " document.forms['eventingcmd'].queue.value = queue;" +
- " document.forms['eventingcmd'].submit();" +
- "} </script>");
-
- pw.printf("<p class='statline ui-state-highlight'>Apache Sling Job Handling%s%n</p>",
- msg != null ? " : " + ResponseUtil.escapeXml(msg) : "");
- pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
- pw.println("<span style='float: left; margin-left: 1em'>Apache Sling Job Handling: Overall Statistics</span>");
- this.printForm(pw, null, "Reset Stats", "reset");
- pw.println("</div>");
-
- pw.println("<table class='nicetable'><tbody>");
- String topics = this.jobConsumerManager.getTopics();
- if ( topics == null ) {
- topics = "";
- } else {
- final String[] allTopics = topics.split(",");
- final StringBuilder sb = new StringBuilder();
- boolean first = true;
- for(final String t : allTopics) {
- if ( first) {
- first = false;
- } else {
- sb.append("<br/>");
- }
- sb.append(ResponseUtil.escapeXml(t));
- }
- topics = sb.toString();
- }
- Statistics s = this.jobManager.getStatistics();
- pw.printf("<tr><td>Start Time</td><td>%s</td></tr>", formatDate(s.getStartTime()));
- pw.printf("<tr><td>Local topic consumers: </td><td>%s</td></tr>", topics);
- pw.printf("<tr><td>Last Activated</td><td>%s</td></tr>", formatDate(s.getLastActivatedJobTime()));
- pw.printf("<tr><td>Last Finished</td><td>%s</td></tr>", formatDate(s.getLastFinishedJobTime()));
- pw.printf("<tr><td>Queued Jobs</td><td>%s</td></tr>", s.getNumberOfQueuedJobs());
- pw.printf("<tr><td>Active Jobs</td><td>%s</td></tr>", s.getNumberOfActiveJobs());
- pw.printf("<tr><td>Jobs</td><td>%s</td></tr>", s.getNumberOfJobs());
- pw.printf("<tr><td>Finished Jobs</td><td>%s</td></tr>", s.getNumberOfFinishedJobs());
- pw.printf("<tr><td>Failed Jobs</td><td>%s</td></tr>", s.getNumberOfFailedJobs());
- pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td></tr>", s.getNumberOfCancelledJobs());
- pw.printf("<tr><td>Processed Jobs</td><td>%s</td></tr>", s.getNumberOfProcessedJobs());
- pw.printf("<tr><td>Average Processing Time</td><td>%s</td></tr>", formatTime(s.getAverageProcessingTime()));
- pw.printf("<tr><td>Average Waiting Time</td><td>%s</td></tr>", formatTime(s.getAverageWaitingTime()));
- pw.println("</tbody></table>");
- pw.println("<br/>");
-
- pw.println("<table class='nicetable'><tbody>");
- pw.println("<tr><th colspan='2'>Topology Capabilities</th></tr>");
- final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
- if ( cap == null ) {
- pw.print("<tr><td colspan='2'>No topology information available !</td></tr>");
- } else {
- final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
- for(final Map.Entry<String, List<InstanceDescription>> entry : instanceCaps.entrySet()) {
- final StringBuilder sb = new StringBuilder();
- for(final InstanceDescription id : entry.getValue()) {
- if ( sb.length() > 0 ) {
- sb.append("<br/>");
- }
- if ( id.isLocal() ) {
- sb.append("<b>local</b>");
- } else {
- sb.append(ResponseUtil.escapeXml(id.getSlingId()));
- }
- }
- pw.printf("<tr><td>%s</td><td>%s</td></tr>", ResponseUtil.escapeXml(entry.getKey()), sb.toString());
- }
- }
- pw.println("</tbody></table>");
- pw.println("<br/>");
-
- pw.println("<p class='statline'>Scheduled Jobs</p>");
- pw.println("<table class='nicetable'><tbody>");
- final Collection<ScheduledJobInfo> infos = this.jobManager.getScheduledJobs();
- if ( infos.size() == 0 ) {
- pw.print("<tr><td colspan='5'>No jobs currently scheduled.</td></tr>");
- } else {
- pw.println("<tr><th>Schedule</th><th>Job Topic</th><th>Schedules</th></tr>");
- int index = 1;
- for(final ScheduledJobInfo info : infos) {
- pw.printf("<tr><td><b>%s</b></td><td>%s</td><td>",
- String.valueOf(index), ResponseUtil.escapeXml(info.getJobTopic()));
- boolean first = true;
- for(final ScheduleInfo si : info.getSchedules() ) {
- if ( !first ) {
- pw.print("<br/>");
- }
- first = false;
- switch ( si.getType() ) {
- case YEARLY : pw.printf("YEARLY %s %s : %s:%s", si.getMonthOfYear(), si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case MONTHLY : pw.printf("MONTHLY %s : %s:%s", si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case WEEKLY : pw.printf("WEEKLY %s : %s:%s", si.getDayOfWeek(), si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case DAILY : pw.printf("DAILY %s:%s", si.getHourOfDay(), si.getMinuteOfHour());
- break;
- case HOURLY : pw.printf("HOURLY %s", si.getMinuteOfHour());
- break;
- case CRON : pw.printf("CRON %s", ResponseUtil.escapeXml(si.getExpression()));
- break;
- default : pw.printf("AT %s", si.getAt());
- }
- }
- pw.print("</td></tr>");
- index++;
- }
- }
- pw.println("</tbody></table>");
- pw.println("<br/>");
-
- boolean isEmpty = true;
- for(final Queue q : this.jobManager.getQueues()) {
- isEmpty = false;
- final String queueName = q.getName();
- pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
- pw.printf("<span style='float: left; margin-left: 1em'>Active JobQueue: %s %s</span>", ResponseUtil.escapeXml(queueName),
- q.isSuspended() ? "(SUSPENDED)" : "");
- this.printForm(pw, queueName, "Reset Stats", "reset");
- if ( q.isSuspended() ) {
- this.printForm(pw, queueName, "Resume", "resume");
- } else {
- this.printForm(pw, queueName, "Suspend", "suspend");
- }
- this.printForm(pw, queueName, "Test", "test");
- this.printForm(pw, queueName, "Drop All", "dropall");
- pw.println("</div>");
- pw.println("<table class='nicetable'><tbody>");
-
- s = q.getStatistics();
- final QueueConfiguration c = q.getConfiguration();
- pw.println("<tr><th colspan='2'>Statistics</th><th colspan='2'>Configuration</th></tr>");
- pw.printf("<tr><td>Start Time</td><td>%s</td><td>Type</td><td>%s</td></tr>", formatDate(s.getStartTime()), formatType(c.getType()));
- pw.printf("<tr><td>Last Activated</td><td>%s</td><td>Topics</td><td>%s</td></tr>", formatDate(s.getLastActivatedJobTime()), formatArray(c.getTopics()));
- pw.printf("<tr><td>Last Finished</td><td>%s</td><td>Max Parallel</td><td>%s</td></tr>", formatDate(s.getLastFinishedJobTime()), c.getMaxParallel());
- pw.printf("<tr><td>Queued Jobs</td><td>%s</td><td>Max Retries</td><td>%s</td></tr>", s.getNumberOfQueuedJobs(), c.getMaxRetries());
- pw.printf("<tr><td>Active Jobs</td><td>%s</td><td>Retry Delay</td><td>%s ms</td></tr>", s.getNumberOfActiveJobs(), c.getRetryDelayInMs());
- pw.printf("<tr><td>Jobs</td><td>%s</td><td>Priority</td><td>%s</td></tr>", s.getNumberOfJobs(), c.getThreadPriority());
- pw.printf("<tr><td>Finished Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfFinishedJobs());
- pw.printf("<tr><td>Failed Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfFailedJobs());
- pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfCancelledJobs());
- pw.printf("<tr><td>Processed Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfProcessedJobs());
- pw.printf("<tr><td>Average Processing Time</td><td>%s</td><td colspan='2'> </td></tr>", formatTime(s.getAverageProcessingTime()));
- pw.printf("<tr><td>Average Waiting Time</td><td>%s</td><td colspan='2'> </td></tr>", formatTime(s.getAverageWaitingTime()));
- pw.printf("<tr><td>Status Info</td><td colspan='3'>%s</td></tr>", ResponseUtil.escapeXml(q.getStateInfo()));
- pw.println("</tbody></table>");
- pw.println("<br/>");
- }
- if ( isEmpty ) {
- pw.println("<p>No active queues.</p>");
- pw.println("<br/>");
- }
-
- for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
- pw.println("<table class='nicetable'><tbody>");
- pw.printf("<tr><th colspan='2'>Topic Statistics: %s</th></tr>", ResponseUtil.escapeXml(ts.getTopic()));
-
- pw.printf("<tr><td>Last Activated</td><td>%s</td></tr>", formatDate(ts.getLastActivatedJobTime()));
- pw.printf("<tr><td>Last Finished</td><td>%s</td></tr>", formatDate(ts.getLastFinishedJobTime()));
- pw.printf("<tr><td>Finished Jobs</td><td>%s</td></tr>", ts.getNumberOfFinishedJobs());
- pw.printf("<tr><td>Failed Jobs</td><td>%s</td></tr>", ts.getNumberOfFailedJobs());
- pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td></tr>", ts.getNumberOfCancelledJobs());
- pw.printf("<tr><td>Processed Jobs</td><td>%s</td></tr>", ts.getNumberOfProcessedJobs());
- pw.printf("<tr><td>Average Processing Time</td><td>%s</td></tr>", formatTime(ts.getAverageProcessingTime()));
- pw.printf("<tr><td>Average Waiting Time</td><td>%s</td></tr>", formatTime(ts.getAverageWaitingTime()));
- pw.println("</tbody></table>");
- pw.println("<br/>");
- }
-
- pw.println("<p class='statline'>Apache Sling Job Handling - Job Queue Configurations</p>");
- this.printQueueConfiguration(req, pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
- final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
- for(final InternalQueueConfiguration c : configs ) {
- this.printQueueConfiguration(req, pw, c);
- }
- }
-
- private void printQueueConfiguration(final HttpServletRequest req, final PrintWriter pw, final InternalQueueConfiguration c) {
- pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
- pw.printf("<span style='float: left; margin-left: 1em'>Job Queue Configuration: %s</span>%n",
- ResponseUtil.escapeXml(c.getName()));
- pw.printf("<button id='edit' class='ui-state-default ui-corner-all' onclick='javascript:window.location=\"%s%s/configMgr/%s\";'>Edit</button>",
- req.getContextPath(), req.getServletPath(), c.getPid());
- this.printForm(pw, c.getName(), "Test", "test");
-
- pw.println("</div>");
- pw.println("<table class='nicetable'><tbody>");
- pw.println("<tr><th colspan='2'>Configuration</th></tr>");
- pw.printf("<tr><td>Valid</td><td>%s</td></tr>", c.isValid());
- pw.printf("<tr><td>Type</td><td>%s</td></tr>", formatType(c.getType()));
- pw.printf("<tr><td>Topics</td><td>%s</td></tr>", formatArray(c.getTopics()));
- pw.printf("<tr><td>Max Parallel</td><td>%s</td></tr>", c.getMaxParallel());
- pw.printf("<tr><td>Max Retries</td><td>%s</td></tr>", c.getMaxRetries());
- pw.printf("<tr><td>Retry Delay</td><td>%s ms</td></tr>", c.getRetryDelayInMs());
- pw.printf("<tr><td>Priority</td><td>%s</td></tr>", c.getThreadPriority());
- pw.printf("<tr><td>Ranking</td><td>%s</td></tr>", c.getRanking());
-
- pw.println("</tbody></table>");
- pw.println("<br/>");
- }
-
- /**
- * Format an array for html rendering.
- */
- private String formatArray(final String[] array) {
- if ( array == null || array.length == 0 ) {
- return "";
- }
- final StringBuilder sb = new StringBuilder();
- boolean first = true;
- for(final String s : array ) {
- if ( !first ) {
- sb.append('\n');
- }
- first = false;
- sb.append(s);
- }
- return ResponseUtil.escapeXml(sb.toString());
- }
-
- private String formatType(final QueueConfiguration.Type type) {
- switch ( type ) {
- case ORDERED : return "Ordered";
- case TOPIC_ROUND_ROBIN : return "Topic Round Robin";
- case UNORDERED : return "Parallel";
- }
- return type.toString();
- }
- /** Default date format used. */
- private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS yyyy-MMM-dd");
-
- /**
- * Format a date
- */
- private synchronized String formatDate(final long time) {
- if ( time == -1 ) {
- return "-";
- }
- final Date d = new Date(time);
- return dateFormat.format(d);
- }
-
- /**
- * Format time (= duration)
- */
- private String formatTime(final long time) {
- if ( time == 0 ) {
- return "-";
- }
- if ( time < 1000 ) {
- return time + " ms";
- } else if ( time < 1000 * 60 ) {
- return time / 1000 + " secs";
- }
- final long min = time / 1000 / 60;
- final long secs = (time - min * 1000 * 60);
- return min + " min " + secs / 1000 + " secs";
- }
-
- private void printForm(final PrintWriter pw,
- final String qeueName,
- final String buttonLabel,
- final String cmd) {
- pw.printf("<button class='ui-state-default ui-corner-all' onclick='javascript:eventingsubmit(\"%s\", \"%s\");'>" +
- "%s</button>", ResponseUtil.escapeXml(cmd), (qeueName != null ? ResponseUtil.escapeXml(qeueName) : ""), ResponseUtil.escapeXml(buttonLabel));
- }
-
- @Override
- public JobResult process(final Job job) {
- logger.info("Received test job {}", job.getTopic());
- return JobResult.OK;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java
deleted file mode 100644
index 8bf4033..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import java.util.Date;
-
-import javax.management.StandardMBean;
-
-import org.apache.sling.event.jobs.Statistics;
-import org.apache.sling.event.jobs.jmx.StatisticsMBean;
-
-public abstract class AbstractJobStatistics extends StandardMBean implements
- StatisticsMBean {
-
- public AbstractJobStatistics() {
- super(StatisticsMBean.class, false);
- }
-
- protected abstract Statistics getStatistics();
-
- public long getAverageProcessingTime() {
- return getStatistics().getAverageProcessingTime();
- }
-
- public long getAverageWaitingTime() {
- return getStatistics().getAverageWaitingTime();
- }
-
- public long getLastActivatedJobTime() {
- return getStatistics().getLastActivatedJobTime();
- }
-
- public long getLastFinishedJobTime() {
- return getStatistics().getLastFinishedJobTime();
- }
-
- public long getNumberOfActiveJobs() {
- return getStatistics().getNumberOfActiveJobs();
- }
-
- public long getNumberOfCancelledJobs() {
- return getStatistics().getNumberOfCancelledJobs();
- }
-
- public long getStartTime() {
- return getStatistics().getStartTime();
- }
-
- public Date getStartDate() {
- return new Date(getStartTime());
- }
-
- public long getNumberOfFinishedJobs() {
- return getStatistics().getNumberOfFinishedJobs();
- }
-
- public long getNumberOfFailedJobs() {
- return getStatistics().getNumberOfFailedJobs();
- }
-
- public long getNumberOfProcessedJobs() {
- return getStatistics().getNumberOfProcessedJobs();
- }
-
- public long getNumberOfQueuedJobs() {
- return getStatistics().getNumberOfQueuedJobs();
- }
-
- public long getNumberOfJobs() {
- return getStatistics().getNumberOfJobs();
- }
-
- public void reset() {
- getStatistics().reset();
- }
-
- public Date getLastActivatedJobDate() {
- return new Date(getStatistics().getLastActivatedJobTime());
- }
-
- public Date getLastFinishedJobDate() {
- return new Date(getStatistics().getLastFinishedJobTime());
- }
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java
deleted file mode 100644
index 0e696d6..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.event.jobs.JobManager;
-import org.apache.sling.event.jobs.Statistics;
-import org.apache.sling.event.jobs.jmx.StatisticsMBean;
-
-@Component
-@Service(value = StatisticsMBean.class)
-@Properties(@Property(name = "jmx.objectname", value = "org.apache.sling:type=queues,name=AllQueues"))
-public class AllJobStatisticsMBean extends AbstractJobStatistics {
-
- private static final long TTL = 1000L;
- private long agregateStatisticsTTL = 0L;
- private Statistics aggregateStatistics;
- @Reference
- private JobManager jobManager;
-
- /**
- * @return the aggregate stats from the job manager.
- */
- @Override
- protected Statistics getStatistics() {
- if (System.currentTimeMillis() > agregateStatisticsTTL) {
- aggregateStatistics = jobManager.getStatistics();
- agregateStatisticsTTL = System.currentTimeMillis() + TTL;
- }
- return aggregateStatistics;
- }
-
- @Override
- public String getName() {
- return "All Queues";
- }
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java
deleted file mode 100644
index 35772ba..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import org.apache.sling.event.jobs.Statistics;
-
-/**
- * Dummy stats that just returns 0 for all info, used where the queue doesnt
- * implement the Statistics interface.
- */
-public class EmptyStatistics implements Statistics {
-
- public long getStartTime() {
- return 0;
- }
-
- public long getNumberOfFinishedJobs() {
- return 0;
- }
-
- public long getNumberOfCancelledJobs() {
- return 0;
- }
-
- public long getNumberOfFailedJobs() {
- return 0;
- }
-
- public long getNumberOfProcessedJobs() {
- return 0;
- }
-
- public long getNumberOfActiveJobs() {
- return 0;
- }
-
- public long getNumberOfQueuedJobs() {
- return 0;
- }
-
- public long getNumberOfJobs() {
- return 0;
- }
-
- public long getLastActivatedJobTime() {
- return 0;
- }
-
- public long getLastFinishedJobTime() {
- return 0;
- }
-
- public long getAverageWaitingTime() {
- return 0;
- }
-
- public long getAverageProcessingTime() {
- return 0;
- }
-
- public void reset() {
- }
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java
deleted file mode 100644
index 615ac90..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.Statistics;
-
-/**
- * An MBean that provides statistics from
- */
-public class QueueMBeanImpl extends AbstractJobStatistics {
-
- private final String name;
-
- private final Statistics statistics;
-
- public QueueMBeanImpl(Queue queue) {
- this.name = queue.getName();
- if (queue instanceof Statistics) {
- this.statistics = (Statistics) queue;
- } else {
- this.statistics = new EmptyStatistics();
- }
- }
-
- @Override
- protected Statistics getStatistics() {
- return this.statistics;
- }
-
- @Override
- public String getName() {
- return name;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java
deleted file mode 100644
index dca3e33..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import org.apache.sling.event.jobs.Queue;
-
-public class QueueStatusEvent {
-
- private final Queue queue;
- private final Queue oldqueue;
-
-
- public QueueStatusEvent(final Queue queue, final Queue oldqueue) {
- this.queue = queue;
- this.oldqueue = oldqueue;
- }
- public boolean isNew() {
- return this.oldqueue == null;
- }
-
- public boolean isUpdate() {
- return this.queue == this.oldqueue;
- }
-
- public boolean isRemoved() {
- return this.queue == null;
- }
-
- public Queue getQueue() {
- return queue;
- }
-
- public Queue getOldQueue() {
- return oldqueue;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java
deleted file mode 100644
index 83a56e9..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.sling.event.impl.jobs.jmx;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javax.management.AttributeChangeNotification;
-import javax.management.MBeanNotificationInfo;
-import javax.management.Notification;
-import javax.management.NotificationBroadcasterSupport;
-import javax.management.StandardEmitterMBean;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.jmx.QueuesMBean;
-import org.apache.sling.event.jobs.jmx.StatisticsMBean;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-
-@Component
-@Service(value = { QueuesMBean.class })
-@Property(name = "jmx.objectname", value = "org.apache.sling:type=queues,name=QueueNames")
-public class QueuesMBeanImpl extends StandardEmitterMBean implements QueuesMBean {
-
- private static final String QUEUE_NOTIFICATION = "org.apache.sling.event.queue";
- private static final String[] NOTIFICATION_TYPES = { QUEUE_NOTIFICATION };
- private Map<String, QueueMBeanHolder> queues = new ConcurrentHashMap<String, QueueMBeanHolder>();
- private String[] names;
- private AtomicLong sequence = new AtomicLong(System.currentTimeMillis());
- private BundleContext bundleContext;
-
- class QueueMBeanHolder {
-
- QueueMBeanHolder(String name, QueueMBeanImpl queueMBean,
- ServiceRegistration registration) {
- this.name = name;
- this.queueMBean = queueMBean;
- this.registration = registration;
- }
-
- QueueMBeanImpl queueMBean;
- ServiceRegistration registration;
- String name;
-
- }
-
- public QueuesMBeanImpl() {
- super(QueuesMBean.class, false, new NotificationBroadcasterSupport(
- new MBeanNotificationInfo(NOTIFICATION_TYPES,
- Notification.class.getName(),
- "Notifications about queues")));
- }
-
- @Activate
- public void activate(final BundleContext bc) {
- bundleContext = bc;
- }
-
- @Deactivate
- public void deactivate() {
- bundleContext = null;
- }
-
- public void sendEvent(final QueueStatusEvent e) {
- if (e.isNew()) {
- bindQueueMBean(e);
- } else if (e.isRemoved()) {
- unbindQueueMBean(e);
- } else {
- updateQueueMBean(e);
- }
- }
-
- private void updateQueueMBean(QueueStatusEvent e) {
- QueueMBeanHolder queueMBeanHolder = queues.get(e.getQueue().getName());
- if (queueMBeanHolder != null) {
- String[] oldQueue = getQueueNames();
- names = null;
- this.sendNotification(new AttributeChangeNotification(this,
- sequence.incrementAndGet(), System.currentTimeMillis(),
- "Queue " + e.getQueue().getName() + " updated ",
- "queueNames", "String[]", oldQueue, getQueueNames()));
- }
- }
-
- private void unbindQueueMBean(QueueStatusEvent e) {
- QueueMBeanHolder queueMBeanHolder = queues.get(e.getOldQueue().getName());
- if (queueMBeanHolder != null) {
- removeAndNotify(queueMBeanHolder);
- }
- }
-
- private void bindQueueMBean(QueueStatusEvent e) {
- QueueMBeanHolder queueMBeanHolder = queues.get(e.getQueue().getName());
- if (queueMBeanHolder != null) {
- removeAndNotify(queueMBeanHolder);
- }
- addAndNotify(e.getQueue());
- }
-
- private void addAndNotify(Queue queue) {
- String[] oldQueue = getQueueNames();
- QueueMBeanHolder queueMBeanHolder = add(queue);
- names = null;
- this.sendNotification(new AttributeChangeNotification(this, sequence
- .incrementAndGet(), System.currentTimeMillis(), "Queue "
- + queueMBeanHolder.name + " added ", "queueNames", "String[]",
- oldQueue, getQueueNames()));
- }
-
- private void removeAndNotify(QueueMBeanHolder queueMBeanHolder) {
- String[] oldQueue = getQueueNames();
- remove(queueMBeanHolder);
- names = null;
- this.sendNotification(new AttributeChangeNotification(this, sequence
- .incrementAndGet(), System.currentTimeMillis(), "Queue "
- + queueMBeanHolder.name + " removed ", "queueNames",
- "String[]", oldQueue, getQueueNames()));
- }
-
- private QueueMBeanHolder add(Queue queue) {
- QueueMBeanImpl queueMBean = new QueueMBeanImpl(queue);
- ServiceRegistration serviceRegistration = bundleContext
- .registerService(StatisticsMBean.class.getName(), queueMBean,
- createProperties(
- "jmx.objectname","org.apache.sling:type=queues,name="+queue.getName(),
- Constants.SERVICE_DESCRIPTION, "QueueMBean for queue "+queue.getName(),
- Constants.SERVICE_VENDOR, "The Apache Software Foundation"));
- QueueMBeanHolder queueMBeanHolder = new QueueMBeanHolder(
- queue.getName(), queueMBean, serviceRegistration);
- queues.put(queueMBeanHolder.name, queueMBeanHolder);
- return queueMBeanHolder;
- }
-
- private Dictionary<String, Object> createProperties(Object ... values) {
- Dictionary<String, Object> props = new Hashtable<String, Object>();
- for ( int i = 0; i < values.length; i+=2) {
- props.put((String) values[i], values[i+1]);
- }
- return props;
- }
-
- private void remove(QueueMBeanHolder queueMBeanHolder) {
- queueMBeanHolder.registration.unregister();
- queues.remove(queueMBeanHolder.name);
- }
-
- @Override
- public String[] getQueueNames() {
- if (names == null) {
- List<String> lnames = new ArrayList<String>(queues.keySet());
- Collections.sort(lnames);
- names = lnames.toArray(new String[lnames.size()]);
- }
- return names;
- }
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java
deleted file mode 100644
index 590026f..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.notifications;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
-import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
-import org.apache.sling.api.resource.observation.ResourceChangeListener;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This component receives resource added events and sends a job
- * created event.
- */
-@Component
-public class NewJobSender implements ResourceChangeListener, ExternalResourceChangeListener {
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** The job manager configuration. */
- @Reference
- private JobManagerConfiguration configuration;
-
- /** The event admin. */
- @Reference
- private EventAdmin eventAdmin;
-
- /** Service registration for the event handler. */
- private volatile ServiceRegistration<ResourceChangeListener> listenerRegistration;
-
- /**
- * Activate this component.
- * Register an event handler.
- */
- @Activate
- protected void activate(final BundleContext bundleContext) {
- final Dictionary<String, Object> properties = new Hashtable<String, Object>();
- properties.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Job Topic Manager Event Handler");
- properties.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
- properties.put(ResourceChangeListener.CHANGES, ChangeType.ADDED.toString());
- properties.put(ResourceChangeListener.PATHS, this.configuration.getLocalJobsPath());
-
- this.listenerRegistration = bundleContext.registerService(ResourceChangeListener.class, this, properties);
- }
-
- /**
- * Deactivate this component.
- * Unregister the event handler.
- */
- @Deactivate
- protected void deactivate() {
- if ( this.listenerRegistration != null ) {
- this.listenerRegistration.unregister();
- this.listenerRegistration = null;
- }
- }
-
- @Override
- public void onChange(final List<ResourceChange> resourceChanges) {
- for(final ResourceChange resourceChange : resourceChanges) {
- logger.debug("Received event {}", resourceChange);
-
- final String path = resourceChange.getPath();
-
- final int topicStart = this.configuration.getLocalJobsPath().length() + 1;
- final int topicEnd = path.indexOf('/', topicStart);
- if ( topicEnd != -1 ) {
- final String topic = path.substring(topicStart, topicEnd).replace('.', '/');
- final String jobId = path.substring(topicEnd + 1);
-
- if ( path.indexOf("_", topicEnd + 1) != -1 ) {
- // only job id and topic are guaranteed
- final Dictionary<String, Object> properties = new Hashtable<String, Object>();
- properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, jobId);
- properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC, topic);
-
- // we also set internally the queue name
- final String queueName = this.configuration.getQueueConfigurationManager().getQueueInfo(topic).queueName;
- properties.put(Job.PROPERTY_JOB_QUEUE_NAME, queueName);
-
- final Event jobEvent = new Event(NotificationConstants.TOPIC_JOB_ADDED, properties);
- // as this is send within handling an event, we do sync call
- this.eventAdmin.sendEvent(jobEvent);
- }
- }
- }
-
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java
deleted file mode 100644
index d45d818..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.notifications;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import org.apache.sling.event.impl.jobs.JobImpl;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.consumer.JobConsumer;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.service.event.EventConstants;
-
-public abstract class NotificationUtility {
-
- /** Event property containing the time for job start and job finished events. */
- public static final String PROPERTY_TIME = ":time";
-
- /**
- * Helper method for sending the notification events.
- */
- public static void sendNotification(final EventAdmin eventAdmin,
- final String eventTopic,
- final Job job,
- final Long time) {
- if ( eventAdmin != null ) {
- // create new copy of job object
- final Job jobCopy = new JobImpl(job.getTopic(), job.getId(), ((JobImpl)job).getProperties());
- sendNotificationInternal(eventAdmin, eventTopic, jobCopy, time);
- }
- }
-
- /**
- * Helper method for sending the notification events.
- */
- private static void sendNotificationInternal(final EventAdmin eventAdmin,
- final String eventTopic,
- final Job job,
- final Long time) {
- final Dictionary<String, Object> eventProps = new Hashtable<String, Object>();
- // add basic job properties
- eventProps.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, job.getId());
- eventProps.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC, job.getTopic());
- // copy payload
- for(final String name : job.getPropertyNames()) {
- eventProps.put(name, job.getProperty(name));
- }
- // remove async handler
- eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER);
- // add timestamp
- eventProps.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
- // add internal time information
- if ( time != null ) {
- eventProps.put(PROPERTY_TIME, time);
- }
- eventAdmin.postEvent(new Event(eventTopic, eventProps));
- }
-
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java
deleted file mode 100644
index d2c2286..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sling.event.impl.jobs.JobHandler;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.consumer.JobExecutionContext;
-import org.apache.sling.event.jobs.consumer.JobExecutionResult;
-
-/**
- * Implementation of the job execution context passed to
- * job executors.
- */
-public class JobExecutionContextImpl implements JobExecutionContext {
-
- /**
- * Call back interface to the queue.
- */
- public interface ASyncHandler {
- void finished(Job.JobState state);
- }
-
- /**
- * Boolean to check whether init progress has been called.
- */
- private final AtomicBoolean initProgressIsCalled = new AtomicBoolean(false);
-
- /**
- * Flag to indicate whether this is async processing
- */
- private final AtomicBoolean isAsync = new AtomicBoolean(false);
-
- private final ASyncHandler asyncHandler;
-
- private final JobHandler handler;
-
- public JobExecutionContextImpl(final JobHandler handler,
- final ASyncHandler asyncHandler) {
- this.handler = handler;
- this.asyncHandler = asyncHandler;
- }
-
- public void markAsync() {
- this.isAsync.set(true);
- }
-
- @Override
- public void initProgress(final int steps,
- final long eta) {
- if ( initProgressIsCalled.compareAndSet(false, true) ) {
- handler.persistJobProperties(handler.getJob().startProgress(steps, eta));
- }
- }
-
- @Override
- public void incrementProgressCount(final int steps) {
- if ( initProgressIsCalled.get() ) {
- handler.persistJobProperties(handler.getJob().setProgress(steps));
- }
- }
-
- @Override
- public void updateProgress(final long eta) {
- if ( initProgressIsCalled.get() ) {
- handler.persistJobProperties(handler.getJob().update(eta));
- }
- }
-
- @Override
- public void log(final String message, Object... args) {
- handler.persistJobProperties(handler.getJob().log(message, args));
- }
-
- @Override
- public boolean isStopped() {
- return handler.isStopped();
- }
-
- @Override
- public void asyncProcessingFinished(final JobExecutionResult result) {
- synchronized ( this ) {
- if ( isAsync.compareAndSet(true, false) ) {
- Job.JobState state = null;
- if ( result.succeeded() ) {
- state = Job.JobState.SUCCEEDED;
- } else if ( result.failed() ) {
- state = Job.JobState.QUEUED;
- } else if ( result.cancelled() ) {
- if ( handler.isStopped() ) {
- state = Job.JobState.STOPPED;
- } else {
- state = Job.JobState.ERROR;
- }
- }
- asyncHandler.finished(state);
- } else {
- throw new IllegalStateException("Job is not processed async or is already finished: " + handler.getJob().getId());
- }
- }
- }
-
- @Override
- public ResultBuilder result() {
- return new ResultBuilderImpl();
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java
deleted file mode 100644
index 28083e4..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import org.apache.sling.event.impl.jobs.InternalJobState;
-import org.apache.sling.event.jobs.consumer.JobExecutionResult;
-
-/**
- * The job execution result.
- */
-public class JobExecutionResultImpl implements JobExecutionResult {
-
- /** Constant object for the success case. */
- public static final JobExecutionResultImpl SUCCEEDED = new JobExecutionResultImpl(InternalJobState.SUCCEEDED, null, null);
- /** Constant object for the cancelled case. */
- public static final JobExecutionResultImpl CANCELLED = new JobExecutionResultImpl(InternalJobState.CANCELLED, null, null);
- /** Constant object for the failed case. */
- public static final JobExecutionResultImpl FAILED = new JobExecutionResultImpl(InternalJobState.FAILED, null, null);
-
- /** The state of the execution. */
- private final InternalJobState state;
-
- /** Optional message. */
- private final String message;
-
- /** Optional retry delay. */
- private final Long retryDelayInMs;
-
- /**
- * Create a new result
- * @param state The result state
- * @param message Optional Message
- * @param retryDelayInMs Optional retry delay
- */
- public JobExecutionResultImpl(final InternalJobState state,
- final String message,
- final Long retryDelayInMs) {
- this.state = state;
- this.message = message;
- this.retryDelayInMs = retryDelayInMs;
- }
-
- /**
- * Get the internal state
- * @return The state.
- */
- public InternalJobState getState() {
- return this.state;
- }
-
- @Override
- public boolean succeeded() {
- return this.state == InternalJobState.SUCCEEDED;
- }
-
- @Override
- public boolean cancelled() {
- return this.state == InternalJobState.CANCELLED;
- }
-
- @Override
- public boolean failed() {
- return this.state == InternalJobState.FAILED;
- }
-
- @Override
- public String getMessage() {
- return this.message;
- }
-
- @Override
- public Long getRetryDelayInMs() {
- return this.retryDelayInMs;
- }
-
- @Override
- public String toString() {
- return "JobExecutionResultImpl [state=" + state + ", message="
- + message + ", retryDelayInMs=" + retryDelayInMs + "]";
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java
deleted file mode 100644
index 0ab1a6b..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java
+++ /dev/null
@@ -1,708 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.event.impl.EventingThreadPool;
-import org.apache.sling.event.impl.jobs.InternalJobState;
-import org.apache.sling.event.impl.jobs.JobHandler;
-import org.apache.sling.event.impl.jobs.JobImpl;
-import org.apache.sling.event.impl.jobs.JobTopicTraverser;
-import org.apache.sling.event.impl.jobs.Utility;
-import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
-import org.apache.sling.event.impl.jobs.notifications.NotificationUtility;
-import org.apache.sling.event.impl.support.BatchResourceRemover;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.Job.JobState;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.QueueConfiguration.Type;
-import org.apache.sling.event.jobs.Statistics;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The job blocking queue extends the blocking queue by some
- * functionality for the job event handling.
- */
-public class JobQueueImpl
- implements Queue {
-
- /** Default timeout for suspend. */
- private static final long MAX_SUSPEND_TIME = 1000 * 60 * 60; // 60 mins
-
- /** The logger. */
- private final Logger logger;
-
- /** Configuration. */
- private final InternalQueueConfiguration configuration;
-
- /** The queue name. */
- private volatile String queueName;
-
- /** Are we still running? */
- private volatile boolean running;
-
- /** Suspended since. */
- private final AtomicLong suspendedSince = new AtomicLong(-1);
-
- /** Services used by the queues. */
- private final QueueServices services;
-
- /** The map of events we're processing. */
- private final Map<String, JobHandler> processingJobsLists = new HashMap<String, JobHandler>();
-
- private final ThreadPool threadPool;
-
- /** Async counter. */
- private final AtomicInteger asyncCounter = new AtomicInteger();
-
- /** Flag for outdated. */
- private final AtomicBoolean isOutdated = new AtomicBoolean(false);
-
- /** A marker for closing the queue. */
- private final AtomicBoolean closeMarker = new AtomicBoolean(false);
-
- /** A marker for doing a full cache search. */
- private final AtomicBoolean doFullCacheSearch = new AtomicBoolean(false);
-
- /** A counter for rescheduling. */
- private final AtomicInteger waitCounter = new AtomicInteger();
-
- /** The job cache. */
- private final QueueJobCache cache;
-
- /** Semaphore for handling the max number of jobs. */
- private final Semaphore available;
-
- /** Guard for having only one thread executing start jobs. */
- private final AtomicBoolean startJobsGuard = new AtomicBoolean(false);
-
- /** Lock for close/start. */
- private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
-
- /** Sleeping until is only set for ordered queues if a job is rescheduled. */
- private volatile long isSleepingUntil = -1;
-
- /**
- * Create a new queue.
- *
- * @param name The queue name
- * @param config The queue configuration
- * @param services The queue services
- * @param topics The topics handled by this queue
- *
- * @return {@code JobQueueImpl} if there are jobs to process, {@code null} otherwise.
- */
- public static JobQueueImpl createQueue(final String name,
- final InternalQueueConfiguration config,
- final QueueServices services,
- final Set<String> topics) {
- final QueueJobCache cache = new QueueJobCache(services.configuration, name, services.statisticsManager, config.getType(), topics);
- if ( cache.isEmpty() ) {
- return null;
- }
- return new JobQueueImpl(name, config, services, cache);
- }
-
- /**
- * Create a new queue.
- *
- * @param name The queue name
- * @param config The queue configuration
- * @param services The queue services
- * @param cache The job cache
- */
- private JobQueueImpl(final String name,
- final InternalQueueConfiguration config,
- final QueueServices services,
- final QueueJobCache cache) {
- if ( config.getOwnThreadPoolSize() > 0 ) {
- this.threadPool = new EventingThreadPool(services.threadPoolManager, config.getOwnThreadPoolSize());
- } else {
- this.threadPool = services.eventingThreadPool;
- }
- this.queueName = name;
- this.configuration = config;
- this.services = services;
- this.logger = LoggerFactory.getLogger(this.getClass().getName() + '.' + name);
- this.running = true;
- this.cache = cache;
- this.available = new Semaphore(config.getMaxParallel(), true);
- logger.info("Starting job queue {}", queueName);
- logger.debug("Configuration for job queue={}", configuration);
- }
-
- /**
- * Return the queue configuration
- */
- @Override
- public InternalQueueConfiguration getConfiguration() {
- return this.configuration;
- }
-
- /**
- * Get the name of the job queue.
- */
- @Override
- public String getName() {
- return this.queueName;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#getStatistics()
- */
- @Override
- public Statistics getStatistics() {
- return this.services.statisticsManager.getQueueStatistics(this.queueName);
- }
-
- /**
- * Start the job queue.
- * This method might be called concurrently, therefore we use a guard
- */
- public void startJobs() {
- if ( this.startJobsGuard.compareAndSet(false, true) ) {
- // we start as many jobs in parallel as possible
- while ( this.running && !this.isOutdated.get() && !this.isSuspended() && this.available.tryAcquire() ) {
- boolean started = false;
- this.lock.writeLock().lock();
- try {
- final JobHandler handler = this.cache.getNextJob(this.services.jobConsumerManager,
- this.services.statisticsManager, this, this.doFullCacheSearch.getAndSet(false));
- if ( handler != null ) {
- started = true;
- this.threadPool.execute(new Runnable() {
-
- @Override
- public void run() {
- // update thread priority and name
- final Thread currentThread = Thread.currentThread();
- final String oldName = currentThread.getName();
- final int oldPriority = currentThread.getPriority();
-
- currentThread.setName(oldName + "-" + handler.getJob().getQueueName() + "(" + handler.getJob().getTopic() + ")");
- if ( configuration.getThreadPriority() != null ) {
- switch ( configuration.getThreadPriority() ) {
- case NORM : currentThread.setPriority(Thread.NORM_PRIORITY);
- break;
- case MIN : currentThread.setPriority(Thread.MIN_PRIORITY);
- break;
- case MAX : currentThread.setPriority(Thread.MAX_PRIORITY);
- break;
- }
- }
-
- try {
- startJob(handler);
- } finally {
- currentThread.setPriority(oldPriority);
- currentThread.setName(oldName);
- }
- // and try to launch another job
- startJobs();
- }
- });
- } else {
- // no job available, stop look
- break;
- }
-
- } finally {
- if ( !started ) {
- this.available.release();
- }
- this.lock.writeLock().unlock();
- }
- }
- this.startJobsGuard.set(false);
- }
- }
-
- private void startJob(final JobHandler handler) {
- try {
- this.closeMarker.set(false);
- try {
- final JobImpl job = handler.getJob();
- handler.started = System.currentTimeMillis();
-
- this.services.configuration.getAuditLogger().debug("START OK : {}", job.getId());
- // sanity check for the queued property
- Calendar queued = job.getProperty(JobImpl.PROPERTY_JOB_QUEUED, Calendar.class);
- if ( queued == null ) {
- // we simply use a date of ten seconds ago
- queued = Calendar.getInstance();
- queued.setTimeInMillis(System.currentTimeMillis() - 10000);
- }
- final long queueTime = handler.started - queued.getTimeInMillis();
- // update statistics
- this.services.statisticsManager.jobStarted(this.queueName, job.getTopic(), queueTime);
- // send notification
- NotificationUtility.sendNotification(this.services.eventAdmin, NotificationConstants.TOPIC_JOB_STARTED, job, queueTime);
-
- synchronized ( this.processingJobsLists ) {
- this.processingJobsLists.put(job.getId(), handler);
- }
-
- JobExecutionResultImpl result = JobExecutionResultImpl.CANCELLED;
- Job.JobState resultState = Job.JobState.ERROR;
- final JobExecutionContextImpl ctx = new JobExecutionContextImpl(handler, new JobExecutionContextImpl.ASyncHandler() {
-
- @Override
- public void finished(final JobState state) {
- services.jobConsumerManager.unregisterListener(job.getId());
- finishedJob(job.getId(), state, true);
- asyncCounter.decrementAndGet();
- }
- });
-
- try {
- synchronized ( ctx ) {
- result = (JobExecutionResultImpl)handler.getConsumer().process(job, ctx);
- if ( result == null ) { // ASYNC processing
- services.jobConsumerManager.registerListener(job.getId(), handler.getConsumer(), ctx);
- asyncCounter.incrementAndGet();
- ctx.markAsync();
- } else {
- if ( result.succeeded() ) {
- resultState = Job.JobState.SUCCEEDED;
- } else if ( result.failed() ) {
- resultState = Job.JobState.QUEUED;
- } else if ( result.cancelled() ) {
- if ( handler.isStopped() ) {
- resultState = Job.JobState.STOPPED;
- } else {
- resultState = Job.JobState.ERROR;
- }
- }
- }
- }
- } catch (final Throwable t) { //NOSONAR
- logger.error("Unhandled error occured in job processor " + t.getMessage() + " while processing job " + Utility.toString(job), t);
- // we don't reschedule if an exception occurs
- result = JobExecutionResultImpl.CANCELLED;
- resultState = Job.JobState.ERROR;
- } finally {
- if ( result != null ) {
- if ( result.getRetryDelayInMs() != null ) {
- job.setProperty(JobImpl.PROPERTY_DELAY_OVERRIDE, result.getRetryDelayInMs());
- }
- if ( result.getMessage() != null ) {
- job.setProperty(Job.PROPERTY_RESULT_MESSAGE, result.getMessage());
- }
- this.finishedJob(job.getId(), resultState, false);
- }
- }
- } catch (final Exception re) {
- // if an exception occurs, we just log
- this.logger.error("Exception during job processing.", re);
- }
- } finally {
- this.available.release();
- }
- }
-
- /**
- * Outdate this queue.
- */
- public void outdate() {
- if ( this.isOutdated.compareAndSet(false, true) ) {
- final String name = this.getName() + "<outdated>(" + this.hashCode() + ")";
- this.logger.info("Outdating queue {}, renaming to {}.", this.queueName, name);
- this.queueName = name;
- }
- }
-
- /**
- * Check if the queue can be closed
- */
- public boolean tryToClose() {
- // resume the queue as we want to close it!
- this.resume();
- this.lock.writeLock().lock();
- try {
- // check if possible
- if ( this.canBeClosed() ) {
- if ( this.closeMarker.get() ) {
- this.close();
- return true;
- }
- this.closeMarker.set(true);
- }
- } finally {
- this.lock.writeLock().unlock();
- }
- return false;
- }
-
- /**
- * Check whether this queue can be closed
- */
- private boolean canBeClosed() {
- return !this.isSuspended()
- && this.asyncCounter.get() == 0
- && this.waitCounter.get() == 0
- && this.available.availablePermits() == this.configuration.getMaxParallel();
- }
-
- /**
- * Close this queue.
- */
- public void close() {
- this.running = false;
- this.logger.debug("Shutting down job queue {}", queueName);
- this.resume();
-
- synchronized ( this.processingJobsLists ) {
- this.processingJobsLists.clear();
- }
- if ( this.configuration.getOwnThreadPoolSize() > 0 ) {
- ((EventingThreadPool)this.threadPool).release();
- }
-
- this.logger.info("Stopped job queue {}", this.queueName);
- }
-
- /**
- * Periodic maintenance
- */
- public void maintain() {
- // check suspended
- final long since = this.suspendedSince.get();
- if ( since != -1 && since + MAX_SUSPEND_TIME < System.currentTimeMillis() ) {
- logger.info("Waking up suspended queue. It has been suspended for more than {}ms", MAX_SUSPEND_TIME);
- this.resume();
- }
-
- // set full cache search
- this.doFullCacheSearch.set(true);
-
- this.startJobs();
- }
-
- /**
- * Inform the queue about new job for the given topics.
- * @param topics the new topics
- */
- public void wakeUpQueue(final Set<String> topics) {
- this.cache.handleNewTopics(topics);
- }
-
- /**
- * Put a job back in the queue
- * @param handler The job handler
- */
- private void requeue(final JobHandler handler) {
- this.cache.reschedule(this.queueName, handler, this.services.statisticsManager);
- this.startJobs();
- }
-
- private static final class RescheduleInfo {
- public boolean reschedule = false;
- // processing time is only set of state is SUCCEEDED
- public long processingTime;
- public Job.JobState state;
- public InternalJobState finalState;
- }
-
- private RescheduleInfo handleReschedule(final JobHandler handler, final Job.JobState resultState) {
- final RescheduleInfo info = new RescheduleInfo();
- info.state = resultState;
- switch ( resultState ) {
- case SUCCEEDED : // job is finished
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Finished job {}", Utility.toString(handler.getJob()));
- }
- info.processingTime = System.currentTimeMillis() - handler.started;
- info.finalState = InternalJobState.SUCCEEDED;
- break;
- case QUEUED : // check if we exceeded the number of retries
- final int retries = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRIES, 0);
- int retryCount = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_COUNT, 0);
-
- retryCount++;
- if ( retries != -1 && retryCount > retries ) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Cancelled job {}", Utility.toString(handler.getJob()));
- }
- info.finalState = InternalJobState.CANCELLED;
- } else {
- info.reschedule = true;
- handler.getJob().retry();
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Failed job {}", Utility.toString(handler.getJob()));
- }
- info.finalState = InternalJobState.FAILED;
- }
- break;
- default : // consumer cancelled the job (STOPPED, GIVEN_UP, ERROR)
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Cancelled job {}", Utility.toString(handler.getJob()));
- }
- info.finalState = InternalJobState.CANCELLED;
- break;
- }
-
- if ( info.state == Job.JobState.QUEUED && !info.reschedule ) {
- info.state = Job.JobState.GIVEN_UP;
- }
- return info;
- }
-
- /**
- * Handle job finish and determine whether to reschedule or cancel the job
- */
- private boolean finishedJob(final String jobId,
- final Job.JobState resultState,
- final boolean isAsync) {
- this.services.configuration.getAuditLogger().debug("FINISHED {} : {}", resultState, jobId);
- this.logger.debug("Received finish for job {}, resultState={}", jobId, resultState);
-
- // get job handler
- final JobHandler handler;
- // let's remove the event from our processing list
- synchronized ( this.processingJobsLists ) {
- handler = this.processingJobsLists.remove(jobId);
- }
-
- if ( !this.running ) {
- this.logger.warn("Queue is not running anymore. Discarding finish for {}", jobId);
- return false;
- }
-
- if ( handler == null ) {
- this.logger.warn("This job has never been started by this queue: {}", jobId);
- return false;
- }
-
- // handle the rescheduling of the job
- final RescheduleInfo rescheduleInfo = this.handleReschedule(handler, resultState);
-
- if ( !rescheduleInfo.reschedule ) {
- // we keep cancelled jobs and succeeded jobs if the queue is configured like this.
- final boolean keepJobs = rescheduleInfo.state != Job.JobState.SUCCEEDED || this.configuration.isKeepJobs();
- handler.finished(rescheduleInfo.state, keepJobs, rescheduleInfo.processingTime);
- } else {
- this.reschedule(handler);
- }
- // update statistics
- this.services.statisticsManager.jobEnded(this.queueName,
- handler.getJob().getTopic(),
- rescheduleInfo.finalState,
- rescheduleInfo.processingTime);
- // send notification
- NotificationUtility.sendNotification(this.services.eventAdmin,
- rescheduleInfo.finalState.getTopic(),
- handler.getJob(), rescheduleInfo.processingTime);
-
- return rescheduleInfo.reschedule;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#resume()
- */
- @Override
- public void resume() {
- if ( this.suspendedSince.getAndSet(-1) != -1 ) {
- this.logger.debug("Waking up suspended queue {}", queueName);
- this.startJobs();
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#suspend()
- */
- @Override
- public void suspend() {
- if ( this.suspendedSince.compareAndSet(-1, System.currentTimeMillis()) ) {
- this.logger.debug("Suspending queue {}", queueName);
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#isSuspended()
- */
- @Override
- public boolean isSuspended() {
- return this.suspendedSince.get() != -1;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#removeAll()
- */
- @Override
- public synchronized void removeAll() {
- final Set<String> topics = this.cache.getTopics();
- logger.debug("Removing all jobs for queue {} : {}", queueName, topics);
-
- if ( !topics.isEmpty() ) {
-
- final ResourceResolver resolver = this.services.configuration.createResourceResolver();
- try {
- final Resource baseResource = resolver.getResource(this.services.configuration.getLocalJobsPath());
-
- // sanity check - should never be null
- if ( baseResource != null ) {
- final BatchResourceRemover brr = new BatchResourceRemover();
-
- for(final String t : topics) {
- final Resource topicResource = baseResource.getChild(t.replace('/', '.'));
- if ( topicResource != null ) {
- JobTopicTraverser.traverse(logger, topicResource, new JobTopicTraverser.JobCallback() {
-
- @Override
- public boolean handle(final JobImpl job) {
- final Resource jobResource = topicResource.getResourceResolver().getResource(job.getResourcePath());
- // sanity check
- if ( jobResource != null ) {
- try {
- brr.delete(jobResource);
- } catch ( final PersistenceException ignore) {
- logger.error("Unable to remove job " + job, ignore);
- topicResource.getResourceResolver().revert();
- topicResource.getResourceResolver().refresh();
- }
- }
- return true;
- }
- });
- }
- }
- try {
- resolver.commit();
- } catch ( final PersistenceException ignore) {
- logger.error("Unable to remove jobs", ignore);
- }
- }
- } finally {
- resolver.close();
- }
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#getState(java.lang.String)
- */
- @Override
- public Object getState(final String key) {
- if ( this.configuration.getType() == Type.ORDERED ) {
- if ( "isSleepingUntil".equals(key) ) {
- return this.isSleepingUntil;
- }
- }
- return null;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Queue#getStateInfo()
- */
- @Override
- public String getStateInfo() {
- return "outdated=" + this.isOutdated.get() +
- ", suspendedSince=" + this.suspendedSince.get() +
- ", asyncJobs=" + this.asyncCounter.get() +
- ", waitCount=" + this.waitCounter.get() +
- ", jobCount=" + String.valueOf(this.configuration.getMaxParallel() - this.available.availablePermits() +
- (this.configuration.getType() == Type.ORDERED ? ", isSleepingUntil=" + this.isSleepingUntil : ""));
- }
-
- /**
- * Get the retry delay for a job.
- * @param handler The job handler.
- * @return The retry delay
- */
- private long getRetryDelay(final JobHandler handler) {
- long delay = this.configuration.getRetryDelayInMs();
- if ( handler.getJob().getProperty(JobImpl.PROPERTY_DELAY_OVERRIDE) != null ) {
- delay = handler.getJob().getProperty(JobImpl.PROPERTY_DELAY_OVERRIDE, Long.class);
- } else if ( handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_DELAY) != null ) {
- delay = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_DELAY, Long.class);
- }
- return delay;
- }
-
- public boolean stopJob(final JobImpl job) {
- final JobHandler handler;
- synchronized ( this.processingJobsLists ) {
- handler = this.processingJobsLists.get(job.getId());
- }
- if ( handler != null ) {
- handler.stop();
- }
- return handler != null;
- }
-
- private void reschedule(final JobHandler handler) {
- // we delay putting back the job until the retry delay is over
- final long delay = this.getRetryDelay(handler);
- if ( delay > 0 ) {
- if ( this.configuration.getType() == Type.ORDERED ) {
- this.cache.setIsBlocked(true);
- }
- handler.addToRetryList();
- final Date fireDate = new Date();
- fireDate.setTime(System.currentTimeMillis() + delay);
- if ( this.configuration.getType() == Type.ORDERED ) {
- this.isSleepingUntil = fireDate.getTime();
- }
-
- final String jobName = "Waiting:" + queueName + ":" + handler.hashCode();
- final Runnable t = new Runnable() {
- @Override
- public void run() {
- try {
- if ( handler.removeFromRetryList() ) {
- requeue(handler);
- }
- waitCounter.decrementAndGet();
- } finally {
- if ( configuration.getType() == Type.ORDERED ) {
- isSleepingUntil = -1;
- cache.setIsBlocked(false);
- startJobs();
- }
- }
- }
- };
- this.waitCounter.incrementAndGet();
- if ( !services.scheduler.schedule(t, services.scheduler.AT(fireDate).name(jobName)) ) {
- // if scheduling fails run the thread directly
- t.run();
- }
- } else {
- // put directly into queue
- this.requeue(handler);
- }
- }
-}
-
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java
deleted file mode 100644
index ebcc92d..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.event.impl.jobs.JobConsumerManager;
-import org.apache.sling.event.impl.jobs.JobHandler;
-import org.apache.sling.event.impl.jobs.JobImpl;
-import org.apache.sling.event.impl.jobs.JobTopicTraverser;
-import org.apache.sling.event.impl.jobs.Utility;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
-import org.apache.sling.event.jobs.Job.JobState;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.QueueConfiguration;
-import org.apache.sling.event.jobs.QueueConfiguration.Type;
-import org.apache.sling.event.jobs.consumer.JobExecutor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The queue job cache caches jobs per queue based on the topics the queue is actively
- * processing.
- */
-public class QueueJobCache {
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** The maximum of pre loaded jobs for a topic. */
- private final int maxPreloadLimit = 10;
-
- /** The job manager configuration. */
- private final JobManagerConfiguration configuration;
-
- /** The set of topics handled by this queue. */
- private final Set<String> topics;
-
- /** The set of new topics to scan. */
- private final Set<String> topicsWithNewJobs = new HashSet<String>();
-
- /** The cache of current objects. */
- private final List<JobImpl> cache = new ArrayList<JobImpl>();
-
- /** The queue type. */
- private final QueueConfiguration.Type queueType;
-
- /** Block the cache - for ordered queues only. */
- private final AtomicBoolean queueIsBlocked = new AtomicBoolean(false);
-
- /**
- * Create a new queue job cache
- * @param configuration Current job manager configuration
- * @param queueName The queue name
- * @param statisticsManager The statistics manager
- * @param queueType The queue type
- * @param topics The topics handled by this queue.
- */
- public QueueJobCache(final JobManagerConfiguration configuration,
- final String queueName,
- final StatisticsManager statisticsManager,
- final QueueConfiguration.Type queueType,
- final Set<String> topics) {
- this.configuration = configuration;
- this.queueType = queueType;
- this.topics = new ConcurrentSkipListSet<String>(topics);
- this.fillCache(queueName, statisticsManager);
- }
-
- /**
- * All topics of this queue.
- * @return The topics.
- */
- public Set<String> getTopics() {
- return this.topics;
- }
-
- /**
- * Check whether there are jobs for this queue
- * @return {@code true} if there is any job outstanding.
- */
- public boolean isEmpty() {
- boolean result = true;
- synchronized ( this.cache ) {
- result = this.cache.isEmpty();
- }
- if ( result ) {
- synchronized ( this.topicsWithNewJobs ) {
- result = this.topicsWithNewJobs.isEmpty();
- }
- }
- return result;
- }
-
- public void setIsBlocked(final boolean value) {
- this.queueIsBlocked.set(value);
- }
-
- /**
- * Fill the cache.
- * No need to sync as this is called from the constructor.
- */
- private void fillCache(final String queueName, final StatisticsManager statisticsManager) {
- final Set<String> checkingTopics = new HashSet<String>();
- checkingTopics.addAll(this.topics);
- if ( !checkingTopics.isEmpty() ) {
- this.loadJobs(queueName, checkingTopics, statisticsManager);
- }
- }
-
- /**
- * Get the next job.
- * This method is potentially called concurrently, and
- * {@link #reschedule(String, JobHandler, StatisticsManager)} and {@link #handleNewTopics(Set)}
- * can be called concurrently.
- * @param jobConsumerManager The job consumer manager
- * @param statisticsManager The statistics manager
- * @param queue The queue
- * @param doFull Whether to do a full scan
- * @return The job handler or {@code null}.
- */
- public JobHandler getNextJob(final JobConsumerManager jobConsumerManager,
- final StatisticsManager statisticsManager,
- final Queue queue,
- final boolean doFull) {
- JobHandler handler = null;
-
- if ( !this.queueIsBlocked.get() ) {
- synchronized ( this.cache ) {
- boolean retry;
- do {
- retry = false;
- if ( this.cache.isEmpty() ) {
- final Set<String> checkingTopics = new HashSet<String>();
- synchronized ( this.topicsWithNewJobs ) {
- checkingTopics.addAll(this.topicsWithNewJobs);
- this.topicsWithNewJobs.clear();
- }
- if ( doFull ) {
- checkingTopics.addAll(this.topics);
- }
- if ( !checkingTopics.isEmpty() ) {
- this.loadJobs(queue.getName(), checkingTopics, statisticsManager);
- }
- }
-
- if ( !this.cache.isEmpty() ) {
- final JobImpl job = this.cache.remove(0);
- final JobExecutor consumer = jobConsumerManager.getExecutor(job.getTopic());
-
- handler = new JobHandler(job, consumer, this.configuration);
- if ( consumer != null ) {
- if ( !handler.startProcessing(queue) ) {
- statisticsManager.jobDequeued(queue.getName(), handler.getJob().getTopic());
- if ( logger.isDebugEnabled() ) {
- logger.debug("Discarding removed job {}", Utility.toString(job));
- }
- handler = null;
- retry = true;
- }
- } else {
- statisticsManager.jobDequeued(queue.getName(), handler.getJob().getTopic());
- // no consumer on this instance, assign to another instance
- handler.reassign();
-
- handler = null;
- retry = true;
- }
-
- }
- } while ( handler == null && retry);
- }
- }
- return handler;
- }
-
- /**
- * Load the next N x numberOf(topics) jobs
- * @param checkingTopics The set of topics to check.
- */
- private void loadJobs( final String queueName, final Set<String> checkingTopics,
- final StatisticsManager statisticsManager) {
- logger.debug("Starting jobs loading from {}...", checkingTopics);
-
- final Map<String, List<JobImpl>> topicCache = new HashMap<String, List<JobImpl>>();
-
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource baseResource = resolver.getResource(this.configuration.getLocalJobsPath());
- // sanity check - should never be null
- if ( baseResource != null ) {
- for(final String topic : checkingTopics) {
-
- final Resource topicResource = baseResource.getChild(topic.replace('/', '.'));
- if ( topicResource != null ) {
- topicCache.put(topic, loadJobs(queueName, topic, topicResource, statisticsManager));
- }
- }
- }
- } finally {
- resolver.close();
- }
- orderTopics(topicCache);
-
- logger.debug("Finished jobs loading {}", this.cache.size());
- }
-
- /**
- * Order the topics based on the queue type and put them in the cache.
- * @param topicCache The topic based cache
- */
- private void orderTopics(final Map<String, List<JobImpl>> topicCache) {
- if ( this.queueType == Type.ORDERED
- || this.queueType == Type.UNORDERED) {
- for(final List<JobImpl> list : topicCache.values()) {
- this.cache.addAll(list);
- }
- Collections.sort(this.cache);
- } else {
- // topic round robin
- boolean done = true;
- do {
- done = true;
- for(final Map.Entry<String, List<JobImpl>> entry : topicCache.entrySet()) {
- if ( !entry.getValue().isEmpty() ) {
- this.cache.add(entry.getValue().remove(0));
- if ( !entry.getValue().isEmpty() ) {
- done = false;
- }
- }
- }
- } while ( !done ) ;
- }
- }
-
- /**
- * Load the next N x numberOf(topics) jobs.
- * @param topic The topic
- * @param topicResource The parent resource of the jobs
- * @return The cache which will be filled with the jobs.
- */
- private List<JobImpl> loadJobs(final String queueName, final String topic,
- final Resource topicResource,
- final StatisticsManager statisticsManager) {
- logger.debug("Loading jobs from topic {}", topic);
- final List<JobImpl> list = new ArrayList<JobImpl>();
-
- final AtomicBoolean scanTopic = new AtomicBoolean(false);
-
- JobTopicTraverser.traverse(logger, topicResource, new JobTopicTraverser.JobCallback() {
-
- @Override
- public boolean handle(final JobImpl job) {
- if ( job.getProcessingStarted() == null && !job.hasReadErrors() ) {
- list.add(job);
- statisticsManager.jobQueued(queueName, topic);
- if ( list.size() == maxPreloadLimit ) {
- scanTopic.set(true);
- }
- } else if ( job.getProcessingStarted() != null ) {
- logger.debug("Ignoring job {} - processing already started.", job);
- } else {
- // error reading job
- scanTopic.set(true);
- if ( job.isReadErrorRecoverable() ) {
- logger.debug("Ignoring job {} due to recoverable read errors.", job);
- } else {
- logger.debug("Failing job {} due to unrecoverable read errors.", job);
- final JobHandler handler = new JobHandler(job, null, configuration);
- handler.finished(JobState.ERROR, true, null);
- }
- }
- return list.size() < maxPreloadLimit;
- }
- });
- if ( scanTopic.get() ) {
- synchronized ( this.topicsWithNewJobs ) {
- this.topicsWithNewJobs.add(topic);
- }
- }
- logger.debug("Caching {} jobs for topic {}", list.size(), topic);
-
- return list;
- }
-
- /**
- * Inform the queue cache about topics containing new jobs
- * @param topics The set of topics to scan
- */
- public void handleNewTopics(final Set<String> topics) {
- logger.debug("Update cache to handle new event for topics {}", topics);
- synchronized ( this.topicsWithNewJobs ) {
- this.topicsWithNewJobs.addAll(topics);
- }
- this.topics.addAll(topics);
- }
-
- /**
- * Reschedule a job
- * Reschedule the job and add it back into the cache.
- * @param queueName The queue name
- * @param handler The job handler
- * @param statisticsManager The statistics manager
- */
- public void reschedule(final String queueName, final JobHandler handler, final StatisticsManager statisticsManager) {
- synchronized ( this.cache ) {
- if ( handler.reschedule() ) {
- if ( this.queueType == Type.ORDERED ) {
- this.cache.add(0, handler.getJob());
- } else {
- this.cache.add(handler.getJob());
- }
- statisticsManager.jobQueued(queueName, handler.getJob().getTopic());
- }
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java
deleted file mode 100644
index fd7fcf8..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.commons.scheduler.Scheduler;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.commons.threads.ThreadPoolManager;
-import org.apache.sling.event.impl.EventingThreadPool;
-import org.apache.sling.event.impl.jobs.JobConsumerManager;
-import org.apache.sling.event.impl.jobs.JobHandler;
-import org.apache.sling.event.impl.jobs.JobImpl;
-import org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener;
-import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager;
-import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
-import org.apache.sling.event.impl.jobs.jmx.QueueStatusEvent;
-import org.apache.sling.event.impl.jobs.jmx.QueuesMBeanImpl;
-import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.NotificationConstants;
-import org.apache.sling.event.jobs.Queue;
-import org.apache.sling.event.jobs.jmx.QueuesMBean;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Implementation of the queue manager.
- */
-@Component(immediate=true)
-@Service(value={Runnable.class, QueueManager.class, EventHandler.class})
-@Properties({
- @Property(name=Scheduler.PROPERTY_SCHEDULER_PERIOD, longValue=60),
- @Property(name=Scheduler.PROPERTY_SCHEDULER_CONCURRENT, boolValue=false),
- @Property(name=EventConstants.EVENT_TOPIC, value=NotificationConstants.TOPIC_JOB_ADDED)
-})
-public class QueueManager
- implements Runnable, EventHandler, ConfigurationChangeListener {
-
- /** Default logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Reference
- private EventAdmin eventAdmin;
-
- @Reference
- private Scheduler scheduler;
-
- @Reference
- private JobConsumerManager jobConsumerManager;
-
- @Reference
- private QueuesMBean queuesMBean;
-
- @Reference
- private ThreadPoolManager threadPoolManager;
-
- /**
- * Our thread pool.
- */
- @Reference(referenceInterface=EventingThreadPool.class)
- private ThreadPool threadPool;
-
- /** The job manager configuration. */
- @Reference
- private JobManagerConfiguration configuration;
-
- @Reference
- private StatisticsManager statisticsManager;
-
- /** Lock object for the queues map - we don't want to sync directly on the concurrent map. */
- private final Object queuesLock = new Object();
-
- /** All active queues. */
- private final Map<String, JobQueueImpl> queues = new ConcurrentHashMap<String, JobQueueImpl>();
-
- /** We count the scheduler runs. */
- private volatile long schedulerRuns;
-
- /** Flag whether the manager is active or suspended. */
- private final AtomicBoolean isActive = new AtomicBoolean(false);
-
- /** The queue services. */
- private volatile QueueServices queueServices;
-
- /**
- * Activate this component.
- * @param props Configuration properties
- */
- @Activate
- protected void activate(final Map<String, Object> props) {
- logger.info("Apache Sling Queue Manager starting on instance {}", Environment.APPLICATION_ID);
- this.queueServices = new QueueServices();
- queueServices.configuration = this.configuration;
- queueServices.eventAdmin = this.eventAdmin;
- queueServices.jobConsumerManager = this.jobConsumerManager;
- queueServices.scheduler = this.scheduler;
- queueServices.threadPoolManager = this.threadPoolManager;
- queueServices.statisticsManager = statisticsManager;
- queueServices.eventingThreadPool = this.threadPool;
- this.configuration.addListener(this);
- logger.info("Apache Sling Queue Manager started on instance {}", Environment.APPLICATION_ID);
- }
-
- /**
- * Deactivate this component.
- */
- @Deactivate
- protected void deactivate() {
- logger.debug("Apache Sling Queue Manager stopping on instance {}", Environment.APPLICATION_ID);
-
- this.configuration.removeListener(this);
- final Iterator<JobQueueImpl> i = this.queues.values().iterator();
- while ( i.hasNext() ) {
- final JobQueueImpl jbq = i.next();
- jbq.close();
- // update mbeans
- ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, jbq));
- }
- this.queues.clear();
- this.queueServices = null;
- logger.info("Apache Sling Queue Manager stopped on instance {}", Environment.APPLICATION_ID);
- }
-
- /**
- * This method is invoked periodically by the scheduler.
- * It searches for idle queues and stops them after a timeout. If a queue
- * is idle for two consecutive clean up calls, it is removed.
- * @see java.lang.Runnable#run()
- */
- private void maintain() {
- this.schedulerRuns++;
- logger.debug("Queue manager maintenance: Starting #{}", this.schedulerRuns);
-
- // queue maintenance
- if ( this.isActive.get() ) {
- for(final JobQueueImpl jbq : this.queues.values() ) {
- jbq.maintain();
- }
- }
-
- // full topic scan is done every third run
- if ( schedulerRuns % 3 == 0 && this.isActive.get() ) {
- this.fullTopicScan();
- }
-
- // we only do a full clean up on every fifth run
- final boolean doFullCleanUp = (schedulerRuns % 5 == 0);
-
- if ( doFullCleanUp ) {
- // check for idle queue
- logger.debug("Checking for idle queues...");
-
- // we synchronize to avoid creating a queue which is about to be removed during cleanup
- synchronized ( queuesLock ) {
- final Iterator<Map.Entry<String, JobQueueImpl>> i = this.queues.entrySet().iterator();
- while ( i.hasNext() ) {
- final Map.Entry<String, JobQueueImpl> current = i.next();
- final JobQueueImpl jbq = current.getValue();
- if ( jbq.tryToClose() ) {
- logger.debug("Removing idle job queue {}", jbq);
- // remove
- i.remove();
- // update mbeans
- ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, jbq));
- }
- }
- }
- }
- logger.debug("Queue manager maintenance: Finished #{}", this.schedulerRuns);
- }
-
- /**
- * Start a new queue
- * This method first searches the corresponding queue - if such a queue
- * does not exist yet, it is created and started.
- *
- * @param queueInfo The queue info
- * @param topics The topics
- */
- private void start(final QueueInfo queueInfo,
- final Set<String> topics) {
- final InternalQueueConfiguration config = queueInfo.queueConfiguration;
- // get or create queue
- boolean isNewQueue = false;
- JobQueueImpl queue = null;
- // we synchronize to avoid creating a queue which is about to be removed during cleanup
- synchronized ( queuesLock ) {
- queue = this.queues.get(queueInfo.queueName);
- // check for reconfiguration, we really do an identity check here(!)
- if ( queue != null && queue.getConfiguration() != config ) {
- this.outdateQueue(queue);
- // we use a new queue with the configuration
- queue = null;
- }
- if ( queue == null ) {
- queue = JobQueueImpl.createQueue(queueInfo.queueName, config, queueServices, topics);
- // on startup the queue might be empty and we get null back from createQueue
- if ( queue != null ) {
- isNewQueue = true;
- queues.put(queueInfo.queueName, queue);
- ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(queue, null));
- }
- }
- }
- if ( queue != null ) {
- if ( !isNewQueue ) {
- queue.wakeUpQueue(topics);
- }
- queue.startJobs();
- }
- }
-
- /**
- * This method is invoked periodically by the scheduler.
- * In the default configuration every minute
- * @see java.lang.Runnable#run()
- */
- @Override
- public void run() {
- this.maintain();
- }
-
- private void outdateQueue(final JobQueueImpl queue) {
- // remove the queue with the old name
- // check for main queue
- final String oldName = ResourceHelper.filterQueueName(queue.getName());
- this.queues.remove(oldName);
- // check if we can close or have to rename
- if ( queue.tryToClose() ) {
- // copy statistics
- // update mbeans
- ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, queue));
- } else {
- queue.outdate();
- // readd with new name
- String newName = ResourceHelper.filterName(queue.getName());
- int index = 0;
- while ( this.queues.containsKey(newName) ) {
- newName = ResourceHelper.filterName(queue.getName()) + '$' + String.valueOf(index++);
- }
- this.queues.put(newName, queue);
- // update mbeans
- ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(queue, queue));
- }
- }
-
- /**
- * Outdate all queues.
- */
- private void restart() {
- // let's rename/close all queues and clear them
- synchronized ( queuesLock ) {
- final List<JobQueueImpl> queues = new ArrayList<JobQueueImpl>(this.queues.values());
- for(final JobQueueImpl queue : queues ) {
- this.outdateQueue(queue);
- }
- }
- // check if we're still active
- final JobManagerConfiguration config = this.configuration;
- if ( config != null ) {
- final List<Job> rescheduleList = this.configuration.clearJobRetryList();
- for(final Job j : rescheduleList) {
- final JobHandler jh = new JobHandler((JobImpl)j, null, this.configuration);
- jh.reschedule();
- }
- }
- }
-
- /**
- * @param name The queue name
- * @return The queue or {@code null}.
- * @see org.apache.sling.event.jobs.JobManager#getQueue(java.lang.String)
- */
- public Queue getQueue(final String name) {
- return this.queues.get(name);
- }
-
- /**
- * @return An iterator for the available queues.
- * @see org.apache.sling.event.jobs.JobManager#getQueues()
- */
- public Iterable<Queue> getQueues() {
- final Iterator<JobQueueImpl> jqI = this.queues.values().iterator();
- return new Iterable<Queue>() {
-
- @Override
- public Iterator<Queue> iterator() {
- return new Iterator<Queue>() {
-
- @Override
- public boolean hasNext() {
- return jqI.hasNext();
- }
-
- @Override
- public Queue next() {
- return jqI.next();
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
- }
-
- /**
- * This method is called whenever the topology or queue configurations change.
- * @param active Whether the job handling is active atm.
- */
- @Override
- public void configurationChanged(final boolean active) {
- // are we still active?
- if ( this.configuration != null ) {
- logger.debug("Topology changed {}", active);
- this.isActive.set(active);
- if ( active ) {
- fullTopicScan();
- } else {
- this.restart();
- }
- }
- }
-
- private void fullTopicScan() {
- logger.debug("Scanning repository for existing topics...");
- final Set<String> topics = this.scanTopics();
- final Map<QueueInfo, Set<String>> mapping = this.updateTopicMapping(topics);
- // start queues
- for(final Map.Entry<QueueInfo, Set<String>> entry : mapping.entrySet() ) {
- this.start(entry.getKey(), entry.getValue());
- }
- }
-
- /**
- * Scan the resource tree for topics.
- */
- private Set<String> scanTopics() {
- final Set<String> topics = new HashSet<String>();
-
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- final Resource baseResource = resolver.getResource(this.configuration.getLocalJobsPath());
-
- // sanity check - should never be null
- if ( baseResource != null ) {
- final Iterator<Resource> topicIter = baseResource.listChildren();
- while ( topicIter.hasNext() ) {
- final Resource topicResource = topicIter.next();
- final String topic = topicResource.getName().replace('.', '/');
- logger.debug("Found topic {}", topic);
- topics.add(topic);
- }
- }
- } finally {
- resolver.close();
- }
- return topics;
- }
-
- /**
- * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
- */
- @Override
- public void handleEvent(final Event event) {
- final String topic = (String)event.getProperty(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC);
- if ( this.isActive.get() && topic != null ) {
- final QueueInfo info = this.configuration.getQueueConfigurationManager().getQueueInfo(topic);
- this.start(info, Collections.singleton(topic));
- }
- }
-
- /**
- * Get the latest mapping from queue name to topics
- */
- private Map<QueueInfo, Set<String>> updateTopicMapping(final Set<String> topics) {
- final Map<QueueInfo, Set<String>> mapping = new HashMap<QueueConfigurationManager.QueueInfo, Set<String>>();
- for(final String topic : topics) {
- final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(topic);
- Set<String> queueTopics = mapping.get(queueInfo);
- if ( queueTopics == null ) {
- queueTopics = new HashSet<String>();
- mapping.put(queueInfo, queueTopics);
- }
- queueTopics.add(topic);
- }
-
- this.logger.debug("Established new topic mapping: {}", mapping);
- return mapping;
- }
-
- protected void bindThreadPool(final EventingThreadPool etp) {
- this.threadPool = etp;
- }
-
- protected void unbindThreadPool(final EventingThreadPool etp) {
- if ( this.threadPool == etp ) {
- this.threadPool = null;
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java
deleted file mode 100644
index e39235f..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import org.apache.sling.commons.scheduler.Scheduler;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.commons.threads.ThreadPoolManager;
-import org.apache.sling.event.impl.jobs.JobConsumerManager;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
-import org.osgi.service.event.EventAdmin;
-
-/**
- * The queue services class is a helper class containing all
- * services used by the queue implementations.
- * This avoids passing a set of separate objects.
- */
-public class QueueServices {
-
- public JobManagerConfiguration configuration;
-
- public JobConsumerManager jobConsumerManager;
-
- public EventAdmin eventAdmin;
-
- public ThreadPoolManager threadPoolManager;
-
- public Scheduler scheduler;
-
- public StatisticsManager statisticsManager;
-
- public ThreadPool eventingThreadPool;
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java
deleted file mode 100644
index 93684d9..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.queues;
-
-import org.apache.sling.event.impl.jobs.InternalJobState;
-import org.apache.sling.event.jobs.consumer.JobExecutionContext.ResultBuilder;
-import org.apache.sling.event.jobs.consumer.JobExecutionResult;
-
-public class ResultBuilderImpl implements ResultBuilder {
-
- private volatile String message;
-
- private volatile Long retryDelayInMs;
-
- @Override
- public JobExecutionResult failed(final long retryDelayInMs) {
- this.retryDelayInMs = retryDelayInMs;
- return new JobExecutionResultImpl(InternalJobState.FAILED, message, retryDelayInMs);
- }
-
- @Override
- public ResultBuilder message(final String message) {
- this.message = message;
- return this;
- }
-
- @Override
- public JobExecutionResult succeeded() {
- return new JobExecutionResultImpl(InternalJobState.SUCCEEDED, message, retryDelayInMs);
- }
-
- @Override
- public JobExecutionResult failed() {
- return new JobExecutionResultImpl(InternalJobState.FAILED, message, retryDelayInMs);
- }
-
- @Override
- public JobExecutionResult cancelled() {
- return new JobExecutionResultImpl(InternalJobState.CANCELLED, message, retryDelayInMs);
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java
deleted file mode 100644
index 7e46985..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.scheduling;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sling.event.impl.support.ScheduleInfoImpl;
-import org.apache.sling.event.jobs.JobBuilder.ScheduleBuilder;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-
-/**
- * The builder implementation for scheduled jobs.
- */
-public final class JobScheduleBuilderImpl implements ScheduleBuilder {
-
- private final String topic;
-
- private final Map<String, Object> properties;
-
- private final String scheduleName;
-
- private final JobSchedulerImpl jobScheduler;
-
- private volatile boolean suspend = false;
-
- private final List<ScheduleInfoImpl> schedules = new ArrayList<ScheduleInfoImpl>();
-
- public JobScheduleBuilderImpl(
- final String topic,
- final Map<String, Object> properties,
- final String name,
- final JobSchedulerImpl jobScheduler) {
- this.topic = topic;
- this.properties = properties;
- this.scheduleName = name;
- this.jobScheduler = jobScheduler;
- }
-
- @Override
- public ScheduleBuilder weekly(final int day, final int hour, final int minute) {
- schedules.add(ScheduleInfoImpl.WEEKLY(day, hour, minute));
- return this;
- }
-
- @Override
- public ScheduleBuilder daily(final int hour, final int minute) {
- schedules.add(ScheduleInfoImpl.DAILY(hour, minute));
- return this;
- }
-
- @Override
- public ScheduleBuilder hourly(final int minute) {
- schedules.add(ScheduleInfoImpl.HOURLY(minute));
- return this;
- }
-
- @Override
- public ScheduleBuilder at(final Date date) {
- schedules.add(ScheduleInfoImpl.AT(date));
- return this;
- }
-
- @Override
- public ScheduleBuilder monthly(final int day, final int hour, final int minute) {
- schedules.add(ScheduleInfoImpl.MONTHLY(day, hour, minute));
- return this;
- }
-
- @Override
- public ScheduleBuilder yearly(final int month, final int day, final int hour, final int minute) {
- schedules.add(ScheduleInfoImpl.YEARLY(month, day, hour, minute));
- return this;
- }
-
- @Override
- public ScheduleBuilder cron(final String expression) {
- schedules.add(ScheduleInfoImpl.CRON(expression));
- return this;
- }
-
- @Override
- public ScheduledJobInfo add() {
- return this.add(null);
- }
-
- @Override
- public ScheduledJobInfo add(final List<String> errors) {
- return this.jobScheduler.addScheduledJob(topic,
- properties,
- scheduleName,
- suspend,
- schedules,
- errors);
- }
-
- @Override
- public ScheduleBuilder suspend() {
- this.suspend = true;
- return this;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java
deleted file mode 100644
index b4b7612..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.scheduling;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sling.api.resource.ModifiableValueMap;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
-import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChangeListener;
-import org.apache.sling.commons.scheduler.JobContext;
-import org.apache.sling.commons.scheduler.ScheduleOptions;
-import org.apache.sling.commons.scheduler.Scheduler;
-import org.apache.sling.event.impl.jobs.JobManagerImpl;
-import org.apache.sling.event.impl.jobs.Utility;
-import org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.impl.support.ScheduleInfoImpl;
-import org.apache.sling.event.jobs.JobBuilder;
-import org.apache.sling.event.jobs.ScheduleInfo;
-import org.apache.sling.event.jobs.ScheduleInfo.ScheduleType;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-import org.osgi.service.event.Event;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * The scheduler for managing scheduled jobs.
- *
- * This is not a component by itself, it's directly created from the job manager.
- * The job manager is also registering itself as an event handler and forwards
- * the events to this service.
- */
-public class JobSchedulerImpl
- implements ConfigurationChangeListener,
- ResourceChangeListener, ExternalResourceChangeListener,
- org.apache.sling.commons.scheduler.Job {
-
- private static final String PROPERTY_READ_JOB = "properties";
-
- private static final String PROPERTY_SCHEDULE_INDEX = "index";
-
- /** Default logger */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** Is this active? */
- private final AtomicBoolean active = new AtomicBoolean(false);
-
- /** Central job handling configuration. */
- private final JobManagerConfiguration configuration;
-
- /** Scheduler service. */
- private final Scheduler scheduler;
-
- /** Job manager. */
- private final JobManagerImpl jobManager;
-
- /** Scheduled job handler. */
- private final ScheduledJobHandler scheduledJobHandler;
-
- /** All scheduled jobs, by scheduler name */
- private final Map<String, ScheduledJobInfoImpl> scheduledJobs = new HashMap<String, ScheduledJobInfoImpl>();
-
- /**
- * Create the scheduler
- * @param configuration Central job manager configuration
- * @param scheduler The scheduler service
- * @param jobManager The job manager
- */
- public JobSchedulerImpl(final JobManagerConfiguration configuration,
- final Scheduler scheduler,
- final JobManagerImpl jobManager) {
- this.configuration = configuration;
- this.scheduler = scheduler;
- this.jobManager = jobManager;
-
- this.configuration.addListener(this);
-
- this.scheduledJobHandler = new ScheduledJobHandler(configuration, this);
- }
-
- /**
- * Deactivate this component.
- */
- public void deactivate() {
- this.configuration.removeListener(this);
-
- this.scheduledJobHandler.deactivate();
-
- if ( this.active.compareAndSet(true, false) ) {
- this.stopScheduling();
- }
- synchronized ( this.scheduledJobs ) {
- this.scheduledJobs.clear();
- }
- }
-
- /**
- * @see org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener#configurationChanged(boolean)
- */
- @Override
- public void configurationChanged(final boolean processingActive) {
- // scheduling is only active if
- // - processing is active and
- // - configuration is still available and active
- // - and current instance is leader
- final boolean schedulingActive;
- if ( processingActive ) {
- final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
- if ( caps != null && caps.isActive() ) {
- schedulingActive = caps.isLeader();
- } else {
- schedulingActive = false;
- }
- } else {
- schedulingActive = false;
- }
-
- // switch activation based on current state and new state
- if ( schedulingActive ) {
- // activate if inactive
- if ( this.active.compareAndSet(false, true) ) {
- this.startScheduling();
- }
- } else {
- // deactivate if active
- if ( this.active.compareAndSet(true, false) ) {
- this.stopScheduling();
- }
- }
- }
-
- /**
- * Start all scheduled jobs
- */
- private void startScheduling() {
- synchronized ( this.scheduledJobs ) {
- for(final ScheduledJobInfo info : this.scheduledJobs.values()) {
- this.startScheduledJob(((ScheduledJobInfoImpl)info));
- }
- }
- }
-
- /**
- * Stop all scheduled jobs.
- */
- private void stopScheduling() {
- synchronized ( this.scheduledJobs ) {
- for(final ScheduledJobInfo info : this.scheduledJobs.values()) {
- this.stopScheduledJob((ScheduledJobInfoImpl)info);
- }
- }
- }
-
- /**
- * Add a scheduled job
- */
- public void scheduleJob(final ScheduledJobInfoImpl info) {
- synchronized ( this.scheduledJobs ) {
- this.scheduledJobs.put(info.getName(), info);
- this.startScheduledJob(info);
- }
- }
-
- /**
- * Unschedule a scheduled job
- */
- public void unscheduleJob(final ScheduledJobInfoImpl info) {
- synchronized ( this.scheduledJobs ) {
- if ( this.scheduledJobs.remove(info.getName()) != null ) {
- this.stopScheduledJob(info);
- }
- }
- }
-
- /**
- * Remove a scheduled job
- */
- public void removeJob(final ScheduledJobInfoImpl info) {
- this.unscheduleJob(info);
- this.scheduledJobHandler.remove(info);
- }
-
- /**
- * Start a scheduled job
- * @param info The scheduling info
- */
- private void startScheduledJob(final ScheduledJobInfoImpl info) {
- if ( this.active.get() ) {
- if ( !info.isSuspended() ) {
- this.configuration.getAuditLogger().debug("SCHEDULED OK name={}, topic={}, properties={} : {}",
- new Object[] {info.getName(),
- info.getJobTopic(),
- info.getJobProperties()},
- info.getSchedules());
- int index = 0;
- for(final ScheduleInfo si : info.getSchedules()) {
- final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
- ScheduleOptions options = null;
- switch ( si.getType() ) {
- case DAILY:
- case WEEKLY:
- case HOURLY:
- case MONTHLY:
- case YEARLY:
- case CRON:
- options = this.scheduler.EXPR(((ScheduleInfoImpl)si).getCronExpression());
-
- break;
- case DATE:
- options = this.scheduler.AT(((ScheduleInfoImpl)si).getNextScheduledExecution());
- break;
- }
- // Create configuration for scheduled job
- final Map<String, Serializable> config = new HashMap<String, Serializable>();
- config.put(PROPERTY_READ_JOB, info);
- config.put(PROPERTY_SCHEDULE_INDEX, index);
- this.scheduler.schedule(this, options.name(name).config(config).canRunConcurrently(false));
- index++;
- }
- } else {
- this.configuration.getAuditLogger().debug("SCHEDULED SUSPENDED name={}, topic={}, properties={} : {}",
- new Object[] {info.getName(),
- info.getJobTopic(),
- info.getJobProperties(),
- info.getSchedules()});
- }
- }
- }
-
- /**
- * Stop a scheduled job
- * @param info The scheduling info
- */
- private void stopScheduledJob(final ScheduledJobInfoImpl info) {
- final Scheduler localScheduler = this.scheduler;
- if ( localScheduler != null ) {
- this.configuration.getAuditLogger().debug("SCHEDULED STOP name={}, topic={}, properties={} : {}",
- new Object[] {info.getName(),
- info.getJobTopic(),
- info.getJobProperties(),
- info.getSchedules()});
- for(int index = 0; index<info.getSchedules().size(); index++) {
- final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
- localScheduler.unschedule(name);
- }
- }
- }
-
- /**
- * @see org.apache.sling.commons.scheduler.Job#execute(org.apache.sling.commons.scheduler.JobContext)
- */
- @Override
- public void execute(final JobContext context) {
- if ( !active.get() ) {
- // not active anymore, simply return
- return;
- }
- final ScheduledJobInfoImpl info = (ScheduledJobInfoImpl) context.getConfiguration().get(PROPERTY_READ_JOB);
-
- if ( info.isSuspended() ) {
- return;
- }
-
- this.jobManager.addJob(info.getJobTopic(), info.getJobProperties());
- final int index = (Integer)context.getConfiguration().get(PROPERTY_SCHEDULE_INDEX);
- final Iterator<ScheduleInfo> iter = info.getSchedules().iterator();
- ScheduleInfo si = iter.next();
- for(int i=0; i<index; i++) {
- si = iter.next();
- }
- // if scheduled once (DATE), remove from schedule
- if ( si.getType() == ScheduleType.DATE ) {
- if ( index == 0 && info.getSchedules().size() == 1 ) {
- // remove
- this.scheduledJobHandler.remove(info);
- } else {
- // update schedule list
- final List<ScheduleInfo> infos = new ArrayList<ScheduleInfo>();
- for(final ScheduleInfo i : info.getSchedules() ) {
- if ( i != si ) { // no need to use equals
- infos.add(i);
- }
- }
- info.update(infos);
- this.scheduledJobHandler.updateSchedule(info.getName(), infos);
- }
- }
- }
-
- /**
- * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
- */
- public void handleEvent(final Event event) {
- if ( ResourceHelper.BUNDLE_EVENT_STARTED.equals(event.getTopic())
- || ResourceHelper.BUNDLE_EVENT_UPDATED.equals(event.getTopic()) ) {
- this.scheduledJobHandler.bundleEvent();
- }
- }
-
- /**
- * Helper method which just logs the exception in debug mode.
- * @param e
- */
- private void ignoreException(final Exception e) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Ignored exception " + e.getMessage(), e);
- }
- }
-
- /**
- * Create a schedule builder for a currently scheduled job
- */
- public JobBuilder.ScheduleBuilder createJobBuilder(final ScheduledJobInfoImpl info) {
- final JobBuilder.ScheduleBuilder sb = new JobScheduleBuilderImpl(info.getJobTopic(),
- info.getJobProperties(), info.getName(), this);
- return (info.isSuspended() ? sb.suspend() : sb);
- }
-
- private enum Operation {
- LESS,
- LESS_OR_EQUALS,
- EQUALS,
- GREATER_OR_EQUALS,
- GREATER
- }
-
- /**
- * Check if the job matches the template
- */
- private boolean match(final ScheduledJobInfoImpl job, final Map<String, Object> template) {
- if ( template != null ) {
- for(final Map.Entry<String, Object> current : template.entrySet()) {
- final String key = current.getKey();
- final char firstChar = key.length() > 0 ? key.charAt(0) : 0;
- final String propName;
- final Operation op;
- if ( firstChar == '=' ) {
- propName = key.substring(1);
- op = Operation.EQUALS;
- } else if ( firstChar == '<' ) {
- final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
- if ( secondChar == '=' ) {
- op = Operation.LESS_OR_EQUALS;
- propName = key.substring(2);
- } else {
- op = Operation.LESS;
- propName = key.substring(1);
- }
- } else if ( firstChar == '>' ) {
- final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
- if ( secondChar == '=' ) {
- op = Operation.GREATER_OR_EQUALS;
- propName = key.substring(2);
- } else {
- op = Operation.GREATER;
- propName = key.substring(1);
- }
- } else {
- propName = key;
- op = Operation.EQUALS;
- }
- final Object value = current.getValue();
-
- if ( op == Operation.EQUALS ) {
- if ( !value.equals(job.getJobProperties().get(propName)) ) {
- return false;
- }
- } else {
- if ( value instanceof Comparable ) {
- @SuppressWarnings({ "unchecked", "rawtypes" })
- final int result = ((Comparable)value).compareTo(job.getJobProperties().get(propName));
- if ( op == Operation.LESS && result > -1 ) {
- return false;
- } else if ( op == Operation.LESS_OR_EQUALS && result > 0 ) {
- return false;
- } else if ( op == Operation.GREATER_OR_EQUALS && result < 0 ) {
- return false;
- } else if ( op == Operation.GREATER && result < 1 ) {
- return false;
- }
- } else {
- // if the value is not comparable we simply don't match
- return false;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Get all scheduled jobs
- */
- public Collection<ScheduledJobInfo> getScheduledJobs(final String topic,
- final long limit,
- final Map<String, Object>... templates) {
- final List<ScheduledJobInfo> jobs = new ArrayList<ScheduledJobInfo>();
- long count = 0;
- synchronized ( this.scheduledJobs ) {
- for(final ScheduledJobInfoImpl job : this.scheduledJobs.values() ) {
- boolean add = true;
- if ( topic != null && !topic.equals(job.getJobTopic()) ) {
- add = false;
- }
- if ( add && templates != null && templates.length != 0 ) {
- add = false;
- for (Map<String,Object> template : templates) {
- add = this.match(job, template);
- if ( add ) {
- break;
- }
- }
- }
- if ( add ) {
- jobs.add(job);
- count++;
- if ( limit > 0 && count == limit ) {
- break;
- }
- }
- }
- }
- return jobs;
- }
-
- /**
- * Change the suspended flag for a scheduled job
- * @param info The schedule info
- * @param flag The corresponding flag
- */
- public void setSuspended(final ScheduledJobInfoImpl info, final boolean flag) {
- final ResourceResolver resolver = configuration.createResourceResolver();
- try {
- final StringBuilder sb = new StringBuilder(this.configuration.getScheduledJobsPath(true));
- sb.append(ResourceHelper.filterName(info.getName()));
- final String path = sb.toString();
-
- final Resource eventResource = resolver.getResource(path);
- if ( eventResource != null ) {
- final ModifiableValueMap mvm = eventResource.adaptTo(ModifiableValueMap.class);
- if ( flag ) {
- mvm.put(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED, Boolean.TRUE);
- } else {
- mvm.remove(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED);
- }
- resolver.commit();
- }
- if ( flag ) {
- this.stopScheduledJob(info);
- } else {
- this.startScheduledJob(info);
- }
- } catch (final PersistenceException pe) {
- // we ignore the exception if removing fails
- ignoreException(pe);
- } finally {
- resolver.close();
- }
- }
-
- /**
- * Add a scheduled job
- * @param topic The job topic
- * @param properties The job properties
- * @param scheduleName The schedule name
- * @param isSuspended Whether it is suspended
- * @param scheduleInfos The scheduling information
- * @param errors Optional list to contain potential errors
- * @return A new job info or {@code null}
- */
- public ScheduledJobInfo addScheduledJob(final String topic,
- final Map<String, Object> properties,
- final String scheduleName,
- final boolean isSuspended,
- final List<ScheduleInfoImpl> scheduleInfos,
- final List<String> errors) {
- final List<String> msgs = new ArrayList<String>();
- if ( scheduleName == null || scheduleName.length() == 0 ) {
- msgs.add("Schedule name not specified");
- }
- final String errorMessage = Utility.checkJob(topic, properties);
- if ( errorMessage != null ) {
- msgs.add(errorMessage);
- }
- if ( scheduleInfos.size() == 0 ) {
- msgs.add("No schedule defined for " + scheduleName);
- }
- for(final ScheduleInfoImpl info : scheduleInfos) {
- info.check(msgs);
- }
- if ( msgs.size() == 0 ) {
- try {
- final ScheduledJobInfo info = this.scheduledJobHandler.addOrUpdateJob(topic, properties, scheduleName, isSuspended, scheduleInfos);
- if ( info != null ) {
- return info;
- }
- msgs.add("Unable to persist scheduled job.");
- } catch ( final PersistenceException pe) {
- msgs.add("Unable to persist scheduled job: " + scheduleName);
- logger.warn("Unable to persist scheduled job", pe);
- }
- } else {
- for(final String msg : msgs) {
- logger.warn(msg);
- }
- }
- if ( errors != null ) {
- errors.addAll(msgs);
- }
- return null;
- }
-
- public void maintenance() {
- this.scheduledJobHandler.maintenance();
- }
-
- /**
- * @see org.apache.sling.api.resource.observation.ResourceChangeListener#onChange(java.util.List)
- */
- @Override
- public void onChange(List<ResourceChange> changes) {
- for(final ResourceChange change : changes ) {
- if ( change.getPath() != null && change.getPath().startsWith(this.configuration.getScheduledJobsPath(true)) ) {
- if ( change.getType() == ResourceChange.ChangeType.REMOVED ) {
- // removal
- logger.debug("Remove scheduled job {}", change.getPath());
- this.scheduledJobHandler.handleRemove(change.getPath());
- } else {
- // add or update
- logger.debug("Add or update scheduled job {}, event {}", change.getPath(), change.getType());
- this.scheduledJobHandler.handleAddUpdate(change.getPath());
- }
- }
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java
deleted file mode 100644
index deb3955..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java
+++ /dev/null
@@ -1,545 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.scheduling;
-
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.sling.api.resource.ModifiableValueMap;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceUtil;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
-import org.apache.sling.event.impl.support.Environment;
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.impl.support.ScheduleInfoImpl;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.ScheduleInfo;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- */
-public class ScheduledJobHandler implements Runnable {
-
- public static final class Holder {
- public Calendar created;
- public ScheduledJobInfoImpl info;
- public long read;
- }
-
- /** Logger. */
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
- /** The job manager configuration. */
- private final JobManagerConfiguration configuration;
-
- /** The job scheduler. */
- private final JobSchedulerImpl jobScheduler;
-
- /** The map of all scheduled jobs, key is the filtered schedule name */
- private final Map<String, Holder> scheduledJobs = new HashMap<String, Holder>();
-
- private final AtomicLong lastBundleActivity = new AtomicLong();
-
- private final AtomicBoolean isRunning = new AtomicBoolean(true);
-
- /** A local queue for serializing the event processing. */
- private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
-
- /**
- * @param configuration Current job manager configuration
- */
- public ScheduledJobHandler(final JobManagerConfiguration configuration,
- final JobSchedulerImpl jobScheduler) {
- this.configuration = configuration;
- this.jobScheduler = jobScheduler;
- final Thread t = new Thread(this, "Apache Sling Scheduled Job Handler Thread");
- t.setDaemon(true);
- t.start();
-
- this.addFullScan();
- }
-
- /**
- * Add a task/runnable to the queue
- */
- private void addTask(final Runnable r) {
- try {
- this.queue.put(r);
- } catch (final InterruptedException e) {
- this.ignoreException(e);
- Thread.currentThread().interrupt();
- }
- }
- /**
- * Add a full scan to the task queue
- */
- private void addFullScan() {
- this.addTask(new Runnable() {
- @Override
- public void run() {
- scan();
- }
- });
- }
-
- public void deactivate() {
- this.isRunning.set(false);
- this.queue.clear();
- // put a NOP runnable to wake up the queue
- this.addTask(new Runnable() {
- @Override
- public void run() {
- // do nothing
- }
- });
- }
-
- @Override
- public void run() {
- while ( this.isRunning.get() ) {
- Runnable r = null;
- try {
- r = this.queue.take();
- } catch (final InterruptedException e) {
- this.ignoreException(e);
- Thread.currentThread().interrupt();
- this.isRunning.set(false);
- }
- if ( this.isRunning.get() && r != null) {
- r.run();
- }
- }
- }
-
- private void scan() {
- final ResourceResolver resolver = configuration.createResourceResolver();
- if ( resolver != null ) {
- try {
- logger.debug("Scanning for scheduled jobs...");
- final String path = this.configuration.getScheduledJobsPath(false);
- final Resource startResource = resolver.getResource(path);
- if ( startResource != null ) {
- final Map<String, Holder> newScheduledJobs = new HashMap<String, Holder>();
- synchronized ( this.scheduledJobs ) {
- for(final Resource rsrc : startResource.getChildren()) {
- if ( !isRunning.get() ) {
- break;
- }
- handleAddOrUpdate(newScheduledJobs, rsrc);
- }
- if ( isRunning.get() ) {
- for(final Holder h : this.scheduledJobs.values()) {
- if ( h.info != null ) {
- this.jobScheduler.unscheduleJob(h.info);
- }
- }
- this.scheduledJobs.clear();
- this.scheduledJobs.putAll(newScheduledJobs);
- }
- }
- }
- logger.debug("Finished scanning for scheduled jobs...");
- } finally {
- resolver.close();
- }
- }
- }
-
- /**
- * Read a scheduled job from the resource
- * @return The job or <code>null</code>
- */
- private Map<String, Object> readScheduledJob(final Resource eventResource) {
- try {
- final ValueMap vm = ResourceHelper.getValueMap(eventResource);
- final Map<String, Object> properties = ResourceHelper.cloneValueMap(vm);
-
- @SuppressWarnings("unchecked")
- final List<Exception> readErrorList = (List<Exception>) properties.remove(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
- if ( readErrorList != null ) {
- for(final Exception e : readErrorList) {
- logger.warn("Unable to read scheduled job from " + eventResource.getPath(), e);
- }
- } else {
- return properties;
- }
- } catch (final InstantiationException ie) {
- // something happened with the resource in the meantime
- this.ignoreException(ie);
- }
- return null;
- }
-
- /**
- * Write a scheduled job to the resource tree.
- * @throws PersistenceException
- */
- public ScheduledJobInfoImpl addOrUpdateJob(
- final String jobTopic,
- final Map<String, Object> jobProperties,
- final String scheduleName,
- final boolean suspend,
- final List<ScheduleInfoImpl> scheduleInfos)
- throws PersistenceException {
- final Map<String, Object> properties = this.writeScheduledJob(jobTopic, jobProperties, scheduleName, suspend, scheduleInfos);
-
- final String key = ResourceHelper.filterName(scheduleName);
- synchronized ( this.scheduledJobs ) {
- final Holder h = this.scheduledJobs.remove(key);
- if ( h != null && h.info != null ) {
- this.jobScheduler.unscheduleJob(h.info);
- }
- final Holder holder = new Holder();
- holder.created = (Calendar) properties.get(Job.PROPERTY_JOB_CREATED);
- holder.read = System.currentTimeMillis();
- holder.info = this.addOrUpdateScheduledJob(properties, h == null ? null : h.info);
-
- this.jobScheduler.scheduleJob(holder.info);
- return holder.info;
- }
- }
-
- private Map<String, Object> writeScheduledJob(final String jobTopic,
- final Map<String, Object> jobProperties,
- final String scheduleName,
- final boolean suspend,
- final List<ScheduleInfoImpl> scheduleInfos)
- throws PersistenceException {
- final ResourceResolver resolver = this.configuration.createResourceResolver();
- try {
- // create properties
- final Map<String, Object> properties = new HashMap<String, Object>();
-
- if ( jobProperties != null ) {
- for(final Map.Entry<String, Object> entry : jobProperties.entrySet() ) {
- final String propName = entry.getKey();
- if ( !ResourceHelper.ignoreProperty(propName) ) {
- properties.put(propName, entry.getValue());
- }
- }
- }
-
- properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
- properties.put(Job.PROPERTY_JOB_CREATED, Calendar.getInstance());
- properties.put(Job.PROPERTY_JOB_CREATED_INSTANCE, Environment.APPLICATION_ID);
-
- // put scheduler name and scheduler info
- properties.put(ResourceHelper.PROPERTY_SCHEDULE_NAME, scheduleName);
- final String[] infoArray = new String[scheduleInfos.size()];
- int index = 0;
- for(final ScheduleInfoImpl info : scheduleInfos) {
- infoArray[index] = info.getSerializedString();
- index++;
- }
- properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, infoArray);
- if ( suspend ) {
- properties.put(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED, Boolean.TRUE);
- }
-
- // create path and resource
- properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, ResourceHelper.RESOURCE_TYPE_SCHEDULED_JOB);
-
- final String path = this.configuration.getScheduledJobsPath(true) + ResourceHelper.filterName(scheduleName);
-
- // update existing resource
- final Resource existingInfo = resolver.getResource(path);
- if ( existingInfo != null ) {
- resolver.delete(existingInfo);
- logger.debug("Updating scheduled job {} at {}", properties, path);
- } else {
- logger.debug("Storing new scheduled job {} at {}", properties, path);
- }
- ResourceHelper.getOrCreateResource(resolver,
- path,
- properties);
- // put back real schedule infos
- properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, scheduleInfos);
-
- return properties;
- } finally {
- resolver.close();
- }
- }
-
- private ScheduledJobInfoImpl addOrUpdateScheduledJob(
- final Map<String, Object> properties,
- final ScheduledJobInfoImpl oldInfo) {
- properties.remove(ResourceResolver.PROPERTY_RESOURCE_TYPE);
- properties.remove(Job.PROPERTY_JOB_CREATED);
- properties.remove(Job.PROPERTY_JOB_CREATED_INSTANCE);
-
- final String jobTopic = (String) properties.remove(ResourceHelper.PROPERTY_JOB_TOPIC);
- final String schedulerName = (String) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_NAME);
-
- final ScheduledJobInfoImpl info;
- if ( oldInfo == null ) {
- info = new ScheduledJobInfoImpl(jobScheduler, schedulerName);
- } else {
- info = oldInfo;
- }
- info.update(jobTopic, properties);
-
- return info;
- }
-
- /**
- * A bundle event occurred which means we can try loading jobs that previously
- * failed because of missing classes.
- */
- public void bundleEvent() {
- this.lastBundleActivity.set(System.currentTimeMillis());
- this.addTask(new Runnable() {
- @Override
- public void run() {
- final Map<String, Holder> updateJobs = new HashMap<String, ScheduledJobHandler.Holder>();
- synchronized ( scheduledJobs ) {
- for(final Map.Entry<String, Holder> entry : scheduledJobs.entrySet()) {
- if ( entry.getValue().info == null && entry.getValue().read < lastBundleActivity.get() ) {
- updateJobs.put(entry.getKey(), entry.getValue());
- }
- }
- }
- if ( !updateJobs.isEmpty() && isRunning.get() ) {
- ResourceResolver resolver = configuration.createResourceResolver();
- if ( resolver != null ) {
- try {
- for(final Map.Entry<String, Holder> entry : updateJobs.entrySet()) {
- final String path = configuration.getScheduledJobsPath(true) + entry.getKey();
- final Resource rsrc = resolver.getResource(path);
- if ( !isRunning.get() ) {
- break;
- }
- if ( rsrc != null ) {
- synchronized ( scheduledJobs ) {
- handleAddOrUpdate(scheduledJobs, rsrc);
- }
- }
- }
- } finally {
- resolver.close();
- }
- }
- }
- }
- });
- }
-
- /**
- * Handle observation event for removing a scheduled job
- * @param path The path to the job
- */
- public void handleRemove(final String path) {
- this.addTask(new Runnable() {
- @Override
- public void run() {
- if ( isRunning.get() ) {
- final String scheduleKey = ResourceHelper.filterName(ResourceUtil.getName(path));
- if ( scheduleKey != null ) {
- synchronized ( scheduledJobs ) {
- final Holder h = scheduledJobs.remove(scheduleKey);
- if ( h != null && h.info != null ) {
- jobScheduler.unscheduleJob(h.info);
- }
- }
- }
- }
- }
- });
- }
-
- /**
- * Handle observation event for adding or updating a scheduled job
- * @param path The path to the job
- */
- public void handleAddUpdate(final String path) {
- this.addTask(new Runnable() {
- @Override
- public void run() {
- if ( isRunning.get() ) {
- final ResourceResolver resolver = configuration.createResourceResolver();
- if ( resolver != null ) {
- try {
- final Resource rsrc = resolver.getResource(path);
- if ( rsrc != null ) {
- synchronized ( scheduledJobs ) {
- handleAddOrUpdate(scheduledJobs, rsrc);
- }
- }
- } finally {
- resolver.close();
- }
- }
- }
- }
- });
- }
-
- /**
- * Handle add or update of a resource
- * @param newScheduledJobs The map to store the jobs
- * @param rsrc The resource containing the job
- */
- private void handleAddOrUpdate(final Map<String, Holder> newScheduledJobs, final Resource rsrc) {
- final String id = ResourceHelper.filterName(rsrc.getName());
- final Holder scheduled = this.scheduledJobs.remove(id);
- boolean read = false;
- if ( scheduled != null ) {
- // check if loading failed and we can retry
- if ( scheduled.info == null || scheduled.read < this.lastBundleActivity.get() ) {
- read = true;
- }
- // check if this is an update
- if ( scheduled.info != null ) {
- final ValueMap vm = ResourceUtil.getValueMap(rsrc);
- final Calendar changed = (Calendar) vm.get(Job.PROPERTY_JOB_CREATED);
- if ( changed != null && scheduled.created.compareTo(changed) < 0 ) {
- read = true;
- }
- }
- if ( !read ) {
- // nothing changes
- newScheduledJobs.put(id, scheduled);
- }
- } else {
- read = true;
- }
- if ( read ) {
- // read
- final Holder holder = new Holder();
- holder.read = System.currentTimeMillis();
-
- final Map<String, Object> properties = this.readScheduledJob(rsrc);
- if ( properties != null ) {
- holder.created = (Calendar) properties.get(Job.PROPERTY_JOB_CREATED);
- holder.info = this.addOrUpdateScheduledJob(properties, scheduled != null ? scheduled.info : null);
- }
- newScheduledJobs.put(id, holder);
-
- if ( holder.info == null && scheduled != null && scheduled.info != null ) {
- this.jobScheduler.unscheduleJob(scheduled.info);
- }
- if ( holder.info != null ) {
- this.jobScheduler.scheduleJob(holder.info);
- }
- }
- }
-
- /**
- * Remove a scheduled job
- * @param info The schedule info
- */
- public void remove(final ScheduledJobInfoImpl info) {
- final String scheduleKey = ResourceHelper.filterName(info.getName());
-
- final ResourceResolver resolver = configuration.createResourceResolver();
- try {
- final StringBuilder sb = new StringBuilder(configuration.getScheduledJobsPath(true));
- sb.append(scheduleKey);
- final String path = sb.toString();
-
- final Resource eventResource = resolver.getResource(path);
- if ( eventResource != null ) {
- resolver.delete(eventResource);
- resolver.commit();
- }
- } catch (final PersistenceException pe) {
- // we ignore the exception if removing fails
- ignoreException(pe);
- } finally {
- resolver.close();
- }
-
- synchronized ( this.scheduledJobs ) {
- final Holder h = scheduledJobs.remove(scheduleKey);
- if ( h != null && h.info != null ) {
- jobScheduler.unscheduleJob(h.info);
- }
- }
- }
-
- public void updateSchedule(final String scheduleName, final Collection<ScheduleInfo> scheduleInfo) {
-
- final ResourceResolver resolver = configuration.createResourceResolver();
- try {
- final String scheduleKey = ResourceHelper.filterName(scheduleName);
-
- final StringBuilder sb = new StringBuilder(configuration.getScheduledJobsPath(true));
- sb.append(scheduleKey);
- final String path = sb.toString();
-
- final Resource rsrc = resolver.getResource(path);
- // This is an update, if we can't find the resource we ignore it
- if ( rsrc != null ) {
- final Calendar now = Calendar.getInstance();
-
- // update holder first
- synchronized ( scheduledJobs ) {
- final Holder h = scheduledJobs.get(scheduleKey);
- if ( h != null ) {
- h.created = now;
- }
- }
-
- final ModifiableValueMap mvm = rsrc.adaptTo(ModifiableValueMap.class);
- mvm.put(Job.PROPERTY_JOB_CREATED, now);
- final String[] infoArray = new String[scheduleInfo.size()];
- int index = 0;
- for(final ScheduleInfo si : scheduleInfo) {
- infoArray[index] = ((ScheduleInfoImpl)si).getSerializedString();
- index++;
- }
- mvm.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, infoArray);
-
- try {
- resolver.commit();
- } catch ( final PersistenceException pe) {
- logger.warn("Unable to update scheduled job " + scheduleName, pe);
- }
- }
- } finally {
- resolver.close();
- }
- }
-
- /**
- * Helper method which just logs the exception in debug mode.
- * @param e The exception
- */
- private void ignoreException(final Exception e) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Ignored exception " + e.getMessage(), e);
- }
- }
-
- public void maintenance() {
- this.addFullScan();
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobInfoImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobInfoImpl.java
deleted file mode 100644
index 99fcdb4..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobInfoImpl.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.scheduling;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.impl.support.ScheduleInfoImpl;
-import org.apache.sling.event.jobs.Job;
-import org.apache.sling.event.jobs.JobBuilder.ScheduleBuilder;
-import org.apache.sling.event.jobs.ScheduleInfo;
-import org.apache.sling.event.jobs.ScheduledJobInfo;
-
-/**
- * The job schedule information.
- * It holds all required information like
- * - the name of the schedule
- * - the job topic
- * - the job properties
- * - scheduling information
- */
-public class ScheduledJobInfoImpl implements ScheduledJobInfo, Serializable {
-
- private static final long serialVersionUID = 1L;
-
- private final String scheduleName;
-
- private final JobSchedulerImpl jobScheduler;
-
- private final AtomicBoolean isSuspended = new AtomicBoolean(false);
-
- private volatile List<ScheduleInfo> scheduleInfos;
-
- private volatile String jobTopic;
-
- private volatile Map<String, Object> jobProperties;
-
- /**
- * Create a new info object
- * @param jobScheduler The job scheduler
- * @param scheduleName The unique name
- */
- public ScheduledJobInfoImpl(final JobSchedulerImpl jobScheduler,
- final String scheduleName) {
- this.jobScheduler = jobScheduler;
- this.scheduleName = scheduleName;
- }
-
- /**
- * Update/set the job related information
- * @param jobTopic The job topic
- * @param jobProperties The job properties
- */
- public void update(final String jobTopic,
- final Map<String, Object> jobProperties) {
- final boolean isSuspended = jobProperties.remove(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED) != null;
- @SuppressWarnings("unchecked")
- final List<ScheduleInfo> scheduleInfos = (List<ScheduleInfo>) jobProperties.remove(ResourceHelper.PROPERTY_SCHEDULE_INFO);
-
- this.jobTopic = jobTopic;
- this.jobProperties = jobProperties;
- this.scheduleInfos = Collections.unmodifiableList(scheduleInfos);
-
- this.isSuspended.set(isSuspended);
- }
-
- /**
- * Update the scheduling information
- * @param scheduleInfos The new schedule
- */
- public void update(final List<ScheduleInfo> scheduleInfos) {
- this.scheduleInfos = Collections.unmodifiableList(scheduleInfos);
- }
-
- /**
- * Get the schedule name
- */
- public String getName() {
- return this.scheduleName;
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#getSchedules()
- */
- @Override
- public Collection<ScheduleInfo> getSchedules() {
- return this.scheduleInfos;
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#getNextScheduledExecution()
- */
- @Override
- public Date getNextScheduledExecution() {
- Date result = null;
- for(final ScheduleInfo info : this.scheduleInfos) {
- final Date newResult = ((ScheduleInfoImpl)info).getNextScheduledExecution();
- if ( result == null || result.getTime() > newResult.getTime() ) {
- result = newResult;
- }
- }
- return result;
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#getJobTopic()
- */
- @Override
- public String getJobTopic() {
- return this.jobTopic;
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#getJobProperties()
- */
- @Override
- public Map<String, Object> getJobProperties() {
- return this.jobProperties;
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#unschedule()
- */
- @Override
- public void unschedule() {
- this.jobScheduler.removeJob(this);
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#reschedule()
- */
- @Override
- public ScheduleBuilder reschedule() {
- return this.jobScheduler.createJobBuilder(this);
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#suspend()
- */
- @Override
- public void suspend() {
- if ( this.isSuspended.compareAndSet(false, true) ) {
- this.jobScheduler.setSuspended(this, true);
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#resume()
- */
- @Override
- public void resume() {
- if ( this.isSuspended.compareAndSet(true, false) ) {
- this.jobScheduler.setSuspended(this, false);
- }
- }
-
- /**
- * @see org.apache.sling.event.jobs.ScheduledJobInfo#isSuspended()
- */
- @Override
- public boolean isSuspended() {
- return this.isSuspended.get();
- }
-
- /**
- * Get the scheduler job id
- */
- public String getSchedulerJobId() {
- return Job.class.getName() + ":" + this.scheduleName;
- }
-}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/stats/StatisticsImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/stats/StatisticsImpl.java
deleted file mode 100644
index 00a278f..0000000
--- a/src/main/java/org/apache/sling/event/impl/jobs/stats/StatisticsImpl.java
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.event.impl.jobs.stats;
-
-import org.apache.sling.event.jobs.Statistics;
-
-/**
- * Implementation of the statistics.
- */
-public class StatisticsImpl implements Statistics {
-
- private volatile long startTime;
-
- private volatile long activeJobs;
-
- private volatile long queuedJobs;
-
- private volatile long lastActivated = -1;
-
- private volatile long lastFinished = -1;
-
- private volatile long averageWaitingTime;
-
- private volatile long averageProcessingTime;
-
- private volatile long waitingTime;
-
- private volatile long processingTime;
-
- private volatile long waitingCount;
-
- private volatile long processingCount;
-
- private volatile long finishedJobs;
-
- private volatile long failedJobs;
-
- private volatile long cancelledJobs;
-
- public StatisticsImpl() {
- this(System.currentTimeMillis());
- }
-
- public StatisticsImpl(final long startTime) {
- this.startTime = startTime;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getStartTime()
- */
- @Override
- public synchronized long getStartTime() {
- return startTime;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getNumberOfProcessedJobs()
- */
- @Override
- public synchronized long getNumberOfProcessedJobs() {
- return getNumberOfCancelledJobs() + getNumberOfFailedJobs() + getNumberOfFinishedJobs();
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getNumberOfActiveJobs()
- */
- @Override
- public synchronized long getNumberOfActiveJobs() {
- return activeJobs;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getNumberOfQueuedJobs()
- */
- @Override
- public synchronized long getNumberOfQueuedJobs() {
- return queuedJobs;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getNumberOfJobs()
- */
- @Override
- public synchronized long getNumberOfJobs() {
- return activeJobs + queuedJobs;
- }
-
- /**
- * @see org.apache.sling.event.jobs.Statistics#getAverageWaitingTime()
- */
- @Override
- public synchronized long getAverageWaitingTime() {
- return averageWaitingTime;
- }
-
- /**
... 7663 lines suppressed ...
[sling-org-apache-sling-event-api] 28/32: SLING-11316: Update to parent 47 (#2)
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 6cee307d3c2782b1c6de4cf5a7525a6a22a078f7
Author: Ashok Pelluru <37...@users.noreply.github.com>
AuthorDate: Thu May 12 17:23:39 2022 +0200
SLING-11316: Update to parent 47 (#2)
---
pom.xml | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/pom.xml b/pom.xml
index fd48b70..a453864 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,13 +22,12 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.sling</groupId>
- <artifactId>sling</artifactId>
- <version>30</version>
+ <artifactId>sling-bundle-parent</artifactId>
+ <version>47</version>
<relativePath />
</parent>
<artifactId>org.apache.sling.event.api</artifactId>
- <packaging>bundle</packaging>
<version>1.0.1-SNAPSHOT</version>
<name>Apache Sling Event API</name>
@@ -40,19 +39,27 @@
<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-event-api.git</url>
+ <tag>HEAD</tag>
</scm>
<properties>
+ <project.build.outputTimestamp>1</project.build.outputTimestamp>
<site.jira.version.id>12340350</site.jira.version.id>
</properties>
<build>
<plugins>
<plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
</project>
[sling-org-apache-sling-event-api] 13/32: Fix typo in pom.xml : conncetion should be connection
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 5858dc8870232aa3fc0655e9599deccd271dfef6
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Oct 23 12:06:30 2017 +0300
Fix typo in pom.xml : conncetion should be connection
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 9312e32..fd48b70 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,7 @@
</description>
<scm>
- <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</conncetion>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-event-api.git</url>
</scm>
[sling-org-apache-sling-event-api] 10/32: Update to parent pom 30
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 8aaaa778638189c7ff9253d500a538a3590f2a37
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue May 9 14:26:06 2017 +0000
Update to parent pom 30
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1794588 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 22 +---------------------
1 file changed, 1 insertion(+), 21 deletions(-)
diff --git a/pom.xml b/pom.xml
index 35b17cb..0ddfe3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
<parent>
<groupId>org.apache.sling</groupId>
<artifactId>sling</artifactId>
- <version>29</version>
+ <version>30</version>
<relativePath />
</parent>
@@ -52,26 +52,6 @@
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
- <configuration>
- <instructions>
- <_plugin>org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir=${project.build.outputDirectory};</_plugin>
- </instructions>
- </configuration>
- <dependencies>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr.bnd</artifactId>
- <version>1.7.2</version>
- </dependency>
- </dependencies>
- </plugin>
- <plugin>
- <groupId>org.apache.rat</groupId>
- <artifactId>apache-rat-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
[sling-org-apache-sling-event-api] 06/32: SLING-6739 : site.jira.version.id set to version Event API 1.0.0
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 73ee24abb9fd609c0b48d71edf7e0ee4b485f6af
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Apr 12 07:56:36 2017 +0000
SLING-6739 : site.jira.version.id set to version Event API 1.0.0
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1791084 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index ff65800..6894d87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
</scm>
<properties>
- <site.jira.version.id>12315369</site.jira.version.id>
+ <site.jira.version.id>12340350</site.jira.version.id>
</properties>
<build>
[sling-org-apache-sling-event-api] 16/32: Updating badges for org-apache-sling-event-api
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 401dc90b9825f0f5a74d01ce25c59181e2bb3cc9
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Jun 8 12:55:08 2018 -0400
Updating badges for org-apache-sling-event-api
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d2402bb..fd67fbc 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+
+ [](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-event-api-1.8) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.event.api%22) [ project.
@@ -11,4 +15,4 @@ Note that the default implementation has been spun-off into a separate bundle: s
Sling Event API defines the API for Jobs. It defines Job, JobManager and Queue, as well as consumer Apis for a
JobConsumer. There are ancillary APIs to support the work of these core interfaces. The core APIs are exported from
-org.apache.sling.event.jobs with the consumers exported from org.apache.sling.event.jobs.consumer.
\ No newline at end of file
+org.apache.sling.event.jobs with the consumers exported from org.apache.sling.event.jobs.consumer.
[sling-org-apache-sling-event-api] 23/32: Adding sonarqube badges as per SLING-9682
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 03410665045cb6c9e004e405ac84aa8697712cd0
Author: Dan Klco <dk...@apache.org>
AuthorDate: Mon Aug 24 17:18:08 2020 -0400
Adding sonarqube badges as per SLING-9682
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 207073f..e37f9bf 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[<img src="https://sling.apache.org/res/logos/sling.png"/>](https://sling.apache.org)
+[](https://sling.apache.org)
- [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/master/) [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/mas [...]
+ [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/master/) [](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-event-api) [, 1 deletion(-)
diff --git a/README.md b/README.md
index e37f9bf..e01f109 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
[](https://sling.apache.org)
- [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/master/) [](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-event-api) [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/master/) [](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-event-api) [, 1 deletion(-)
diff --git a/src/main/java/org/apache/sling/event/jobs/consumer/package-info.java b/src/main/java/org/apache/sling/event/jobs/consumer/package-info.java
index 5237caa..7deff3d 100644
--- a/src/main/java/org/apache/sling/event/jobs/consumer/package-info.java
+++ b/src/main/java/org/apache/sling/event/jobs/consumer/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@org.osgi.annotation.versioning.Version("1.2.1")
+@org.osgi.annotation.versioning.Version("1.3.0")
package org.apache.sling.event.jobs.consumer;
[sling-org-apache-sling-event-api] 29/32: SLING-11709 - Set up Jira autolinks to all Sling Github projects
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit d151fdf0da58ed0dbf18002788f8965b0ebcef84
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Apr 5 00:12:55 2023 +0300
SLING-11709 - Set up Jira autolinks to all Sling Github projects
Update .asf.yaml to respect defaults
---
.asf.yaml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..4db293a
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,11 @@
+github:
+ autolink_jira:
+ - "SLING"
+ - "OAK"
+ - "JCR"
+ - "JCRVLT"
+ - "INFRA"
+ - "FELIX"
+ - "MNG"
+notifications:
+ jira_options: "link"
[sling-org-apache-sling-event-api] 20/32: SLING-7245 - Validate pull requests using Jenkins
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 9d2fa80cc6ffb653cdfeac8bae2e0887c17a7066
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Jan 30 09:27:32 2019 +0100
SLING-7245 - Validate pull requests using Jenkins
---
Jenkinsfile | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..f582519
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+slingOsgiBundleBuild()
[sling-org-apache-sling-event-api] 11/32: SLING-7167 Adjust READMEs
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit ca79319f812dc642f4bea2dc606d4ad7f9340001
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Tue Oct 3 09:54:01 2017 +0000
SLING-7167 Adjust READMEs
add uniform header linking to Sling project
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1810838 13f79535-47bb-0310-9956-ffa450edef68
---
README.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index b53763d..d2402bb 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
-# Sling Event (Jobs) API bundle.
+# Apache Sling Event API
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
For user documentation see https://sling.apache.org/documentation/bundles/apache-sling-eventing-and-job-handling.html.
This README contains information on the API details.
Note that the default implementation has been spun-off into a separate bundle: sling.event.resource
-# Bundle
+## Bundle
Sling Event API defines the API for Jobs. It defines Job, JobManager and Queue, as well as consumer Apis for a
JobConsumer. There are ancillary APIs to support the work of these core interfaces. The core APIs are exported from
[sling-org-apache-sling-event-api] 22/32: Updating badges for org-apache-sling-event-api
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 70fb37fa5a881121812031b3d28a361916fb91e4
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Mon Aug 24 18:44:09 2020 +0200
Updating badges for org-apache-sling-event-api
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7a8431d..207073f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
[<img src="https://sling.apache.org/res/logos/sling.png"/>](https://sling.apache.org)
- [](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-event-api/job/master) [](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.event.api%22) [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/master/) [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-event-api/job/mas [...]
# Apache Sling Event API
[sling-org-apache-sling-event-api] 08/32: [maven-release-plugin] prepare release org.apache.sling.event.api-1.0.0
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 92de6869fb3ad2272404cf3d60845a193af2588f
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Apr 12 08:23:03 2017 +0000
[maven-release-plugin] prepare release org.apache.sling.event.api-1.0.0
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1791087 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pom.xml b/pom.xml
index 51fb044..f1c5cf1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
<artifactId>org.apache.sling.event.api</artifactId>
<packaging>bundle</packaging>
- <version>0.0.1-SNAPSHOT</version>
+ <version>1.0.0</version>
<name>Apache Sling Event API</name>
<description>
@@ -37,9 +37,9 @@
</description>
<scm>
- <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</connection>
- <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</developerConnection>
- <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/api</url>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.event.api-1.0.0</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.event.api-1.0.0</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.event.api-1.0.0</url>
</scm>
<properties>
[sling-org-apache-sling-event-api] 17/32: SLING-7216 - [nice-to-have] Add a CODE_OF_CONDUCT file to every module
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit a159ab25a50b5dbdd897b012f97f88acc656d426
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Sep 19 18:04:00 2018 +0200
SLING-7216 - [nice-to-have] Add a CODE_OF_CONDUCT file to every module
---
CODE_OF_CONDUCT.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..52f21cb
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
[sling-org-apache-sling-event-api] 04/32: SLING-6739 : README adjusted to reflect the new status of this API bundle
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 01655d155b938d1797ecd2082b48b3c1f4601b86
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Mar 29 09:55:23 2017 +0000
SLING-6739 : README adjusted to reflect the new status of this API bundle
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1789319 13f79535-47bb-0310-9956-ffa450edef68
---
README.md | 85 +++++----------------------------------------------------------
1 file changed, 6 insertions(+), 79 deletions(-)
diff --git a/README.md b/README.md
index a017cb5..b53763d 100644
--- a/README.md
+++ b/README.md
@@ -1,85 +1,12 @@
-# Sling Event (Jobs) bundle.
+# Sling Event (Jobs) API bundle.
For user documentation see https://sling.apache.org/documentation/bundles/apache-sling-eventing-and-job-handling.html.
-This README contains information on the bundle, APIs and implementation details.
+This README contains information on the API details.
+
+Note that the default implementation has been spun-off into a separate bundle: sling.event.resource
# Bundle
-Sling Event contains support for Jobs. It provides an Api for Job, JobManager and Queue, as well as consumer Apis for a
+Sling Event API defines the API for Jobs. It defines Job, JobManager and Queue, as well as consumer Apis for a
JobConsumer. There are ancillary APIs to support the work of these core interfaces. The core APIs are exported from
-org.apache.sling.event.jobs with the consumers exported from org.apache.sling.event.jobs.consumer.
-
-
-# Design and implementation
-
-
-## Processing model
-
-Jobs are created using the JobManager API. When a Job is created the JobManager writes an entry into the resource tree
-(usually backed up by JCR) via the ResourceResolver.
-
-For notification of interested parties - not for processing the jobs(!) - adding a job emits an OSGi
-Event on org/apache/sling/api/resource/Resource/ADDED topic, which is picked up by the
-[NewJobSender](src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java) which emits a new OSGi
-Event on the org/apache/sling/event/notification/job/ADDED topic.
-
-Similar other notification events are sent out if the state of a job changes. However these events are just FYI events.
-The events used are contained in [NotificationConstants.java](src/main/java/org/apache/sling/event/jobs/NotificationConstants.java).
-
-The QueueManager which identifies the queue from s job is periodically scanning the resource tree for new jobs. In
-addition it listens to the org/apache/sling/event/notification/job/ADDED topic for optimization and picking up new
-jobs faster (than by scanning). Once a new job is found, the manager triggers the JobQueueImpl to start processing.
-Various other operations ensure that the JobQueueImpl runs jobs according to its configuration. These are either
-periodic maintenance classes or triggered by calls to the QueueManager or triggered by Jobs on the queue changing state.
-
-The queue is maintained by the JobManagerImpl, but each Queue is managed by a JobQueueImpl that receives calls from the
-QueueManager to process jobs. Any thread may update the persisted job state, by resolving the Job name and performing the operation.
-
-## Storage
-
-The JobManager uses the resource tree and therefore by default the JCR repository provided by Oak for persisting Jobs. The
-content tree structure was developed in conjunction with advice from the Oak team to avoid write concurrency issues and the
-need for maintaining in Oak repository locks. To avoid OakMergeConflicts on write, each job gets a UUID. If a new job is
-created on a Sling instance, this instance decides - based on configuration - which Sling instance will process the job
-and writes the new job to an area dedicated to that instance (/var/eventing/jobs/assigned/<SlingID>). Each Sling instance
-only reads and modifies its own subtree under /var/eventing/jobs/assigned/<SlingID>. This prevents more than one instance from
-attempting to process a Job at the same time. This means that when a Job is started, the path of the job is formed from
-the target SlingID and the queue name. The target SlingID is formed from the queue configuration informed
-by properties contained within Topology. Every Sling instance advertises is capabilities for processing jobs via topology,
-hence the JobManager pre-allocates Jobs to instance when the job is created.
-
-The JobQueueImpl then receives the job. The JobQueueImpl only considers jobs allocated to the local instance, and runs
-those jobs.
-
-Since the SlingID is part of the JobID, there is no risk of 2 instances writing to the same job at the same time when the
-job is allocated or re-allocated to an instance. In the case of first allocation, the creating sling instance will perform
-the write operation and the target sling instance wont know about the job until after Oak commits. In the case of re-allocation
-the target sling instance is dead, the cluster leader performs the write and the new target sling instnance wont see the job
-until after Oak commits.
-
-## Topology changes
-
-When a Sling instance in a cluster is shutdown, it will stop processing all the jobs allocated to it. When it shuts down
-a Topology change event propagates and the cluster leader scans all instances under /var/eventing/jobs/assigned/ to see
-if there are any instances that don't exist any more. If there are, the topology leader moves those jobs to a different
-node by deleting the Oak node and writing a new node into the new targetId assigned location. Any jobs that cant be re-assigned
-are written to the unassigned location.
-
-## Known issues with current implementation and design
-
-These issues may have been addressed since this document was written, if they have please remove the known issues.
-
-1. Pre-allocation of jobs to queues bound to instances will not ensure load is distributed amongst available instances
-especially when the queues are large, as jobs complexity and resource requirements vary wildly.
-2. When the topology changes, with many jobs the cost of reallocating jobs may be prohibitive.
-
-
-# Scheduled Jobs.
-
-In addition to one off jobs the bundle has support for scheduled jobs. The schedule is stored in /var/eventing/scheduled-jobs,
-and the cluster leader uses the Sling commons Scheduler service to run a schedules which add jobs to the appropriate queues
-using the job manager. For info see org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl.execute which is called by the
-Sling commons Scheduler service.
-
-
-
+org.apache.sling.event.jobs with the consumers exported from org.apache.sling.event.jobs.consumer.
\ No newline at end of file
[sling-org-apache-sling-event-api] 21/32: Updating badges for org-apache-sling-event-api
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit a4eec75ef5ba4c2971eadc0c539897d9a89ab615
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Jan 31 13:11:37 2019 +0100
Updating badges for org-apache-sling-event-api
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index fd67fbc..7a8431d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+[<img src="https://sling.apache.org/res/logos/sling.png"/>](https://sling.apache.org)
- [](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-event-api-1.8) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.event.api%22) [](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-event-api/job/master) [](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.event.api%22) [
diff --git a/pom.xml b/pom.xml
index 6894d87..51fb044 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,32 +69,10 @@
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
</plugin>
- <!-- plain unit tests -->
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>**/it/**</exclude>
- </excludes>
- </configuration>
- </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
- <profiles>
- <profile>
- <id>port-java8</id>
- <activation>
- <activeByDefault>false</activeByDefault>
- <jdk>1.8</jdk>
- </activation>
- <properties>
- <min.port>38000</min.port>
- <max.port>38999</max.port>
- </properties>
- </profile>
- </profiles>
</project>
[sling-org-apache-sling-event-api] 25/32: SLING-10676 - add or update SECURITY.md
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 3ada74de5c62a96114c7e589feb7f765f2f72e3e
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Wed Jul 28 16:24:10 2021 +0200
SLING-10676 - add or update SECURITY.md
---
SECURITY.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/SECURITY.md b/SECURITY.md
index e69de29..2e2f930 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy for Apache Sling modules
+
+This module is part of the [Apache Sling Project](https://sling.apache.org), a
+project of the [Apache Software Foundation](https://apache.org) (ASF).
+
+It follows the ASF's [vulnerability handling process](https://apache.org/security/#vulnerability-handling) and
+provides its own [security information page](http://sling.apache.org/project-information/security.html).
+
+## Reporting a Vulnerability
+
+To report a new vulnerability you have discovered in an Apache Sling module,
+please follow the instructions on the
+[project's security page](http://sling.apache.org/project-information/security.html) .
\ No newline at end of file
[sling-org-apache-sling-event-api] 14/32: SLING-7214 - Add the LICENSE file to every module
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 028bcb74a1fddac41b1c88e729905ed19b3aca7a
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Oct 23 18:38:23 2017 +0200
SLING-7214 - Add the LICENSE file to every module
---
LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 202 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
[sling-org-apache-sling-event-api] 12/32: SLING-7162 - Update SCM information in all POM files to reference Git
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit ed0da02106b296dead04e8d37c9dc1deba1263c2
Author: Robert Munteanu <ro...@gmail.com>
AuthorDate: Fri Oct 20 19:13:14 2017 +0200
SLING-7162 - Update SCM information in all POM files to reference Git
---
pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pom.xml b/pom.xml
index 0ddfe3e..9312e32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,9 +37,9 @@
</description>
<scm>
- <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</connection>
- <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</developerConnection>
- <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/api</url>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</conncetion>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-event-api.git</url>
</scm>
<properties>
[sling-org-apache-sling-event-api] 18/32: SLING-7215 - [nice-to-have] Add a CONTRIBUTING file to every module
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 697014c9b230533324c27659146ae4e1c26364ff
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Sep 19 18:29:17 2018 +0200
SLING-7215 - [nice-to-have] Add a CONTRIBUTING file to every module
---
CONTRIBUTING.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ca36072
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.
[sling-org-apache-sling-event-api] 15/32: SLING-7213 - Add a default .gitignore file to every module
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 30a5e210380244330abec126a9ae20f06fcf2fe3
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Oct 24 13:41:00 2017 +0200
SLING-7213 - Add a default .gitignore file to every module
---
.gitignore | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 33554c7..5b783ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,17 @@
-jackrabbit
+/target
+.idea
+.classpath
+.metadata
+.project
+.settings
+.externalToolBuilders
+maven-eclipse.xml
+*.swp
+*.iml
+*.ipr
+*.iws
+*.bak
+.vlt
+.DS_Store
+jcr.log
+atlassian-ide-plugin.xml
[sling-org-apache-sling-event-api] 01/32: SLING-6739 : copied bundles/extensions/event into event/api and event/resources - done via svn cp to preserve history of both new parts
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 139dab1d32d494e3b73674c45f85611c27965a4d
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Mar 29 07:57:17 2017 +0000
SLING-6739 : copied bundles/extensions/event into event/api and event/resources - done via svn cp to preserve history of both new parts
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1789290 13f79535-47bb-0310-9956-ffa450edef68
---
.gitignore | 1 +
README.md | 85 +++
pom.xml | 377 ++++++++++
.../sling/event/impl/EnvironmentComponent.java | 82 +++
.../sling/event/impl/EventingThreadPool.java | 129 ++++
.../sling/event/impl/jobs/InternalJobState.java | 42 ++
.../sling/event/impl/jobs/JobBuilderImpl.java | 70 ++
.../sling/event/impl/jobs/JobConsumerManager.java | 511 ++++++++++++++
.../apache/sling/event/impl/jobs/JobHandler.java | 284 ++++++++
.../org/apache/sling/event/impl/jobs/JobImpl.java | 411 +++++++++++
.../sling/event/impl/jobs/JobManagerImpl.java | 759 +++++++++++++++++++++
.../sling/event/impl/jobs/JobTopicTraverser.java | 176 +++++
.../org/apache/sling/event/impl/jobs/Utility.java | 281 ++++++++
.../jobs/config/ConfigurationChangeListener.java | 33 +
.../impl/jobs/config/ConfigurationConstants.java | 48 ++
.../jobs/config/InternalQueueConfiguration.java | 402 +++++++++++
.../impl/jobs/config/JobManagerConfiguration.java | 661 ++++++++++++++++++
.../impl/jobs/config/MainQueueConfiguration.java | 115 ++++
.../jobs/config/QueueConfigurationManager.java | 169 +++++
.../impl/jobs/config/TopologyCapabilities.java | 324 +++++++++
.../event/impl/jobs/config/TopologyHandler.java | 114 ++++
.../event/impl/jobs/console/InventoryPlugin.java | 471 +++++++++++++
.../event/impl/jobs/console/WebConsolePlugin.java | 453 ++++++++++++
.../event/impl/jobs/jmx/AbstractJobStatistics.java | 100 +++
.../event/impl/jobs/jmx/AllJobStatisticsMBean.java | 57 ++
.../sling/event/impl/jobs/jmx/EmptyStatistics.java | 79 +++
.../sling/event/impl/jobs/jmx/QueueMBeanImpl.java | 50 ++
.../event/impl/jobs/jmx/QueueStatusEvent.java | 51 ++
.../sling/event/impl/jobs/jmx/QueuesMBeanImpl.java | 185 +++++
.../impl/jobs/notifications/NewJobSender.java | 123 ++++
.../jobs/notifications/NotificationUtility.java | 77 +++
.../impl/jobs/queues/JobExecutionContextImpl.java | 124 ++++
.../impl/jobs/queues/JobExecutionResultImpl.java | 97 +++
.../sling/event/impl/jobs/queues/JobQueueImpl.java | 708 +++++++++++++++++++
.../event/impl/jobs/queues/QueueJobCache.java | 344 ++++++++++
.../sling/event/impl/jobs/queues/QueueManager.java | 447 ++++++++++++
.../event/impl/jobs/queues/QueueServices.java | 49 ++
.../event/impl/jobs/queues/ResultBuilderImpl.java | 57 ++
.../jobs/scheduling/JobScheduleBuilderImpl.java | 120 ++++
.../impl/jobs/scheduling/JobSchedulerImpl.java | 566 +++++++++++++++
.../impl/jobs/scheduling/ScheduledJobHandler.java | 545 +++++++++++++++
.../impl/jobs/scheduling/ScheduledJobInfoImpl.java | 193 ++++++
.../event/impl/jobs/stats/StatisticsImpl.java | 319 +++++++++
.../event/impl/jobs/stats/StatisticsManager.java | 182 +++++
.../event/impl/jobs/stats/TopicStatisticsImpl.java | 169 +++++
.../event/impl/jobs/tasks/CheckTopologyTask.java | 333 +++++++++
.../sling/event/impl/jobs/tasks/CleanUpTask.java | 275 ++++++++
.../impl/jobs/tasks/FindUnfinishedJobsTask.java | 137 ++++
.../event/impl/jobs/tasks/HistoryCleanUpTask.java | 255 +++++++
.../sling/event/impl/jobs/tasks/UpgradeTask.java | 279 ++++++++
.../event/impl/support/BatchResourceRemover.java | 55 ++
.../sling/event/impl/support/Environment.java | 35 +
.../event/impl/support/ExactTopicMatcher.java | 44 ++
.../event/impl/support/PackageTopicMatcher.java | 51 ++
.../sling/event/impl/support/ResourceHelper.java | 426 ++++++++++++
.../sling/event/impl/support/ScheduleInfoImpl.java | 421 ++++++++++++
.../impl/support/SubPackagesTopicMatcher.java | 52 ++
.../sling/event/impl/support/TopicMatcher.java | 28 +
.../event/impl/support/TopicMatcherHelper.java | 69 ++
src/main/java/org/apache/sling/event/jobs/Job.java | 351 ++++++++++
.../org/apache/sling/event/jobs/JobBuilder.java | 161 +++++
.../org/apache/sling/event/jobs/JobManager.java | 223 ++++++
.../sling/event/jobs/NotificationConstants.java | 106 +++
.../java/org/apache/sling/event/jobs/Queue.java | 97 +++
.../sling/event/jobs/QueueConfiguration.java | 111 +++
.../org/apache/sling/event/jobs/ScheduleInfo.java | 89 +++
.../apache/sling/event/jobs/ScheduledJobInfo.java | 89 +++
.../org/apache/sling/event/jobs/Statistics.java | 112 +++
.../apache/sling/event/jobs/TopicStatistics.java | 87 +++
.../sling/event/jobs/consumer/JobConsumer.java | 133 ++++
.../event/jobs/consumer/JobExecutionContext.java | 144 ++++
.../event/jobs/consumer/JobExecutionResult.java | 71 ++
.../sling/event/jobs/consumer/JobExecutor.java | 104 +++
.../sling/event/jobs/consumer/package-info.java | 23 +
.../apache/sling/event/jobs/jmx/QueuesMBean.java | 28 +
.../sling/event/jobs/jmx/StatisticsMBean.java | 34 +
.../apache/sling/event/jobs/jmx/package-info.java | 23 +
.../org/apache/sling/event/jobs/package-info.java | 23 +
src/main/resources/SLING-INF/nodetypes/event.cnd | 42 ++
.../java/org/apache/sling/event/impl/Barrier.java | 57 ++
.../java/org/apache/sling/event/impl/TestUtil.java | 53 ++
.../jobs/InstanceDescriptionComparatorTest.java | 199 ++++++
.../event/impl/jobs/JobConsumerManagerTest.java | 198 ++++++
.../apache/sling/event/impl/jobs/JobsImplTest.java | 62 ++
.../sling/event/impl/jobs/StatisticsImplTest.java | 268 ++++++++
.../apache/sling/event/impl/jobs/UtilityTest.java | 80 +++
.../config/InternalQueueConfigurationTest.java | 195 ++++++
.../jobs/config/JobManagerConfigurationTest.java | 259 +++++++
.../impl/jobs/config/TopologyCapabilitiesTest.java | 71 ++
.../impl/jobs/jmx/AllJobStatisticsMBeanTest.java | 69 ++
.../sling/event/impl/jobs/jmx/DummyStatistics.java | 84 +++
.../event/impl/jobs/jmx/QueuesMBeanImplTest.java | 134 ++++
.../impl/jobs/tasks/HistoryCleanUpTaskTest.java | 106 +++
.../sling/event/it/AbstractJobHandlingTest.java | 462 +++++++++++++
.../java/org/apache/sling/event/it/ChaosTest.java | 402 +++++++++++
.../apache/sling/event/it/ClassloadingTest.java | 211 ++++++
.../org/apache/sling/event/it/HistoryTest.java | 141 ++++
.../org/apache/sling/event/it/JobHandlingTest.java | 436 ++++++++++++
.../apache/sling/event/it/OrderedQueueTest.java | 173 +++++
.../apache/sling/event/it/RoundRobinQueueTest.java | 172 +++++
.../org/apache/sling/event/it/SchedulingTest.java | 87 +++
.../org/apache/sling/event/it/TimedJobsTest.java | 86 +++
.../apache/sling/event/it/TopicMatchingTest.java | 168 +++++
.../apache/sling/event/it/UnorderedQueueTest.java | 172 +++++
104 files changed, 19496 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..33554c7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+jackrabbit
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a017cb5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,85 @@
+# Sling Event (Jobs) bundle.
+
+For user documentation see https://sling.apache.org/documentation/bundles/apache-sling-eventing-and-job-handling.html.
+This README contains information on the bundle, APIs and implementation details.
+
+# Bundle
+
+Sling Event contains support for Jobs. It provides an Api for Job, JobManager and Queue, as well as consumer Apis for a
+JobConsumer. There are ancillary APIs to support the work of these core interfaces. The core APIs are exported from
+org.apache.sling.event.jobs with the consumers exported from org.apache.sling.event.jobs.consumer.
+
+
+# Design and implementation
+
+
+## Processing model
+
+Jobs are created using the JobManager API. When a Job is created the JobManager writes an entry into the resource tree
+(usually backed up by JCR) via the ResourceResolver.
+
+For notification of interested parties - not for processing the jobs(!) - adding a job emits an OSGi
+Event on org/apache/sling/api/resource/Resource/ADDED topic, which is picked up by the
+[NewJobSender](src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java) which emits a new OSGi
+Event on the org/apache/sling/event/notification/job/ADDED topic.
+
+Similar other notification events are sent out if the state of a job changes. However these events are just FYI events.
+The events used are contained in [NotificationConstants.java](src/main/java/org/apache/sling/event/jobs/NotificationConstants.java).
+
+The QueueManager which identifies the queue from s job is periodically scanning the resource tree for new jobs. In
+addition it listens to the org/apache/sling/event/notification/job/ADDED topic for optimization and picking up new
+jobs faster (than by scanning). Once a new job is found, the manager triggers the JobQueueImpl to start processing.
+Various other operations ensure that the JobQueueImpl runs jobs according to its configuration. These are either
+periodic maintenance classes or triggered by calls to the QueueManager or triggered by Jobs on the queue changing state.
+
+The queue is maintained by the JobManagerImpl, but each Queue is managed by a JobQueueImpl that receives calls from the
+QueueManager to process jobs. Any thread may update the persisted job state, by resolving the Job name and performing the operation.
+
+## Storage
+
+The JobManager uses the resource tree and therefore by default the JCR repository provided by Oak for persisting Jobs. The
+content tree structure was developed in conjunction with advice from the Oak team to avoid write concurrency issues and the
+need for maintaining in Oak repository locks. To avoid OakMergeConflicts on write, each job gets a UUID. If a new job is
+created on a Sling instance, this instance decides - based on configuration - which Sling instance will process the job
+and writes the new job to an area dedicated to that instance (/var/eventing/jobs/assigned/<SlingID>). Each Sling instance
+only reads and modifies its own subtree under /var/eventing/jobs/assigned/<SlingID>. This prevents more than one instance from
+attempting to process a Job at the same time. This means that when a Job is started, the path of the job is formed from
+the target SlingID and the queue name. The target SlingID is formed from the queue configuration informed
+by properties contained within Topology. Every Sling instance advertises is capabilities for processing jobs via topology,
+hence the JobManager pre-allocates Jobs to instance when the job is created.
+
+The JobQueueImpl then receives the job. The JobQueueImpl only considers jobs allocated to the local instance, and runs
+those jobs.
+
+Since the SlingID is part of the JobID, there is no risk of 2 instances writing to the same job at the same time when the
+job is allocated or re-allocated to an instance. In the case of first allocation, the creating sling instance will perform
+the write operation and the target sling instance wont know about the job until after Oak commits. In the case of re-allocation
+the target sling instance is dead, the cluster leader performs the write and the new target sling instnance wont see the job
+until after Oak commits.
+
+## Topology changes
+
+When a Sling instance in a cluster is shutdown, it will stop processing all the jobs allocated to it. When it shuts down
+a Topology change event propagates and the cluster leader scans all instances under /var/eventing/jobs/assigned/ to see
+if there are any instances that don't exist any more. If there are, the topology leader moves those jobs to a different
+node by deleting the Oak node and writing a new node into the new targetId assigned location. Any jobs that cant be re-assigned
+are written to the unassigned location.
+
+## Known issues with current implementation and design
+
+These issues may have been addressed since this document was written, if they have please remove the known issues.
+
+1. Pre-allocation of jobs to queues bound to instances will not ensure load is distributed amongst available instances
+especially when the queues are large, as jobs complexity and resource requirements vary wildly.
+2. When the topology changes, with many jobs the cost of reallocating jobs may be prohibitive.
+
+
+# Scheduled Jobs.
+
+In addition to one off jobs the bundle has support for scheduled jobs. The schedule is stored in /var/eventing/scheduled-jobs,
+and the cluster leader uses the Sling commons Scheduler service to run a schedules which add jobs to the appropriate queues
+using the job manager. For info see org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl.execute which is called by the
+Sling commons Scheduler service.
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4c12105
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,377 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>30</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.event</artifactId>
+ <packaging>bundle</packaging>
+ <version>4.2.3-SNAPSHOT</version>
+
+ <name>Apache Sling Event Support</name>
+ <description>
+ Support for eventing.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event</url>
+ </scm>
+
+ <properties>
+ <site.jira.version.id>12315369</site.jira.version.id>
+ <exam.version>4.4.0</exam.version>
+ <url.version>2.4.5</url.version>
+ <bundle.build.dir>${basedir}/target</bundle.build.dir>
+ <bundle.file.name>${bundle.build.dir}/${project.build.finalName}.jar</bundle.file.name>
+ <min.port>37000</min.port>
+ <max.port>37999</max.port>
+ </properties>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.servlet;javax.servlet.http;resolution:=optional,
+ org.apache.felix.inventory;resolution:=optional,
+ *
+ </Import-Package>
+ <DynamicImport-Package>
+ javax.servlet,
+ javax.servlet.http,
+ org.apache.felix.inventory
+ </DynamicImport-Package>
+ <Sling-Nodetypes>
+ SLING-INF/nodetypes/event.cnd
+ </Sling-Nodetypes>
+ <Sling-Namespaces>
+ slingevent=http://sling.apache.org/jcr/event/1.0
+ </Sling-Namespaces>
+ <Embed-Dependency>
+ jackrabbit-jcr-commons;inline="org/apache/jackrabbit/util/ISO9075.*|org/apache/jackrabbit/util/ISO8601.*|org/apache/jackrabbit/util/XMLChar.*",
+ org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/PropertiesUtil.*",
+ quartz;inline="org/quartz/CronExpression.*|org/quartz/ValueSet.*"
+ </Embed-Dependency>
+ <_plugin>org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir=${project.build.outputDirectory};</_plugin>
+ </instructions>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.bnd</artifactId>
+ <version>1.7.2</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>derby.log</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <!-- plain unit tests -->
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>**/it/**</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>reserve-network-port</id>
+ <goals>
+ <goal>reserve-network-port</goal>
+ </goals>
+ <phase>pre-integration-test</phase>
+ <configuration>
+ <portNames>
+ <portName>http.port</portName>
+ </portNames>
+ <minPortNumber>${min.port}</minPortNumber>
+ <maxPortNumber>${max.port}</maxPortNumber>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- integration tests run with pax-exam -->
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>project.bundle.file</name>
+ <value>${bundle.file.name}</value>
+ </property>
+ <property>
+ <name>bundle.build.dir</name>
+ <value>${bundle.build.dir}</value>
+ </property>
+ <property>
+ <name>org.osgi.service.http.port</name>
+ <value>${http.port}</value>
+ </property>
+ </systemProperties>
+ <argLine>
+ -Xmx2048m -XX:MaxPermSize=512m
+ </argLine>
+ <includes>
+ <include>**/it/*</include>
+ </includes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${basedir}</directory>
+ <includes>
+ <include>derby.log</include>
+ </includes>
+ </fileset>
+ <fileset>
+ <directory>jackrabbit</directory>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <excludePackageNames>org.apache.sling.event.impl:org.apache.sling.event.impl.jobs:org.apache.sling.event.impl.jobs.config:org.apache.sling.event.impl.jobs.console:org.apache.sling.event.impl.jobs.jmx:org.apache.sling.event.impl.jobs.notifications:org.apache.sling.event.impl.jobs.queues:org.apache.sling.event.impl.jobs.scheduling:org.apache.sling.event.impl.jobs.stats:org.apache.sling.event.impl.jobs.tasks:org.apache.sling.event.impl.support</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>port-java8</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ <jdk>1.8</jdk>
+ </activation>
+ <properties>
+ <min.port>38000</min.port>
+ <max.port>38999</max.port>
+ </properties>
+ </profile>
+ </profiles>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.discovery.api</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.inventory</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.event</artifactId>
+ <version>1.3.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.settings</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.11.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.scheduler</artifactId>
+ <version>2.4.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.threads</artifactId>
+ <version>3.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.discovery.commons</artifactId>
+ <version>1.0.12</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.serviceusermapper</artifactId>
+ <version>1.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr-commons</artifactId>
+ <version>2.11.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.quartz-scheduler</groupId>
+ <artifactId>quartz</artifactId>
+ <version>2.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Webconsole -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <!-- Testing -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.cm</artifactId>
+ <version>1.5.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.tools</artifactId>
+ <version>1.0.6</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+ <version>1.6.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-cm</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-aether</artifactId>
+ <version>${url.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>5.4.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java b/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java
new file mode 100644
index 0000000..b3f7929
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/EnvironmentComponent.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.settings.SlingSettingsService;
+
+/**
+ * Environment component. This component provides "global settings"
+ * to all services, like the application id and the thread pool.
+ * @since 3.0
+ *
+ * This component needs to be immediate to set the global variables
+ * (application id and thread pool).
+ */
+@Component(immediate=true)
+@Service(value=EnvironmentComponent.class)
+public class EnvironmentComponent {
+
+ /**
+ * Our thread pool.
+ */
+ @Reference(referenceInterface=EventingThreadPool.class)
+ private ThreadPool threadPool;
+
+ /** Sling settings service. */
+ @Reference
+ private SlingSettingsService settingsService;
+
+ /**
+ * Activate this component.
+ */
+ @Activate
+ protected void activate() {
+ // Set the application id and the thread pool
+ Environment.APPLICATION_ID = this.settingsService.getSlingId();
+ Environment.THREAD_POOL = this.threadPool;
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ protected void deactivate() {
+ // Unset the thread pool
+ if ( Environment.THREAD_POOL == this.threadPool ) {
+ Environment.THREAD_POOL = null;
+ }
+ }
+
+ protected void bindThreadPool(final EventingThreadPool etp) {
+ this.threadPool = etp;
+ }
+
+ protected void unbindThreadPool(final EventingThreadPool etp) {
+ if ( this.threadPool == etp ) {
+ this.threadPool = null;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java b/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java
new file mode 100644
index 0000000..0821866
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/EventingThreadPool.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl;
+
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.commons.threads.ModifiableThreadPoolConfig;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.commons.threads.ThreadPoolConfig;
+import org.apache.sling.commons.threads.ThreadPoolConfig.ThreadPriority;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+
+
+/**
+ * The configurable eventing thread pool.
+ */
+@Component(label="Apache Sling Job Thread Pool",
+ description="This is the thread pool used by the Apache Sling job handling. The "
+ + "threads from this pool are merely used for executing jobs. By limiting this pool, it is "
+ + "possible to limit the maximum number of parallel processed jobs - regardless of the queue "
+ + "configuration.",
+ metatype=true)
+@Service(value=EventingThreadPool.class)
+public class EventingThreadPool implements ThreadPool {
+
+ @Reference
+ private ThreadPoolManager threadPoolManager;
+
+ /** The real thread pool used. */
+ private org.apache.sling.commons.threads.ThreadPool threadPool;
+
+ private static final int DEFAULT_POOL_SIZE = 35;
+
+ @Property(intValue=DEFAULT_POOL_SIZE,
+ label="Pool Size",
+ description="The size of the thread pool. This pool is used to execute jobs and therefore "
+ + "limits the maximum number of jobs executed in parallel.")
+ private static final String PROPERTY_POOL_SIZE = "minPoolSize";
+
+ public EventingThreadPool() {
+ // default constructor
+ }
+
+ public EventingThreadPool(final ThreadPoolManager tpm, final int poolSize) {
+ this.threadPoolManager = tpm;
+ this.configure(poolSize);
+ }
+
+ public void release() {
+ this.deactivate();
+ }
+
+ /**
+ * Activate this component.
+ */
+ @Activate
+ @Modified
+ protected void activate(final Map<String, Object> props) {
+ final int maxPoolSize = PropertiesUtil.toInteger(props.get(PROPERTY_POOL_SIZE), DEFAULT_POOL_SIZE);
+ this.configure(maxPoolSize);
+ }
+
+ private void configure(final int maxPoolSize) {
+ final ModifiableThreadPoolConfig config = new ModifiableThreadPoolConfig();
+ config.setMinPoolSize(maxPoolSize);
+ config.setMaxPoolSize(config.getMinPoolSize());
+ config.setQueueSize(-1); // unlimited
+ config.setShutdownGraceful(true);
+ config.setPriority(ThreadPriority.NORM);
+ config.setDaemon(true);
+ this.threadPool = threadPoolManager.create(config, "Apache Sling Job Thread Pool");
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ protected void deactivate() {
+ this.threadPoolManager.release(this.threadPool);
+ }
+
+ /**
+ * @see org.apache.sling.commons.threads.ThreadPool#execute(java.lang.Runnable)
+ */
+ @Override
+ public void execute(final Runnable runnable) {
+ threadPool.execute(runnable);
+ }
+
+ /**
+ * @see org.apache.sling.commons.threads.ThreadPool#getConfiguration()
+ */
+ @Override
+ public ThreadPoolConfig getConfiguration() {
+ return threadPool.getConfiguration();
+ }
+
+ /**
+ * @see org.apache.sling.commons.threads.ThreadPool#getName()
+ */
+ @Override
+ public String getName() {
+ return threadPool.getName();
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java b/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java
new file mode 100644
index 0000000..6fe1994
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/InternalJobState.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.consumer.JobExecutor;
+
+/**
+ * The state of the job after it has been processed by a {@link JobExecutor}.
+ */
+public enum InternalJobState {
+
+ SUCCEEDED(NotificationConstants.TOPIC_JOB_FINISHED), // processing finished successfully
+ FAILED(NotificationConstants.TOPIC_JOB_FAILED), // processing failed, can be retried
+ CANCELLED(NotificationConstants.TOPIC_JOB_CANCELLED); // processing failed permanently
+
+ private final String topic;
+
+ InternalJobState(final String topic) {
+ this.topic = topic;
+ }
+
+ public String getTopic() {
+ return this.topic;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
new file mode 100644
index 0000000..a662066
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.sling.event.impl.jobs.scheduling.JobScheduleBuilderImpl;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.JobBuilder;
+
+/**
+ * Fluent builder API
+ */
+public class JobBuilderImpl implements JobBuilder {
+
+ private final String topic;
+
+ private final JobManagerImpl jobManager;
+
+ private Map<String, Object> properties;
+
+ public JobBuilderImpl(final JobManagerImpl manager, final String topic) {
+ this.jobManager = manager;
+ this.topic = topic;
+ }
+
+
+ @Override
+ public JobBuilder properties(final Map<String, Object> props) {
+ this.properties = props;
+ return this;
+ }
+
+ @Override
+ public Job add() {
+ return this.add(null);
+ }
+
+ @Override
+ public Job add(final List<String> errors) {
+ return this.jobManager.addJob(this.topic, this.properties, errors);
+ }
+
+ @Override
+ public ScheduleBuilder schedule() {
+ return new JobScheduleBuilderImpl(
+ this.topic,
+ this.properties,
+ UUID.randomUUID().toString(),
+ this.jobManager.getJobScheduler());
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java b/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java
new file mode 100644
index 0000000..08660e8
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobConsumerManager.java
@@ -0,0 +1,511 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.References;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.discovery.PropertyProvider;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.impl.support.TopicMatcher;
+import org.apache.sling.event.impl.support.TopicMatcherHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.apache.sling.event.jobs.consumer.JobConsumer.JobResult;
+import org.apache.sling.event.jobs.consumer.JobExecutionContext;
+import org.apache.sling.event.jobs.consumer.JobExecutionResult;
+import org.apache.sling.event.jobs.consumer.JobExecutor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This component manages/keeps track of all job consumer services.
+ */
+@Component(label="Apache Sling Job Consumer Manager",
+ description="The consumer manager controls the job consumer (= processors). "
+ + "It can be used to temporarily disable job processing on the current instance. Other instances "
+ + "in a cluster are not affected.",
+ metatype=true)
+@Service(value=JobConsumerManager.class)
+@References({
+ @Reference(referenceInterface=JobConsumer.class,
+ cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy=ReferencePolicy.DYNAMIC),
+ @Reference(referenceInterface=JobExecutor.class,
+ cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy=ReferencePolicy.DYNAMIC)
+})
+@Property(name="org.apache.sling.installer.configuration.persist", boolValue=false,
+ label="Distribute config",
+ description="If this is disabled, the configuration is not persisted on save in the cluster and is "
+ + "only used on the current instance. This option should always be disabled!")
+public class JobConsumerManager {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Property(unbounded=PropertyUnbounded.ARRAY, value = "*",
+ label="Topic Whitelist",
+ description="This is a list of topics which currently should be "
+ + "processed by this instance. Leaving it empty, all job consumers are disabled. Putting a '*' as "
+ + "one entry, enables all job consumers. Adding separate topics enables job consumers for exactly "
+ + "this topic.")
+ private static final String PROPERTY_WHITELIST = "job.consumermanager.whitelist";
+
+ @Property(unbounded=PropertyUnbounded.ARRAY,
+ label="Topic Blacklist",
+ description="This is a list of topics which currently shouldn't be "
+ + "processed by this instance. Leaving it empty, all job consumers are enabled. Putting a '*' as "
+ + "one entry, disables all job consumers. Adding separate topics disables job consumers for exactly "
+ + "this topic.")
+ private static final String PROPERTY_BLACKLIST = "job.consumermanager.blacklist";
+
+ /** The map with the consumers, keyed by topic, sorted by service ranking. */
+ private final Map<String, List<ConsumerInfo>> topicToConsumerMap = new HashMap<String, List<ConsumerInfo>>();
+
+ /** ServiceRegistration for propagation. */
+ private ServiceRegistration propagationService;
+
+ private String topics;
+
+ private TopicMatcher[] whitelistMatchers;
+
+ private TopicMatcher[] blacklistMatchers;
+
+ private volatile long changeCount;
+
+ private BundleContext bundleContext;
+
+ private final Map<String, Object[]> listenerMap = new HashMap<String, Object[]>();
+
+ private Dictionary<String, Object> getRegistrationProperties() {
+ final Dictionary<String, Object> serviceProps = new Hashtable<String, Object>();
+ serviceProps.put(PropertyProvider.PROPERTY_PROPERTIES, TopologyCapabilities.PROPERTY_TOPICS);
+ // we add a changing property to the service registration
+ // to make sure a modification event is really sent
+ synchronized ( this ) {
+ serviceProps.put("changeCount", this.changeCount++);
+ }
+ return serviceProps;
+ }
+
+ @Activate
+ protected void activate(final BundleContext bc, final Map<String, Object> props) {
+ this.bundleContext = bc;
+ this.modified(bc, props);
+ }
+
+ @Modified
+ protected void modified(final BundleContext bc, final Map<String, Object> props) {
+ final boolean wasEnabled = this.propagationService != null;
+ this.whitelistMatchers = TopicMatcherHelper.buildMatchers(PropertiesUtil.toStringArray(props.get(PROPERTY_WHITELIST)));
+ this.blacklistMatchers = TopicMatcherHelper.buildMatchers(PropertiesUtil.toStringArray(props.get(PROPERTY_BLACKLIST)));
+
+ final boolean enable = this.whitelistMatchers != null && this.blacklistMatchers != TopicMatcherHelper.MATCH_ALL;
+ if ( wasEnabled != enable ) {
+ synchronized ( this.topicToConsumerMap ) {
+ this.calculateTopics(enable);
+ }
+ if ( enable ) {
+ logger.debug("Registering property provider with: {}", this.topics);
+ this.propagationService = bc.registerService(PropertyProvider.class.getName(),
+ new PropertyProvider() {
+
+ @Override
+ public String getProperty(final String name) {
+ if ( TopologyCapabilities.PROPERTY_TOPICS.equals(name) ) {
+ return topics;
+ }
+ return null;
+ }
+ }, this.getRegistrationProperties());
+ } else {
+ logger.debug("Unregistering property provider with");
+ this.propagationService.unregister();
+ this.propagationService = null;
+ }
+ } else if ( enable ) {
+ // update properties
+ synchronized ( this.topicToConsumerMap ) {
+ this.calculateTopics(true);
+ }
+ logger.debug("Updating property provider with: {}", this.topics);
+ this.propagationService.setProperties(this.getRegistrationProperties());
+ }
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if ( this.propagationService != null ) {
+ this.propagationService.unregister();
+ this.propagationService = null;
+ }
+ this.bundleContext = null;
+ synchronized ( this.topicToConsumerMap ) {
+ this.topicToConsumerMap.clear();
+ this.listenerMap.clear();
+ }
+ }
+
+ /**
+ * Get the executor for the topic.
+ * @param topic The job topic
+ * @return A consumer or <code>null</code>
+ */
+ public JobExecutor getExecutor(final String topic) {
+ synchronized ( this.topicToConsumerMap ) {
+ final List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
+ if ( consumers != null ) {
+ return consumers.get(0).getExecutor(this.bundleContext);
+ }
+ int pos = topic.lastIndexOf('/');
+ if ( pos > 0 ) {
+ final String category = topic.substring(0, pos + 1).concat("*");
+ final List<ConsumerInfo> categoryConsumers = this.topicToConsumerMap.get(category);
+ if ( categoryConsumers != null ) {
+ return categoryConsumers.get(0).getExecutor(this.bundleContext);
+ }
+
+ // search deep consumers (since 1.2 of the consumer package)
+ do {
+ final String subCategory = topic.substring(0, pos + 1).concat("**");
+ final List<ConsumerInfo> subCategoryConsumers = this.topicToConsumerMap.get(subCategory);
+ if ( subCategoryConsumers != null ) {
+ return subCategoryConsumers.get(0).getExecutor(this.bundleContext);
+ }
+ pos = topic.lastIndexOf('/', pos - 1);
+ } while ( pos > 0 );
+ }
+ }
+ return null;
+ }
+
+ public void registerListener(final String key, final JobExecutor consumer, final JobExecutionContext handler) {
+ synchronized ( this.topicToConsumerMap ) {
+ this.listenerMap.put(key, new Object[] {consumer, handler});
+ }
+ }
+
+ public void unregisterListener(final String key) {
+ synchronized ( this.topicToConsumerMap ) {
+ this.listenerMap.remove(key);
+ }
+ }
+
+ /**
+ * Return the topics information of this instance.
+ */
+ public String getTopics() {
+ return this.topics;
+ }
+
+ /**
+ * Bind a new consumer
+ * @param serviceReference The service reference to the consumer.
+ */
+ protected void bindJobConsumer(final ServiceReference serviceReference) {
+ this.bindService(serviceReference, true);
+ }
+
+ /**
+ * Unbind a consumer
+ * @param serviceReference The service reference to the consumer.
+ */
+ protected void unbindJobConsumer(final ServiceReference serviceReference) {
+ this.unbindService(serviceReference, true);
+ }
+
+ /**
+ * Bind a new executor
+ * @param serviceReference The service reference to the executor.
+ */
+ protected void bindJobExecutor(final ServiceReference serviceReference) {
+ this.bindService(serviceReference, false);
+ }
+
+ /**
+ * Unbind a executor
+ * @param serviceReference The service reference to the executor.
+ */
+ protected void unbindJobExecutor(final ServiceReference serviceReference) {
+ this.unbindService(serviceReference, false);
+ }
+
+ /**
+ * Bind a consumer or executor
+ * @param serviceReference The service reference to the consumer or executor.
+ * @param isConsumer Indicating whether this is a JobConsumer or JobExecutor
+ */
+ private void bindService(final ServiceReference serviceReference, final boolean isConsumer) {
+ final String[] topics = PropertiesUtil.toStringArray(serviceReference.getProperty(JobConsumer.PROPERTY_TOPICS));
+ if ( topics != null && topics.length > 0 ) {
+ final ConsumerInfo info = new ConsumerInfo(serviceReference, isConsumer);
+ boolean changed = false;
+ synchronized ( this.topicToConsumerMap ) {
+ for(final String t : topics) {
+ if ( t != null ) {
+ final String topic = t.trim();
+ if ( topic.length() > 0 ) {
+ List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
+ if ( consumers == null ) {
+ consumers = new ArrayList<JobConsumerManager.ConsumerInfo>();
+ this.topicToConsumerMap.put(topic, consumers);
+ changed = true;
+ }
+ consumers.add(info);
+ Collections.sort(consumers);
+ }
+ }
+ }
+ if ( changed ) {
+ this.calculateTopics(this.propagationService != null);
+ }
+ }
+ if ( changed && this.propagationService != null ) {
+ logger.debug("Updating property provider with: {}", this.topics);
+ this.propagationService.setProperties(this.getRegistrationProperties());
+ }
+ }
+ }
+
+ /**
+ * Unbind a consumer or executor
+ * @param serviceReference The service reference to the consumer or executor.
+ * @param isConsumer Indicating whether this is a JobConsumer or JobExecutor
+ */
+ private void unbindService(final ServiceReference serviceReference, final boolean isConsumer) {
+ final String[] topics = PropertiesUtil.toStringArray(serviceReference.getProperty(JobConsumer.PROPERTY_TOPICS));
+ if ( topics != null && topics.length > 0 ) {
+ final ConsumerInfo info = new ConsumerInfo(serviceReference, isConsumer);
+ boolean changed = false;
+ synchronized ( this.topicToConsumerMap ) {
+ for(final String t : topics) {
+ if ( t != null ) {
+ final String topic = t.trim();
+ if ( topic.length() > 0 ) {
+ final List<ConsumerInfo> consumers = this.topicToConsumerMap.get(topic);
+ if ( consumers != null ) { // sanity check
+ for(final ConsumerInfo oldConsumer : consumers) {
+ if ( oldConsumer.equals(info) && oldConsumer.executor != null ) {
+ // notify listener
+ for(final Object[] listenerObjects : this.listenerMap.values()) {
+ if ( listenerObjects[0] == oldConsumer.executor ) {
+ final JobExecutionContext context = (JobExecutionContext)listenerObjects[1];
+ context.asyncProcessingFinished(context.result().failed());
+ break;
+ }
+ }
+ }
+ }
+ consumers.remove(info);
+ if ( consumers.size() == 0 ) {
+ this.topicToConsumerMap.remove(topic);
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+ if ( changed ) {
+ this.calculateTopics(this.propagationService != null);
+ }
+ }
+ if ( changed && this.propagationService != null ) {
+ logger.debug("Updating property provider with: {}", this.topics);
+ this.propagationService.setProperties(this.getRegistrationProperties());
+ }
+ }
+ }
+
+ private boolean match(final String topic, final TopicMatcher[] matchers) {
+ for(final TopicMatcher m : matchers) {
+ if ( m.match(topic) != null ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void calculateTopics(final boolean enabled) {
+ if ( enabled ) {
+ // create a sorted list - this ensures that the property value
+ // is always the same for the same topics.
+ final List<String> topicList = new ArrayList<String>();
+ for(final String topic : this.topicToConsumerMap.keySet() ) {
+ // check whitelist
+ if ( this.match(topic, this.whitelistMatchers) ) {
+ // and blacklist
+ if ( this.blacklistMatchers == null || !this.match(topic, this.blacklistMatchers) ) {
+ topicList.add(topic);
+ }
+ }
+ }
+ Collections.sort(topicList);
+
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for(final String topic : topicList ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append(topic);
+ }
+ this.topics = sb.toString();
+ } else {
+ this.topics = null;
+ }
+ }
+
+ /**
+ * Internal class caching some consumer infos like service id and ranking.
+ */
+ private final static class ConsumerInfo implements Comparable<ConsumerInfo> {
+
+ public final ServiceReference serviceReference;
+ private final boolean isConsumer;
+ public JobExecutor executor;
+ public final int ranking;
+ public final long serviceId;
+
+ public ConsumerInfo(final ServiceReference serviceReference, final boolean isConsumer) {
+ this.serviceReference = serviceReference;
+ this.isConsumer = isConsumer;
+ final Object sr = serviceReference.getProperty(Constants.SERVICE_RANKING);
+ if ( sr == null || !(sr instanceof Integer)) {
+ this.ranking = 0;
+ } else {
+ this.ranking = (Integer)sr;
+ }
+ this.serviceId = (Long)serviceReference.getProperty(Constants.SERVICE_ID);
+ }
+
+ @Override
+ public int compareTo(final ConsumerInfo o) {
+ if ( this.ranking < o.ranking ) {
+ return 1;
+ } else if (this.ranking > o.ranking ) {
+ return -1;
+ }
+ // If ranks are equal, then sort by service id in descending order.
+ return (this.serviceId < o.serviceId) ? -1 : 1;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if ( obj instanceof ConsumerInfo ) {
+ return ((ConsumerInfo)obj).serviceId == this.serviceId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return serviceReference.hashCode();
+ }
+
+ public JobExecutor getExecutor(final BundleContext bundleContext) {
+ if ( executor == null ) {
+ if ( this.isConsumer ) {
+ executor = new JobConsumerWrapper((JobConsumer) bundleContext.getService(this.serviceReference));
+ } else {
+ executor = (JobExecutor) bundleContext.getService(this.serviceReference);
+ }
+ }
+ return executor;
+ }
+ }
+
+ private final static class JobConsumerWrapper implements JobExecutor {
+
+ private final JobConsumer consumer;
+
+ public JobConsumerWrapper(final JobConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public JobExecutionResult process(final Job job, final JobExecutionContext context) {
+ final JobConsumer.AsyncHandler asyncHandler =
+ new JobConsumer.AsyncHandler() {
+
+ final Object asyncLock = new Object();
+ final AtomicBoolean asyncDone = new AtomicBoolean(false);
+
+ private void check(final JobExecutionResult result) {
+ synchronized ( asyncLock ) {
+ if ( !asyncDone.get() ) {
+ asyncDone.set(true);
+ context.asyncProcessingFinished(result);
+ } else {
+ throw new IllegalStateException("Job is already marked as processed");
+ }
+ }
+ }
+
+ @Override
+ public void ok() {
+ this.check(context.result().succeeded());
+ }
+
+ @Override
+ public void failed() {
+ this.check(context.result().failed());
+ }
+
+ @Override
+ public void cancel() {
+ this.check(context.result().cancelled());
+ }
+ };
+ ((JobImpl)job).setProperty(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER, asyncHandler);
+ final JobConsumer.JobResult result = this.consumer.process(job);
+ if ( result == JobResult.ASYNC ) {
+ return null;
+ } else if ( result == JobResult.FAILED) {
+ return context.result().failed();
+ } else if ( result == JobResult.OK) {
+ return context.result().succeeded();
+ }
+ return context.result().cancelled();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java
new file mode 100644
index 0000000..6337b6c
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobHandler.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.consumer.JobExecutor;
+
+
+/**
+ * This object adds actions to a {@link JobImpl}.
+ */
+public class JobHandler {
+
+ private final JobImpl job;
+
+ public volatile long started = -1;
+
+ private volatile boolean isStopped = false;
+
+ private final JobManagerConfiguration configuration;
+
+ private final JobExecutor consumer;
+
+ public JobHandler(final JobImpl job,
+ final JobExecutor consumer,
+ final JobManagerConfiguration configuration) {
+ this.job = job;
+ this.consumer = consumer;
+ this.configuration = configuration;
+ }
+
+ public JobImpl getJob() {
+ return this.job;
+ }
+
+ public JobExecutor getConsumer() {
+ return this.consumer;
+ }
+
+ public boolean startProcessing(final Queue queue) {
+ this.isStopped = false;
+ return this.persistJobProperties(this.job.prepare(queue));
+ }
+
+ /**
+ * Reschedule the job
+ * Update the retry count and remove the started time.
+ * @return <code>true</code> if rescheduling was successful, <code>false</code> otherwise.
+ */
+ public boolean reschedule() {
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource jobResource = resolver.getResource(job.getResourcePath());
+ if ( jobResource != null ) {
+ final ModifiableValueMap mvm = jobResource.adaptTo(ModifiableValueMap.class);
+ mvm.put(Job.PROPERTY_JOB_RETRY_COUNT, job.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class));
+ if ( job.getProperty(Job.PROPERTY_RESULT_MESSAGE) != null ) {
+ mvm.put(Job.PROPERTY_RESULT_MESSAGE, job.getProperty(Job.PROPERTY_RESULT_MESSAGE));
+ }
+ mvm.remove(Job.PROPERTY_JOB_STARTED_TIME);
+ mvm.put(JobImpl.PROPERTY_JOB_QUEUED, Calendar.getInstance());
+ try {
+ resolver.commit();
+ return true;
+ } catch ( final PersistenceException pe ) {
+ this.configuration.getMainLogger().debug("Unable to update reschedule properties for job " + job.getId(), pe);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+
+ return false;
+ }
+
+ /**
+ * Finish a job.
+ * @param state The state of the processing
+ * @param keepJobInHistory whether to keep the job in the job history.
+ * @param duration the duration of the processing.
+ */
+ public void finished(final Job.JobState state,
+ final boolean keepJobInHistory,
+ final Long duration) {
+ final boolean isSuccess = (state == Job.JobState.SUCCEEDED);
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource jobResource = resolver.getResource(job.getResourcePath());
+ if ( jobResource != null ) {
+ try {
+ String newPath = null;
+ if ( keepJobInHistory ) {
+ final ValueMap vm = ResourceHelper.getValueMap(jobResource);
+ newPath = this.configuration.getStoragePath(job.getTopic(), job.getId(), isSuccess);
+ final Map<String, Object> props = new HashMap<String, Object>(vm);
+ props.put(JobImpl.PROPERTY_FINISHED_STATE, state.name());
+ if ( isSuccess ) {
+ // we set the finish date to start date + duration
+ final Date finishDate = new Date();
+ finishDate.setTime(job.getProcessingStarted().getTime().getTime() + duration);
+ final Calendar finishCal = Calendar.getInstance();
+ finishCal.setTime(finishDate);
+ props.put(JobImpl.PROPERTY_FINISHED_DATE, finishCal);
+ } else {
+ // current time is good enough
+ props.put(JobImpl.PROPERTY_FINISHED_DATE, Calendar.getInstance());
+ }
+ if ( job.getProperty(Job.PROPERTY_RESULT_MESSAGE) != null ) {
+ props.put(Job.PROPERTY_RESULT_MESSAGE, job.getProperty(Job.PROPERTY_RESULT_MESSAGE));
+ }
+ ResourceHelper.getOrCreateResource(resolver, newPath, props);
+ }
+ resolver.delete(jobResource);
+ resolver.commit();
+
+ if ( keepJobInHistory && configuration.getMainLogger().isDebugEnabled() ) {
+ if ( isSuccess ) {
+ configuration.getMainLogger().debug("Kept successful job {} at {}", Utility.toString(job), newPath);
+ } else {
+ configuration.getMainLogger().debug("Moved cancelled job {} to {}", Utility.toString(job), newPath);
+ }
+ }
+ } catch ( final PersistenceException pe ) {
+ this.configuration.getMainLogger().warn("Unable to finish job " + job.getId(), pe);
+ } catch (final InstantiationException ie) {
+ // something happened with the resource in the meantime
+ this.configuration.getMainLogger().debug("Unable to instantiate job", ie);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ }
+
+ /**
+ * Reassign to a new instance.
+ */
+ public void reassign() {
+ final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(job.getTopic());
+ // Sanity check if queue configuration has changed
+ final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
+ final String targetId = (caps == null ? null : caps.detectTarget(job.getTopic(), job.getProperties(), queueInfo));
+
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource jobResource = resolver.getResource(job.getResourcePath());
+ if ( jobResource != null ) {
+ try {
+ final ValueMap vm = ResourceHelper.getValueMap(jobResource);
+ final String newPath = this.configuration.getUniquePath(targetId, job.getTopic(), job.getId(), job.getProperties());
+
+ final Map<String, Object> props = new HashMap<String, Object>(vm);
+ props.remove(Job.PROPERTY_JOB_QUEUE_NAME);
+ if ( targetId == null ) {
+ props.remove(Job.PROPERTY_JOB_TARGET_INSTANCE);
+ } else {
+ props.put(Job.PROPERTY_JOB_TARGET_INSTANCE, targetId);
+ }
+ props.remove(Job.PROPERTY_JOB_STARTED_TIME);
+
+ try {
+ ResourceHelper.getOrCreateResource(resolver, newPath, props);
+ resolver.delete(jobResource);
+ resolver.commit();
+ } catch ( final PersistenceException pe ) {
+ this.configuration.getMainLogger().warn("Unable to reassign job " + job.getId(), pe);
+ }
+ } catch (final InstantiationException ie) {
+ // something happened with the resource in the meantime
+ this.configuration.getMainLogger().debug("Unable to instantiate job", ie);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ }
+
+ /**
+ * Update the property of a job in the resource tree
+ * @param propNames the property names to update
+ * @return {@code true} if the update was successful.
+ */
+ public boolean persistJobProperties(final String... propNames) {
+ if ( propNames != null ) {
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource jobResource = resolver.getResource(job.getResourcePath());
+ if ( jobResource != null ) {
+ final ModifiableValueMap mvm = jobResource.adaptTo(ModifiableValueMap.class);
+ for(final String propName : propNames) {
+ final Object val = job.getProperty(propName);
+ if ( val != null ) {
+ if ( val.getClass().isEnum() ) {
+ mvm.put(propName, val.toString());
+ } else {
+ mvm.put(propName, val);
+ }
+ } else {
+ mvm.remove(propName);
+ }
+ }
+ resolver.commit();
+
+ return true;
+ } else {
+ this.configuration.getMainLogger().debug("No job resource found at {}", job.getResourcePath());
+ }
+ } catch ( final PersistenceException ignore ) {
+ this.configuration.getMainLogger().debug("Unable to persist properties", ignore);
+ } finally {
+ resolver.close();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isStopped() {
+ return this.isStopped;
+ }
+
+ public void stop() {
+ this.isStopped = true;
+ }
+
+ public void addToRetryList() {
+ this.configuration.addJobToRetryList(this.job);
+
+ }
+
+ public boolean removeFromRetryList() {
+ return this.configuration.removeJobFromRetryList(this.job);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.job.getId().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( ! (obj instanceof JobHandler) ) {
+ return false;
+ }
+ return this.job.getId().equals(((JobHandler)obj).job.getId());
+ }
+
+ @Override
+ public String toString() {
+ return "JobHandler(" + this.job.getId() + ")";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java
new file mode 100644
index 0000000..5514ebf
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobImpl.java
@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.Queue;
+
+/**
+ * This object encapsulates all information about a job.
+ */
+public class JobImpl implements Job, Comparable<JobImpl> {
+
+ /** Internal job property containing the resource path. */
+ public static final String PROPERTY_RESOURCE_PATH = "slingevent:path";
+
+ /** Internal job property containing optional delay override. */
+ public static final String PROPERTY_DELAY_OVERRIDE = ":slingevent:delayOverride";
+
+ /**
+ * Internal job property specifying when the job was put into the queue.
+ */
+ public static final String PROPERTY_JOB_QUEUED = "event.job.queued.time";
+
+ /**
+ * This property contains the finished state of a job once it's marked as finished.
+ * The value is either "CANCELLED" or "SUCCEEDED".
+ * This property is read-only and can't be specified when the job is created.
+ */
+ public static final String PROPERTY_FINISHED_STATE = "slingevent:finishedState";
+
+ private final ValueMap properties;
+
+ private final String topic;
+
+ private final String path;
+
+ private final String jobId;
+
+ private final List<Exception> readErrorList;
+
+ private final long counter;
+
+ /**
+ * Create a new job instance
+ *
+ * @param topic The job topic
+ * @param name The unique job name (optional)
+ * @param jobId The unique (internal) job id
+ * @param properties Non-null map of properties, at least containing {@link #PROPERTY_RESOURCE_PATH}
+ */
+ @SuppressWarnings("unchecked")
+ public JobImpl(final String topic,
+ final String jobId,
+ final Map<String, Object> properties) {
+ this.topic = topic;
+ this.jobId = jobId;
+ this.path = (String)properties.remove(PROPERTY_RESOURCE_PATH);
+ this.readErrorList = (List<Exception>) properties.remove(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
+
+ this.properties = new ValueMapDecorator(properties);
+ this.properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, jobId);
+ final int lastPos = jobId.lastIndexOf('_');
+ this.counter = Long.valueOf(jobId.substring(lastPos + 1));
+ }
+
+ /**
+ * Get the full resource path.
+ */
+ public String getResourcePath() {
+ return this.path;
+ }
+
+ /**
+ * Did we have read errors?
+ */
+ public boolean hasReadErrors() {
+ return this.readErrorList != null;
+ }
+
+ /**
+ * Is the error recoverable?
+ */
+ public boolean isReadErrorRecoverable() {
+ boolean result = true;
+ if ( this.readErrorList != null ) {
+ for(final Exception e : this.readErrorList) {
+ if ( e instanceof RuntimeException ) {
+ result = false;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get all properties
+ */
+ public Map<String, Object> getProperties() {
+ return this.properties;
+ }
+
+ /**
+ * Update the information for a retry
+ */
+ public void retry() {
+ final int retries = this.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class);
+ this.properties.put(Job.PROPERTY_JOB_RETRY_COUNT, retries + 1);
+ this.properties.remove(Job.PROPERTY_JOB_STARTED_TIME);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getTopic()
+ */
+ @Override
+ public String getTopic() {
+ return this.topic;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getId()
+ */
+ @Override
+ public String getId() {
+ return this.jobId;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String)
+ */
+ @Override
+ public Object getProperty(final String name) {
+ return this.properties.get(name);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String, java.lang.Class)
+ */
+ @Override
+ public <T> T getProperty(final String name, final Class<T> type) {
+ return this.properties.get(name, type);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProperty(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public <T> T getProperty(final String name, final T defaultValue) {
+ return this.properties.get(name, defaultValue);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getPropertyNames()
+ */
+ @Override
+ public Set<String> getPropertyNames() {
+ return this.properties.keySet();
+ }
+
+ @Override
+ public int getRetryCount() {
+ return this.getProperty(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class);
+ }
+
+ @Override
+ public int getNumberOfRetries() {
+ return this.getProperty(Job.PROPERTY_JOB_RETRIES, Integer.class);
+ }
+
+ @Override
+ public String getQueueName() {
+ return this.getProperty(Job.PROPERTY_JOB_QUEUE_NAME, String.class);
+ }
+
+ @Override
+ public String getTargetInstance() {
+ return this.getProperty(Job.PROPERTY_JOB_TARGET_INSTANCE, String.class);
+ }
+
+ @Override
+ public Calendar getProcessingStarted() {
+ return this.getProperty(Job.PROPERTY_JOB_STARTED_TIME, Calendar.class);
+ }
+
+ @Override
+ public Calendar getCreated() {
+ return this.getProperty(Job.PROPERTY_JOB_CREATED, Calendar.class);
+ }
+
+ @Override
+ public String getCreatedInstance() {
+ return this.getProperty(Job.PROPERTY_JOB_CREATED_INSTANCE, String.class);
+ }
+
+ /**
+ * Update information about the queue.
+ */
+ public void updateQueueInfo(final Queue queue) {
+ this.properties.put(Job.PROPERTY_JOB_QUEUE_NAME, queue.getName());
+ this.properties.put(Job.PROPERTY_JOB_RETRIES, queue.getConfiguration().getMaxRetries());
+ }
+
+ public void setProperty(final String name, final Object value) {
+ if ( value == null ) {
+ this.properties.remove(name);
+ } else {
+ this.properties.put(name, value);
+ }
+ }
+
+ /**
+ * Prepare a new job execution
+ */
+ public String[] prepare(final Queue queue) {
+ this.updateQueueInfo(queue);
+ this.properties.remove(JobImpl.PROPERTY_DELAY_OVERRIDE);
+ this.properties.remove(Job.PROPERTY_JOB_PROGRESS_LOG);
+ this.properties.remove(Job.PROPERTY_JOB_PROGRESS_ETA);
+ this.properties.remove(Job.PROPERTY_JOB_PROGRESS_STEPS);
+ this.properties.remove(Job.PROPERTY_JOB_PROGRESS_STEP);
+ this.properties.remove(Job.PROPERTY_RESULT_MESSAGE);
+ this.properties.put(Job.PROPERTY_JOB_STARTED_TIME, Calendar.getInstance());
+ return new String[] {Job.PROPERTY_JOB_QUEUE_NAME, Job.PROPERTY_JOB_RETRIES,
+ Job.PROPERTY_JOB_PROGRESS_LOG, Job.PROPERTY_JOB_PROGRESS_ETA, PROPERTY_JOB_PROGRESS_STEPS,
+ PROPERTY_JOB_PROGRESS_STEP, Job.PROPERTY_RESULT_MESSAGE, Job.PROPERTY_JOB_STARTED_TIME};
+ }
+
+ public String[] startProgress(final int steps, final long eta) {
+ if ( steps > 0 ) {
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, steps);
+ }
+ if ( eta > 0 ) {
+ final Date finishDate = new Date(System.currentTimeMillis() + eta * 1000);
+ final Calendar finishCal = Calendar.getInstance();
+ finishCal.setTime(finishDate);
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, finishCal);
+ }
+ return new String[] {Job.PROPERTY_JOB_PROGRESS_ETA, PROPERTY_JOB_PROGRESS_STEPS};
+ }
+
+ public String[] setProgress(final int step) {
+ final int steps = this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, -1);
+ if ( steps > 0 && step > 0 ) {
+ int current = this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEP, 0);
+ current += step;
+ if ( current > steps ) {
+ current = steps;
+ }
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_STEP, current);
+
+ final Calendar now = Calendar.getInstance();
+ final long elapsed = now.getTimeInMillis() - this.getProcessingStarted().getTimeInMillis();
+
+ final long eta = elapsed * steps / step;
+ now.setTimeInMillis(eta);
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, now);
+ return new String[] {Job.PROPERTY_JOB_PROGRESS_STEP, Job.PROPERTY_JOB_PROGRESS_ETA};
+ }
+ return null;
+ }
+
+ public String update(final long eta) {
+ if ( eta > 0 ) {
+ final Date finishDate = new Date(System.currentTimeMillis() + eta * 1000);
+ final Calendar finishCal = Calendar.getInstance();
+ finishCal.setTime(finishDate);
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_ETA, eta);
+ } else {
+ this.properties.remove(Job.PROPERTY_JOB_PROGRESS_ETA);
+ }
+ return Job.PROPERTY_JOB_PROGRESS_ETA;
+ }
+
+ public String log(final String message, final Object... args) {
+ final String logEntry = MessageFormat.format(message, args);
+ final String[] entries = this.getProperty(Job.PROPERTY_JOB_PROGRESS_LOG, String[].class);
+ if ( entries == null ) {
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_LOG, new String[] {logEntry});
+ } else {
+ final String[] newEntries = new String[entries.length + 1];
+ System.arraycopy(entries, 0, newEntries, 0, entries.length);
+ newEntries[entries.length] = logEntry;
+ this.setProperty(Job.PROPERTY_JOB_PROGRESS_LOG, newEntries);
+ }
+ return Job.PROPERTY_JOB_PROGRESS_LOG;
+ }
+
+ @Override
+ public JobState getJobState() {
+ final String enumValue = this.getProperty(JobImpl.PROPERTY_FINISHED_STATE, String.class);
+ if ( enumValue == null ) {
+ if ( this.getProcessingStarted() != null ) {
+ return JobState.ACTIVE;
+ }
+ return JobState.QUEUED;
+ }
+ return JobState.valueOf(enumValue);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getFinishedDate()
+ */
+ @Override
+ public Calendar getFinishedDate() {
+ return this.getProperty(Job.PROPERTY_FINISHED_DATE, Calendar.class);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getResultMessage()
+ */
+ @Override
+ public String getResultMessage() {
+ return this.getProperty(Job.PROPERTY_RESULT_MESSAGE, String.class);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProgressLog()
+ */
+ @Override
+ public String[] getProgressLog() {
+ return this.getProperty(Job.PROPERTY_JOB_PROGRESS_LOG, String[].class);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProgressStepCount()
+ */
+ @Override
+ public int getProgressStepCount() {
+ return this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEPS, -1);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getFinishedProgressStep()
+ */
+ @Override
+ public int getFinishedProgressStep() {
+ return this.getProperty(Job.PROPERTY_JOB_PROGRESS_STEP, 0);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Job#getProgressETA()
+ */
+ @Override
+ public Calendar getProgressETA() {
+ return this.getProperty(Job.PROPERTY_JOB_PROGRESS_ETA, Calendar.class);
+ }
+
+ @Override
+ public int compareTo(final JobImpl o) {
+ int result = this.getCreated().compareTo(o.getCreated());
+ if ( result == 0 ) {
+ if ( this.counter < o.counter ) {
+ result = -1;
+ } else if ( this.counter > o.counter ) {
+ result = 1;
+ } else {
+ result = this.jobId.compareTo(o.jobId);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.jobId.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if ( obj == this ) {
+ return true;
+ }
+ if ( obj instanceof JobImpl ) {
+ return this.jobId.equals(((JobImpl)obj).jobId);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "JobImpl [properties=" + properties + ", topic=" + topic
+ + ", path=" + path + ", jobId=" + jobId + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
new file mode 100644
index 0000000..b76fda4
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
@@ -0,0 +1,759 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.util.ISO9075;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.QuerySyntaxException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.impl.jobs.notifications.NotificationUtility;
+import org.apache.sling.event.impl.jobs.queues.JobQueueImpl;
+import org.apache.sling.event.impl.jobs.queues.QueueManager;
+import org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl;
+import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
+import org.apache.sling.event.impl.jobs.tasks.CleanUpTask;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.Job.JobState;
+import org.apache.sling.event.jobs.JobBuilder;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.ScheduledJobInfo;
+import org.apache.sling.event.jobs.Statistics;
+import org.apache.sling.event.jobs.TopicStatistics;
+import org.apache.sling.event.jobs.jmx.QueuesMBean;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Implementation of the job manager.
+ */
+@Component(immediate=true)
+@Service(value={JobManager.class, EventHandler.class, Runnable.class})
+@Properties({
+ @Property(name="scheduler.period", longValue=60),
+ @Property(name="scheduler.concurrent", boolValue=false),
+ @Property(name=EventConstants.EVENT_TOPIC,
+ value={ResourceHelper.BUNDLE_EVENT_STARTED,
+ ResourceHelper.BUNDLE_EVENT_UPDATED})
+})
+public class JobManagerImpl
+ implements JobManager, EventHandler, Runnable {
+
+ /** Default logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Reference
+ private EventAdmin eventAdmin;
+
+ @Reference
+ private Scheduler scheduler;
+
+ @Reference
+ private JobConsumerManager jobConsumerManager;
+
+ @Reference
+ private QueuesMBean queuesMBean;
+
+ @Reference
+ private ThreadPoolManager threadPoolManager;
+
+ /** The job manager configuration. */
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ @Reference
+ private StatisticsManager statisticsManager;
+
+ @Reference
+ private QueueManager qManager;
+
+ private volatile CleanUpTask maintenanceTask;
+
+ /** Job Scheduler. */
+ private org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl jobScheduler;
+
+ private volatile ServiceRegistration<ResourceChangeListener> changeListenerReg;
+
+ /**
+ * Activate this component.
+ * @param props Configuration properties
+ */
+ @Activate
+ protected void activate(final BundleContext ctx, final Map<String, Object> props) throws LoginException {
+ this.jobScheduler = new org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl(this.configuration, this.scheduler, this);
+ this.maintenanceTask = new CleanUpTask(this.configuration, this.jobScheduler);
+
+ final Dictionary<String, Object> regProps = new Hashtable<>();
+ regProps.put(ResourceChangeListener.PATHS, this.configuration.getScheduledJobsPath(false));
+ regProps.put(ResourceChangeListener.CHANGES, new String[] {
+ ResourceChange.ChangeType.ADDED.name(),
+ ResourceChange.ChangeType.CHANGED.name(),
+ ResourceChange.ChangeType.REMOVED.name()
+ });
+ regProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+ regProps.put(Constants.SERVICE_DESCRIPTION, "Resource change listener for scheduled jobs");
+ this.changeListenerReg = ctx.registerService(ResourceChangeListener.class, this.jobScheduler, regProps);
+ logger.info("Apache Sling Job Manager started on instance {}", Environment.APPLICATION_ID);
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ protected void deactivate() {
+ logger.debug("Apache Sling Job Manager stopping on instance {}", Environment.APPLICATION_ID);
+
+ if ( this.changeListenerReg != null ) {
+ this.changeListenerReg.unregister();
+ this.changeListenerReg = null;
+ }
+
+ this.jobScheduler.deactivate();
+
+ this.maintenanceTask = null;
+ logger.info("Apache Sling Job Manager stopped on instance {}", Environment.APPLICATION_ID);
+ }
+
+ /**
+ * This method is invoked periodically by the scheduler.
+ * In the default configuration every minute
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ // invoke maintenance task
+ final CleanUpTask task = this.maintenanceTask;
+ if ( task != null ) {
+ task.run();
+ }
+ }
+
+ /**
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ @Override
+ public void handleEvent(final Event event) {
+ this.jobScheduler.handleEvent(event);
+ }
+
+ /**
+ * Return our internal statistics object.
+ *
+ * @see org.apache.sling.event.jobs.JobManager#getStatistics()
+ */
+ @Override
+ public synchronized Statistics getStatistics() {
+ return this.statisticsManager.getGlobalStatistics();
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getTopicStatistics()
+ */
+ @Override
+ public Iterable<TopicStatistics> getTopicStatistics() {
+ return this.statisticsManager.getTopicStatistics().values();
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getQueue(java.lang.String)
+ */
+ @Override
+ public Queue getQueue(final String name) {
+ return qManager.getQueue(ResourceHelper.filterQueueName(name));
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getQueues()
+ */
+ @Override
+ public Iterable<Queue> getQueues() {
+ return qManager.getQueues();
+ }
+
+ /**
+ * Remove a job.
+ * If the job is already in the storage area, it's removed forever.
+ * Otherwise it's moved to the storage area.
+ */
+ private boolean internalRemoveJobById(final String jobId, final boolean forceRemove) {
+ logger.debug("Trying to remove job {}", jobId);
+ boolean result = true;
+ JobImpl job = (JobImpl)this.getJobById(jobId);
+ if ( job != null ) {
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Found removal job: {}", Utility.toString(job));
+ }
+ final JobImpl retryJob = (JobImpl)this.configuration.getJobFromRetryList(jobId);
+ if ( retryJob != null ) {
+ job = retryJob;
+ }
+ // currently running?
+ if ( !forceRemove && job.getProcessingStarted() != null ) {
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Unable to remove job - job is started: {}", Utility.toString(job));
+ }
+ result = false;
+ } else {
+ final boolean isHistoryJob = this.configuration.isStoragePath(job.getResourcePath());
+ // if history job, simply remove - otherwise move to history!
+ if ( isHistoryJob ) {
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource jobResource = resolver.getResource(job.getResourcePath());
+ if ( jobResource != null ) {
+ resolver.delete(jobResource);
+ resolver.commit();
+ logger.debug("Removed job with id: {}", jobId);
+ } else {
+ logger.debug("Unable to remove job with id - resource already removed: {}", jobId);
+ }
+ NotificationUtility.sendNotification(this.eventAdmin, NotificationConstants.TOPIC_JOB_REMOVED, job, null);
+ } catch ( final PersistenceException pe) {
+ logger.warn("Unable to remove job at " + job.getResourcePath(), pe);
+ result = false;
+ } finally {
+ resolver.close();
+ }
+ } else {
+ final JobHandler jh = new JobHandler(job, null, this.configuration);
+ jh.finished(Job.JobState.DROPPED, true, null);
+ }
+ this.configuration.getAuditLogger().debug("REMOVE OK : {}", jobId);
+ }
+ } else {
+ logger.debug("Job for removal does not exist (anymore): {}", jobId);
+ }
+ return result;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#addJob(java.lang.String, java.util.Map)
+ */
+ @Override
+ public Job addJob(String topic, Map<String, Object> properties) {
+ return this.addJob(topic, properties, null);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getJobById(java.lang.String)
+ */
+ @Override
+ public Job getJobById(final String id) {
+ logger.debug("Getting job by id: {}", id);
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ final StringBuilder buf = new StringBuilder(64);
+ try {
+
+ buf.append("/jcr:root");
+ buf.append(this.configuration.getJobsBasePathWithSlash());
+ buf.append("/element(*,");
+ buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
+ buf.append(")[@");
+ buf.append(ResourceHelper.PROPERTY_JOB_ID);
+ buf.append(" = '");
+ buf.append(id);
+ buf.append("']");
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Exceuting query: {}", buf.toString());
+ }
+ final Iterator<Resource> result = resolver.findResources(buf.toString(), "xpath");
+
+ while ( result.hasNext() ) {
+ final Resource jobResource = result.next();
+ // sanity check for the path
+ if ( this.configuration.isJob(jobResource.getPath()) ) {
+ final JobImpl job = Utility.readJob(logger, jobResource);
+ if ( job != null ) {
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Found job with id {} = {}", id, Utility.toString(job));
+ }
+ return job;
+ }
+ }
+ }
+ } catch (final QuerySyntaxException qse) {
+ logger.warn("Query syntax wrong " + buf.toString(), qse);
+ } finally {
+ resolver.close();
+ }
+ logger.debug("Job not found with id: {}", id);
+ return null;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getJob(java.lang.String, java.util.Map)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Job getJob(final String topic, final Map<String, Object> template) {
+ final Iterable<Job> iter;
+ if ( template == null ) {
+ iter = this.findJobs(QueryType.ALL, topic, 1, (Map<String, Object>[])null);
+ } else {
+ iter = this.findJobs(QueryType.ALL, topic, 1, template);
+ }
+ final Iterator<Job> i = iter.iterator();
+ if ( i.hasNext() ) {
+ return i.next();
+ }
+ return null;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#removeJobById(java.lang.String)
+ */
+ @Override
+ public boolean removeJobById(final String jobId) {
+ return this.internalRemoveJobById(jobId, true);
+ }
+
+ private enum Operation {
+ LESS,
+ LESS_OR_EQUALS,
+ EQUALS,
+ GREATER_OR_EQUALS,
+ GREATER
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#findJobs(org.apache.sling.event.jobs.JobManager.QueryType, java.lang.String, long, java.util.Map[])
+ */
+ @Override
+ public Collection<Job> findJobs(final QueryType type,
+ final String topic,
+ final long limit,
+ final Map<String, Object>... templates) {
+ final boolean isHistoryQuery = type == QueryType.HISTORY
+ || type == QueryType.SUCCEEDED
+ || type == QueryType.CANCELLED
+ || type == QueryType.DROPPED
+ || type == QueryType.ERROR
+ || type == QueryType.GIVEN_UP
+ || type == QueryType.STOPPED;
+ final List<Job> result = new ArrayList<Job>();
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ final StringBuilder buf = new StringBuilder(64);
+ try {
+
+ buf.append("/jcr:root");
+ buf.append(this.configuration.getJobsBasePathWithSlash());
+ buf.append("/element(*,");
+ buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
+ buf.append(")[@");
+ buf.append(ISO9075.encode(ResourceHelper.PROPERTY_JOB_TOPIC));
+ if (topic != null) {
+ buf.append(" = '");
+ buf.append(topic);
+ buf.append("'");
+ }
+
+ // restricting on the type - history or unfinished
+ if ( isHistoryQuery ) {
+ buf.append(" and @");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ if ( type == QueryType.SUCCEEDED || type == QueryType.DROPPED || type == QueryType.ERROR || type == QueryType.GIVEN_UP || type == QueryType.STOPPED ) {
+ buf.append(" = '");
+ buf.append(type.name());
+ buf.append("'");
+ } else if ( type == QueryType.CANCELLED ) {
+ buf.append(" and (@");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ buf.append(" = '");
+ buf.append(QueryType.DROPPED.name());
+ buf.append("' or @");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ buf.append(" = '");
+ buf.append(QueryType.ERROR.name());
+ buf.append("' or @");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ buf.append(" = '");
+ buf.append(QueryType.GIVEN_UP.name());
+ buf.append("' or @");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ buf.append(" = '");
+ buf.append(QueryType.STOPPED.name());
+ buf.append("')");
+ }
+ } else {
+ buf.append(" and not(@");
+ buf.append(ISO9075.encode(JobImpl.PROPERTY_FINISHED_STATE));
+ buf.append(")");
+ if ( type == QueryType.ACTIVE ) {
+ buf.append(" and @");
+ buf.append(ISO9075.encode(Job.PROPERTY_JOB_STARTED_TIME));
+ } else if ( type == QueryType.QUEUED ) {
+ buf.append(" and not(@");
+ buf.append(ISO9075.encode(Job.PROPERTY_JOB_STARTED_TIME));
+ buf.append(")");
+ }
+ }
+
+ if ( templates != null && templates.length > 0 ) {
+ int index = 0;
+ for (final Map<String,Object> template : templates) {
+ // skip empty templates
+ if ( template.size() == 0 ) {
+ continue;
+ }
+ if ( index == 0 ) {
+ buf.append(" and (");
+ } else {
+ buf.append(" or ");
+ }
+ buf.append('(');
+ final Iterator<Map.Entry<String, Object>> i = template.entrySet().iterator();
+ boolean first = true;
+ while ( i.hasNext() ) {
+ final Map.Entry<String, Object> current = i.next();
+ final String key = ISO9075.encode(current.getKey());
+ final char firstChar = key.length() > 0 ? key.charAt(0) : 0;
+ final String propName;
+ final Operation op;
+ if ( firstChar == '=' ) {
+ propName = key.substring(1);
+ op = Operation.EQUALS;
+ } else if ( firstChar == '<' ) {
+ final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
+ if ( secondChar == '=' ) {
+ op = Operation.LESS_OR_EQUALS;
+ propName = key.substring(2);
+ } else {
+ op = Operation.LESS;
+ propName = key.substring(1);
+ }
+ } else if ( firstChar == '>' ) {
+ final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
+ if ( secondChar == '=' ) {
+ op = Operation.GREATER_OR_EQUALS;
+ propName = key.substring(2);
+ } else {
+ op = Operation.GREATER;
+ propName = key.substring(1);
+ }
+ } else {
+ propName = key;
+ op = Operation.EQUALS;
+ }
+
+ if ( first ) {
+ first = false;
+ buf.append('@');
+ } else {
+ buf.append(" and @");
+ }
+ buf.append(propName);
+ buf.append(' ');
+ switch ( op ) {
+ case EQUALS : buf.append('=');break;
+ case LESS : buf.append('<'); break;
+ case LESS_OR_EQUALS : buf.append("<="); break;
+ case GREATER : buf.append('>'); break;
+ case GREATER_OR_EQUALS : buf.append(">="); break;
+ }
+ buf.append(" '");
+ buf.append(current.getValue());
+ buf.append("'");
+ }
+ buf.append(')');
+ index++;
+ }
+ if ( index > 0 ) {
+ buf.append(')');
+ }
+ }
+ buf.append("] order by @");
+ if ( isHistoryQuery ) {
+ buf.append(JobImpl.PROPERTY_FINISHED_DATE);
+ buf.append(" descending");
+ } else {
+ buf.append(Job.PROPERTY_JOB_CREATED);
+ buf.append(" ascending");
+ }
+ final Iterator<Resource> iter = resolver.findResources(buf.toString(), "xpath");
+ long count = 0;
+
+ while ( iter.hasNext() && (limit < 1 || count < limit) ) {
+ final Resource jobResource = iter.next();
+ // sanity check for the path
+ if ( this.configuration.isJob(jobResource.getPath()) ) {
+ final JobImpl job = Utility.readJob(logger, jobResource);
+ if ( job != null ) {
+ count++;
+ result.add(job);
+ }
+ }
+ }
+ } catch (final QuerySyntaxException qse) {
+ logger.warn("Query syntax wrong " + buf.toString(), qse);
+ } finally {
+ resolver.close();
+ }
+ return result;
+ }
+
+ /**
+ * Persist the job in the resource tree
+ * @param jobTopic The required job topic
+ * @param jobName The optional job name
+ * @param passedJobProperties The optional job properties
+ * @return The persisted job or <code>null</code>.
+ */
+ private Job addJobInternal(final String jobTopic,
+ final Map<String, Object> jobProperties,
+ final List<String> errors) {
+ final QueueInfo info = this.configuration.getQueueConfigurationManager().getQueueInfo(jobTopic);
+
+ final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
+ info.targetId = (caps == null ? null : caps.detectTarget(jobTopic, jobProperties, info));
+
+ if ( logger.isDebugEnabled() ) {
+ if ( info.targetId != null ) {
+ logger.debug("Persisting job {} into queue {}, target={}", new Object[] {Utility.toString(jobTopic, jobProperties), info.queueName, info.targetId});
+ } else {
+ logger.debug("Persisting job {} into queue {}", Utility.toString(jobTopic, jobProperties), info.queueName);
+ }
+ }
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final JobImpl job = this.writeJob(resolver,
+ jobTopic,
+ jobProperties,
+ info);
+ if ( info.targetId != null ) {
+ this.configuration.getAuditLogger().debug("ASSIGN OK {} : {}",
+ info.targetId, job.getId());
+ } else {
+ this.configuration.getAuditLogger().debug("UNASSIGN OK : {}",
+ job.getId());
+ }
+ return job;
+ } catch (final PersistenceException re ) {
+ // something went wrong, so let's log it
+ this.logger.error("Exception during persisting new job '" + Utility.toString(jobTopic, jobProperties) + "'", re);
+ } finally {
+ resolver.close();
+ }
+ if ( errors != null ) {
+ errors.add("Unable to persist new job.");
+ }
+
+ return null;
+ }
+
+ /**
+ * Write a job to the resource tree.
+ * @param resolver The resolver resolver
+ * @param event The event
+ * @param info The queue information (queue name etc.)
+ * @throws PersistenceException
+ */
+ private JobImpl writeJob(final ResourceResolver resolver,
+ final String jobTopic,
+ final Map<String, Object> jobProperties,
+ final QueueInfo info)
+ throws PersistenceException {
+ final String jobId = this.configuration.getUniqueId(jobTopic);
+ final String path = this.configuration.getUniquePath(info.targetId, jobTopic, jobId, jobProperties);
+
+ // create properties
+ final Map<String, Object> properties = new HashMap<String, Object>();
+
+ if ( jobProperties != null ) {
+ for(final Map.Entry<String, Object> entry : jobProperties.entrySet() ) {
+ final String propName = entry.getKey();
+ if ( !ResourceHelper.ignoreProperty(propName) ) {
+ properties.put(propName, entry.getValue());
+ }
+ }
+ }
+
+ properties.put(ResourceHelper.PROPERTY_JOB_ID, jobId);
+ properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
+ properties.put(Job.PROPERTY_JOB_QUEUE_NAME, info.queueConfiguration.getName());
+ properties.put(Job.PROPERTY_JOB_RETRY_COUNT, 0);
+ properties.put(Job.PROPERTY_JOB_RETRIES, info.queueConfiguration.getMaxRetries());
+
+ properties.put(Job.PROPERTY_JOB_CREATED, Calendar.getInstance());
+ properties.put(JobImpl.PROPERTY_JOB_QUEUED, Calendar.getInstance());
+ properties.put(Job.PROPERTY_JOB_CREATED_INSTANCE, Environment.APPLICATION_ID);
+ if ( info.targetId != null ) {
+ properties.put(Job.PROPERTY_JOB_TARGET_INSTANCE, info.targetId);
+ } else {
+ properties.remove(Job.PROPERTY_JOB_TARGET_INSTANCE);
+ }
+
+ // create path and resource
+ properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, ResourceHelper.RESOURCE_TYPE_JOB);
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Storing new job {} at {}", Utility.toString(jobTopic, properties), path);
+ }
+ ResourceHelper.getOrCreateResource(resolver,
+ path,
+ properties);
+
+ // update property types - priority, add path and create job
+ properties.put(JobImpl.PROPERTY_RESOURCE_PATH, path);
+ return new JobImpl(jobTopic, jobId, properties);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#stopJobById(java.lang.String)
+ */
+ @Override
+ public void stopJobById(final String jobId) {
+ this.stopJobById(jobId, true);
+ }
+
+ private void stopJobById(final String jobId, final boolean forward) {
+ final JobImpl job = (JobImpl)this.getJobById(jobId);
+ if ( job != null && !this.configuration.isStoragePath(job.getResourcePath()) ) {
+ // get the queue configuration
+ final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(job.getTopic());
+ final JobQueueImpl queue = (JobQueueImpl)this.qManager.getQueue(queueInfo.queueName);
+
+ boolean stopped = false;
+ if ( queue != null ) {
+ stopped = queue.stopJob(job);
+ }
+ if ( forward && !stopped ) {
+ // mark the job as stopped
+ final JobHandler jh = new JobHandler(job, null, this.configuration);
+ jh.finished(JobState.STOPPED, true, null);
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#createJob(java.lang.String)
+ */
+ @Override
+ public JobBuilder createJob(final String topic) {
+ return new JobBuilderImpl(this, topic);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getScheduledJobs()
+ */
+ @Override
+ public Collection<ScheduledJobInfo> getScheduledJobs() {
+ return this.jobScheduler.getScheduledJobs(null, -1, (Map<String, Object>[])null);
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#getScheduledJobs()
+ */
+ @Override
+ public Collection<ScheduledJobInfo> getScheduledJobs(final String topic,
+ final long limit,
+ final Map<String, Object>... templates) {
+ return this.jobScheduler.getScheduledJobs(topic, limit, templates);
+ }
+
+ /**
+ * Internal method to add a job
+ */
+ public Job addJob(final String topic,
+ final Map<String, Object> properties,
+ final List<String> errors) {
+ final String errorMessage = Utility.checkJob(topic, properties);
+ if ( errorMessage != null ) {
+ logger.warn("{}", errorMessage);
+ if ( errors != null ) {
+ errors.add(errorMessage);
+ }
+ this.configuration.getAuditLogger().debug("ADD FAILED topic={}, properties={} : {}",
+ new Object[] {topic,
+ properties,
+ errorMessage});
+ return null;
+ }
+ final List<String> errorList = new ArrayList<String>();
+ Job result = this.addJobInternal(topic, properties, errorList);
+ if ( errors != null ) {
+ errors.addAll(errorList);
+ }
+ if ( result == null ) {
+ this.configuration.getAuditLogger().debug("ADD FAILED topic={}, properties={} : {}",
+ new Object[] {topic,
+ properties,
+ errorList});
+ } else {
+ this.configuration.getAuditLogger().debug("ADD OK topic={}, properties={} : {}",
+ new Object[] {topic,
+ properties,
+ result.getId()});
+ }
+
+ return result;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.JobManager#retryJobById(java.lang.String)
+ */
+ @Override
+ public Job retryJobById(final String jobId) {
+ final JobImpl job = (JobImpl)this.getJobById(jobId);
+ if ( job != null && this.configuration.isStoragePath(job.getResourcePath()) ) {
+ this.internalRemoveJobById(jobId, true);
+ return this.addJob(job.getTopic(), job.getProperties());
+ }
+ return null;
+ }
+
+ public JobSchedulerImpl getJobScheduler() {
+ return this.jobScheduler;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java b/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java
new file mode 100644
index 0000000..1981e77
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/JobTopicTraverser.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.slf4j.Logger;
+
+/**
+ * The job topic traverser is an utility class to traverse all jobs
+ * of a specific topic in order of creation.
+ *
+ * The traverser can be used with two different callbacks,
+ * the resource callback is called with a resource object,
+ * the job callback with a job object created from the
+ * resource.
+ */
+public class JobTopicTraverser {
+
+ /**
+ * Callback called for each found job.
+ */
+ public interface JobCallback {
+
+ /**
+ * Callback handle for a job.
+ * If the callback signals to stop traversing, the current minute is still
+ * processed completely (to ensure correct ordering of jobs).
+ * @param job The job to handle
+ * @return <code>true</code> If processing should continue, <code>false</code> otherwise.
+ */
+ boolean handle(final JobImpl job);
+ }
+
+ /**
+ * Callback called for each found resource.
+ */
+ public interface ResourceCallback {
+
+ /**
+ * Callback handle for a resource.
+ * The callback is called in sorted order on a minute base, all resources within a minute
+ * are not necessarily called in correct time order!
+ * If the callback signals to stop traversing, the traversal is stopped
+ * immediately.
+ * @param rsrc The resource to handle
+ * @return <code>true</code> If processing should continue, <code>false</code> otherwise.
+ */
+ boolean handle(final Resource rsrc);
+ }
+
+ /**
+ * Traverse the topic and call the callback for each found job.
+ *
+ * Once the callback notifies to stop traversing by returning false, the current minute
+ * will be processed completely (to ensure correct ordering of jobs) and then the
+ * traversal stops.
+ *
+ * @param logger The logger to use for debug logging
+ * @param topicResource The topic resource
+ * @param handler The callback
+ */
+ public static void traverse(final Logger logger,
+ final Resource topicResource,
+ final JobCallback handler) {
+ traverse(logger, topicResource, handler, null);
+ }
+
+ /**
+ * Traverse the topic and call the callback for each found resource.
+ *
+ * Once the callback notifies to stop traversing by returning false, the
+ * traversal stops.
+ *
+ * @param logger The logger to use for debug logging
+ * @param topicResource The topic resource
+ * @param handler The callback
+ */
+ public static void traverse(final Logger logger,
+ final Resource topicResource,
+ final ResourceCallback handler) {
+ traverse(logger, topicResource, null, handler);
+ }
+
+ /**
+ * Internal method for traversal
+ * @param logger The logger to use for debug logging
+ * @param topicResource The topic resource
+ * @param jobHandler The job callback
+ * @param resourceHandler The resource callback
+ */
+ private static void traverse(final Logger logger,
+ final Resource topicResource,
+ final JobCallback jobHandler,
+ final ResourceCallback resourceHandler) {
+ logger.debug("Processing topic {}", topicResource.getName().replace('.', '/'));
+ // now years
+ for(final Resource yearResource: Utility.getSortedChildren(logger, "year", topicResource)) {
+ logger.debug("Processing year {}", yearResource.getName());
+
+ // now months
+ for(final Resource monthResource: Utility.getSortedChildren(logger, "month", yearResource)) {
+ logger.debug("Processing month {}", monthResource.getName());
+
+ // now days
+ for(final Resource dayResource: Utility.getSortedChildren(logger, "day", monthResource)) {
+ logger.debug("Processing day {}", dayResource.getName());
+
+ // now hours
+ for(final Resource hourResource: Utility.getSortedChildren(logger, "hour", dayResource)) {
+ logger.debug("Processing hour {}", hourResource.getName());
+
+ // now minutes
+ for(final Resource minuteResource: Utility.getSortedChildren(logger, "minute", hourResource)) {
+ logger.debug("Processing minute {}", minuteResource.getName());
+
+ // now jobs
+ final List<JobImpl> jobs = new ArrayList<JobImpl>();
+ // we use an iterator to skip removed entries
+ // see SLING-4073
+ final Iterator<Resource> jobIter = minuteResource.listChildren();
+ while ( jobIter.hasNext() ) {
+ final Resource jobResource = jobIter.next();
+ if ( resourceHandler != null ) {
+ if ( !resourceHandler.handle(jobResource) ) {
+ return;
+ }
+ } else {
+ final JobImpl job = Utility.readJob(logger, jobResource);
+ if ( job != null ) {
+ logger.debug("Found job {}", jobResource.getName());
+ jobs.add(job);
+ }
+ }
+ }
+
+ if ( jobHandler != null ) {
+ Collections.sort(jobs);
+
+ boolean stop = false;
+ for(final JobImpl job : jobs) {
+ if ( !jobHandler.handle(job) ) {
+ stop = true;
+ }
+ }
+ if ( stop ) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/Utility.java b/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
new file mode 100644
index 0000000..c93fe51
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.osgi.service.event.Event;
+import org.slf4j.Logger;
+
+public abstract class Utility {
+
+ public static volatile boolean LOG_DEPRECATION_WARNINGS = true;
+
+ /**
+ * Check if the job topic is a valid OSGI event name (see 113.3.1 of the OSGI spec)
+ * @return <code>null</code> if the topic is syntactically correct otherwise an error description is returned
+ */
+ public static String checkJobTopic(final Object jobTopic) {
+ String message = null;
+ if ( jobTopic != null ) {
+ if ( jobTopic instanceof String ) {
+ try {
+ new Event((String)jobTopic, (Dictionary<String, Object>)null);
+ } catch (final IllegalArgumentException iae) {
+ message = String.format("Discarding job - job has an illegal job topic '%s'",jobTopic);
+ }
+
+ } else {
+ message = "Discarding job - job topic is not of type string";
+ }
+ } else {
+ message = "Discarding job - job topic is missing";
+ }
+ return message;
+ }
+
+ /**
+ * Check the job.
+ * @return <code>null</code> if the topic topic is correct and all properties are serializable,
+ * otherwise an error description is returned
+ */
+ public static String checkJob(final Object jobTopic, final Map<String, Object> properties) {
+ final String msg = checkJobTopic(jobTopic);
+ if ( msg == null ) {
+ if ( properties != null ) {
+ for(final Object val : properties.values()) {
+ if ( val != null && !(val instanceof Serializable) ) {
+ return "Discarding job - properties must be serializable: " + jobTopic + " : " + properties;
+ }
+ }
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Create an event from a job
+ * @param job The job
+ * @return New event object.
+ */
+ public static Event toEvent(final Job job) {
+ final Map<String, Object> eventProps = new HashMap<String, Object>();
+ eventProps.putAll(((JobImpl)job).getProperties());
+ eventProps.put(ResourceHelper.PROPERTY_JOB_ID, job.getId());
+ eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER);
+ return new Event(job.getTopic(), eventProps);
+ }
+
+ /**
+ * Append properties to the string builder
+ */
+ private static void appendProperties(final StringBuilder sb,
+ final Map<String, Object> properties) {
+ if ( properties != null ) {
+ sb.append(", properties=");
+ boolean first = true;
+ for(final String propName : properties.keySet()) {
+ if ( propName.equals(ResourceHelper.PROPERTY_JOB_ID)
+ || propName.equals(ResourceHelper.PROPERTY_JOB_TOPIC) ) {
+ continue;
+ }
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(propName);
+ sb.append('=');
+ final Object value = properties.get(propName);
+ // the toString() method of Calendar is very verbose
+ // therefore we do a toString for these objects based
+ // on a date
+ if ( value instanceof Calendar ) {
+ sb.append(value.getClass().getName());
+ sb.append('(');
+ sb.append(((Calendar)value).getTime());
+ sb.append(')');
+ } else {
+ sb.append(value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Improved toString method for a job.
+ * This method prints out the job topic and all of the properties.
+ */
+ public static String toString(final String jobTopic,
+ final Map<String, Object> properties) {
+ final StringBuilder sb = new StringBuilder("Sling Job ");
+ sb.append("[topic=");
+ sb.append(jobTopic);
+ appendProperties(sb, properties);
+
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Improved toString method for a job.
+ * This method prints out the job topic and all of the properties.
+ */
+ public static String toString(final Job job) {
+ if ( job != null ) {
+ final StringBuilder sb = new StringBuilder("Sling Job ");
+ sb.append("[topic=");
+ sb.append(job.getTopic());
+ sb.append(", id=");
+ sb.append(job.getId());
+ appendProperties(sb, ((JobImpl)job).getProperties());
+ sb.append("]");
+ return sb.toString();
+ }
+ return "<null>";
+ }
+
+ /**
+ * Read a job
+ */
+ public static JobImpl readJob(final Logger logger, final Resource resource) {
+ JobImpl job = null;
+ if ( resource != null ) {
+ try {
+ final ValueMap vm = ResourceHelper.getValueMap(resource);
+
+ // check job topic and job id
+ final String errorMessage = Utility.checkJobTopic(vm.get(ResourceHelper.PROPERTY_JOB_TOPIC));
+ final String jobId = vm.get(ResourceHelper.PROPERTY_JOB_ID, String.class);
+ if ( errorMessage == null && jobId != null ) {
+ final String topic = vm.get(ResourceHelper.PROPERTY_JOB_TOPIC, String.class);
+ final Map<String, Object> jobProperties = ResourceHelper.cloneValueMap(vm);
+
+ jobProperties.put(JobImpl.PROPERTY_RESOURCE_PATH, resource.getPath());
+ // convert to integers (JCR supports only long...)
+ jobProperties.put(Job.PROPERTY_JOB_RETRIES, vm.get(Job.PROPERTY_JOB_RETRIES, Integer.class));
+ jobProperties.put(Job.PROPERTY_JOB_RETRY_COUNT, vm.get(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class));
+ if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS) != null ) {
+ jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEPS, vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS, Integer.class));
+ }
+ if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEP) != null ) {
+ jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEP, vm.get(Job.PROPERTY_JOB_PROGRESS_STEP, Integer.class));
+ }
+ @SuppressWarnings("unchecked")
+ final List<Exception> readErrorList = (List<Exception>) jobProperties.get(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
+ if ( readErrorList != null ) {
+ for(final Exception e : readErrorList) {
+ logger.warn("Unable to read job from " + resource.getPath(), e);
+ }
+ }
+ job = new JobImpl(topic,
+ jobId,
+ jobProperties);
+ } else {
+ if ( errorMessage != null ) {
+ logger.warn("{} : {}", errorMessage, resource.getPath());
+ } else if ( jobId == null ) {
+ logger.warn("Discarding job - no job id found : {}", resource.getPath());
+ }
+ // remove the job as the topic is invalid anyway
+ try {
+ resource.getResourceResolver().delete(resource);
+ resource.getResourceResolver().commit();
+ } catch ( final PersistenceException ignore) {
+ logger.debug("Unable to remove job resource.", ignore);
+ }
+ }
+ } catch (final InstantiationException ie) {
+ // something happened with the resource in the meantime
+ logger.debug("Unable to instantiate resource.", ie);
+ } catch (final RuntimeException re) {
+ logger.debug("Unable to read resource.", re);
+ }
+
+ }
+ return job;
+ }
+
+ private static final Comparator<Resource> RESOURCE_COMPARATOR = new Comparator<Resource>() {
+
+ @Override
+ public int compare(final Resource o1, final Resource o2) {
+ Integer value1 = null;
+ try {
+ value1 = Integer.valueOf(o1.getName());
+ } catch ( final NumberFormatException nfe) {
+ // ignore
+ }
+ Integer value2 = null;
+ try {
+ value2 = Integer.valueOf(o2.getName());
+ } catch ( final NumberFormatException nfe) {
+ // ignore
+ }
+ if ( value1 != null && value2 != null ) {
+ return value1.compareTo(value2);
+ }
+ return o1.getName().compareTo(o2.getName());
+ }
+ };
+
+ /**
+ * Helper method to read all children of a resource and sort them by name
+ * @param type The type of resources (for debugging)
+ * @param rsrc The parent resource
+ * @return Sorted list of children.
+ */
+ public static List<Resource> getSortedChildren(final Logger logger, final String type, final Resource rsrc) {
+ final List<Resource> children = new ArrayList<Resource>();
+ final Iterator<Resource> monthIter = rsrc.listChildren();
+ while ( monthIter.hasNext() ) {
+ final Resource monthResource = monthIter.next();
+ children.add(monthResource);
+ logger.debug("Found {} : {}", type, monthResource.getName());
+ }
+ Collections.sort(children, RESOURCE_COMPARATOR);
+ return children;
+ }
+
+ /**
+ * Log a deprecation warning on level info into the log
+ * @param logger The logger to use
+ * @param message The message.
+ */
+ public static void logDeprecated(final Logger logger, final String message) {
+ if ( LOG_DEPRECATION_WARNINGS && logger.isInfoEnabled() ) {
+ logger.info("DEPRECATION-WARNING: " + message, new Exception(message));
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java
new file mode 100644
index 0000000..71b6fe6
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationChangeListener.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+/**
+ * Listener interface to get topology / queue changes.
+ * Components interested in configuration changes can subscribe
+ * themselves using the {@link JobManagerConfiguration}.
+ */
+public interface ConfigurationChangeListener {
+
+ /**
+ * Notify about a configuration change.
+ * @param active {@code true} if job processing is active, otherwise {@code false}
+ */
+ void configurationChanged(final boolean active);
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java
new file mode 100644
index 0000000..18f4886
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/ConfigurationConstants.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+/**
+ * Constants for the queue configuration.
+ */
+public abstract class ConfigurationConstants {
+
+ public static final int NUMBER_OF_PROCESSORS = Runtime.getRuntime().availableProcessors();
+
+ public static final String DEFAULT_TYPE = "UNORDERED";
+ public static final String DEFAULT_PRIORITY = "NORM";
+ public static final int DEFAULT_RETRIES = 10;
+ public static final long DEFAULT_RETRY_DELAY = 2000;
+ public static final int DEFAULT_MAX_PARALLEL = 15;
+ public static final boolean DEFAULT_KEEP_JOBS = false;
+ public static final int DEFAULT_THREAD_POOL_SIZE = 0;
+ public static final boolean DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE = false;
+
+ public static final String PROP_NAME = "queue.name";
+ public static final String PROP_TYPE = "queue.type";
+ public static final String PROP_TOPICS = "queue.topics";
+ public static final String PROP_MAX_PARALLEL = "queue.maxparallel";
+ public static final String PROP_RETRIES = "queue.retries";
+ public static final String PROP_RETRY_DELAY = "queue.retrydelay";
+ public static final String PROP_PRIORITY = "queue.priority";
+ public static final String PROP_KEEP_JOBS = "queue.keepJobs";
+ public static final String PROP_THREAD_POOL_SIZE = "queue.threadPoolSize";
+ public static final String PROP_PREFER_RUN_ON_CREATION_INSTANCE = "queue.preferRunOnCreationInstance";
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java
new file mode 100644
index 0000000..c8457a2
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/InternalQueueConfiguration.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.event.impl.support.TopicMatcher;
+import org.apache.sling.event.impl.support.TopicMatcherHelper;
+import org.apache.sling.event.jobs.QueueConfiguration;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(metatype=true,
+ name="org.apache.sling.event.jobs.QueueConfiguration",
+ label="Apache Sling Job Queue Configuration",
+ description="The configuration of a job processing queue.",
+ configurationFactory=true, policy=ConfigurationPolicy.REQUIRE)
+@Service(value={InternalQueueConfiguration.class})
+@Properties({
+ @Property(name=ConfigurationConstants.PROP_NAME,
+ label="Name",
+ description="The name of the queue. If matching is used the token {0} can be used to substitute the real value."),
+ @Property(name=ConfigurationConstants.PROP_TOPICS,
+ unbounded=PropertyUnbounded.ARRAY,
+ label="Topics",
+ description="This value is required and lists the topics processed by "
+ + "this queue. The value is a list of strings. If a string ends with a dot, "
+ + "all topics in exactly this package match. If the string ends with a star, "
+ + "all topics in this package and all subpackages match. If the string neither "
+ + "ends with a dot nor with a star, this is assumed to define an exact topic."),
+ @Property(name=ConfigurationConstants.PROP_TYPE,
+ value=ConfigurationConstants.DEFAULT_TYPE,
+ options={@PropertyOption(name="UNORDERED",value="Parallel"),
+ @PropertyOption(name="ORDERED",value="Ordered"),
+ @PropertyOption(name="TOPIC_ROUND_ROBIN",value="Topic Round Robin")},
+ label="Type",
+ description="The queue type."),
+ @Property(name=ConfigurationConstants.PROP_MAX_PARALLEL,
+ doubleValue=ConfigurationConstants.DEFAULT_MAX_PARALLEL,
+ label="Maximum Parallel Jobs",
+ description="The maximum number of parallel jobs started for this queue. "
+ + "A value of -1 is substituted with the number of available processors. "
+ + "Positive integer values specify number of processors to use. Can be greater than number of processors. "
+ + "A decimal number between 0.0 and 1.0 is treated as a fraction of available processors. "
+ + "For example 0.5 means half of the available processors. For ordered queue types this value is ignored (always enforced to be 1)."),
+ @Property(name=ConfigurationConstants.PROP_RETRIES,
+ intValue=ConfigurationConstants.DEFAULT_RETRIES,
+ label="Maximum Retries",
+ description="The maximum number of times a failed job slated "
+ + "for retries is actually retried. If a job has been retried this number of "
+ + "times and still fails, it is not rescheduled and assumed to have failed. The "
+ + "default value is 10."),
+ @Property(name=ConfigurationConstants.PROP_RETRY_DELAY,
+ longValue=ConfigurationConstants.DEFAULT_RETRY_DELAY,
+ label="Retry Delay",
+ description="The number of milliseconds to sleep between two "
+ + "consecutive retries of a job which failed and was set to be retried. The "
+ + "default value is 2 seconds. This value is only relevant if there is a single "
+ + "failed job in the queue. If there are multiple failed jobs, each job is "
+ + "retried in turn without an intervening delay."),
+ @Property(name=ConfigurationConstants.PROP_PRIORITY,
+ value=ConfigurationConstants.DEFAULT_PRIORITY,
+ options={@PropertyOption(name="NORM",value="Norm"),
+ @PropertyOption(name="MIN",value="Min"),
+ @PropertyOption(name="MAX",value="Max")},
+ label="Priority",
+ description="The priority for the threads used by this queue. Default is norm."),
+ @Property(name=ConfigurationConstants.PROP_KEEP_JOBS,
+ boolValue=ConfigurationConstants.DEFAULT_KEEP_JOBS,
+ label="Keep History",
+ description="If this option is enabled, successful finished jobs are kept "
+ + "to provide a complete history."),
+ @Property(name=ConfigurationConstants.PROP_PREFER_RUN_ON_CREATION_INSTANCE,
+ boolValue=ConfigurationConstants.DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE,
+ label="Prefer Creation Instance",
+ description="If this option is enabled, the jobs are tried to "
+ + "be run on the instance where the job was created."),
+ @Property(name=ConfigurationConstants.PROP_THREAD_POOL_SIZE,
+ intValue=ConfigurationConstants.DEFAULT_THREAD_POOL_SIZE,
+ label="Thread Pool Size",
+ description="Optional configuration value for a thread pool to be used by "
+ + "this queue. If this is value has a positive number of threads configuration, this queue uses "
+ + "an own thread pool with the configured number of threads."),
+ @Property(name=Constants.SERVICE_RANKING,
+ intValue=0,
+ propertyPrivate=false,
+ label="Ranking",
+ description="Integer value defining the ranking of this queue configuration. "
+ + "If more than one queue matches a job topic, the one with the highest ranking is used."),
+ @Property(name="webconsole.configurationFactory.nameHint", value="Queue: {" + ConfigurationConstants.PROP_NAME + "}")
+})
+public class InternalQueueConfiguration
+ implements QueueConfiguration, Comparable<InternalQueueConfiguration> {
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** The name of the queue. */
+ private String name;
+
+ /** The queue type. */
+ private Type type;
+
+ /** Number of retries. */
+ private int retries;
+
+ /** Retry delay. */
+ private long retryDelay;
+
+ /** Thread priority. */
+ private ThreadPriority priority;
+
+ /** The maximum number of parallel processes (for non ordered queues) */
+ private int maxParallelProcesses;
+
+ /** The ordering. */
+ private int serviceRanking;
+
+ /** The matchers for topics. */
+ private TopicMatcher[] matchers;
+
+ /** The configured topics. */
+ private String[] topics;
+
+ /** Keep jobs. */
+ private boolean keepJobs;
+
+ /** Valid flag. */
+ private boolean valid = false;
+
+ /** Optional thread pool size. */
+ private int ownThreadPoolSize;
+
+ /** Prefer creation instance. */
+ private boolean preferCreationInstance;
+
+ private String pid;
+
+ /**
+ * Create a new configuration from a config
+ */
+ public static InternalQueueConfiguration fromConfiguration(final Map<String, Object> params) {
+ final InternalQueueConfiguration c = new InternalQueueConfiguration();
+ c.activate(params);
+ return c;
+ }
+
+ public InternalQueueConfiguration() {
+ // nothing to do, see activate
+ }
+
+ /**
+ * Create a new queue configuration
+ */
+ @Activate
+ protected void activate(final Map<String, Object> params) {
+ this.name = PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_NAME), null);
+ try {
+ this.priority = ThreadPriority.valueOf(PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_PRIORITY), ConfigurationConstants.DEFAULT_PRIORITY));
+ } catch ( final IllegalArgumentException iae) {
+ logger.warn("Invalid value for queue priority. Using default instead of : {}", params.get(ConfigurationConstants.PROP_PRIORITY));
+ this.priority = ThreadPriority.valueOf(ConfigurationConstants.DEFAULT_PRIORITY);
+ }
+ try {
+ this.type = Type.valueOf(PropertiesUtil.toString(params.get(ConfigurationConstants.PROP_TYPE), ConfigurationConstants.DEFAULT_TYPE));
+ } catch ( final IllegalArgumentException iae) {
+ logger.error("Invalid value for queue type configuration: {}", params.get(ConfigurationConstants.PROP_TYPE));
+ this.type = null;
+ }
+ this.retries = PropertiesUtil.toInteger(params.get(ConfigurationConstants.PROP_RETRIES), ConfigurationConstants.DEFAULT_RETRIES);
+ this.retryDelay = PropertiesUtil.toLong(params.get(ConfigurationConstants.PROP_RETRY_DELAY), ConfigurationConstants.DEFAULT_RETRY_DELAY);
+
+ // Float values are treated as percentage. int values are treated as number of cores, -1 == all available
+ // Note: the value is based on the core count at startup. It will not change dynamically if core count changes.
+ int cores = ConfigurationConstants.NUMBER_OF_PROCESSORS;
+ final double inMaxParallel = PropertiesUtil.toDouble(params.get(ConfigurationConstants.PROP_MAX_PARALLEL),
+ ConfigurationConstants.DEFAULT_MAX_PARALLEL);
+ logger.debug("Max parallel for queue {} is {}", this.name, inMaxParallel);
+ if ((inMaxParallel == Math.floor(inMaxParallel)) && !Double.isInfinite(inMaxParallel)) {
+ // integral type
+ if ((int) inMaxParallel == 0) {
+ logger.warn("Max threads property for {} set to zero.", this.name);
+ }
+ this.maxParallelProcesses = (inMaxParallel <= -1 ? cores : (int) inMaxParallel);
+ } else {
+ // percentage (rounded)
+ if ((inMaxParallel > 0.0) && (inMaxParallel < 1.0)) {
+ this.maxParallelProcesses = (int) Math.round(cores * inMaxParallel);
+ } else {
+ logger.warn("Invalid queue max parallel value for queue {}. Using {}", this.name, cores);
+ this.maxParallelProcesses = cores;
+ }
+ }
+ logger.debug("Thread pool size for {} was set to {}", this.name, this.maxParallelProcesses);
+
+ // ignore parallel setting for ordered queues
+ if ( this.type == Type.ORDERED ) {
+ this.maxParallelProcesses = 1;
+ }
+ final String[] topicsParam = PropertiesUtil.toStringArray(params.get(ConfigurationConstants.PROP_TOPICS));
+ this.matchers = TopicMatcherHelper.buildMatchers(topicsParam);
+ if ( this.matchers == null ) {
+ this.topics = null;
+ } else {
+ this.topics = topicsParam;
+ }
+ this.keepJobs = PropertiesUtil.toBoolean(params.get(ConfigurationConstants.PROP_KEEP_JOBS), ConfigurationConstants.DEFAULT_KEEP_JOBS);
+ this.serviceRanking = PropertiesUtil.toInteger(params.get(Constants.SERVICE_RANKING), 0);
+ this.ownThreadPoolSize = PropertiesUtil.toInteger(params.get(ConfigurationConstants.PROP_THREAD_POOL_SIZE), ConfigurationConstants.DEFAULT_THREAD_POOL_SIZE);
+ this.preferCreationInstance = PropertiesUtil.toBoolean(params.get(ConfigurationConstants.PROP_PREFER_RUN_ON_CREATION_INSTANCE), ConfigurationConstants.DEFAULT_PREFER_RUN_ON_CREATION_INSTANCE);
+ this.pid = (String)params.get(Constants.SERVICE_PID);
+ this.valid = this.checkIsValid();
+ }
+
+ /**
+ * Check if this configuration is valid,
+ * If it is invalid, it is ignored.
+ */
+ private boolean checkIsValid() {
+ if ( type == null ) {
+ return false;
+ }
+ boolean hasMatchers = false;
+ if ( this.matchers != null ) {
+ for(final TopicMatcher m : this.matchers ) {
+ if ( m != null ) {
+ hasMatchers = true;
+ break;
+ }
+ }
+ }
+ if ( !hasMatchers ) {
+ return false;
+ }
+ if ( name == null || name.length() == 0 ) {
+ return false;
+ }
+ if ( retries < -1 ) {
+ return false;
+ }
+ if ( maxParallelProcesses < 1 ) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isValid() {
+ return this.valid;
+ }
+
+ /**
+ * Check if the queue processes the event.
+ * @param topic The topic of the event
+ * @return The queue name or <code>null</code>
+ */
+ public String match(final String topic) {
+ if ( this.matchers != null ) {
+ for(final TopicMatcher m : this.matchers ) {
+ if ( m != null ) {
+ final String rep = m.match(topic);
+ if ( rep != null ) {
+ return this.name.replace("{0}", rep);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the name of the queue.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getRetryDelayInMs()
+ */
+ @Override
+ public long getRetryDelayInMs() {
+ return this.retryDelay;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getMaxRetries()
+ */
+ @Override
+ public int getMaxRetries() {
+ return this.retries;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getType()
+ */
+ @Override
+ public Type getType() {
+ return this.type;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getMaxParallel()
+ */
+ @Override
+ public int getMaxParallel() {
+ return this.maxParallelProcesses;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getTopics()
+ */
+ @Override
+ public String[] getTopics() {
+ return this.topics;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.QueueConfiguration#getRanking()
+ */
+ @Override
+ public int getRanking() {
+ return this.serviceRanking;
+ }
+
+ public String getPid() {
+ return this.pid;
+ }
+
+ @Override
+ public boolean isKeepJobs() {
+ return this.keepJobs;
+ }
+
+ @Override
+ public int getOwnThreadPoolSize() {
+ return this.ownThreadPoolSize;
+ }
+
+ @Override
+ public boolean isPreferRunOnCreationInstance() {
+ return this.preferCreationInstance;
+ }
+
+ @Override
+ public String toString() {
+ return "Queue-Configuration(" + this.hashCode() + ") : {" +
+ "name=" + this.name +
+ ", type=" + this.type +
+ ", topics=" + (this.matchers == null ? "[]" : Arrays.toString(this.matchers)) +
+ ", maxParallelProcesses=" + this.maxParallelProcesses +
+ ", retries=" + this.retries +
+ ", retryDelayInMs=" + this.retryDelay +
+ ", keepJobs=" + this.keepJobs +
+ ", preferRunOnCreationInstance=" + this.preferCreationInstance +
+ ", ownThreadPoolSize=" + this.ownThreadPoolSize +
+ ", serviceRanking=" + this.serviceRanking +
+ ", pid=" + this.pid +
+ ", isValid=" + this.isValid() + "}";
+ }
+
+ @Override
+ public int compareTo(final InternalQueueConfiguration other) {
+ if ( this.serviceRanking < other.serviceRanking ) {
+ return 1;
+ } else if ( this.serviceRanking > other.serviceRanking ) {
+ return -1;
+ }
+ return 0;
+ }
+
+ @Override
+ public ThreadPriority getThreadPriority() {
+ return this.priority;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java
new file mode 100644
index 0000000..3cc8b72
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/JobManagerConfiguration.java
@@ -0,0 +1,661 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.sql.Date;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.commons.InitDelayingTopologyEventListener;
+import org.apache.sling.event.impl.EnvironmentComponent;
+import org.apache.sling.event.impl.jobs.Utility;
+import org.apache.sling.event.impl.jobs.tasks.CheckTopologyTask;
+import org.apache.sling.event.impl.jobs.tasks.FindUnfinishedJobsTask;
+import org.apache.sling.event.impl.jobs.tasks.UpgradeTask;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Configuration of the job handling
+ *
+ */
+@Component(immediate=true, metatype=true,
+ label="Apache Sling Job Manager",
+ description="This is the central service of the job handling.",
+ name="org.apache.sling.event.impl.jobs.jcr.PersistenceHandler")
+@Service(value={JobManagerConfiguration.class})
+@Properties({
+ @Property(name=JobManagerConfiguration.PROPERTY_DISABLE_DISTRIBUTION,
+ boolValue=JobManagerConfiguration.DEFAULT_DISABLE_DISTRIBUTION,
+ label="Disable Distribution",
+ description="If the distribution is disabled, all jobs will be processed on the leader only! "
+ + "Please use this switch with care."),
+ @Property(name=JobManagerConfiguration.PROPERTY_LOG_DEPRECATION_WARNINGS,
+ boolValue=JobManagerConfiguration.DEFAULT_LOG_DEPRECATION_WARNINGS,
+ label="Deprecation Warnings",
+ description="If this switch is enabled, deprecation warnings will be logged with the INFO level."),
+ @Property(name=JobManagerConfiguration.PROPERTY_STARTUP_DELAY,
+ longValue=JobManagerConfiguration.DEFAULT_STARTUP_DELAY,
+ label="Startup Delay",
+ description="Specify amount in seconds that job manager waits on startup before starting with job handling. "
+ + "This can be used to allow enough time to restart a cluster before jobs are eventually reassigned."),
+ @Property(name=JobManagerConfiguration.PROPERTY_REPOSITORY_PATH,
+ value=JobManagerConfiguration.DEFAULT_REPOSITORY_PATH, propertyPrivate=true),
+ @Property(name=JobManagerConfiguration.PROPERTY_SCHEDULED_JOBS_PATH,
+ value=JobManagerConfiguration.DEFAULT_SCHEDULED_JOBS_PATH, propertyPrivate=true),
+ @Property(name=JobManagerConfiguration.PROPERTY_BACKGROUND_LOAD_DELAY,
+ longValue=JobManagerConfiguration.DEFAULT_BACKGROUND_LOAD_DELAY, propertyPrivate=true),
+})
+public class JobManagerConfiguration {
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger("org.apache.sling.event.impl.jobs");
+
+ /** Audit Logger. */
+ private final Logger auditLogger = LoggerFactory.getLogger("org.apache.sling.event.jobs.audit");
+
+ /** Default resource path for jobs. */
+ public static final String DEFAULT_REPOSITORY_PATH = "/var/eventing/jobs";
+
+ /** Default background load delay. */
+ public static final long DEFAULT_BACKGROUND_LOAD_DELAY = 10;
+
+ /** Default startup delay. */
+ public static final long DEFAULT_STARTUP_DELAY = 30;
+
+ /** Default for disabling the distribution. */
+ public static final boolean DEFAULT_DISABLE_DISTRIBUTION = false;
+
+ /** Default resource path for scheduled jobs. */
+ public static final String DEFAULT_SCHEDULED_JOBS_PATH = "/var/eventing/scheduled-jobs";
+
+ /** The path where all jobs are stored. */
+ public static final String PROPERTY_REPOSITORY_PATH = "repository.path";
+
+ /** The background loader waits this time of seconds after startup before loading events from the repository. (in secs) */
+ public static final String PROPERTY_BACKGROUND_LOAD_DELAY = "load.delay";
+
+ /** The entire job handling waits time amount of seconds until it starts - to allow avoiding reassign on restart of a cluster */
+ public static final String PROPERTY_STARTUP_DELAY = "startup.delay";
+
+ /** Configuration switch for distributing the jobs. */
+ public static final String PROPERTY_DISABLE_DISTRIBUTION = "job.consumermanager.disableDistribution";
+
+ /** Configuration property for the scheduled jobs path. */
+ public static final String PROPERTY_SCHEDULED_JOBS_PATH = "job.scheduled.jobs.path";
+
+ /** Default value for background loading. */
+ public static final boolean DEFAULT_BACKGROUND_LOAD_SEARCH = true;
+
+ /** Configuration property for deprecation warnings. */
+ public static final String PROPERTY_LOG_DEPRECATION_WARNINGS = "job.log.deprecation";
+
+ /** Default value for deprecation warnings. */
+ public static final boolean DEFAULT_LOG_DEPRECATION_WARNINGS = true;
+
+ /** The jobs base path with a slash. */
+ private String jobsBasePathWithSlash;
+
+ /** The base path for assigned jobs. */
+ private String assignedJobsPath;
+
+ /** The base path for unassigned jobs. */
+ private String unassignedJobsPath;
+
+ /** The base path for assigned jobs to the current instance. */
+ private String localJobsPath;
+
+ /** The base path for assigned jobs to the current instance - ending with a slash. */
+ private String localJobsPathWithSlash;
+
+ private String previousVersionAnonPath;
+
+ private String previousVersionIdentifiedPath;
+
+ private volatile long backgroundLoadDelay;
+
+ private volatile long startupDelay;
+
+ private volatile InitDelayingTopologyEventListener startupDelayListener;
+
+ private volatile boolean disabledDistribution;
+
+ private String storedCancelledJobsPath;
+
+ private String storedSuccessfulJobsPath;
+
+ /** The resource path where scheduled jobs are stored. */
+ private String scheduledJobsPath;
+
+ /** The resource path where scheduled jobs are stored - ending with a slash. */
+ private String scheduledJobsPathWithSlash;
+
+ /** List of topology awares. */
+ private final List<ConfigurationChangeListener> listeners = new ArrayList<ConfigurationChangeListener>();
+
+ /** The environment component. */
+ @Reference
+ private EnvironmentComponent environment;
+
+ @Reference
+ private ResourceResolverFactory resourceResolverFactory;
+
+ @Reference
+ private QueueConfigurationManager queueConfigManager;
+
+ @Reference
+ private Scheduler scheduler;
+
+ @Reference
+ private ServiceUserMapped serviceUserMapped;
+
+ /** Is this still active? */
+ private final AtomicBoolean active = new AtomicBoolean(false);
+
+ /** The topology capabilities. */
+ private volatile TopologyCapabilities topologyCapabilities;
+
+ /**
+ * Activate this component.
+ * @param props Configuration properties
+ * @throws RuntimeException If the default paths can't be created
+ */
+ @Activate
+ protected void activate(final Map<String, Object> props) {
+ this.update(props);
+ this.jobsBasePathWithSlash = PropertiesUtil.toString(props.get(PROPERTY_REPOSITORY_PATH),
+ DEFAULT_REPOSITORY_PATH) + '/';
+
+ // create initial resources
+ this.assignedJobsPath = this.jobsBasePathWithSlash + "assigned";
+ this.unassignedJobsPath = this.jobsBasePathWithSlash + "unassigned";
+
+ this.localJobsPath = this.assignedJobsPath.concat("/").concat(Environment.APPLICATION_ID);
+ this.localJobsPathWithSlash = this.localJobsPath.concat("/");
+
+ this.previousVersionAnonPath = this.jobsBasePathWithSlash + "anon";
+ this.previousVersionIdentifiedPath = this.jobsBasePathWithSlash + "identified";
+
+ this.storedCancelledJobsPath = this.jobsBasePathWithSlash + "cancelled";
+ this.storedSuccessfulJobsPath = this.jobsBasePathWithSlash + "finished";
+
+ this.scheduledJobsPath = PropertiesUtil.toString(props.get(PROPERTY_SCHEDULED_JOBS_PATH),
+ DEFAULT_SCHEDULED_JOBS_PATH);
+ this.scheduledJobsPathWithSlash = this.scheduledJobsPath + "/";
+
+ // create initial resources
+ final ResourceResolver resolver = this.createResourceResolver();
+ try {
+ ResourceHelper.getOrCreateBasePath(resolver, this.getLocalJobsPath());
+ ResourceHelper.getOrCreateBasePath(resolver, this.getUnassignedJobsPath());
+ } catch ( final PersistenceException pe ) {
+ logger.error("Unable to create default paths: " + pe.getMessage(), pe);
+ throw new RuntimeException(pe);
+ } finally {
+ resolver.close();
+ }
+ this.active.set(true);
+
+ // SLING-5560 : use an InitDelayingTopologyEventListener
+ if (this.startupDelay > 0) {
+ logger.debug("activate: job manager will start in {} sec. ({})", this.startupDelay, PROPERTY_STARTUP_DELAY);
+ this.startupDelayListener = new InitDelayingTopologyEventListener(startupDelay, new TopologyEventListener() {
+
+ @Override
+ public void handleTopologyEvent(TopologyEvent event) {
+ doHandleTopologyEvent(event);
+ }
+ }, this.scheduler, logger);
+ } else {
+ logger.debug("activate: job manager will start without delay. ({}:{})", PROPERTY_STARTUP_DELAY, this.startupDelay);
+ }
+ }
+
+ /**
+ * Update with a new configuration
+ */
+ @Modified
+ protected void update(final Map<String, Object> props) {
+ this.disabledDistribution = PropertiesUtil.toBoolean(props.get(PROPERTY_DISABLE_DISTRIBUTION), DEFAULT_DISABLE_DISTRIBUTION);
+ this.backgroundLoadDelay = PropertiesUtil.toLong(props.get(PROPERTY_BACKGROUND_LOAD_DELAY), DEFAULT_BACKGROUND_LOAD_DELAY);
+ // SLING-5560: note that currently you can't change the startupDelay to have
+ // an immediate effect - it will only have an effect on next activation.
+ // (as 'startup delay runnable' is already scheduled in activate)
+ this.startupDelay = PropertiesUtil.toLong(props.get(PROPERTY_STARTUP_DELAY), DEFAULT_STARTUP_DELAY);
+ Utility.LOG_DEPRECATION_WARNINGS = PropertiesUtil.toBoolean(props.get(PROPERTY_LOG_DEPRECATION_WARNINGS), DEFAULT_LOG_DEPRECATION_WARNINGS);
+ }
+
+ /**
+ * Deactivate
+ */
+ @Deactivate
+ protected void deactivate() {
+ this.active.set(false);
+ if ( this.startupDelayListener != null) {
+ this.startupDelayListener.dispose();
+ this.startupDelayListener = null;
+ }
+ this.stopProcessing();
+ }
+
+ /**
+ * Is this component still active?
+ * @return Active?
+ */
+ public boolean isActive() {
+ return this.active.get();
+ }
+
+ /**
+ * Create a new resource resolver for reading and writing the resource tree.
+ * The resolver needs to be closed by the client.
+ * @return A resource resolver or {@code null} if the component is already deactivated.
+ * @throws RuntimeException if the resolver can't be created.
+ */
+ public ResourceResolver createResourceResolver() {
+ ResourceResolver resolver = null;
+ final ResourceResolverFactory factory = this.resourceResolverFactory;
+ if ( factory != null ) {
+ try {
+ resolver = this.resourceResolverFactory.getServiceResourceResolver(null);
+ } catch ( final LoginException le) {
+ logger.error("Unable to create new resource resolver: " + le.getMessage(), le);
+ throw new RuntimeException(le);
+ }
+ }
+ return resolver;
+ }
+
+ /**
+ * Get the current topology capabilities.
+ * @return The capabilities or {@code null}
+ */
+ public TopologyCapabilities getTopologyCapabilities() {
+ return this.topologyCapabilities;
+ }
+
+ public QueueConfigurationManager getQueueConfigurationManager() {
+ return this.queueConfigManager;
+ }
+
+ /**
+ * Get main logger.
+ * @return The main logger.
+ */
+ public Logger getMainLogger() {
+ return this.logger;
+ }
+
+ /**
+ * Get the resource path for all assigned jobs.
+ * @return The path - does not end with a slash.
+ */
+ public String getAssginedJobsPath() {
+ return this.assignedJobsPath;
+ }
+
+ /**
+ * Get the resource path for all unassigned jobs.
+ * @return The path - does not end with a slash.
+ */
+ public String getUnassignedJobsPath() {
+ return this.unassignedJobsPath;
+ }
+
+ /**
+ * Get the resource path for all jobs assigned to the current instance
+ * @return The path - does not end with a slash
+ */
+ public String getLocalJobsPath() {
+ return this.localJobsPath;
+ }
+
+ /** Counter for jobs without an id. */
+ private final AtomicLong jobCounter = new AtomicLong(0);
+
+ /**
+ * Create a unique job path (folder and name) for the job.
+ */
+ public String getUniquePath(final String targetId,
+ final String topic,
+ final String jobId,
+ final Map<String, Object> jobProperties) {
+ final String topicName = topic.replace('/', '.');
+ final StringBuilder sb = new StringBuilder();
+ if ( targetId != null ) {
+ sb.append(this.assignedJobsPath);
+ sb.append('/');
+ sb.append(targetId);
+ } else {
+ sb.append(this.unassignedJobsPath);
+ }
+ sb.append('/');
+ sb.append(topicName);
+ sb.append('/');
+ sb.append(jobId);
+
+ return sb.toString();
+ }
+
+ /**
+ * Get the unique job id
+ */
+ public String getUniqueId(final String jobTopic) {
+ final Calendar now = Calendar.getInstance();
+ final StringBuilder sb = new StringBuilder();
+ sb.append(now.get(Calendar.YEAR));
+ sb.append('/');
+ sb.append(now.get(Calendar.MONTH) + 1);
+ sb.append('/');
+ sb.append(now.get(Calendar.DAY_OF_MONTH));
+ sb.append('/');
+ sb.append(now.get(Calendar.HOUR_OF_DAY));
+ sb.append('/');
+ sb.append(now.get(Calendar.MINUTE));
+ sb.append('/');
+ sb.append(Environment.APPLICATION_ID);
+ sb.append('_');
+ sb.append(jobCounter.getAndIncrement());
+
+ return sb.toString();
+ }
+
+ public boolean isLocalJob(final String jobPath) {
+ return jobPath != null && jobPath.startsWith(this.localJobsPathWithSlash);
+ }
+
+ public boolean isJob(final String jobPath) {
+ return jobPath.startsWith(this.jobsBasePathWithSlash);
+ }
+
+ public String getJobsBasePathWithSlash() {
+ return this.jobsBasePathWithSlash;
+ }
+
+ public String getPreviousVersionAnonPath() {
+ return this.previousVersionAnonPath;
+ }
+
+ public String getPreviousVersionIdentifiedPath() {
+ return this.previousVersionIdentifiedPath;
+ }
+
+ public boolean disableDistribution() {
+ return this.disabledDistribution;
+ }
+
+ public String getStoredCancelledJobsPath() {
+ return this.storedCancelledJobsPath;
+ }
+
+ public String getStoredSuccessfulJobsPath() {
+ return this.storedSuccessfulJobsPath;
+ }
+
+ /**
+ * Get the storage path for finished jobs.
+ * @param topic Topic of the finished job
+ * @param jobId The job id of the finished job.
+ * @param isSuccess Whether processing was successful or not
+ * @return The complete storage path
+ */
+ public String getStoragePath(final String topic, final String jobId, final boolean isSuccess) {
+ final String topicName = topic.replace('/', '.');
+ final StringBuilder sb = new StringBuilder();
+ if ( isSuccess ) {
+ sb.append(this.storedSuccessfulJobsPath);
+ } else {
+ sb.append(this.storedCancelledJobsPath);
+ }
+ sb.append('/');
+ sb.append(topicName);
+ sb.append('/');
+ sb.append(jobId);
+
+ return sb.toString();
+
+ }
+
+ /**
+ * Check whether this is a storage path.
+ */
+ public boolean isStoragePath(final String path) {
+ return path.startsWith(this.storedCancelledJobsPath) || path.startsWith(this.storedSuccessfulJobsPath);
+ }
+
+ /**
+ * Get the scheduled jobs path
+ * @param slash If {@code false} the path is returned, if {@code true} the path appended with a slash is returned.
+ * @return The path for the scheduled jobs
+ */
+ public String getScheduledJobsPath(final boolean slash) {
+ return (slash ? this.scheduledJobsPathWithSlash : this.scheduledJobsPath);
+ }
+
+ /**
+ * Stop processing
+ * @param deactivate Whether to deactivate the capabilities
+ */
+ private void stopProcessing() {
+ logger.debug("Stopping job processing...");
+ final TopologyCapabilities caps = this.topologyCapabilities;
+
+ if ( caps != null ) {
+ // deactivate old capabilities - this stops all background processes
+ caps.deactivate();
+ this.topologyCapabilities = null;
+
+ // stop all listeners
+ this.notifiyListeners();
+ }
+ logger.debug("Job processing stopped");
+ }
+
+ /**
+ * Start processing
+ * @param eventType The event type
+ * @param newCaps The new capabilities
+ */
+ private void startProcessing(final Type eventType, final TopologyCapabilities newCaps) {
+ logger.debug("Starting job processing...");
+ // create new capabilities and update view
+ this.topologyCapabilities = newCaps;
+
+ // before we propagate the new topology we do some maintenance
+ if ( eventType == Type.TOPOLOGY_INIT ) {
+ final UpgradeTask task = new UpgradeTask(this);
+ task.run();
+
+ final FindUnfinishedJobsTask rt = new FindUnfinishedJobsTask(this);
+ rt.run();
+
+ final CheckTopologyTask mt = new CheckTopologyTask(this);
+ mt.fullRun();
+
+ notifiyListeners();
+ } else {
+ // and run checker again in some seconds (if leader)
+ // notify listeners afterwards
+ final Scheduler local = this.scheduler;
+ if ( local != null ) {
+ final Runnable r = new Runnable() {
+
+ @Override
+ public void run() {
+ if ( newCaps == topologyCapabilities && newCaps.isActive()) {
+ // start listeners
+ notifiyListeners();
+ if ( newCaps.isLeader() && newCaps.isActive() ) {
+ final CheckTopologyTask mt = new CheckTopologyTask(JobManagerConfiguration.this);
+ mt.fullRun();
+ }
+ }
+ }
+ };
+ if ( !local.schedule(r, local.AT(new Date(System.currentTimeMillis() + this.backgroundLoadDelay * 1000))) ) {
+ // if for whatever reason scheduling doesn't work, let's run now
+ r.run();
+ }
+ }
+ }
+ logger.debug("Job processing started");
+ }
+
+ /**
+ * Notify all listeners
+ */
+ private void notifiyListeners() {
+ synchronized ( this.listeners ) {
+ final TopologyCapabilities caps = this.topologyCapabilities;
+ for(final ConfigurationChangeListener l : this.listeners) {
+ l.configurationChanged(caps != null);
+ }
+ }
+ }
+
+ /**
+ * This method is invoked asynchronously from the TopologyHandler.
+ * Therefore this method can't be invoked concurrently
+ * @see org.apache.sling.discovery.TopologyEventListener#handleTopologyEvent(org.apache.sling.discovery.TopologyEvent)
+ */
+ public void handleTopologyEvent(TopologyEvent event) {
+ if ( this.startupDelayListener != null ) {
+ // with startup.delay > 0
+ this.startupDelayListener.handleTopologyEvent(event);
+ } else {
+ // classic (startup.delay <= 0)
+ this.logger.debug("Received topology event {}", event);
+ doHandleTopologyEvent(event);
+ }
+ }
+
+ void doHandleTopologyEvent(final TopologyEvent event) {
+
+ // check if there is a change of properties which doesn't affect us
+ // but we need to use the new view !
+ boolean stopProcessing = true;
+ if ( event.getType() == Type.PROPERTIES_CHANGED ) {
+ final Map<String, String> newAllInstances = TopologyCapabilities.getAllInstancesMap(event.getNewView());
+ if ( this.topologyCapabilities != null && this.topologyCapabilities.isSame(newAllInstances) ) {
+ logger.debug("No changes in capabilities - updating topology capabilities with new view");
+ stopProcessing = false;
+ }
+ }
+
+ final TopologyEvent.Type eventType = event.getType();
+
+ if ( eventType == Type.TOPOLOGY_CHANGING ) {
+ this.stopProcessing();
+
+ } else if ( eventType == Type.TOPOLOGY_INIT
+ || event.getType() == Type.TOPOLOGY_CHANGED
+ || event.getType() == Type.PROPERTIES_CHANGED ) {
+
+ if ( stopProcessing ) {
+ this.stopProcessing();
+ }
+
+ this.startProcessing(eventType, new TopologyCapabilities(event.getNewView(), this));
+ }
+ }
+
+ /**
+ * Add a topology aware listener
+ * @param service Listener to notify about changes.
+ */
+ public void addListener(final ConfigurationChangeListener service) {
+ synchronized ( this.listeners ) {
+ this.listeners.add(service);
+ service.configurationChanged(this.topologyCapabilities != null);
+ }
+ }
+
+ /**
+ * Remove a topology aware listener
+ * @param service Listener to notify about changes.
+ */
+ public void removeListener(final ConfigurationChangeListener service) {
+ synchronized ( this.listeners ) {
+ this.listeners.remove(service);
+ }
+ }
+
+ private final Map<String, Job> retryList = new HashMap<String, Job>();
+
+ public void addJobToRetryList(final Job job) {
+ synchronized ( retryList ) {
+ retryList.put(job.getId(), job);
+ }
+ }
+
+ public List<Job> clearJobRetryList() {
+ final List<Job> result = new ArrayList<Job>();
+ synchronized ( this.retryList ) {
+ result.addAll(retryList.values());
+ retryList.clear();
+ }
+ return result;
+ }
+
+ public boolean removeJobFromRetryList(final Job job) {
+ synchronized ( retryList ) {
+ return retryList.remove(job.getId()) != null;
+ }
+ }
+
+ public Job getJobFromRetryList(final String jobId) {
+ synchronized ( retryList ) {
+ return retryList.get(jobId);
+ }
+ }
+
+ /**
+ * The audit logger is logging actions for auditing.
+ * @return The logger
+ */
+ public Logger getAuditLogger() {
+ return this.auditLogger;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java b/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java
new file mode 100644
index 0000000..9287e6b
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/MainQueueConfiguration.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
+import org.apache.felix.scr.annotations.Service;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * This is the configuration for the main queue.
+ *
+ */
+@Component(label="Apache Sling Job Default Queue",
+ description="The configuration of the default job queue.",
+ name="org.apache.sling.event.impl.jobs.DefaultJobManager",
+ metatype=true)
+@Service(value=MainQueueConfiguration.class)
+@Properties({
+ @Property(name=ConfigurationConstants.PROP_PRIORITY,
+ value=ConfigurationConstants.DEFAULT_PRIORITY,
+ options={@PropertyOption(name="NORM",value="Norm"),
+ @PropertyOption(name="MIN",value="Min"),
+ @PropertyOption(name="MAX",value="Max")},
+ label="Priority",
+ description="The priority for the threads used by this queue. Default is norm."),
+ @Property(name=ConfigurationConstants.PROP_RETRIES,
+ intValue=ConfigurationConstants.DEFAULT_RETRIES,
+ label="Maximum Retries",
+ description="The maximum number of times a failed job slated "
+ + "for retries is actually retried. If a job has been retried this number of "
+ + "times and still fails, it is not rescheduled and assumed to have failed. The "
+ + "default value is 10."),
+ @Property(name=ConfigurationConstants.PROP_RETRY_DELAY,
+ longValue=ConfigurationConstants.DEFAULT_RETRY_DELAY,
+ label="Retry Delay",
+ description="The number of milliseconds to sleep between two "
+ + "consecutive retries of a job which failed and was set to be retried. The "
+ + "default value is 2 seconds. This value is only relevant if there is a single "
+ + "failed job in the queue. If there are multiple failed jobs, each job is "
+ + "retried in turn without an intervening delay."),
+ @Property(name=ConfigurationConstants.PROP_MAX_PARALLEL,
+ intValue=ConfigurationConstants.DEFAULT_MAX_PARALLEL,
+ label="Maximum Parallel Jobs",
+ description="The maximum number of parallel jobs started for this queue. "
+ + "A value of -1 is substituted with the number of available processors."),
+})
+public class MainQueueConfiguration {
+
+ public static final String MAIN_QUEUE_NAME = "<main queue>";
+
+ /** Default logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private InternalQueueConfiguration mainConfiguration;
+
+ /**
+ * Activate this component.
+ * @param props Configuration properties
+ */
+ @Activate
+ protected void activate(final Map<String, Object> props) {
+ this.update(props);
+ }
+
+ /**
+ * Configure this component.
+ * @param props Configuration properties
+ */
+ @Modified
+ protected void update(final Map<String, Object> props) {
+ // create a new dictionary with the missing info and do some sanity puts
+ final Map<String, Object> queueProps = new HashMap<String, Object>(props);
+ queueProps.put(ConfigurationConstants.PROP_TOPICS, "*");
+ queueProps.put(ConfigurationConstants.PROP_NAME, MAIN_QUEUE_NAME);
+ queueProps.put(ConfigurationConstants.PROP_TYPE, InternalQueueConfiguration.Type.UNORDERED);
+ queueProps.put(Constants.SERVICE_PID, "org.apache.sling.event.impl.jobs.DefaultJobManager");
+ logger.debug("properties for queue {}: {}", MAIN_QUEUE_NAME, queueProps);
+ this.mainConfiguration = InternalQueueConfiguration.fromConfiguration(queueProps);
+ }
+
+ /**
+ * Return the main queue configuration object.
+ * @return The main queue configuration object.
+ */
+ public InternalQueueConfiguration getMainConfiguration() {
+ return this.mainConfiguration;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java b/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java
new file mode 100644
index 0000000..ce2b79a
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/QueueConfigurationManager.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.event.impl.support.ResourceHelper;
+
+
+/**
+ * The queue manager manages queue configurations.
+ */
+@Component
+@Service(value=QueueConfigurationManager.class)
+@Reference(referenceInterface=InternalQueueConfiguration.class, policy=ReferencePolicy.DYNAMIC,
+ cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
+ bind="bindConfig", unbind="unbindConfig", updated="updateConfig")
+public class QueueConfigurationManager {
+
+ /** Empty configuration array. */
+ private static final InternalQueueConfiguration[] EMPTY_CONFIGS = new InternalQueueConfiguration[0];
+
+ /** Configurations - ordered by service ranking. */
+ private volatile InternalQueueConfiguration[] orderedConfigs = EMPTY_CONFIGS;
+
+ /** All configurations. */
+ private final List<InternalQueueConfiguration> configurations = new ArrayList<InternalQueueConfiguration>();
+
+ /** The main queue configuration. */
+ @Reference
+ private MainQueueConfiguration mainQueueConfiguration;
+
+ /**
+ * Add a new queue configuration.
+ * @param config A new queue configuration.
+ */
+ protected void bindConfig(final InternalQueueConfiguration config) {
+ synchronized ( configurations ) {
+ configurations.add(config);
+ this.createConfigurationCache();
+ }
+ }
+
+ /**
+ * Remove a queue configuration.
+ * @param config The queue configuration.
+ */
+ protected void unbindConfig(final InternalQueueConfiguration config) {
+ synchronized ( configurations ) {
+ configurations.remove(config);
+ this.createConfigurationCache();
+ }
+ }
+
+ /**
+ * Update a queue configuration.
+ * @param config The queue configuration.
+ */
+ protected void updateConfig(final InternalQueueConfiguration config) {
+ // InternalQueueConfiguration does not implement modified atm,
+ // but we handle this case anyway
+ synchronized ( configurations ) {
+ this.createConfigurationCache();
+ }
+ }
+
+ /**
+ * Create the configurations cache used by clients.
+ */
+ private void createConfigurationCache() {
+ if ( this.configurations.isEmpty() ) {
+ this.orderedConfigs = EMPTY_CONFIGS;
+ } else {
+ Collections.sort(configurations);
+ orderedConfigs = configurations.toArray(new InternalQueueConfiguration[configurations.size()]);
+ }
+ }
+
+ /**
+ * Return all configurations.
+ * @return An array with all queue configurations except the main queue. Array might be empty.
+ */
+ public InternalQueueConfiguration[] getConfigurations() {
+ return orderedConfigs;
+ }
+
+ /**
+ * Get the configuration for the main queue.
+ * @return The configuration for the main queue.
+ */
+ public InternalQueueConfiguration getMainQueueConfiguration() {
+ return this.mainQueueConfiguration.getMainConfiguration();
+ }
+
+ public static final class QueueInfo {
+ public InternalQueueConfiguration queueConfiguration;
+ public String queueName;
+ public String targetId;
+
+ @Override
+ public String toString() {
+ return queueName;
+ }
+
+ @Override
+ public int hashCode() {
+ return queueName.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if ( obj == this ) {
+ return true;
+ }
+ if ( obj instanceof QueueInfo ) {
+ return ((QueueInfo)obj).queueName.equals(this.queueName);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Find the queue configuration for the job.
+ * This method only returns a configuration if one matches.
+ */
+ public QueueInfo getQueueInfo(final String topic) {
+ final InternalQueueConfiguration[] configurations = this.getConfigurations();
+ for(final InternalQueueConfiguration config : configurations) {
+ if ( config.isValid() ) {
+ final String qn = config.match(topic);
+ if ( qn != null ) {
+ final QueueInfo result = new QueueInfo();
+ result.queueConfiguration = config;
+ result.queueName = ResourceHelper.filterName(qn);
+
+ return result;
+ }
+ }
+ }
+ final QueueInfo result = new QueueInfo();
+ result.queueConfiguration = this.mainQueueConfiguration.getMainConfiguration();
+ result.queueName = result.queueConfiguration.getName();
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java
new file mode 100644
index 0000000..75532fb
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyCapabilities.java
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.event.jobs.QueueConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The capabilities of a topology.
+ */
+public class TopologyCapabilities {
+
+ public static final String PROPERTY_TOPICS = "org.apache.sling.event.jobs.consumer.topics";
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** Map: key: topic, value: sling IDs */
+ private final Map<String, List<InstanceDescription>> instanceCapabilities;
+
+ /** Round robin map. */
+ private final Map<String, Integer> roundRobinMap = new HashMap<String, Integer>();
+
+ /** Instance map. */
+ private final Map<String, InstanceDescription> instanceMap = new HashMap<String, InstanceDescription>();
+
+ /** Is this the leader of the cluster? */
+ private final boolean isLeader;
+
+ /** Is this still active? */
+ private volatile boolean active = true;
+
+ /** All instances. */
+ private final Map<String, String> allInstances;
+
+ /** Instance comparator. */
+ private final InstanceDescriptionComparator instanceComparator;
+
+ /** JobManagerConfiguration. */
+ private final JobManagerConfiguration jobManagerConfiguration;
+
+ /** Topology view. */
+ private final TopologyView view;
+
+ public static final class InstanceDescriptionComparator implements Comparator<InstanceDescription> {
+
+ private final String localClusterId;
+
+
+ public InstanceDescriptionComparator(final String clusterId) {
+ this.localClusterId = clusterId;
+ }
+
+ @Override
+ public int compare(final InstanceDescription o1, final InstanceDescription o2) {
+ if ( o1.getSlingId().equals(o2.getSlingId()) ) {
+ return 0;
+ }
+ final boolean o1IsLocalCluster = localClusterId.equals(o1.getClusterView().getId());
+ final boolean o2IsLocalCluster = localClusterId.equals(o2.getClusterView().getId());
+ if ( o1IsLocalCluster && !o2IsLocalCluster ) {
+ return -1;
+ }
+ if ( !o1IsLocalCluster && o2IsLocalCluster ) {
+ return 1;
+ }
+ if ( o1IsLocalCluster ) {
+ if ( o1.isLeader() && !o2.isLeader() ) {
+ return -1;
+ } else if ( o2.isLeader() && !o1.isLeader() ) {
+ return 1;
+ }
+ }
+ return o1.getSlingId().compareTo(o2.getSlingId());
+ }
+ }
+
+ public static Map<String, String> getAllInstancesMap(final TopologyView view) {
+ final Map<String, String> allInstances = new TreeMap<String, String>();
+
+ for(final InstanceDescription desc : view.getInstances() ) {
+ final String topics = desc.getProperty(PROPERTY_TOPICS);
+ if ( topics != null && topics.length() > 0 ) {
+ allInstances.put(desc.getSlingId(), topics);
+ } else {
+ allInstances.put(desc.getSlingId(), "");
+ }
+ }
+ return allInstances;
+ }
+
+ /**
+ * Create a new instance
+ * @param view The new view
+ * @param config The current job manager configuration.
+ */
+ public TopologyCapabilities(final TopologyView view,
+ final JobManagerConfiguration config) {
+ this.jobManagerConfiguration = config;
+ this.instanceComparator = new InstanceDescriptionComparator(view.getLocalInstance().getClusterView().getId());
+ this.isLeader = view.getLocalInstance().isLeader();
+ this.allInstances = getAllInstancesMap(view);
+ final Map<String, List<InstanceDescription>> newCaps = new HashMap<String, List<InstanceDescription>>();
+ for(final InstanceDescription desc : view.getInstances() ) {
+ final String topics = desc.getProperty(PROPERTY_TOPICS);
+ if ( topics != null && topics.length() > 0 ) {
+ this.logger.debug("Detected capabilities of {} : {}", desc.getSlingId(), topics);
+ for(final String topic : topics.split(",") ) {
+ List<InstanceDescription> list = newCaps.get(topic);
+ if ( list == null ) {
+ list = new ArrayList<InstanceDescription>();
+ newCaps.put(topic, list);
+ }
+ list.add(desc);
+ Collections.sort(list, this.instanceComparator);
+ }
+ }
+ this.instanceMap.put(desc.getSlingId(), desc);
+ }
+ this.instanceCapabilities = newCaps;
+ this.view = view;
+ }
+
+ /**
+ * Is this capabilities the same as represented by the provided instance map?
+ * @param newAllInstancesMap The instance map
+ * @return {@code true} if they represent the same state.
+ */
+ public boolean isSame(final Map<String, String> newAllInstancesMap) {
+ return this.allInstances.equals(newAllInstancesMap);
+ }
+
+ /**
+ * Deactivate this object.
+ */
+ public void deactivate() {
+ this.active = false;
+ }
+
+ /**
+ * Is this object still active?
+ * If it is not active anymore it should not be used!
+ * @return {@code true} if still active.
+ */
+ public boolean isActive() {
+ return this.active && this.jobManagerConfiguration.isActive() && this.view.isCurrent();
+ }
+
+ /**
+ * Is this instance still active?
+ * @param instanceId The instance id
+ * @return {@code true} if the instance is active.
+ */
+ public boolean isActive(final String instanceId) {
+ return this.allInstances.containsKey(instanceId);
+ }
+ /**
+ * Is the current instance the leader?
+ */
+ public boolean isLeader() {
+ return this.isLeader;
+ }
+
+ /**
+ * Add instances to the list if not already included
+ */
+ private void addAll(final List<InstanceDescription> potentialTargets, final List<InstanceDescription> newTargets) {
+ if ( newTargets != null ) {
+ for(final InstanceDescription desc : newTargets) {
+ boolean found = false;
+ for(final InstanceDescription existingDesc : potentialTargets) {
+ if ( desc.getSlingId().equals(existingDesc.getSlingId()) ) {
+ found = true;
+ break;
+ }
+ }
+ if ( !found ) {
+ potentialTargets.add(desc);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the potential targets (Sling IDs) sorted by ID
+ * @return A list of instance descriptions. The list might be empty.
+ */
+ public List<InstanceDescription> getPotentialTargets(final String jobTopic) {
+ // calculate potential targets
+ final List<InstanceDescription> potentialTargets = new ArrayList<InstanceDescription>();
+
+ // first: topic targets - directly handling the topic
+ addAll(potentialTargets, this.instanceCapabilities.get(jobTopic));
+
+ // second: category targets - handling the topic category
+ int pos = jobTopic.lastIndexOf('/');
+ if ( pos > 0 ) {
+ final String category = jobTopic.substring(0, pos + 1).concat("*");
+ addAll(potentialTargets, this.instanceCapabilities.get(category));
+
+ // search deep consumers (since 1.2 of the consumer package)
+ do {
+ final String subCategory = jobTopic.substring(0, pos + 1).concat("**");
+ addAll(potentialTargets, this.instanceCapabilities.get(subCategory));
+
+ pos = jobTopic.lastIndexOf('/', pos - 1);
+ } while ( pos > 0 );
+ }
+ Collections.sort(potentialTargets, this.instanceComparator);
+
+ return potentialTargets;
+ }
+
+ /**
+ * Detect the target instance.
+ */
+ public String detectTarget(final String jobTopic, final Map<String, Object> jobProperties,
+ final QueueInfo queueInfo) {
+ final List<InstanceDescription> potentialTargets = this.getPotentialTargets(jobTopic);
+ logger.debug("Potential targets for {} : {}", jobTopic, potentialTargets);
+ String createdOn = null;
+ if ( jobProperties != null ) {
+ createdOn = (String) jobProperties.get(org.apache.sling.event.jobs.Job.PROPERTY_JOB_CREATED_INSTANCE);
+ }
+ if ( createdOn == null ) {
+ createdOn = Environment.APPLICATION_ID;
+ }
+ final InstanceDescription createdOnInstance = this.instanceMap.get(createdOn);
+
+ if ( potentialTargets != null && potentialTargets.size() > 0 ) {
+ if ( createdOnInstance != null ) {
+ // create a list with local targets first.
+ final List<InstanceDescription> localTargets = new ArrayList<InstanceDescription>();
+ for(final InstanceDescription desc : potentialTargets) {
+ if ( desc.getClusterView().getId().equals(createdOnInstance.getClusterView().getId()) ) {
+ if ( !this.jobManagerConfiguration.disableDistribution() || desc.isLeader() ) {
+ localTargets.add(desc);
+ }
+ }
+ }
+ if ( localTargets.size() > 0 ) {
+ potentialTargets.clear();
+ potentialTargets.addAll(localTargets);
+ logger.debug("Potential targets filtered for {} : {}", jobTopic, potentialTargets);
+ }
+ }
+ // check prefer run on creation instance
+ if ( queueInfo.queueConfiguration.isPreferRunOnCreationInstance() ) {
+ InstanceDescription creationDesc = null;
+ for(final InstanceDescription desc : potentialTargets) {
+ if ( desc.getSlingId().equals(createdOn) ) {
+ creationDesc = desc;
+ break;
+ }
+ }
+ if ( creationDesc != null ) {
+ potentialTargets.clear();
+ potentialTargets.add(creationDesc);
+ logger.debug("Potential targets reduced to creation instance for {} : {}", jobTopic, potentialTargets);
+ }
+ }
+ if ( queueInfo.queueConfiguration.getType() == QueueConfiguration.Type.ORDERED ) {
+ // for ordered queues we always pick the first as we have to pick the same target on each cluster view
+ // on all instances (TODO - we could try to do some round robin of the whole queue)
+ final String result = potentialTargets.get(0).getSlingId();
+ logger.debug("Target for {} : {}", jobTopic, result);
+
+ return result;
+ }
+ // TODO - this is a simple round robin which is not based on the actual load
+ // of the instances
+ Integer index = this.roundRobinMap.get(jobTopic);
+ if ( index == null ) {
+ index = 0;
+ }
+ if ( index >= potentialTargets.size() ) {
+ index = 0;
+ }
+ this.roundRobinMap.put(jobTopic, index + 1);
+ final String result = potentialTargets.get(index).getSlingId();
+ logger.debug("Target for {} : {}", jobTopic, result);
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the instance capabilities.
+ * @return The map of instance capabilities.
+ */
+ public Map<String, List<InstanceDescription>> getInstanceCapabilities() {
+ return this.instanceCapabilities;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java
new file mode 100644
index 0000000..2c49ca2
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/config/TopologyHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.config;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This topology handler is handling the topology change events asynchronously
+ * and processes them by queuing them.
+ */
+@Component
+@Service(value = TopologyEventListener.class)
+public class TopologyHandler implements TopologyEventListener, Runnable {
+
+ /** The logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
+
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ /** A local queue for async handling of the events */
+ private final BlockingQueue<QueueItem> queue = new LinkedBlockingQueue<QueueItem>();
+
+ /** Active flag. */
+ private final AtomicBoolean isActive = new AtomicBoolean(false);
+
+ @Activate
+ protected void activate() {
+ this.isActive.set(true);
+ final Thread thread = new Thread(this, "Apache Sling Job Topology Listener Thread");
+ thread.setDaemon(true);
+
+ thread.start();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ this.isActive.set(false);
+ this.queue.clear();
+ try {
+ this.queue.put(new QueueItem());
+ } catch ( final InterruptedException ie) {
+ logger.warn("Thread got interrupted.", ie);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void handleTopologyEvent(final TopologyEvent event) {
+ final QueueItem item = new QueueItem();
+ item.event = event;
+ try {
+ this.queue.put(item);
+ } catch ( final InterruptedException ie) {
+ logger.warn("Thread got interrupted.", ie);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void run() {
+ while ( isActive.get() ) {
+ QueueItem item = null;
+ try {
+ item = this.queue.take();
+ } catch ( final InterruptedException ie) {
+ logger.warn("Thread got interrupted.", ie);
+ Thread.currentThread().interrupt();
+ isActive.set(false);
+ }
+ if ( isActive.get() && item != null && item.event != null ) {
+ final JobManagerConfiguration config = this.configuration;
+ if ( config != null ) {
+ config.handleTopologyEvent(item.event);
+ }
+ }
+ }
+ }
+
+ /**
+ * We need a holder class to be able to put something into the queue to stop it.
+ */
+ public static final class QueueItem {
+ public TopologyEvent event;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java b/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java
new file mode 100644
index 0000000..619136c
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/console/InventoryPlugin.java
@@ -0,0 +1,471 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.console;
+
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.inventory.Format;
+import org.apache.felix.inventory.InventoryPrinter;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.event.impl.jobs.JobConsumerManager;
+import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.QueueConfiguration;
+import org.apache.sling.event.jobs.ScheduleInfo;
+import org.apache.sling.event.jobs.ScheduledJobInfo;
+import org.apache.sling.event.jobs.Statistics;
+import org.apache.sling.event.jobs.TopicStatistics;
+
+/**
+ * This is a inventory plugin displaying the active queues, some statistics
+ * and the configurations.
+ * @since 3.2
+ */
+@Component
+@Service(value={InventoryPrinter.class})
+@Properties({
+ @Property(name=InventoryPrinter.NAME, value="slingjobs"),
+ @Property(name=InventoryPrinter.TITLE, value="Sling Jobs"),
+ @Property(name=InventoryPrinter.FORMAT, value={"TEXT", "JSON"}),
+ @Property(name=InventoryPrinter.WEBCONSOLE, boolValue=false)
+})
+public class InventoryPlugin implements InventoryPrinter {
+
+ @Reference
+ private JobManager jobManager;
+
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ @Reference
+ private JobConsumerManager jobConsumerManager;
+
+ /**
+ * Format an array.
+ */
+ private String formatArrayAsText(final String[] array) {
+ if ( array == null || array.length == 0 ) {
+ return "";
+ }
+ return Arrays.toString(array);
+ }
+
+ private String formatType(final QueueConfiguration.Type type) {
+ switch ( type ) {
+ case ORDERED : return "Ordered";
+ case TOPIC_ROUND_ROBIN : return "Topic Round Robin";
+ case UNORDERED : return "Parallel";
+ }
+ return type.toString();
+ }
+
+ /** Default date format used. */
+ private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS yyyy-MMM-dd");
+
+ /**
+ * Format a date
+ */
+ private synchronized String formatDate(final long time) {
+ if ( time == -1 ) {
+ return "-";
+ }
+ final Date d = new Date(time);
+ return dateFormat.format(d);
+ }
+
+ /**
+ * Format time (= duration)
+ */
+ private String formatTime(final long time) {
+ if ( time == 0 ) {
+ return "-";
+ }
+ if ( time < 1000 ) {
+ return time + " ms";
+ } else if ( time < 1000 * 60 ) {
+ return time / 1000 + " secs";
+ }
+ final long min = time / 1000 / 60;
+ final long secs = (time - min * 1000 * 60);
+ return min + " min " + secs / 1000 + " secs";
+ }
+
+ /**
+ * @see org.apache.felix.inventory.InventoryPrinter#print(java.io.PrintWriter, org.apache.felix.inventory.Format, boolean)
+ */
+ @Override
+ public void print(final PrintWriter pw, final Format format, final boolean isZip) {
+ if ( format.equals(Format.TEXT) ) {
+ printText(pw);
+ } else if ( format.equals(Format.JSON) ) {
+ printJson(pw);
+ }
+ }
+
+ private void printText(final PrintWriter pw) {
+ pw.println("Apache Sling Job Handling");
+ pw.println("-------------------------");
+
+ String topics = this.jobConsumerManager.getTopics();
+ if ( topics == null ) {
+ topics = "";
+ }
+
+ Statistics s = this.jobManager.getStatistics();
+ pw.println("Overall Statistics");
+ pw.printf("Start Time : %s%n", formatDate(s.getStartTime()));
+ pw.printf("Local topic consumers: %s%n", topics);
+ pw.printf("Last Activated : %s%n", formatDate(s.getLastActivatedJobTime()));
+ pw.printf("Last Finished : %s%n", formatDate(s.getLastFinishedJobTime()));
+ pw.printf("Queued Jobs : %s%n", s.getNumberOfQueuedJobs());
+ pw.printf("Active Jobs : %s%n", s.getNumberOfActiveJobs());
+ pw.printf("Jobs : %s%n", s.getNumberOfJobs());
+ pw.printf("Finished Jobs : %s%n", s.getNumberOfFinishedJobs());
+ pw.printf("Failed Jobs : %s%n", s.getNumberOfFailedJobs());
+ pw.printf("Cancelled Jobs : %s%n", s.getNumberOfCancelledJobs());
+ pw.printf("Processed Jobs : %s%n", s.getNumberOfProcessedJobs());
+ pw.printf("Average Processing Time : %s%n", formatTime(s.getAverageProcessingTime()));
+ pw.printf("Average Waiting Time : %s%n", formatTime(s.getAverageWaitingTime()));
+ pw.println();
+
+ pw.println("Topology Capabilities");
+ final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
+ if ( cap == null ) {
+ pw.print("No topology information available !");
+ } else {
+ final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
+ for(final Map.Entry<String, List<InstanceDescription>> entry : instanceCaps.entrySet()) {
+ final StringBuilder sb = new StringBuilder();
+ for(final InstanceDescription id : entry.getValue()) {
+ if ( sb.length() > 0 ) {
+ sb.append(", ");
+ }
+ if ( id.isLocal() ) {
+ sb.append("local");
+ } else {
+ sb.append(id.getSlingId());
+ }
+ }
+ pw.printf("%s : %s%n", entry.getKey(), sb.toString());
+ }
+ }
+ pw.println();
+
+ pw.println("Scheduled Jobs");
+ final Collection<ScheduledJobInfo> infos = this.jobManager.getScheduledJobs();
+ if ( infos.size() == 0 ) {
+ pw.print("No jobs currently scheduled");
+ } else {
+ for(final ScheduledJobInfo info : infos) {
+ pw.println("Schedule");
+ pw.printf("Job Topic< : %s%n", info.getJobTopic());
+ pw.print("Schedules : ");
+ boolean first = true;
+ for(final ScheduleInfo si : info.getSchedules() ) {
+ if ( !first ) {
+ pw.print(", ");
+ }
+ first = false;
+ switch ( si.getType() ) {
+ case YEARLY : pw.printf("YEARLY %s %s : %s:%s", si.getMonthOfYear(), si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case MONTHLY : pw.printf("MONTHLY %s : %s:%s", si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case WEEKLY : pw.printf("WEEKLY %s : %s:%s", si.getDayOfWeek(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case DAILY : pw.printf("DAILY %s:%s", si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case HOURLY : pw.printf("HOURLY %s", si.getMinuteOfHour());
+ break;
+ case CRON : pw.printf("CRON %s", si.getExpression());
+ break;
+ default : pw.printf("AT %s", si.getAt());
+ }
+ }
+ pw.println();
+ pw.println();
+ }
+ }
+ pw.println();
+
+ boolean isEmpty = true;
+ for(final Queue q : this.jobManager.getQueues()) {
+ isEmpty = false;
+ pw.printf("Active JobQueue: %s %s%n", q.getName(),
+ q.isSuspended() ? "(SUSPENDED)" : "");
+
+ s = q.getStatistics();
+ final QueueConfiguration c = q.getConfiguration();
+ pw.println("Statistics");
+ pw.printf("Start Time : %s%n", formatDate(s.getStartTime()));
+ pw.printf("Last Activated : %s%n", formatDate(s.getLastActivatedJobTime()));
+ pw.printf("Last Finished : %s%n", formatDate(s.getLastFinishedJobTime()));
+ pw.printf("Queued Jobs : %s%n", s.getNumberOfQueuedJobs());
+ pw.printf("Active Jobs : %s%n", s.getNumberOfActiveJobs());
+ pw.printf("Jobs : %s%n", s.getNumberOfJobs());
+ pw.printf("Finished Jobs : %s%n", s.getNumberOfFinishedJobs());
+ pw.printf("Failed Jobs : %s%n", s.getNumberOfFailedJobs());
+ pw.printf("Cancelled Jobs : %s%n", s.getNumberOfCancelledJobs());
+ pw.printf("Processed Jobs : %s%n", s.getNumberOfProcessedJobs());
+ pw.printf("Average Processing Time : %s%n", formatTime(s.getAverageProcessingTime()));
+ pw.printf("Average Waiting Time : %s%n", formatTime(s.getAverageWaitingTime()));
+ pw.printf("Status Info : %s%n", q.getStateInfo());
+ pw.println("Configuration");
+ pw.printf("Type : %s%n", formatType(c.getType()));
+ pw.printf("Topics : %s%n", formatArrayAsText(c.getTopics()));
+ pw.printf("Max Parallel : %s%n", c.getMaxParallel());
+ pw.printf("Max Retries : %s%n", c.getMaxRetries());
+ pw.printf("Retry Delay : %s ms%n", c.getRetryDelayInMs());
+ pw.printf("Priority : %s%n", c.getThreadPriority());
+ pw.println();
+ }
+ if ( isEmpty ) {
+ pw.println("No active queues.");
+ pw.println();
+ }
+
+ for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
+ pw.printf("Topic Statistics - %s%n", ts.getTopic());
+ pw.printf("Last Activated : %s%n", formatDate(ts.getLastActivatedJobTime()));
+ pw.printf("Last Finished : %s%n", formatDate(ts.getLastFinishedJobTime()));
+ pw.printf("Finished Jobs : %s%n", ts.getNumberOfFinishedJobs());
+ pw.printf("Failed Jobs : %s%n", ts.getNumberOfFailedJobs());
+ pw.printf("Cancelled Jobs : %s%n", ts.getNumberOfCancelledJobs());
+ pw.printf("Processed Jobs : %s%n", ts.getNumberOfProcessedJobs());
+ pw.printf("Average Processing Time : %s%n", formatTime(ts.getAverageProcessingTime()));
+ pw.printf("Average Waiting Time : %s%n", formatTime(ts.getAverageWaitingTime()));
+ pw.println();
+ }
+
+ pw.println("Apache Sling Job Handling - Job Queue Configurations");
+ pw.println("----------------------------------------------------");
+ this.printQueueConfiguration(pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
+ final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
+ for(final InternalQueueConfiguration c : configs ) {
+ this.printQueueConfiguration(pw, c);
+ }
+ }
+
+ private void printQueueConfiguration(final PrintWriter pw, final InternalQueueConfiguration c) {
+ pw.printf("Job Queue Configuration: %s%n",
+ c.getName());
+ pw.printf("Valid : %s%n", c.isValid());
+ pw.printf("Type : %s%n", formatType(c.getType()));
+ pw.printf("Topics : %s%n", formatArrayAsText(c.getTopics()));
+ pw.printf("Max Parallel : %s%n", c.getMaxParallel());
+ pw.printf("Max Retries : %s%n", c.getMaxRetries());
+ pw.printf("Retry Delay : %s ms%n", c.getRetryDelayInMs());
+ pw.printf("Priority : %s%n", c.getThreadPriority());
+ pw.printf("Ranking : %s%n", c.getRanking());
+
+ pw.println();
+ }
+
+ private void printJson(final PrintWriter pw) {
+ pw.println("{");
+ Statistics s = this.jobManager.getStatistics();
+ pw.println(" \"statistics\" : {");
+ pw.printf(" \"startTime\" : %s,%n", s.getStartTime());
+ pw.printf(" \"startTimeText\" : \"%s\",%n", formatDate(s.getStartTime()));
+ pw.printf(" \"lastActivatedJobTime\" : %s,%n", s.getLastActivatedJobTime());
+ pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(s.getLastActivatedJobTime()));
+ pw.printf(" \"lastFinishedJobTime\" : %s,%n", s.getLastFinishedJobTime());
+ pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(s.getLastFinishedJobTime()));
+ pw.printf(" \"numberOfQueuedJobs\" : %s,%n", s.getNumberOfQueuedJobs());
+ pw.printf(" \"numberOfActiveJobs\" : %s,%n", s.getNumberOfActiveJobs());
+ pw.printf(" \"numberOfJobs\" : %s,%n", s.getNumberOfJobs());
+ pw.printf(" \"numberOfFinishedJobs\" : %s,%n", s.getNumberOfFinishedJobs());
+ pw.printf(" \"numberOfFailedJobs\" : %s,%n", s.getNumberOfFailedJobs());
+ pw.printf(" \"numberOfCancelledJobs\" : %s,%n", s.getNumberOfCancelledJobs());
+ pw.printf(" \"numberOfProcessedJobs\" : %s,%n", s.getNumberOfProcessedJobs());
+ pw.printf(" \"averageProcessingTime\" : %s,%n", s.getAverageProcessingTime());
+ pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(s.getAverageProcessingTime()));
+ pw.printf(" \"averageWaitingTime\" : %s,%n", s.getAverageWaitingTime());
+ pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(s.getAverageWaitingTime()));
+ pw.print(" }");
+
+ final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
+ if ( cap != null ) {
+ pw.println(",");
+ pw.println(" \"capabilities\" : [");
+ final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
+ final Iterator<Map.Entry<String, List<InstanceDescription>>> iter = instanceCaps.entrySet().iterator();
+ while ( iter.hasNext() ) {
+ final Map.Entry<String, List<InstanceDescription>> entry = iter.next();
+ final List<String> instances = new ArrayList<String>();
+ for(final InstanceDescription id : entry.getValue()) {
+ if ( id.isLocal() ) {
+ instances.add("local");
+ } else {
+ instances.add(id.getSlingId());
+ }
+ }
+ pw.println(" {");
+ pw.printf(" \"topic\" : \"%s\",%n", entry.getKey());
+ pw.printf(" \"instances\" : %s%n", formatArrayAsJson(instances.toArray(new String[instances.size()])));
+ if ( iter.hasNext() ) {
+ pw.println(" },");
+ } else {
+ pw.println(" }");
+ }
+ }
+ pw.print(" ]");
+ }
+
+ boolean first = true;
+ for(final Queue q : this.jobManager.getQueues()) {
+ pw.println(",");
+ if ( first ) {
+ pw.println(" \"queues\" : [");
+ first = false;
+ }
+ pw.println(" {");
+ pw.printf(" \"name\" : \"%s\",%n", q.getName());
+ pw.printf(" \"suspended\" : %s,%n", q.isSuspended());
+
+ s = q.getStatistics();
+ pw.println(" \"statistics\" : {");
+ pw.printf(" \"startTime\" : %s,%n", s.getStartTime());
+ pw.printf(" \"startTimeText\" : \"%s\",%n", formatDate(s.getStartTime()));
+ pw.printf(" \"lastActivatedJobTime\" : %s,%n", s.getLastActivatedJobTime());
+ pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(s.getLastActivatedJobTime()));
+ pw.printf(" \"lastFinishedJobTime\" : %s,%n", s.getLastFinishedJobTime());
+ pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(s.getLastFinishedJobTime()));
+ pw.printf(" \"numberOfQueuedJobs\" : %s,%n", s.getNumberOfQueuedJobs());
+ pw.printf(" \"numberOfActiveJobs\" : %s,%n", s.getNumberOfActiveJobs());
+ pw.printf(" \"numberOfJobs\" : %s,%n", s.getNumberOfJobs());
+ pw.printf(" \"numberOfFinishedJobs\" : %s,%n", s.getNumberOfFinishedJobs());
+ pw.printf(" \"numberOfFailedJobs\" : %s,%n", s.getNumberOfFailedJobs());
+ pw.printf(" \"numberOfCancelledJobs\" : %s,%n", s.getNumberOfCancelledJobs());
+ pw.printf(" \"numberOfProcessedJobs\" : %s,%n", s.getNumberOfProcessedJobs());
+ pw.printf(" \"averageProcessingTime\" : %s,%n", s.getAverageProcessingTime());
+ pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(s.getAverageProcessingTime()));
+ pw.printf(" \"averageWaitingTime\" : %s,%n", s.getAverageWaitingTime());
+ pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(s.getAverageWaitingTime()));
+ pw.print(" },");
+
+ final QueueConfiguration c = q.getConfiguration();
+ pw.printf(" \"stateInfo\" : \"%s\",%n", q.getStateInfo());
+ pw.println(" \"configuration\" : {");
+ pw.printf(" \"type\" : \"%s\",%n", c.getType());
+ pw.printf(" \"topics\" : \"%s\",%n", formatArrayAsJson(c.getTopics()));
+ pw.printf(" \"maxParallel\" : %s,%n", c.getMaxParallel());
+ pw.printf(" \"maxRetries\" : %s,%n", c.getMaxRetries());
+ pw.printf(" \"retryDelayInMs\" : %s,%n", c.getRetryDelayInMs());
+ pw.printf(" \"priority\" : \"%s\"%n", c.getThreadPriority());
+ pw.println(" }");
+ pw.print(" }");
+ }
+ if ( !first ) {
+ pw.print(" ]");
+ }
+
+ first = true;
+ for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
+ pw.println(",");
+ if ( first ) {
+ pw.println(" \"topicStatistics\" : [");
+ first = false;
+ }
+ pw.println(" {");
+ pw.printf(" \"topic\" : \"%s\",%n", ts.getTopic());
+ pw.printf(" \"lastActivatedJobTime\" : %s,%n", ts.getLastActivatedJobTime());
+ pw.printf(" \"lastActivatedJobTimeText\" : \"%s\",%n", formatDate(ts.getLastActivatedJobTime()));
+ pw.printf(" \"lastFinishedJobTime\" : %s,%n", ts.getLastFinishedJobTime());
+ pw.printf(" \"lastFinishedJobTimeText\" : \"%s\",%n", formatDate(ts.getLastFinishedJobTime()));
+ pw.printf(" \"numberOfFinishedJobs\" : %s,%n", ts.getNumberOfFinishedJobs());
+ pw.printf(" \"numberOfFailedJobs\" : %s,%n", ts.getNumberOfFailedJobs());
+ pw.printf(" \"numberOfCancelledJobs\" : %s,%n", ts.getNumberOfCancelledJobs());
+ pw.printf(" \"numberOfProcessedJobs\" : %s,%n", ts.getNumberOfProcessedJobs());
+ pw.printf(" \"averageProcessingTime\" : %s,%n", ts.getAverageProcessingTime());
+ pw.printf(" \"averageProcessingTimeText\" : \"%s\",%n", formatTime(ts.getAverageProcessingTime()));
+ pw.printf(" \"averageWaitingTime\" : %s,%n", ts.getAverageWaitingTime());
+ pw.printf(" \"averageWaitingTimeText\" : \"%s\"%n", formatTime(ts.getAverageWaitingTime()));
+ pw.print(" }");
+ }
+ if ( !first ) {
+ pw.print(" ]");
+ }
+
+ pw.println(",");
+ pw.println(" \"configurations\" : [");
+ this.printQueueConfigurationJson(pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
+ final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
+ for(final InternalQueueConfiguration c : configs ) {
+ pw.println(",");
+ this.printQueueConfigurationJson(pw, c);
+ }
+ pw.println();
+ pw.println(" ]");
+ pw.println("}");
+ }
+
+ private void printQueueConfigurationJson(final PrintWriter pw, final InternalQueueConfiguration c) {
+ pw.println(" {");
+ pw.printf(" \"name\" : \"%s\",%n", c.getName());
+ pw.printf(" \"valid\" : %s,%n", c.isValid());
+ pw.printf(" \"type\" : \"%s\",%n", c.getType());
+ pw.printf(" \"topics\" : %s,%n", formatArrayAsJson(c.getTopics()));
+ pw.printf(" \"maxParallel\" : %s,%n", c.getMaxParallel());
+ pw.printf(" \"maxRetries\" : %s,%n", c.getMaxRetries());
+ pw.printf(" \"retryDelayInMs\" : %s,%n", c.getRetryDelayInMs());
+ pw.printf(" \"priority\" : \"%s\",%n", c.getThreadPriority());
+ pw.printf(" \"ranking\" : %s%n", c.getRanking());
+ pw.print(" }");
+ }
+
+ /**
+ * Format an array.
+ */
+ private String formatArrayAsJson(final String[] array) {
+ if ( array == null || array.length == 0 ) {
+ return "[]";
+ }
+ final StringBuilder sb = new StringBuilder("[");
+ boolean first = true;
+ for(final String s : array) {
+ if ( !first ) {
+ sb.append(", ");
+ }
+ first = false;
+ sb.append("\"");
+ sb.append(s);
+ sb.append("\"");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java b/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
new file mode 100644
index 0000000..ffe94b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
@@ -0,0 +1,453 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.console;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.request.ResponseUtil;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.event.impl.jobs.JobConsumerManager;
+import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.QueueConfiguration;
+import org.apache.sling.event.jobs.ScheduleInfo;
+import org.apache.sling.event.jobs.ScheduledJobInfo;
+import org.apache.sling.event.jobs.Statistics;
+import org.apache.sling.event.jobs.TopicStatistics;
+import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a web console plugin displaying the active queues, some statistics
+ * and the configurations.
+ * @since 3.0
+ */
+@Component
+@Service(value={javax.servlet.Servlet.class, JobConsumer.class})
+@Properties({
+ @Property(name="felix.webconsole.label", value="slingevent"),
+ @Property(name="felix.webconsole.title", value="Jobs"),
+ @Property(name="felix.webconsole.category", value="Sling"),
+ @Property(name=JobConsumer.PROPERTY_TOPICS, value={"sling/webconsole/test"})
+})
+public class WebConsolePlugin extends HttpServlet implements JobConsumer {
+
+ private static final String SLING_WEBCONSOLE_TEST_JOB_TOPIC = "sling/webconsole/test";
+
+ private static final long serialVersionUID = -6983227434841706385L;
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Reference
+ private JobManager jobManager;
+
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ @Reference
+ private JobConsumerManager jobConsumerManager;
+
+ private static final String PAR_QUEUE = "queue";
+
+ private Queue getQueue(final HttpServletRequest req) {
+ final String name = req.getParameter(PAR_QUEUE);
+ if ( name != null ) {
+ for(final Queue q : this.jobManager.getQueues()) {
+ if ( name.equals(q.getName()) ) {
+ return q;
+ }
+ }
+ }
+ return null;
+ }
+
+ private String getQueueErrorMessage(final HttpServletRequest req, final String command) {
+ final String name = req.getParameter(PAR_QUEUE);
+ if ( name == null || name.length() == 0 ) {
+ return "Queue parameter missing for opertation " + command;
+ }
+ return "Queue with name '" + name + "' not found for operation " + command;
+ }
+
+ @Override
+ protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
+ throws ServletException, IOException {
+ String msg = null;
+ final String cmd = req.getParameter("action");
+ if ( "suspend".equals(cmd) ) {
+ final Queue q = this.getQueue(req);
+ if ( q != null ) {
+ q.suspend();
+ } else {
+ msg = this.getQueueErrorMessage(req, "suspend");
+ }
+ } else if ( "resume".equals(cmd) ) {
+ final Queue q = this.getQueue(req);
+ if ( q != null ) {
+ q.resume();
+ } else {
+ msg = this.getQueueErrorMessage(req, "resume");
+ }
+ } else if ( "reset".equals(cmd) ) {
+ if ( req.getParameter(PAR_QUEUE) == null || req.getParameter(PAR_QUEUE).length() == 0 ) {
+ this.jobManager.getStatistics().reset();
+ } else {
+ final Queue q = this.getQueue(req);
+ if ( q != null ) {
+ q.getStatistics().reset();
+ } else {
+ msg = this.getQueueErrorMessage(req, "reset");
+ }
+ }
+ } else if ( "test".equals(cmd) ) {
+ this.startTestJob();
+ } else if ( "dropall".equals(cmd) ) {
+ final Queue q = this.getQueue(req);
+ if ( q != null ) {
+ q.removeAll();
+ } else {
+ msg = this.getQueueErrorMessage(req, "drop all");
+ }
+ } else {
+ msg = "Unknown command";
+ }
+ final String path = req.getContextPath() + req.getServletPath() + req.getPathInfo();
+ final String redirectTo;
+ if ( msg == null ) {
+ redirectTo = path;
+ } else {
+ redirectTo = path + "?message=" + URLEncoder.encode(msg, "UTF-8");
+ }
+ resp.sendRedirect(resp.encodeRedirectURL(redirectTo));
+ }
+
+ private void startTestJob() {
+ logger.info("Adding test job: {}", SLING_WEBCONSOLE_TEST_JOB_TOPIC);
+ this.jobManager.addJob(SLING_WEBCONSOLE_TEST_JOB_TOPIC, null);
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse res)
+ throws ServletException, IOException {
+ final String msg = req.getParameter("message");
+ final PrintWriter pw = res.getWriter();
+
+ pw.println("<form method='POST' name='eventingcmd'>" +
+ "<input type='hidden' name='action' value=''/>"+
+ "<input type='hidden' name='queue' value=''/>" +
+ "</form>");
+ pw.println("<script type='text/javascript'>");
+ pw.println("function eventingsubmit(action, queue) {" +
+ " document.forms['eventingcmd'].action.value = action;" +
+ " document.forms['eventingcmd'].queue.value = queue;" +
+ " document.forms['eventingcmd'].submit();" +
+ "} </script>");
+
+ pw.printf("<p class='statline ui-state-highlight'>Apache Sling Job Handling%s%n</p>",
+ msg != null ? " : " + ResponseUtil.escapeXml(msg) : "");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+ pw.println("<span style='float: left; margin-left: 1em'>Apache Sling Job Handling: Overall Statistics</span>");
+ this.printForm(pw, null, "Reset Stats", "reset");
+ pw.println("</div>");
+
+ pw.println("<table class='nicetable'><tbody>");
+ String topics = this.jobConsumerManager.getTopics();
+ if ( topics == null ) {
+ topics = "";
+ } else {
+ final String[] allTopics = topics.split(",");
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for(final String t : allTopics) {
+ if ( first) {
+ first = false;
+ } else {
+ sb.append("<br/>");
+ }
+ sb.append(ResponseUtil.escapeXml(t));
+ }
+ topics = sb.toString();
+ }
+ Statistics s = this.jobManager.getStatistics();
+ pw.printf("<tr><td>Start Time</td><td>%s</td></tr>", formatDate(s.getStartTime()));
+ pw.printf("<tr><td>Local topic consumers: </td><td>%s</td></tr>", topics);
+ pw.printf("<tr><td>Last Activated</td><td>%s</td></tr>", formatDate(s.getLastActivatedJobTime()));
+ pw.printf("<tr><td>Last Finished</td><td>%s</td></tr>", formatDate(s.getLastFinishedJobTime()));
+ pw.printf("<tr><td>Queued Jobs</td><td>%s</td></tr>", s.getNumberOfQueuedJobs());
+ pw.printf("<tr><td>Active Jobs</td><td>%s</td></tr>", s.getNumberOfActiveJobs());
+ pw.printf("<tr><td>Jobs</td><td>%s</td></tr>", s.getNumberOfJobs());
+ pw.printf("<tr><td>Finished Jobs</td><td>%s</td></tr>", s.getNumberOfFinishedJobs());
+ pw.printf("<tr><td>Failed Jobs</td><td>%s</td></tr>", s.getNumberOfFailedJobs());
+ pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td></tr>", s.getNumberOfCancelledJobs());
+ pw.printf("<tr><td>Processed Jobs</td><td>%s</td></tr>", s.getNumberOfProcessedJobs());
+ pw.printf("<tr><td>Average Processing Time</td><td>%s</td></tr>", formatTime(s.getAverageProcessingTime()));
+ pw.printf("<tr><td>Average Waiting Time</td><td>%s</td></tr>", formatTime(s.getAverageWaitingTime()));
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+
+ pw.println("<table class='nicetable'><tbody>");
+ pw.println("<tr><th colspan='2'>Topology Capabilities</th></tr>");
+ final TopologyCapabilities cap = this.configuration.getTopologyCapabilities();
+ if ( cap == null ) {
+ pw.print("<tr><td colspan='2'>No topology information available !</td></tr>");
+ } else {
+ final Map<String, List<InstanceDescription>> instanceCaps = cap.getInstanceCapabilities();
+ for(final Map.Entry<String, List<InstanceDescription>> entry : instanceCaps.entrySet()) {
+ final StringBuilder sb = new StringBuilder();
+ for(final InstanceDescription id : entry.getValue()) {
+ if ( sb.length() > 0 ) {
+ sb.append("<br/>");
+ }
+ if ( id.isLocal() ) {
+ sb.append("<b>local</b>");
+ } else {
+ sb.append(ResponseUtil.escapeXml(id.getSlingId()));
+ }
+ }
+ pw.printf("<tr><td>%s</td><td>%s</td></tr>", ResponseUtil.escapeXml(entry.getKey()), sb.toString());
+ }
+ }
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+
+ pw.println("<p class='statline'>Scheduled Jobs</p>");
+ pw.println("<table class='nicetable'><tbody>");
+ final Collection<ScheduledJobInfo> infos = this.jobManager.getScheduledJobs();
+ if ( infos.size() == 0 ) {
+ pw.print("<tr><td colspan='5'>No jobs currently scheduled.</td></tr>");
+ } else {
+ pw.println("<tr><th>Schedule</th><th>Job Topic</th><th>Schedules</th></tr>");
+ int index = 1;
+ for(final ScheduledJobInfo info : infos) {
+ pw.printf("<tr><td><b>%s</b></td><td>%s</td><td>",
+ String.valueOf(index), ResponseUtil.escapeXml(info.getJobTopic()));
+ boolean first = true;
+ for(final ScheduleInfo si : info.getSchedules() ) {
+ if ( !first ) {
+ pw.print("<br/>");
+ }
+ first = false;
+ switch ( si.getType() ) {
+ case YEARLY : pw.printf("YEARLY %s %s : %s:%s", si.getMonthOfYear(), si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case MONTHLY : pw.printf("MONTHLY %s : %s:%s", si.getDayOfMonth(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case WEEKLY : pw.printf("WEEKLY %s : %s:%s", si.getDayOfWeek(), si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case DAILY : pw.printf("DAILY %s:%s", si.getHourOfDay(), si.getMinuteOfHour());
+ break;
+ case HOURLY : pw.printf("HOURLY %s", si.getMinuteOfHour());
+ break;
+ case CRON : pw.printf("CRON %s", ResponseUtil.escapeXml(si.getExpression()));
+ break;
+ default : pw.printf("AT %s", si.getAt());
+ }
+ }
+ pw.print("</td></tr>");
+ index++;
+ }
+ }
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+
+ boolean isEmpty = true;
+ for(final Queue q : this.jobManager.getQueues()) {
+ isEmpty = false;
+ final String queueName = q.getName();
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+ pw.printf("<span style='float: left; margin-left: 1em'>Active JobQueue: %s %s</span>", ResponseUtil.escapeXml(queueName),
+ q.isSuspended() ? "(SUSPENDED)" : "");
+ this.printForm(pw, queueName, "Reset Stats", "reset");
+ if ( q.isSuspended() ) {
+ this.printForm(pw, queueName, "Resume", "resume");
+ } else {
+ this.printForm(pw, queueName, "Suspend", "suspend");
+ }
+ this.printForm(pw, queueName, "Test", "test");
+ this.printForm(pw, queueName, "Drop All", "dropall");
+ pw.println("</div>");
+ pw.println("<table class='nicetable'><tbody>");
+
+ s = q.getStatistics();
+ final QueueConfiguration c = q.getConfiguration();
+ pw.println("<tr><th colspan='2'>Statistics</th><th colspan='2'>Configuration</th></tr>");
+ pw.printf("<tr><td>Start Time</td><td>%s</td><td>Type</td><td>%s</td></tr>", formatDate(s.getStartTime()), formatType(c.getType()));
+ pw.printf("<tr><td>Last Activated</td><td>%s</td><td>Topics</td><td>%s</td></tr>", formatDate(s.getLastActivatedJobTime()), formatArray(c.getTopics()));
+ pw.printf("<tr><td>Last Finished</td><td>%s</td><td>Max Parallel</td><td>%s</td></tr>", formatDate(s.getLastFinishedJobTime()), c.getMaxParallel());
+ pw.printf("<tr><td>Queued Jobs</td><td>%s</td><td>Max Retries</td><td>%s</td></tr>", s.getNumberOfQueuedJobs(), c.getMaxRetries());
+ pw.printf("<tr><td>Active Jobs</td><td>%s</td><td>Retry Delay</td><td>%s ms</td></tr>", s.getNumberOfActiveJobs(), c.getRetryDelayInMs());
+ pw.printf("<tr><td>Jobs</td><td>%s</td><td>Priority</td><td>%s</td></tr>", s.getNumberOfJobs(), c.getThreadPriority());
+ pw.printf("<tr><td>Finished Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfFinishedJobs());
+ pw.printf("<tr><td>Failed Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfFailedJobs());
+ pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfCancelledJobs());
+ pw.printf("<tr><td>Processed Jobs</td><td>%s</td><td colspan='2'> </td></tr>", s.getNumberOfProcessedJobs());
+ pw.printf("<tr><td>Average Processing Time</td><td>%s</td><td colspan='2'> </td></tr>", formatTime(s.getAverageProcessingTime()));
+ pw.printf("<tr><td>Average Waiting Time</td><td>%s</td><td colspan='2'> </td></tr>", formatTime(s.getAverageWaitingTime()));
+ pw.printf("<tr><td>Status Info</td><td colspan='3'>%s</td></tr>", ResponseUtil.escapeXml(q.getStateInfo()));
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+ }
+ if ( isEmpty ) {
+ pw.println("<p>No active queues.</p>");
+ pw.println("<br/>");
+ }
+
+ for(final TopicStatistics ts : this.jobManager.getTopicStatistics()) {
+ pw.println("<table class='nicetable'><tbody>");
+ pw.printf("<tr><th colspan='2'>Topic Statistics: %s</th></tr>", ResponseUtil.escapeXml(ts.getTopic()));
+
+ pw.printf("<tr><td>Last Activated</td><td>%s</td></tr>", formatDate(ts.getLastActivatedJobTime()));
+ pw.printf("<tr><td>Last Finished</td><td>%s</td></tr>", formatDate(ts.getLastFinishedJobTime()));
+ pw.printf("<tr><td>Finished Jobs</td><td>%s</td></tr>", ts.getNumberOfFinishedJobs());
+ pw.printf("<tr><td>Failed Jobs</td><td>%s</td></tr>", ts.getNumberOfFailedJobs());
+ pw.printf("<tr><td>Cancelled Jobs</td><td>%s</td></tr>", ts.getNumberOfCancelledJobs());
+ pw.printf("<tr><td>Processed Jobs</td><td>%s</td></tr>", ts.getNumberOfProcessedJobs());
+ pw.printf("<tr><td>Average Processing Time</td><td>%s</td></tr>", formatTime(ts.getAverageProcessingTime()));
+ pw.printf("<tr><td>Average Waiting Time</td><td>%s</td></tr>", formatTime(ts.getAverageWaitingTime()));
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+ }
+
+ pw.println("<p class='statline'>Apache Sling Job Handling - Job Queue Configurations</p>");
+ this.printQueueConfiguration(req, pw, this.configuration.getQueueConfigurationManager().getMainQueueConfiguration());
+ final InternalQueueConfiguration[] configs = this.configuration.getQueueConfigurationManager().getConfigurations();
+ for(final InternalQueueConfiguration c : configs ) {
+ this.printQueueConfiguration(req, pw, c);
+ }
+ }
+
+ private void printQueueConfiguration(final HttpServletRequest req, final PrintWriter pw, final InternalQueueConfiguration c) {
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+ pw.printf("<span style='float: left; margin-left: 1em'>Job Queue Configuration: %s</span>%n",
+ ResponseUtil.escapeXml(c.getName()));
+ pw.printf("<button id='edit' class='ui-state-default ui-corner-all' onclick='javascript:window.location=\"%s%s/configMgr/%s\";'>Edit</button>",
+ req.getContextPath(), req.getServletPath(), c.getPid());
+ this.printForm(pw, c.getName(), "Test", "test");
+
+ pw.println("</div>");
+ pw.println("<table class='nicetable'><tbody>");
+ pw.println("<tr><th colspan='2'>Configuration</th></tr>");
+ pw.printf("<tr><td>Valid</td><td>%s</td></tr>", c.isValid());
+ pw.printf("<tr><td>Type</td><td>%s</td></tr>", formatType(c.getType()));
+ pw.printf("<tr><td>Topics</td><td>%s</td></tr>", formatArray(c.getTopics()));
+ pw.printf("<tr><td>Max Parallel</td><td>%s</td></tr>", c.getMaxParallel());
+ pw.printf("<tr><td>Max Retries</td><td>%s</td></tr>", c.getMaxRetries());
+ pw.printf("<tr><td>Retry Delay</td><td>%s ms</td></tr>", c.getRetryDelayInMs());
+ pw.printf("<tr><td>Priority</td><td>%s</td></tr>", c.getThreadPriority());
+ pw.printf("<tr><td>Ranking</td><td>%s</td></tr>", c.getRanking());
+
+ pw.println("</tbody></table>");
+ pw.println("<br/>");
+ }
+
+ /**
+ * Format an array for html rendering.
+ */
+ private String formatArray(final String[] array) {
+ if ( array == null || array.length == 0 ) {
+ return "";
+ }
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for(final String s : array ) {
+ if ( !first ) {
+ sb.append('\n');
+ }
+ first = false;
+ sb.append(s);
+ }
+ return ResponseUtil.escapeXml(sb.toString());
+ }
+
+ private String formatType(final QueueConfiguration.Type type) {
+ switch ( type ) {
+ case ORDERED : return "Ordered";
+ case TOPIC_ROUND_ROBIN : return "Topic Round Robin";
+ case UNORDERED : return "Parallel";
+ }
+ return type.toString();
+ }
+ /** Default date format used. */
+ private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS yyyy-MMM-dd");
+
+ /**
+ * Format a date
+ */
+ private synchronized String formatDate(final long time) {
+ if ( time == -1 ) {
+ return "-";
+ }
+ final Date d = new Date(time);
+ return dateFormat.format(d);
+ }
+
+ /**
+ * Format time (= duration)
+ */
+ private String formatTime(final long time) {
+ if ( time == 0 ) {
+ return "-";
+ }
+ if ( time < 1000 ) {
+ return time + " ms";
+ } else if ( time < 1000 * 60 ) {
+ return time / 1000 + " secs";
+ }
+ final long min = time / 1000 / 60;
+ final long secs = (time - min * 1000 * 60);
+ return min + " min " + secs / 1000 + " secs";
+ }
+
+ private void printForm(final PrintWriter pw,
+ final String qeueName,
+ final String buttonLabel,
+ final String cmd) {
+ pw.printf("<button class='ui-state-default ui-corner-all' onclick='javascript:eventingsubmit(\"%s\", \"%s\");'>" +
+ "%s</button>", ResponseUtil.escapeXml(cmd), (qeueName != null ? ResponseUtil.escapeXml(qeueName) : ""), ResponseUtil.escapeXml(buttonLabel));
+ }
+
+ @Override
+ public JobResult process(final Job job) {
+ logger.info("Received test job {}", job.getTopic());
+ return JobResult.OK;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java
new file mode 100644
index 0000000..8bf4033
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AbstractJobStatistics.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import java.util.Date;
+
+import javax.management.StandardMBean;
+
+import org.apache.sling.event.jobs.Statistics;
+import org.apache.sling.event.jobs.jmx.StatisticsMBean;
+
+public abstract class AbstractJobStatistics extends StandardMBean implements
+ StatisticsMBean {
+
+ public AbstractJobStatistics() {
+ super(StatisticsMBean.class, false);
+ }
+
+ protected abstract Statistics getStatistics();
+
+ public long getAverageProcessingTime() {
+ return getStatistics().getAverageProcessingTime();
+ }
+
+ public long getAverageWaitingTime() {
+ return getStatistics().getAverageWaitingTime();
+ }
+
+ public long getLastActivatedJobTime() {
+ return getStatistics().getLastActivatedJobTime();
+ }
+
+ public long getLastFinishedJobTime() {
+ return getStatistics().getLastFinishedJobTime();
+ }
+
+ public long getNumberOfActiveJobs() {
+ return getStatistics().getNumberOfActiveJobs();
+ }
+
+ public long getNumberOfCancelledJobs() {
+ return getStatistics().getNumberOfCancelledJobs();
+ }
+
+ public long getStartTime() {
+ return getStatistics().getStartTime();
+ }
+
+ public Date getStartDate() {
+ return new Date(getStartTime());
+ }
+
+ public long getNumberOfFinishedJobs() {
+ return getStatistics().getNumberOfFinishedJobs();
+ }
+
+ public long getNumberOfFailedJobs() {
+ return getStatistics().getNumberOfFailedJobs();
+ }
+
+ public long getNumberOfProcessedJobs() {
+ return getStatistics().getNumberOfProcessedJobs();
+ }
+
+ public long getNumberOfQueuedJobs() {
+ return getStatistics().getNumberOfQueuedJobs();
+ }
+
+ public long getNumberOfJobs() {
+ return getStatistics().getNumberOfJobs();
+ }
+
+ public void reset() {
+ getStatistics().reset();
+ }
+
+ public Date getLastActivatedJobDate() {
+ return new Date(getStatistics().getLastActivatedJobTime());
+ }
+
+ public Date getLastFinishedJobDate() {
+ return new Date(getStatistics().getLastFinishedJobTime());
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java
new file mode 100644
index 0000000..0e696d6
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/AllJobStatisticsMBean.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.Statistics;
+import org.apache.sling.event.jobs.jmx.StatisticsMBean;
+
+@Component
+@Service(value = StatisticsMBean.class)
+@Properties(@Property(name = "jmx.objectname", value = "org.apache.sling:type=queues,name=AllQueues"))
+public class AllJobStatisticsMBean extends AbstractJobStatistics {
+
+ private static final long TTL = 1000L;
+ private long agregateStatisticsTTL = 0L;
+ private Statistics aggregateStatistics;
+ @Reference
+ private JobManager jobManager;
+
+ /**
+ * @return the aggregate stats from the job manager.
+ */
+ @Override
+ protected Statistics getStatistics() {
+ if (System.currentTimeMillis() > agregateStatisticsTTL) {
+ aggregateStatistics = jobManager.getStatistics();
+ agregateStatisticsTTL = System.currentTimeMillis() + TTL;
+ }
+ return aggregateStatistics;
+ }
+
+ @Override
+ public String getName() {
+ return "All Queues";
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java
new file mode 100644
index 0000000..35772ba
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/EmptyStatistics.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import org.apache.sling.event.jobs.Statistics;
+
+/**
+ * Dummy stats that just returns 0 for all info, used where the queue doesnt
+ * implement the Statistics interface.
+ */
+public class EmptyStatistics implements Statistics {
+
+ public long getStartTime() {
+ return 0;
+ }
+
+ public long getNumberOfFinishedJobs() {
+ return 0;
+ }
+
+ public long getNumberOfCancelledJobs() {
+ return 0;
+ }
+
+ public long getNumberOfFailedJobs() {
+ return 0;
+ }
+
+ public long getNumberOfProcessedJobs() {
+ return 0;
+ }
+
+ public long getNumberOfActiveJobs() {
+ return 0;
+ }
+
+ public long getNumberOfQueuedJobs() {
+ return 0;
+ }
+
+ public long getNumberOfJobs() {
+ return 0;
+ }
+
+ public long getLastActivatedJobTime() {
+ return 0;
+ }
+
+ public long getLastFinishedJobTime() {
+ return 0;
+ }
+
+ public long getAverageWaitingTime() {
+ return 0;
+ }
+
+ public long getAverageProcessingTime() {
+ return 0;
+ }
+
+ public void reset() {
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java
new file mode 100644
index 0000000..615ac90
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueMBeanImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.Statistics;
+
+/**
+ * An MBean that provides statistics from
+ */
+public class QueueMBeanImpl extends AbstractJobStatistics {
+
+ private final String name;
+
+ private final Statistics statistics;
+
+ public QueueMBeanImpl(Queue queue) {
+ this.name = queue.getName();
+ if (queue instanceof Statistics) {
+ this.statistics = (Statistics) queue;
+ } else {
+ this.statistics = new EmptyStatistics();
+ }
+ }
+
+ @Override
+ protected Statistics getStatistics() {
+ return this.statistics;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java
new file mode 100644
index 0000000..dca3e33
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueueStatusEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import org.apache.sling.event.jobs.Queue;
+
+public class QueueStatusEvent {
+
+ private final Queue queue;
+ private final Queue oldqueue;
+
+
+ public QueueStatusEvent(final Queue queue, final Queue oldqueue) {
+ this.queue = queue;
+ this.oldqueue = oldqueue;
+ }
+ public boolean isNew() {
+ return this.oldqueue == null;
+ }
+
+ public boolean isUpdate() {
+ return this.queue == this.oldqueue;
+ }
+
+ public boolean isRemoved() {
+ return this.queue == null;
+ }
+
+ public Queue getQueue() {
+ return queue;
+ }
+
+ public Queue getOldQueue() {
+ return oldqueue;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java
new file mode 100644
index 0000000..83a56e9
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/jmx/QueuesMBeanImpl.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.event.impl.jobs.jmx;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.AttributeChangeNotification;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.StandardEmitterMBean;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.jmx.QueuesMBean;
+import org.apache.sling.event.jobs.jmx.StatisticsMBean;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+@Component
+@Service(value = { QueuesMBean.class })
+@Property(name = "jmx.objectname", value = "org.apache.sling:type=queues,name=QueueNames")
+public class QueuesMBeanImpl extends StandardEmitterMBean implements QueuesMBean {
+
+ private static final String QUEUE_NOTIFICATION = "org.apache.sling.event.queue";
+ private static final String[] NOTIFICATION_TYPES = { QUEUE_NOTIFICATION };
+ private Map<String, QueueMBeanHolder> queues = new ConcurrentHashMap<String, QueueMBeanHolder>();
+ private String[] names;
+ private AtomicLong sequence = new AtomicLong(System.currentTimeMillis());
+ private BundleContext bundleContext;
+
+ class QueueMBeanHolder {
+
+ QueueMBeanHolder(String name, QueueMBeanImpl queueMBean,
+ ServiceRegistration registration) {
+ this.name = name;
+ this.queueMBean = queueMBean;
+ this.registration = registration;
+ }
+
+ QueueMBeanImpl queueMBean;
+ ServiceRegistration registration;
+ String name;
+
+ }
+
+ public QueuesMBeanImpl() {
+ super(QueuesMBean.class, false, new NotificationBroadcasterSupport(
+ new MBeanNotificationInfo(NOTIFICATION_TYPES,
+ Notification.class.getName(),
+ "Notifications about queues")));
+ }
+
+ @Activate
+ public void activate(final BundleContext bc) {
+ bundleContext = bc;
+ }
+
+ @Deactivate
+ public void deactivate() {
+ bundleContext = null;
+ }
+
+ public void sendEvent(final QueueStatusEvent e) {
+ if (e.isNew()) {
+ bindQueueMBean(e);
+ } else if (e.isRemoved()) {
+ unbindQueueMBean(e);
+ } else {
+ updateQueueMBean(e);
+ }
+ }
+
+ private void updateQueueMBean(QueueStatusEvent e) {
+ QueueMBeanHolder queueMBeanHolder = queues.get(e.getQueue().getName());
+ if (queueMBeanHolder != null) {
+ String[] oldQueue = getQueueNames();
+ names = null;
+ this.sendNotification(new AttributeChangeNotification(this,
+ sequence.incrementAndGet(), System.currentTimeMillis(),
+ "Queue " + e.getQueue().getName() + " updated ",
+ "queueNames", "String[]", oldQueue, getQueueNames()));
+ }
+ }
+
+ private void unbindQueueMBean(QueueStatusEvent e) {
+ QueueMBeanHolder queueMBeanHolder = queues.get(e.getOldQueue().getName());
+ if (queueMBeanHolder != null) {
+ removeAndNotify(queueMBeanHolder);
+ }
+ }
+
+ private void bindQueueMBean(QueueStatusEvent e) {
+ QueueMBeanHolder queueMBeanHolder = queues.get(e.getQueue().getName());
+ if (queueMBeanHolder != null) {
+ removeAndNotify(queueMBeanHolder);
+ }
+ addAndNotify(e.getQueue());
+ }
+
+ private void addAndNotify(Queue queue) {
+ String[] oldQueue = getQueueNames();
+ QueueMBeanHolder queueMBeanHolder = add(queue);
+ names = null;
+ this.sendNotification(new AttributeChangeNotification(this, sequence
+ .incrementAndGet(), System.currentTimeMillis(), "Queue "
+ + queueMBeanHolder.name + " added ", "queueNames", "String[]",
+ oldQueue, getQueueNames()));
+ }
+
+ private void removeAndNotify(QueueMBeanHolder queueMBeanHolder) {
+ String[] oldQueue = getQueueNames();
+ remove(queueMBeanHolder);
+ names = null;
+ this.sendNotification(new AttributeChangeNotification(this, sequence
+ .incrementAndGet(), System.currentTimeMillis(), "Queue "
+ + queueMBeanHolder.name + " removed ", "queueNames",
+ "String[]", oldQueue, getQueueNames()));
+ }
+
+ private QueueMBeanHolder add(Queue queue) {
+ QueueMBeanImpl queueMBean = new QueueMBeanImpl(queue);
+ ServiceRegistration serviceRegistration = bundleContext
+ .registerService(StatisticsMBean.class.getName(), queueMBean,
+ createProperties(
+ "jmx.objectname","org.apache.sling:type=queues,name="+queue.getName(),
+ Constants.SERVICE_DESCRIPTION, "QueueMBean for queue "+queue.getName(),
+ Constants.SERVICE_VENDOR, "The Apache Software Foundation"));
+ QueueMBeanHolder queueMBeanHolder = new QueueMBeanHolder(
+ queue.getName(), queueMBean, serviceRegistration);
+ queues.put(queueMBeanHolder.name, queueMBeanHolder);
+ return queueMBeanHolder;
+ }
+
+ private Dictionary<String, Object> createProperties(Object ... values) {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ for ( int i = 0; i < values.length; i+=2) {
+ props.put((String) values[i], values[i+1]);
+ }
+ return props;
+ }
+
+ private void remove(QueueMBeanHolder queueMBeanHolder) {
+ queueMBeanHolder.registration.unregister();
+ queues.remove(queueMBeanHolder.name);
+ }
+
+ @Override
+ public String[] getQueueNames() {
+ if (names == null) {
+ List<String> lnames = new ArrayList<String>(queues.keySet());
+ Collections.sort(lnames);
+ names = lnames.toArray(new String[lnames.size()]);
+ }
+ return names;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java
new file mode 100644
index 0000000..590026f
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NewJobSender.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.notifications;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This component receives resource added events and sends a job
+ * created event.
+ */
+@Component
+public class NewJobSender implements ResourceChangeListener, ExternalResourceChangeListener {
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** The job manager configuration. */
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ /** The event admin. */
+ @Reference
+ private EventAdmin eventAdmin;
+
+ /** Service registration for the event handler. */
+ private volatile ServiceRegistration<ResourceChangeListener> listenerRegistration;
+
+ /**
+ * Activate this component.
+ * Register an event handler.
+ */
+ @Activate
+ protected void activate(final BundleContext bundleContext) {
+ final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ properties.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Job Topic Manager Event Handler");
+ properties.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+ properties.put(ResourceChangeListener.CHANGES, ChangeType.ADDED.toString());
+ properties.put(ResourceChangeListener.PATHS, this.configuration.getLocalJobsPath());
+
+ this.listenerRegistration = bundleContext.registerService(ResourceChangeListener.class, this, properties);
+ }
+
+ /**
+ * Deactivate this component.
+ * Unregister the event handler.
+ */
+ @Deactivate
+ protected void deactivate() {
+ if ( this.listenerRegistration != null ) {
+ this.listenerRegistration.unregister();
+ this.listenerRegistration = null;
+ }
+ }
+
+ @Override
+ public void onChange(final List<ResourceChange> resourceChanges) {
+ for(final ResourceChange resourceChange : resourceChanges) {
+ logger.debug("Received event {}", resourceChange);
+
+ final String path = resourceChange.getPath();
+
+ final int topicStart = this.configuration.getLocalJobsPath().length() + 1;
+ final int topicEnd = path.indexOf('/', topicStart);
+ if ( topicEnd != -1 ) {
+ final String topic = path.substring(topicStart, topicEnd).replace('.', '/');
+ final String jobId = path.substring(topicEnd + 1);
+
+ if ( path.indexOf("_", topicEnd + 1) != -1 ) {
+ // only job id and topic are guaranteed
+ final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, jobId);
+ properties.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC, topic);
+
+ // we also set internally the queue name
+ final String queueName = this.configuration.getQueueConfigurationManager().getQueueInfo(topic).queueName;
+ properties.put(Job.PROPERTY_JOB_QUEUE_NAME, queueName);
+
+ final Event jobEvent = new Event(NotificationConstants.TOPIC_JOB_ADDED, properties);
+ // as this is send within handling an event, we do sync call
+ this.eventAdmin.sendEvent(jobEvent);
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java
new file mode 100644
index 0000000..d45d818
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/notifications/NotificationUtility.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.notifications;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.sling.event.impl.jobs.JobImpl;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+public abstract class NotificationUtility {
+
+ /** Event property containing the time for job start and job finished events. */
+ public static final String PROPERTY_TIME = ":time";
+
+ /**
+ * Helper method for sending the notification events.
+ */
+ public static void sendNotification(final EventAdmin eventAdmin,
+ final String eventTopic,
+ final Job job,
+ final Long time) {
+ if ( eventAdmin != null ) {
+ // create new copy of job object
+ final Job jobCopy = new JobImpl(job.getTopic(), job.getId(), ((JobImpl)job).getProperties());
+ sendNotificationInternal(eventAdmin, eventTopic, jobCopy, time);
+ }
+ }
+
+ /**
+ * Helper method for sending the notification events.
+ */
+ private static void sendNotificationInternal(final EventAdmin eventAdmin,
+ final String eventTopic,
+ final Job job,
+ final Long time) {
+ final Dictionary<String, Object> eventProps = new Hashtable<String, Object>();
+ // add basic job properties
+ eventProps.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_ID, job.getId());
+ eventProps.put(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC, job.getTopic());
+ // copy payload
+ for(final String name : job.getPropertyNames()) {
+ eventProps.put(name, job.getProperty(name));
+ }
+ // remove async handler
+ eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER);
+ // add timestamp
+ eventProps.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+ // add internal time information
+ if ( time != null ) {
+ eventProps.put(PROPERTY_TIME, time);
+ }
+ eventAdmin.postEvent(new Event(eventTopic, eventProps));
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java
new file mode 100644
index 0000000..d2c2286
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionContextImpl.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sling.event.impl.jobs.JobHandler;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.consumer.JobExecutionContext;
+import org.apache.sling.event.jobs.consumer.JobExecutionResult;
+
+/**
+ * Implementation of the job execution context passed to
+ * job executors.
+ */
+public class JobExecutionContextImpl implements JobExecutionContext {
+
+ /**
+ * Call back interface to the queue.
+ */
+ public interface ASyncHandler {
+ void finished(Job.JobState state);
+ }
+
+ /**
+ * Boolean to check whether init progress has been called.
+ */
+ private final AtomicBoolean initProgressIsCalled = new AtomicBoolean(false);
+
+ /**
+ * Flag to indicate whether this is async processing
+ */
+ private final AtomicBoolean isAsync = new AtomicBoolean(false);
+
+ private final ASyncHandler asyncHandler;
+
+ private final JobHandler handler;
+
+ public JobExecutionContextImpl(final JobHandler handler,
+ final ASyncHandler asyncHandler) {
+ this.handler = handler;
+ this.asyncHandler = asyncHandler;
+ }
+
+ public void markAsync() {
+ this.isAsync.set(true);
+ }
+
+ @Override
+ public void initProgress(final int steps,
+ final long eta) {
+ if ( initProgressIsCalled.compareAndSet(false, true) ) {
+ handler.persistJobProperties(handler.getJob().startProgress(steps, eta));
+ }
+ }
+
+ @Override
+ public void incrementProgressCount(final int steps) {
+ if ( initProgressIsCalled.get() ) {
+ handler.persistJobProperties(handler.getJob().setProgress(steps));
+ }
+ }
+
+ @Override
+ public void updateProgress(final long eta) {
+ if ( initProgressIsCalled.get() ) {
+ handler.persistJobProperties(handler.getJob().update(eta));
+ }
+ }
+
+ @Override
+ public void log(final String message, Object... args) {
+ handler.persistJobProperties(handler.getJob().log(message, args));
+ }
+
+ @Override
+ public boolean isStopped() {
+ return handler.isStopped();
+ }
+
+ @Override
+ public void asyncProcessingFinished(final JobExecutionResult result) {
+ synchronized ( this ) {
+ if ( isAsync.compareAndSet(true, false) ) {
+ Job.JobState state = null;
+ if ( result.succeeded() ) {
+ state = Job.JobState.SUCCEEDED;
+ } else if ( result.failed() ) {
+ state = Job.JobState.QUEUED;
+ } else if ( result.cancelled() ) {
+ if ( handler.isStopped() ) {
+ state = Job.JobState.STOPPED;
+ } else {
+ state = Job.JobState.ERROR;
+ }
+ }
+ asyncHandler.finished(state);
+ } else {
+ throw new IllegalStateException("Job is not processed async or is already finished: " + handler.getJob().getId());
+ }
+ }
+ }
+
+ @Override
+ public ResultBuilder result() {
+ return new ResultBuilderImpl();
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java
new file mode 100644
index 0000000..28083e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobExecutionResultImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import org.apache.sling.event.impl.jobs.InternalJobState;
+import org.apache.sling.event.jobs.consumer.JobExecutionResult;
+
+/**
+ * The job execution result.
+ */
+public class JobExecutionResultImpl implements JobExecutionResult {
+
+ /** Constant object for the success case. */
+ public static final JobExecutionResultImpl SUCCEEDED = new JobExecutionResultImpl(InternalJobState.SUCCEEDED, null, null);
+ /** Constant object for the cancelled case. */
+ public static final JobExecutionResultImpl CANCELLED = new JobExecutionResultImpl(InternalJobState.CANCELLED, null, null);
+ /** Constant object for the failed case. */
+ public static final JobExecutionResultImpl FAILED = new JobExecutionResultImpl(InternalJobState.FAILED, null, null);
+
+ /** The state of the execution. */
+ private final InternalJobState state;
+
+ /** Optional message. */
+ private final String message;
+
+ /** Optional retry delay. */
+ private final Long retryDelayInMs;
+
+ /**
+ * Create a new result
+ * @param state The result state
+ * @param message Optional Message
+ * @param retryDelayInMs Optional retry delay
+ */
+ public JobExecutionResultImpl(final InternalJobState state,
+ final String message,
+ final Long retryDelayInMs) {
+ this.state = state;
+ this.message = message;
+ this.retryDelayInMs = retryDelayInMs;
+ }
+
+ /**
+ * Get the internal state
+ * @return The state.
+ */
+ public InternalJobState getState() {
+ return this.state;
+ }
+
+ @Override
+ public boolean succeeded() {
+ return this.state == InternalJobState.SUCCEEDED;
+ }
+
+ @Override
+ public boolean cancelled() {
+ return this.state == InternalJobState.CANCELLED;
+ }
+
+ @Override
+ public boolean failed() {
+ return this.state == InternalJobState.FAILED;
+ }
+
+ @Override
+ public String getMessage() {
+ return this.message;
+ }
+
+ @Override
+ public Long getRetryDelayInMs() {
+ return this.retryDelayInMs;
+ }
+
+ @Override
+ public String toString() {
+ return "JobExecutionResultImpl [state=" + state + ", message="
+ + message + ", retryDelayInMs=" + retryDelayInMs + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java
new file mode 100644
index 0000000..0ab1a6b
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/JobQueueImpl.java
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.event.impl.EventingThreadPool;
+import org.apache.sling.event.impl.jobs.InternalJobState;
+import org.apache.sling.event.impl.jobs.JobHandler;
+import org.apache.sling.event.impl.jobs.JobImpl;
+import org.apache.sling.event.impl.jobs.JobTopicTraverser;
+import org.apache.sling.event.impl.jobs.Utility;
+import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
+import org.apache.sling.event.impl.jobs.notifications.NotificationUtility;
+import org.apache.sling.event.impl.support.BatchResourceRemover;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.Job.JobState;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.QueueConfiguration.Type;
+import org.apache.sling.event.jobs.Statistics;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The job blocking queue extends the blocking queue by some
+ * functionality for the job event handling.
+ */
+public class JobQueueImpl
+ implements Queue {
+
+ /** Default timeout for suspend. */
+ private static final long MAX_SUSPEND_TIME = 1000 * 60 * 60; // 60 mins
+
+ /** The logger. */
+ private final Logger logger;
+
+ /** Configuration. */
+ private final InternalQueueConfiguration configuration;
+
+ /** The queue name. */
+ private volatile String queueName;
+
+ /** Are we still running? */
+ private volatile boolean running;
+
+ /** Suspended since. */
+ private final AtomicLong suspendedSince = new AtomicLong(-1);
+
+ /** Services used by the queues. */
+ private final QueueServices services;
+
+ /** The map of events we're processing. */
+ private final Map<String, JobHandler> processingJobsLists = new HashMap<String, JobHandler>();
+
+ private final ThreadPool threadPool;
+
+ /** Async counter. */
+ private final AtomicInteger asyncCounter = new AtomicInteger();
+
+ /** Flag for outdated. */
+ private final AtomicBoolean isOutdated = new AtomicBoolean(false);
+
+ /** A marker for closing the queue. */
+ private final AtomicBoolean closeMarker = new AtomicBoolean(false);
+
+ /** A marker for doing a full cache search. */
+ private final AtomicBoolean doFullCacheSearch = new AtomicBoolean(false);
+
+ /** A counter for rescheduling. */
+ private final AtomicInteger waitCounter = new AtomicInteger();
+
+ /** The job cache. */
+ private final QueueJobCache cache;
+
+ /** Semaphore for handling the max number of jobs. */
+ private final Semaphore available;
+
+ /** Guard for having only one thread executing start jobs. */
+ private final AtomicBoolean startJobsGuard = new AtomicBoolean(false);
+
+ /** Lock for close/start. */
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+ /** Sleeping until is only set for ordered queues if a job is rescheduled. */
+ private volatile long isSleepingUntil = -1;
+
+ /**
+ * Create a new queue.
+ *
+ * @param name The queue name
+ * @param config The queue configuration
+ * @param services The queue services
+ * @param topics The topics handled by this queue
+ *
+ * @return {@code JobQueueImpl} if there are jobs to process, {@code null} otherwise.
+ */
+ public static JobQueueImpl createQueue(final String name,
+ final InternalQueueConfiguration config,
+ final QueueServices services,
+ final Set<String> topics) {
+ final QueueJobCache cache = new QueueJobCache(services.configuration, name, services.statisticsManager, config.getType(), topics);
+ if ( cache.isEmpty() ) {
+ return null;
+ }
+ return new JobQueueImpl(name, config, services, cache);
+ }
+
+ /**
+ * Create a new queue.
+ *
+ * @param name The queue name
+ * @param config The queue configuration
+ * @param services The queue services
+ * @param cache The job cache
+ */
+ private JobQueueImpl(final String name,
+ final InternalQueueConfiguration config,
+ final QueueServices services,
+ final QueueJobCache cache) {
+ if ( config.getOwnThreadPoolSize() > 0 ) {
+ this.threadPool = new EventingThreadPool(services.threadPoolManager, config.getOwnThreadPoolSize());
+ } else {
+ this.threadPool = services.eventingThreadPool;
+ }
+ this.queueName = name;
+ this.configuration = config;
+ this.services = services;
+ this.logger = LoggerFactory.getLogger(this.getClass().getName() + '.' + name);
+ this.running = true;
+ this.cache = cache;
+ this.available = new Semaphore(config.getMaxParallel(), true);
+ logger.info("Starting job queue {}", queueName);
+ logger.debug("Configuration for job queue={}", configuration);
+ }
+
+ /**
+ * Return the queue configuration
+ */
+ @Override
+ public InternalQueueConfiguration getConfiguration() {
+ return this.configuration;
+ }
+
+ /**
+ * Get the name of the job queue.
+ */
+ @Override
+ public String getName() {
+ return this.queueName;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#getStatistics()
+ */
+ @Override
+ public Statistics getStatistics() {
+ return this.services.statisticsManager.getQueueStatistics(this.queueName);
+ }
+
+ /**
+ * Start the job queue.
+ * This method might be called concurrently, therefore we use a guard
+ */
+ public void startJobs() {
+ if ( this.startJobsGuard.compareAndSet(false, true) ) {
+ // we start as many jobs in parallel as possible
+ while ( this.running && !this.isOutdated.get() && !this.isSuspended() && this.available.tryAcquire() ) {
+ boolean started = false;
+ this.lock.writeLock().lock();
+ try {
+ final JobHandler handler = this.cache.getNextJob(this.services.jobConsumerManager,
+ this.services.statisticsManager, this, this.doFullCacheSearch.getAndSet(false));
+ if ( handler != null ) {
+ started = true;
+ this.threadPool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ // update thread priority and name
+ final Thread currentThread = Thread.currentThread();
+ final String oldName = currentThread.getName();
+ final int oldPriority = currentThread.getPriority();
+
+ currentThread.setName(oldName + "-" + handler.getJob().getQueueName() + "(" + handler.getJob().getTopic() + ")");
+ if ( configuration.getThreadPriority() != null ) {
+ switch ( configuration.getThreadPriority() ) {
+ case NORM : currentThread.setPriority(Thread.NORM_PRIORITY);
+ break;
+ case MIN : currentThread.setPriority(Thread.MIN_PRIORITY);
+ break;
+ case MAX : currentThread.setPriority(Thread.MAX_PRIORITY);
+ break;
+ }
+ }
+
+ try {
+ startJob(handler);
+ } finally {
+ currentThread.setPriority(oldPriority);
+ currentThread.setName(oldName);
+ }
+ // and try to launch another job
+ startJobs();
+ }
+ });
+ } else {
+ // no job available, stop look
+ break;
+ }
+
+ } finally {
+ if ( !started ) {
+ this.available.release();
+ }
+ this.lock.writeLock().unlock();
+ }
+ }
+ this.startJobsGuard.set(false);
+ }
+ }
+
+ private void startJob(final JobHandler handler) {
+ try {
+ this.closeMarker.set(false);
+ try {
+ final JobImpl job = handler.getJob();
+ handler.started = System.currentTimeMillis();
+
+ this.services.configuration.getAuditLogger().debug("START OK : {}", job.getId());
+ // sanity check for the queued property
+ Calendar queued = job.getProperty(JobImpl.PROPERTY_JOB_QUEUED, Calendar.class);
+ if ( queued == null ) {
+ // we simply use a date of ten seconds ago
+ queued = Calendar.getInstance();
+ queued.setTimeInMillis(System.currentTimeMillis() - 10000);
+ }
+ final long queueTime = handler.started - queued.getTimeInMillis();
+ // update statistics
+ this.services.statisticsManager.jobStarted(this.queueName, job.getTopic(), queueTime);
+ // send notification
+ NotificationUtility.sendNotification(this.services.eventAdmin, NotificationConstants.TOPIC_JOB_STARTED, job, queueTime);
+
+ synchronized ( this.processingJobsLists ) {
+ this.processingJobsLists.put(job.getId(), handler);
+ }
+
+ JobExecutionResultImpl result = JobExecutionResultImpl.CANCELLED;
+ Job.JobState resultState = Job.JobState.ERROR;
+ final JobExecutionContextImpl ctx = new JobExecutionContextImpl(handler, new JobExecutionContextImpl.ASyncHandler() {
+
+ @Override
+ public void finished(final JobState state) {
+ services.jobConsumerManager.unregisterListener(job.getId());
+ finishedJob(job.getId(), state, true);
+ asyncCounter.decrementAndGet();
+ }
+ });
+
+ try {
+ synchronized ( ctx ) {
+ result = (JobExecutionResultImpl)handler.getConsumer().process(job, ctx);
+ if ( result == null ) { // ASYNC processing
+ services.jobConsumerManager.registerListener(job.getId(), handler.getConsumer(), ctx);
+ asyncCounter.incrementAndGet();
+ ctx.markAsync();
+ } else {
+ if ( result.succeeded() ) {
+ resultState = Job.JobState.SUCCEEDED;
+ } else if ( result.failed() ) {
+ resultState = Job.JobState.QUEUED;
+ } else if ( result.cancelled() ) {
+ if ( handler.isStopped() ) {
+ resultState = Job.JobState.STOPPED;
+ } else {
+ resultState = Job.JobState.ERROR;
+ }
+ }
+ }
+ }
+ } catch (final Throwable t) { //NOSONAR
+ logger.error("Unhandled error occured in job processor " + t.getMessage() + " while processing job " + Utility.toString(job), t);
+ // we don't reschedule if an exception occurs
+ result = JobExecutionResultImpl.CANCELLED;
+ resultState = Job.JobState.ERROR;
+ } finally {
+ if ( result != null ) {
+ if ( result.getRetryDelayInMs() != null ) {
+ job.setProperty(JobImpl.PROPERTY_DELAY_OVERRIDE, result.getRetryDelayInMs());
+ }
+ if ( result.getMessage() != null ) {
+ job.setProperty(Job.PROPERTY_RESULT_MESSAGE, result.getMessage());
+ }
+ this.finishedJob(job.getId(), resultState, false);
+ }
+ }
+ } catch (final Exception re) {
+ // if an exception occurs, we just log
+ this.logger.error("Exception during job processing.", re);
+ }
+ } finally {
+ this.available.release();
+ }
+ }
+
+ /**
+ * Outdate this queue.
+ */
+ public void outdate() {
+ if ( this.isOutdated.compareAndSet(false, true) ) {
+ final String name = this.getName() + "<outdated>(" + this.hashCode() + ")";
+ this.logger.info("Outdating queue {}, renaming to {}.", this.queueName, name);
+ this.queueName = name;
+ }
+ }
+
+ /**
+ * Check if the queue can be closed
+ */
+ public boolean tryToClose() {
+ // resume the queue as we want to close it!
+ this.resume();
+ this.lock.writeLock().lock();
+ try {
+ // check if possible
+ if ( this.canBeClosed() ) {
+ if ( this.closeMarker.get() ) {
+ this.close();
+ return true;
+ }
+ this.closeMarker.set(true);
+ }
+ } finally {
+ this.lock.writeLock().unlock();
+ }
+ return false;
+ }
+
+ /**
+ * Check whether this queue can be closed
+ */
+ private boolean canBeClosed() {
+ return !this.isSuspended()
+ && this.asyncCounter.get() == 0
+ && this.waitCounter.get() == 0
+ && this.available.availablePermits() == this.configuration.getMaxParallel();
+ }
+
+ /**
+ * Close this queue.
+ */
+ public void close() {
+ this.running = false;
+ this.logger.debug("Shutting down job queue {}", queueName);
+ this.resume();
+
+ synchronized ( this.processingJobsLists ) {
+ this.processingJobsLists.clear();
+ }
+ if ( this.configuration.getOwnThreadPoolSize() > 0 ) {
+ ((EventingThreadPool)this.threadPool).release();
+ }
+
+ this.logger.info("Stopped job queue {}", this.queueName);
+ }
+
+ /**
+ * Periodic maintenance
+ */
+ public void maintain() {
+ // check suspended
+ final long since = this.suspendedSince.get();
+ if ( since != -1 && since + MAX_SUSPEND_TIME < System.currentTimeMillis() ) {
+ logger.info("Waking up suspended queue. It has been suspended for more than {}ms", MAX_SUSPEND_TIME);
+ this.resume();
+ }
+
+ // set full cache search
+ this.doFullCacheSearch.set(true);
+
+ this.startJobs();
+ }
+
+ /**
+ * Inform the queue about new job for the given topics.
+ * @param topics the new topics
+ */
+ public void wakeUpQueue(final Set<String> topics) {
+ this.cache.handleNewTopics(topics);
+ }
+
+ /**
+ * Put a job back in the queue
+ * @param handler The job handler
+ */
+ private void requeue(final JobHandler handler) {
+ this.cache.reschedule(this.queueName, handler, this.services.statisticsManager);
+ this.startJobs();
+ }
+
+ private static final class RescheduleInfo {
+ public boolean reschedule = false;
+ // processing time is only set of state is SUCCEEDED
+ public long processingTime;
+ public Job.JobState state;
+ public InternalJobState finalState;
+ }
+
+ private RescheduleInfo handleReschedule(final JobHandler handler, final Job.JobState resultState) {
+ final RescheduleInfo info = new RescheduleInfo();
+ info.state = resultState;
+ switch ( resultState ) {
+ case SUCCEEDED : // job is finished
+ if ( this.logger.isDebugEnabled() ) {
+ this.logger.debug("Finished job {}", Utility.toString(handler.getJob()));
+ }
+ info.processingTime = System.currentTimeMillis() - handler.started;
+ info.finalState = InternalJobState.SUCCEEDED;
+ break;
+ case QUEUED : // check if we exceeded the number of retries
+ final int retries = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRIES, 0);
+ int retryCount = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_COUNT, 0);
+
+ retryCount++;
+ if ( retries != -1 && retryCount > retries ) {
+ if ( this.logger.isDebugEnabled() ) {
+ this.logger.debug("Cancelled job {}", Utility.toString(handler.getJob()));
+ }
+ info.finalState = InternalJobState.CANCELLED;
+ } else {
+ info.reschedule = true;
+ handler.getJob().retry();
+ if ( this.logger.isDebugEnabled() ) {
+ this.logger.debug("Failed job {}", Utility.toString(handler.getJob()));
+ }
+ info.finalState = InternalJobState.FAILED;
+ }
+ break;
+ default : // consumer cancelled the job (STOPPED, GIVEN_UP, ERROR)
+ if ( this.logger.isDebugEnabled() ) {
+ this.logger.debug("Cancelled job {}", Utility.toString(handler.getJob()));
+ }
+ info.finalState = InternalJobState.CANCELLED;
+ break;
+ }
+
+ if ( info.state == Job.JobState.QUEUED && !info.reschedule ) {
+ info.state = Job.JobState.GIVEN_UP;
+ }
+ return info;
+ }
+
+ /**
+ * Handle job finish and determine whether to reschedule or cancel the job
+ */
+ private boolean finishedJob(final String jobId,
+ final Job.JobState resultState,
+ final boolean isAsync) {
+ this.services.configuration.getAuditLogger().debug("FINISHED {} : {}", resultState, jobId);
+ this.logger.debug("Received finish for job {}, resultState={}", jobId, resultState);
+
+ // get job handler
+ final JobHandler handler;
+ // let's remove the event from our processing list
+ synchronized ( this.processingJobsLists ) {
+ handler = this.processingJobsLists.remove(jobId);
+ }
+
+ if ( !this.running ) {
+ this.logger.warn("Queue is not running anymore. Discarding finish for {}", jobId);
+ return false;
+ }
+
+ if ( handler == null ) {
+ this.logger.warn("This job has never been started by this queue: {}", jobId);
+ return false;
+ }
+
+ // handle the rescheduling of the job
+ final RescheduleInfo rescheduleInfo = this.handleReschedule(handler, resultState);
+
+ if ( !rescheduleInfo.reschedule ) {
+ // we keep cancelled jobs and succeeded jobs if the queue is configured like this.
+ final boolean keepJobs = rescheduleInfo.state != Job.JobState.SUCCEEDED || this.configuration.isKeepJobs();
+ handler.finished(rescheduleInfo.state, keepJobs, rescheduleInfo.processingTime);
+ } else {
+ this.reschedule(handler);
+ }
+ // update statistics
+ this.services.statisticsManager.jobEnded(this.queueName,
+ handler.getJob().getTopic(),
+ rescheduleInfo.finalState,
+ rescheduleInfo.processingTime);
+ // send notification
+ NotificationUtility.sendNotification(this.services.eventAdmin,
+ rescheduleInfo.finalState.getTopic(),
+ handler.getJob(), rescheduleInfo.processingTime);
+
+ return rescheduleInfo.reschedule;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#resume()
+ */
+ @Override
+ public void resume() {
+ if ( this.suspendedSince.getAndSet(-1) != -1 ) {
+ this.logger.debug("Waking up suspended queue {}", queueName);
+ this.startJobs();
+ }
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#suspend()
+ */
+ @Override
+ public void suspend() {
+ if ( this.suspendedSince.compareAndSet(-1, System.currentTimeMillis()) ) {
+ this.logger.debug("Suspending queue {}", queueName);
+ }
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#isSuspended()
+ */
+ @Override
+ public boolean isSuspended() {
+ return this.suspendedSince.get() != -1;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#removeAll()
+ */
+ @Override
+ public synchronized void removeAll() {
+ final Set<String> topics = this.cache.getTopics();
+ logger.debug("Removing all jobs for queue {} : {}", queueName, topics);
+
+ if ( !topics.isEmpty() ) {
+
+ final ResourceResolver resolver = this.services.configuration.createResourceResolver();
+ try {
+ final Resource baseResource = resolver.getResource(this.services.configuration.getLocalJobsPath());
+
+ // sanity check - should never be null
+ if ( baseResource != null ) {
+ final BatchResourceRemover brr = new BatchResourceRemover();
+
+ for(final String t : topics) {
+ final Resource topicResource = baseResource.getChild(t.replace('/', '.'));
+ if ( topicResource != null ) {
+ JobTopicTraverser.traverse(logger, topicResource, new JobTopicTraverser.JobCallback() {
+
+ @Override
+ public boolean handle(final JobImpl job) {
+ final Resource jobResource = topicResource.getResourceResolver().getResource(job.getResourcePath());
+ // sanity check
+ if ( jobResource != null ) {
+ try {
+ brr.delete(jobResource);
+ } catch ( final PersistenceException ignore) {
+ logger.error("Unable to remove job " + job, ignore);
+ topicResource.getResourceResolver().revert();
+ topicResource.getResourceResolver().refresh();
+ }
+ }
+ return true;
+ }
+ });
+ }
+ }
+ try {
+ resolver.commit();
+ } catch ( final PersistenceException ignore) {
+ logger.error("Unable to remove jobs", ignore);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#getState(java.lang.String)
+ */
+ @Override
+ public Object getState(final String key) {
+ if ( this.configuration.getType() == Type.ORDERED ) {
+ if ( "isSleepingUntil".equals(key) ) {
+ return this.isSleepingUntil;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see org.apache.sling.event.jobs.Queue#getStateInfo()
+ */
+ @Override
+ public String getStateInfo() {
+ return "outdated=" + this.isOutdated.get() +
+ ", suspendedSince=" + this.suspendedSince.get() +
+ ", asyncJobs=" + this.asyncCounter.get() +
+ ", waitCount=" + this.waitCounter.get() +
+ ", jobCount=" + String.valueOf(this.configuration.getMaxParallel() - this.available.availablePermits() +
+ (this.configuration.getType() == Type.ORDERED ? ", isSleepingUntil=" + this.isSleepingUntil : ""));
+ }
+
+ /**
+ * Get the retry delay for a job.
+ * @param handler The job handler.
+ * @return The retry delay
+ */
+ private long getRetryDelay(final JobHandler handler) {
+ long delay = this.configuration.getRetryDelayInMs();
+ if ( handler.getJob().getProperty(JobImpl.PROPERTY_DELAY_OVERRIDE) != null ) {
+ delay = handler.getJob().getProperty(JobImpl.PROPERTY_DELAY_OVERRIDE, Long.class);
+ } else if ( handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_DELAY) != null ) {
+ delay = handler.getJob().getProperty(Job.PROPERTY_JOB_RETRY_DELAY, Long.class);
+ }
+ return delay;
+ }
+
+ public boolean stopJob(final JobImpl job) {
+ final JobHandler handler;
+ synchronized ( this.processingJobsLists ) {
+ handler = this.processingJobsLists.get(job.getId());
+ }
+ if ( handler != null ) {
+ handler.stop();
+ }
+ return handler != null;
+ }
+
+ private void reschedule(final JobHandler handler) {
+ // we delay putting back the job until the retry delay is over
+ final long delay = this.getRetryDelay(handler);
+ if ( delay > 0 ) {
+ if ( this.configuration.getType() == Type.ORDERED ) {
+ this.cache.setIsBlocked(true);
+ }
+ handler.addToRetryList();
+ final Date fireDate = new Date();
+ fireDate.setTime(System.currentTimeMillis() + delay);
+ if ( this.configuration.getType() == Type.ORDERED ) {
+ this.isSleepingUntil = fireDate.getTime();
+ }
+
+ final String jobName = "Waiting:" + queueName + ":" + handler.hashCode();
+ final Runnable t = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if ( handler.removeFromRetryList() ) {
+ requeue(handler);
+ }
+ waitCounter.decrementAndGet();
+ } finally {
+ if ( configuration.getType() == Type.ORDERED ) {
+ isSleepingUntil = -1;
+ cache.setIsBlocked(false);
+ startJobs();
+ }
+ }
+ }
+ };
+ this.waitCounter.incrementAndGet();
+ if ( !services.scheduler.schedule(t, services.scheduler.AT(fireDate).name(jobName)) ) {
+ // if scheduling fails run the thread directly
+ t.run();
+ }
+ } else {
+ // put directly into queue
+ this.requeue(handler);
+ }
+ }
+}
+
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java
new file mode 100644
index 0000000..ebcc92d
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueJobCache.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.event.impl.jobs.JobConsumerManager;
+import org.apache.sling.event.impl.jobs.JobHandler;
+import org.apache.sling.event.impl.jobs.JobImpl;
+import org.apache.sling.event.impl.jobs.JobTopicTraverser;
+import org.apache.sling.event.impl.jobs.Utility;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
+import org.apache.sling.event.jobs.Job.JobState;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.QueueConfiguration;
+import org.apache.sling.event.jobs.QueueConfiguration.Type;
+import org.apache.sling.event.jobs.consumer.JobExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The queue job cache caches jobs per queue based on the topics the queue is actively
+ * processing.
+ */
+public class QueueJobCache {
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** The maximum of pre loaded jobs for a topic. */
+ private final int maxPreloadLimit = 10;
+
+ /** The job manager configuration. */
+ private final JobManagerConfiguration configuration;
+
+ /** The set of topics handled by this queue. */
+ private final Set<String> topics;
+
+ /** The set of new topics to scan. */
+ private final Set<String> topicsWithNewJobs = new HashSet<String>();
+
+ /** The cache of current objects. */
+ private final List<JobImpl> cache = new ArrayList<JobImpl>();
+
+ /** The queue type. */
+ private final QueueConfiguration.Type queueType;
+
+ /** Block the cache - for ordered queues only. */
+ private final AtomicBoolean queueIsBlocked = new AtomicBoolean(false);
+
+ /**
+ * Create a new queue job cache
+ * @param configuration Current job manager configuration
+ * @param queueName The queue name
+ * @param statisticsManager The statistics manager
+ * @param queueType The queue type
+ * @param topics The topics handled by this queue.
+ */
+ public QueueJobCache(final JobManagerConfiguration configuration,
+ final String queueName,
+ final StatisticsManager statisticsManager,
+ final QueueConfiguration.Type queueType,
+ final Set<String> topics) {
+ this.configuration = configuration;
+ this.queueType = queueType;
+ this.topics = new ConcurrentSkipListSet<String>(topics);
+ this.fillCache(queueName, statisticsManager);
+ }
+
+ /**
+ * All topics of this queue.
+ * @return The topics.
+ */
+ public Set<String> getTopics() {
+ return this.topics;
+ }
+
+ /**
+ * Check whether there are jobs for this queue
+ * @return {@code true} if there is any job outstanding.
+ */
+ public boolean isEmpty() {
+ boolean result = true;
+ synchronized ( this.cache ) {
+ result = this.cache.isEmpty();
+ }
+ if ( result ) {
+ synchronized ( this.topicsWithNewJobs ) {
+ result = this.topicsWithNewJobs.isEmpty();
+ }
+ }
+ return result;
+ }
+
+ public void setIsBlocked(final boolean value) {
+ this.queueIsBlocked.set(value);
+ }
+
+ /**
+ * Fill the cache.
+ * No need to sync as this is called from the constructor.
+ */
+ private void fillCache(final String queueName, final StatisticsManager statisticsManager) {
+ final Set<String> checkingTopics = new HashSet<String>();
+ checkingTopics.addAll(this.topics);
+ if ( !checkingTopics.isEmpty() ) {
+ this.loadJobs(queueName, checkingTopics, statisticsManager);
+ }
+ }
+
+ /**
+ * Get the next job.
+ * This method is potentially called concurrently, and
+ * {@link #reschedule(String, JobHandler, StatisticsManager)} and {@link #handleNewTopics(Set)}
+ * can be called concurrently.
+ * @param jobConsumerManager The job consumer manager
+ * @param statisticsManager The statistics manager
+ * @param queue The queue
+ * @param doFull Whether to do a full scan
+ * @return The job handler or {@code null}.
+ */
+ public JobHandler getNextJob(final JobConsumerManager jobConsumerManager,
+ final StatisticsManager statisticsManager,
+ final Queue queue,
+ final boolean doFull) {
+ JobHandler handler = null;
+
+ if ( !this.queueIsBlocked.get() ) {
+ synchronized ( this.cache ) {
+ boolean retry;
+ do {
+ retry = false;
+ if ( this.cache.isEmpty() ) {
+ final Set<String> checkingTopics = new HashSet<String>();
+ synchronized ( this.topicsWithNewJobs ) {
+ checkingTopics.addAll(this.topicsWithNewJobs);
+ this.topicsWithNewJobs.clear();
+ }
+ if ( doFull ) {
+ checkingTopics.addAll(this.topics);
+ }
+ if ( !checkingTopics.isEmpty() ) {
+ this.loadJobs(queue.getName(), checkingTopics, statisticsManager);
+ }
+ }
+
+ if ( !this.cache.isEmpty() ) {
+ final JobImpl job = this.cache.remove(0);
+ final JobExecutor consumer = jobConsumerManager.getExecutor(job.getTopic());
+
+ handler = new JobHandler(job, consumer, this.configuration);
+ if ( consumer != null ) {
+ if ( !handler.startProcessing(queue) ) {
+ statisticsManager.jobDequeued(queue.getName(), handler.getJob().getTopic());
+ if ( logger.isDebugEnabled() ) {
+ logger.debug("Discarding removed job {}", Utility.toString(job));
+ }
+ handler = null;
+ retry = true;
+ }
+ } else {
+ statisticsManager.jobDequeued(queue.getName(), handler.getJob().getTopic());
+ // no consumer on this instance, assign to another instance
+ handler.reassign();
+
+ handler = null;
+ retry = true;
+ }
+
+ }
+ } while ( handler == null && retry);
+ }
+ }
+ return handler;
+ }
+
+ /**
+ * Load the next N x numberOf(topics) jobs
+ * @param checkingTopics The set of topics to check.
+ */
+ private void loadJobs( final String queueName, final Set<String> checkingTopics,
+ final StatisticsManager statisticsManager) {
+ logger.debug("Starting jobs loading from {}...", checkingTopics);
+
+ final Map<String, List<JobImpl>> topicCache = new HashMap<String, List<JobImpl>>();
+
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource baseResource = resolver.getResource(this.configuration.getLocalJobsPath());
+ // sanity check - should never be null
+ if ( baseResource != null ) {
+ for(final String topic : checkingTopics) {
+
+ final Resource topicResource = baseResource.getChild(topic.replace('/', '.'));
+ if ( topicResource != null ) {
+ topicCache.put(topic, loadJobs(queueName, topic, topicResource, statisticsManager));
+ }
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ orderTopics(topicCache);
+
+ logger.debug("Finished jobs loading {}", this.cache.size());
+ }
+
+ /**
+ * Order the topics based on the queue type and put them in the cache.
+ * @param topicCache The topic based cache
+ */
+ private void orderTopics(final Map<String, List<JobImpl>> topicCache) {
+ if ( this.queueType == Type.ORDERED
+ || this.queueType == Type.UNORDERED) {
+ for(final List<JobImpl> list : topicCache.values()) {
+ this.cache.addAll(list);
+ }
+ Collections.sort(this.cache);
+ } else {
+ // topic round robin
+ boolean done = true;
+ do {
+ done = true;
+ for(final Map.Entry<String, List<JobImpl>> entry : topicCache.entrySet()) {
+ if ( !entry.getValue().isEmpty() ) {
+ this.cache.add(entry.getValue().remove(0));
+ if ( !entry.getValue().isEmpty() ) {
+ done = false;
+ }
+ }
+ }
+ } while ( !done ) ;
+ }
+ }
+
+ /**
+ * Load the next N x numberOf(topics) jobs.
+ * @param topic The topic
+ * @param topicResource The parent resource of the jobs
+ * @return The cache which will be filled with the jobs.
+ */
+ private List<JobImpl> loadJobs(final String queueName, final String topic,
+ final Resource topicResource,
+ final StatisticsManager statisticsManager) {
+ logger.debug("Loading jobs from topic {}", topic);
+ final List<JobImpl> list = new ArrayList<JobImpl>();
+
+ final AtomicBoolean scanTopic = new AtomicBoolean(false);
+
+ JobTopicTraverser.traverse(logger, topicResource, new JobTopicTraverser.JobCallback() {
+
+ @Override
+ public boolean handle(final JobImpl job) {
+ if ( job.getProcessingStarted() == null && !job.hasReadErrors() ) {
+ list.add(job);
+ statisticsManager.jobQueued(queueName, topic);
+ if ( list.size() == maxPreloadLimit ) {
+ scanTopic.set(true);
+ }
+ } else if ( job.getProcessingStarted() != null ) {
+ logger.debug("Ignoring job {} - processing already started.", job);
+ } else {
+ // error reading job
+ scanTopic.set(true);
+ if ( job.isReadErrorRecoverable() ) {
+ logger.debug("Ignoring job {} due to recoverable read errors.", job);
+ } else {
+ logger.debug("Failing job {} due to unrecoverable read errors.", job);
+ final JobHandler handler = new JobHandler(job, null, configuration);
+ handler.finished(JobState.ERROR, true, null);
+ }
+ }
+ return list.size() < maxPreloadLimit;
+ }
+ });
+ if ( scanTopic.get() ) {
+ synchronized ( this.topicsWithNewJobs ) {
+ this.topicsWithNewJobs.add(topic);
+ }
+ }
+ logger.debug("Caching {} jobs for topic {}", list.size(), topic);
+
+ return list;
+ }
+
+ /**
+ * Inform the queue cache about topics containing new jobs
+ * @param topics The set of topics to scan
+ */
+ public void handleNewTopics(final Set<String> topics) {
+ logger.debug("Update cache to handle new event for topics {}", topics);
+ synchronized ( this.topicsWithNewJobs ) {
+ this.topicsWithNewJobs.addAll(topics);
+ }
+ this.topics.addAll(topics);
+ }
+
+ /**
+ * Reschedule a job
+ * Reschedule the job and add it back into the cache.
+ * @param queueName The queue name
+ * @param handler The job handler
+ * @param statisticsManager The statistics manager
+ */
+ public void reschedule(final String queueName, final JobHandler handler, final StatisticsManager statisticsManager) {
+ synchronized ( this.cache ) {
+ if ( handler.reschedule() ) {
+ if ( this.queueType == Type.ORDERED ) {
+ this.cache.add(0, handler.getJob());
+ } else {
+ this.cache.add(handler.getJob());
+ }
+ statisticsManager.jobQueued(queueName, handler.getJob().getTopic());
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java
new file mode 100644
index 0000000..fd7fcf8
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueManager.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+import org.apache.sling.event.impl.EventingThreadPool;
+import org.apache.sling.event.impl.jobs.JobConsumerManager;
+import org.apache.sling.event.impl.jobs.JobHandler;
+import org.apache.sling.event.impl.jobs.JobImpl;
+import org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener;
+import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager;
+import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager.QueueInfo;
+import org.apache.sling.event.impl.jobs.jmx.QueueStatusEvent;
+import org.apache.sling.event.impl.jobs.jmx.QueuesMBeanImpl;
+import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.NotificationConstants;
+import org.apache.sling.event.jobs.Queue;
+import org.apache.sling.event.jobs.jmx.QueuesMBean;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Implementation of the queue manager.
+ */
+@Component(immediate=true)
+@Service(value={Runnable.class, QueueManager.class, EventHandler.class})
+@Properties({
+ @Property(name=Scheduler.PROPERTY_SCHEDULER_PERIOD, longValue=60),
+ @Property(name=Scheduler.PROPERTY_SCHEDULER_CONCURRENT, boolValue=false),
+ @Property(name=EventConstants.EVENT_TOPIC, value=NotificationConstants.TOPIC_JOB_ADDED)
+})
+public class QueueManager
+ implements Runnable, EventHandler, ConfigurationChangeListener {
+
+ /** Default logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Reference
+ private EventAdmin eventAdmin;
+
+ @Reference
+ private Scheduler scheduler;
+
+ @Reference
+ private JobConsumerManager jobConsumerManager;
+
+ @Reference
+ private QueuesMBean queuesMBean;
+
+ @Reference
+ private ThreadPoolManager threadPoolManager;
+
+ /**
+ * Our thread pool.
+ */
+ @Reference(referenceInterface=EventingThreadPool.class)
+ private ThreadPool threadPool;
+
+ /** The job manager configuration. */
+ @Reference
+ private JobManagerConfiguration configuration;
+
+ @Reference
+ private StatisticsManager statisticsManager;
+
+ /** Lock object for the queues map - we don't want to sync directly on the concurrent map. */
+ private final Object queuesLock = new Object();
+
+ /** All active queues. */
+ private final Map<String, JobQueueImpl> queues = new ConcurrentHashMap<String, JobQueueImpl>();
+
+ /** We count the scheduler runs. */
+ private volatile long schedulerRuns;
+
+ /** Flag whether the manager is active or suspended. */
+ private final AtomicBoolean isActive = new AtomicBoolean(false);
+
+ /** The queue services. */
+ private volatile QueueServices queueServices;
+
+ /**
+ * Activate this component.
+ * @param props Configuration properties
+ */
+ @Activate
+ protected void activate(final Map<String, Object> props) {
+ logger.info("Apache Sling Queue Manager starting on instance {}", Environment.APPLICATION_ID);
+ this.queueServices = new QueueServices();
+ queueServices.configuration = this.configuration;
+ queueServices.eventAdmin = this.eventAdmin;
+ queueServices.jobConsumerManager = this.jobConsumerManager;
+ queueServices.scheduler = this.scheduler;
+ queueServices.threadPoolManager = this.threadPoolManager;
+ queueServices.statisticsManager = statisticsManager;
+ queueServices.eventingThreadPool = this.threadPool;
+ this.configuration.addListener(this);
+ logger.info("Apache Sling Queue Manager started on instance {}", Environment.APPLICATION_ID);
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ protected void deactivate() {
+ logger.debug("Apache Sling Queue Manager stopping on instance {}", Environment.APPLICATION_ID);
+
+ this.configuration.removeListener(this);
+ final Iterator<JobQueueImpl> i = this.queues.values().iterator();
+ while ( i.hasNext() ) {
+ final JobQueueImpl jbq = i.next();
+ jbq.close();
+ // update mbeans
+ ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, jbq));
+ }
+ this.queues.clear();
+ this.queueServices = null;
+ logger.info("Apache Sling Queue Manager stopped on instance {}", Environment.APPLICATION_ID);
+ }
+
+ /**
+ * This method is invoked periodically by the scheduler.
+ * It searches for idle queues and stops them after a timeout. If a queue
+ * is idle for two consecutive clean up calls, it is removed.
+ * @see java.lang.Runnable#run()
+ */
+ private void maintain() {
+ this.schedulerRuns++;
+ logger.debug("Queue manager maintenance: Starting #{}", this.schedulerRuns);
+
+ // queue maintenance
+ if ( this.isActive.get() ) {
+ for(final JobQueueImpl jbq : this.queues.values() ) {
+ jbq.maintain();
+ }
+ }
+
+ // full topic scan is done every third run
+ if ( schedulerRuns % 3 == 0 && this.isActive.get() ) {
+ this.fullTopicScan();
+ }
+
+ // we only do a full clean up on every fifth run
+ final boolean doFullCleanUp = (schedulerRuns % 5 == 0);
+
+ if ( doFullCleanUp ) {
+ // check for idle queue
+ logger.debug("Checking for idle queues...");
+
+ // we synchronize to avoid creating a queue which is about to be removed during cleanup
+ synchronized ( queuesLock ) {
+ final Iterator<Map.Entry<String, JobQueueImpl>> i = this.queues.entrySet().iterator();
+ while ( i.hasNext() ) {
+ final Map.Entry<String, JobQueueImpl> current = i.next();
+ final JobQueueImpl jbq = current.getValue();
+ if ( jbq.tryToClose() ) {
+ logger.debug("Removing idle job queue {}", jbq);
+ // remove
+ i.remove();
+ // update mbeans
+ ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, jbq));
+ }
+ }
+ }
+ }
+ logger.debug("Queue manager maintenance: Finished #{}", this.schedulerRuns);
+ }
+
+ /**
+ * Start a new queue
+ * This method first searches the corresponding queue - if such a queue
+ * does not exist yet, it is created and started.
+ *
+ * @param queueInfo The queue info
+ * @param topics The topics
+ */
+ private void start(final QueueInfo queueInfo,
+ final Set<String> topics) {
+ final InternalQueueConfiguration config = queueInfo.queueConfiguration;
+ // get or create queue
+ boolean isNewQueue = false;
+ JobQueueImpl queue = null;
+ // we synchronize to avoid creating a queue which is about to be removed during cleanup
+ synchronized ( queuesLock ) {
+ queue = this.queues.get(queueInfo.queueName);
+ // check for reconfiguration, we really do an identity check here(!)
+ if ( queue != null && queue.getConfiguration() != config ) {
+ this.outdateQueue(queue);
+ // we use a new queue with the configuration
+ queue = null;
+ }
+ if ( queue == null ) {
+ queue = JobQueueImpl.createQueue(queueInfo.queueName, config, queueServices, topics);
+ // on startup the queue might be empty and we get null back from createQueue
+ if ( queue != null ) {
+ isNewQueue = true;
+ queues.put(queueInfo.queueName, queue);
+ ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(queue, null));
+ }
+ }
+ }
+ if ( queue != null ) {
+ if ( !isNewQueue ) {
+ queue.wakeUpQueue(topics);
+ }
+ queue.startJobs();
+ }
+ }
+
+ /**
+ * This method is invoked periodically by the scheduler.
+ * In the default configuration every minute
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ this.maintain();
+ }
+
+ private void outdateQueue(final JobQueueImpl queue) {
+ // remove the queue with the old name
+ // check for main queue
+ final String oldName = ResourceHelper.filterQueueName(queue.getName());
+ this.queues.remove(oldName);
+ // check if we can close or have to rename
+ if ( queue.tryToClose() ) {
+ // copy statistics
+ // update mbeans
+ ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(null, queue));
+ } else {
+ queue.outdate();
+ // readd with new name
+ String newName = ResourceHelper.filterName(queue.getName());
+ int index = 0;
+ while ( this.queues.containsKey(newName) ) {
+ newName = ResourceHelper.filterName(queue.getName()) + '$' + String.valueOf(index++);
+ }
+ this.queues.put(newName, queue);
+ // update mbeans
+ ((QueuesMBeanImpl)queuesMBean).sendEvent(new QueueStatusEvent(queue, queue));
+ }
+ }
+
+ /**
+ * Outdate all queues.
+ */
+ private void restart() {
+ // let's rename/close all queues and clear them
+ synchronized ( queuesLock ) {
+ final List<JobQueueImpl> queues = new ArrayList<JobQueueImpl>(this.queues.values());
+ for(final JobQueueImpl queue : queues ) {
+ this.outdateQueue(queue);
+ }
+ }
+ // check if we're still active
+ final JobManagerConfiguration config = this.configuration;
+ if ( config != null ) {
+ final List<Job> rescheduleList = this.configuration.clearJobRetryList();
+ for(final Job j : rescheduleList) {
+ final JobHandler jh = new JobHandler((JobImpl)j, null, this.configuration);
+ jh.reschedule();
+ }
+ }
+ }
+
+ /**
+ * @param name The queue name
+ * @return The queue or {@code null}.
+ * @see org.apache.sling.event.jobs.JobManager#getQueue(java.lang.String)
+ */
+ public Queue getQueue(final String name) {
+ return this.queues.get(name);
+ }
+
+ /**
+ * @return An iterator for the available queues.
+ * @see org.apache.sling.event.jobs.JobManager#getQueues()
+ */
+ public Iterable<Queue> getQueues() {
+ final Iterator<JobQueueImpl> jqI = this.queues.values().iterator();
+ return new Iterable<Queue>() {
+
+ @Override
+ public Iterator<Queue> iterator() {
+ return new Iterator<Queue>() {
+
+ @Override
+ public boolean hasNext() {
+ return jqI.hasNext();
+ }
+
+ @Override
+ public Queue next() {
+ return jqI.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * This method is called whenever the topology or queue configurations change.
+ * @param active Whether the job handling is active atm.
+ */
+ @Override
+ public void configurationChanged(final boolean active) {
+ // are we still active?
+ if ( this.configuration != null ) {
+ logger.debug("Topology changed {}", active);
+ this.isActive.set(active);
+ if ( active ) {
+ fullTopicScan();
+ } else {
+ this.restart();
+ }
+ }
+ }
+
+ private void fullTopicScan() {
+ logger.debug("Scanning repository for existing topics...");
+ final Set<String> topics = this.scanTopics();
+ final Map<QueueInfo, Set<String>> mapping = this.updateTopicMapping(topics);
+ // start queues
+ for(final Map.Entry<QueueInfo, Set<String>> entry : mapping.entrySet() ) {
+ this.start(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Scan the resource tree for topics.
+ */
+ private Set<String> scanTopics() {
+ final Set<String> topics = new HashSet<String>();
+
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ final Resource baseResource = resolver.getResource(this.configuration.getLocalJobsPath());
+
+ // sanity check - should never be null
+ if ( baseResource != null ) {
+ final Iterator<Resource> topicIter = baseResource.listChildren();
+ while ( topicIter.hasNext() ) {
+ final Resource topicResource = topicIter.next();
+ final String topic = topicResource.getName().replace('.', '/');
+ logger.debug("Found topic {}", topic);
+ topics.add(topic);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ return topics;
+ }
+
+ /**
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ @Override
+ public void handleEvent(final Event event) {
+ final String topic = (String)event.getProperty(NotificationConstants.NOTIFICATION_PROPERTY_JOB_TOPIC);
+ if ( this.isActive.get() && topic != null ) {
+ final QueueInfo info = this.configuration.getQueueConfigurationManager().getQueueInfo(topic);
+ this.start(info, Collections.singleton(topic));
+ }
+ }
+
+ /**
+ * Get the latest mapping from queue name to topics
+ */
+ private Map<QueueInfo, Set<String>> updateTopicMapping(final Set<String> topics) {
+ final Map<QueueInfo, Set<String>> mapping = new HashMap<QueueConfigurationManager.QueueInfo, Set<String>>();
+ for(final String topic : topics) {
+ final QueueInfo queueInfo = this.configuration.getQueueConfigurationManager().getQueueInfo(topic);
+ Set<String> queueTopics = mapping.get(queueInfo);
+ if ( queueTopics == null ) {
+ queueTopics = new HashSet<String>();
+ mapping.put(queueInfo, queueTopics);
+ }
+ queueTopics.add(topic);
+ }
+
+ this.logger.debug("Established new topic mapping: {}", mapping);
+ return mapping;
+ }
+
+ protected void bindThreadPool(final EventingThreadPool etp) {
+ this.threadPool = etp;
+ }
+
+ protected void unbindThreadPool(final EventingThreadPool etp) {
+ if ( this.threadPool == etp ) {
+ this.threadPool = null;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java
new file mode 100644
index 0000000..e39235f
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/QueueServices.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+import org.apache.sling.event.impl.jobs.JobConsumerManager;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.stats.StatisticsManager;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * The queue services class is a helper class containing all
+ * services used by the queue implementations.
+ * This avoids passing a set of separate objects.
+ */
+public class QueueServices {
+
+ public JobManagerConfiguration configuration;
+
+ public JobConsumerManager jobConsumerManager;
+
+ public EventAdmin eventAdmin;
+
+ public ThreadPoolManager threadPoolManager;
+
+ public Scheduler scheduler;
+
+ public StatisticsManager statisticsManager;
+
+ public ThreadPool eventingThreadPool;
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java
new file mode 100644
index 0000000..93684d9
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/queues/ResultBuilderImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.queues;
+
+import org.apache.sling.event.impl.jobs.InternalJobState;
+import org.apache.sling.event.jobs.consumer.JobExecutionContext.ResultBuilder;
+import org.apache.sling.event.jobs.consumer.JobExecutionResult;
+
+public class ResultBuilderImpl implements ResultBuilder {
+
+ private volatile String message;
+
+ private volatile Long retryDelayInMs;
+
+ @Override
+ public JobExecutionResult failed(final long retryDelayInMs) {
+ this.retryDelayInMs = retryDelayInMs;
+ return new JobExecutionResultImpl(InternalJobState.FAILED, message, retryDelayInMs);
+ }
+
+ @Override
+ public ResultBuilder message(final String message) {
+ this.message = message;
+ return this;
+ }
+
+ @Override
+ public JobExecutionResult succeeded() {
+ return new JobExecutionResultImpl(InternalJobState.SUCCEEDED, message, retryDelayInMs);
+ }
+
+ @Override
+ public JobExecutionResult failed() {
+ return new JobExecutionResultImpl(InternalJobState.FAILED, message, retryDelayInMs);
+ }
+
+ @Override
+ public JobExecutionResult cancelled() {
+ return new JobExecutionResultImpl(InternalJobState.CANCELLED, message, retryDelayInMs);
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java
new file mode 100644
index 0000000..7e46985
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobScheduleBuilderImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.scheduling;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
+import org.apache.sling.event.jobs.JobBuilder.ScheduleBuilder;
+import org.apache.sling.event.jobs.ScheduledJobInfo;
+
+/**
+ * The builder implementation for scheduled jobs.
+ */
+public final class JobScheduleBuilderImpl implements ScheduleBuilder {
+
+ private final String topic;
+
+ private final Map<String, Object> properties;
+
+ private final String scheduleName;
+
+ private final JobSchedulerImpl jobScheduler;
+
+ private volatile boolean suspend = false;
+
+ private final List<ScheduleInfoImpl> schedules = new ArrayList<ScheduleInfoImpl>();
+
+ public JobScheduleBuilderImpl(
+ final String topic,
+ final Map<String, Object> properties,
+ final String name,
+ final JobSchedulerImpl jobScheduler) {
+ this.topic = topic;
+ this.properties = properties;
+ this.scheduleName = name;
+ this.jobScheduler = jobScheduler;
+ }
+
+ @Override
+ public ScheduleBuilder weekly(final int day, final int hour, final int minute) {
+ schedules.add(ScheduleInfoImpl.WEEKLY(day, hour, minute));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder daily(final int hour, final int minute) {
+ schedules.add(ScheduleInfoImpl.DAILY(hour, minute));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder hourly(final int minute) {
+ schedules.add(ScheduleInfoImpl.HOURLY(minute));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder at(final Date date) {
+ schedules.add(ScheduleInfoImpl.AT(date));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder monthly(final int day, final int hour, final int minute) {
+ schedules.add(ScheduleInfoImpl.MONTHLY(day, hour, minute));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder yearly(final int month, final int day, final int hour, final int minute) {
+ schedules.add(ScheduleInfoImpl.YEARLY(month, day, hour, minute));
+ return this;
+ }
+
+ @Override
+ public ScheduleBuilder cron(final String expression) {
+ schedules.add(ScheduleInfoImpl.CRON(expression));
+ return this;
+ }
+
+ @Override
+ public ScheduledJobInfo add() {
+ return this.add(null);
+ }
+
+ @Override
+ public ScheduledJobInfo add(final List<String> errors) {
+ return this.jobScheduler.addScheduledJob(topic,
+ properties,
+ scheduleName,
+ suspend,
+ schedules,
+ errors);
+ }
+
+ @Override
+ public ScheduleBuilder suspend() {
+ this.suspend = true;
+ return this;
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java
new file mode 100644
index 0000000..b4b7612
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/JobSchedulerImpl.java
@@ -0,0 +1,566 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.scheduling;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.commons.scheduler.JobContext;
+import org.apache.sling.commons.scheduler.ScheduleOptions;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.event.impl.jobs.JobManagerImpl;
+import org.apache.sling.event.impl.jobs.Utility;
+import org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
+import org.apache.sling.event.jobs.JobBuilder;
+import org.apache.sling.event.jobs.ScheduleInfo;
+import org.apache.sling.event.jobs.ScheduleInfo.ScheduleType;
+import org.apache.sling.event.jobs.ScheduledJobInfo;
+import org.osgi.service.event.Event;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The scheduler for managing scheduled jobs.
+ *
+ * This is not a component by itself, it's directly created from the job manager.
+ * The job manager is also registering itself as an event handler and forwards
+ * the events to this service.
+ */
+public class JobSchedulerImpl
+ implements ConfigurationChangeListener,
+ ResourceChangeListener, ExternalResourceChangeListener,
+ org.apache.sling.commons.scheduler.Job {
+
+ private static final String PROPERTY_READ_JOB = "properties";
+
+ private static final String PROPERTY_SCHEDULE_INDEX = "index";
+
+ /** Default logger */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** Is this active? */
+ private final AtomicBoolean active = new AtomicBoolean(false);
+
+ /** Central job handling configuration. */
+ private final JobManagerConfiguration configuration;
+
+ /** Scheduler service. */
+ private final Scheduler scheduler;
+
+ /** Job manager. */
+ private final JobManagerImpl jobManager;
+
+ /** Scheduled job handler. */
+ private final ScheduledJobHandler scheduledJobHandler;
+
+ /** All scheduled jobs, by scheduler name */
+ private final Map<String, ScheduledJobInfoImpl> scheduledJobs = new HashMap<String, ScheduledJobInfoImpl>();
+
+ /**
+ * Create the scheduler
+ * @param configuration Central job manager configuration
+ * @param scheduler The scheduler service
+ * @param jobManager The job manager
+ */
+ public JobSchedulerImpl(final JobManagerConfiguration configuration,
+ final Scheduler scheduler,
+ final JobManagerImpl jobManager) {
+ this.configuration = configuration;
+ this.scheduler = scheduler;
+ this.jobManager = jobManager;
+
+ this.configuration.addListener(this);
+
+ this.scheduledJobHandler = new ScheduledJobHandler(configuration, this);
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ public void deactivate() {
+ this.configuration.removeListener(this);
+
+ this.scheduledJobHandler.deactivate();
+
+ if ( this.active.compareAndSet(true, false) ) {
+ this.stopScheduling();
+ }
+ synchronized ( this.scheduledJobs ) {
+ this.scheduledJobs.clear();
+ }
+ }
+
+ /**
+ * @see org.apache.sling.event.impl.jobs.config.ConfigurationChangeListener#configurationChanged(boolean)
+ */
+ @Override
+ public void configurationChanged(final boolean processingActive) {
+ // scheduling is only active if
+ // - processing is active and
+ // - configuration is still available and active
+ // - and current instance is leader
+ final boolean schedulingActive;
+ if ( processingActive ) {
+ final TopologyCapabilities caps = this.configuration.getTopologyCapabilities();
+ if ( caps != null && caps.isActive() ) {
+ schedulingActive = caps.isLeader();
+ } else {
+ schedulingActive = false;
+ }
+ } else {
+ schedulingActive = false;
+ }
+
+ // switch activation based on current state and new state
+ if ( schedulingActive ) {
+ // activate if inactive
+ if ( this.active.compareAndSet(false, true) ) {
+ this.startScheduling();
+ }
+ } else {
+ // deactivate if active
+ if ( this.active.compareAndSet(true, false) ) {
+ this.stopScheduling();
+ }
+ }
+ }
+
+ /**
+ * Start all scheduled jobs
+ */
+ private void startScheduling() {
+ synchronized ( this.scheduledJobs ) {
+ for(final ScheduledJobInfo info : this.scheduledJobs.values()) {
+ this.startScheduledJob(((ScheduledJobInfoImpl)info));
+ }
+ }
+ }
+
+ /**
+ * Stop all scheduled jobs.
+ */
+ private void stopScheduling() {
+ synchronized ( this.scheduledJobs ) {
+ for(final ScheduledJobInfo info : this.scheduledJobs.values()) {
+ this.stopScheduledJob((ScheduledJobInfoImpl)info);
+ }
+ }
+ }
+
+ /**
+ * Add a scheduled job
+ */
+ public void scheduleJob(final ScheduledJobInfoImpl info) {
+ synchronized ( this.scheduledJobs ) {
+ this.scheduledJobs.put(info.getName(), info);
+ this.startScheduledJob(info);
+ }
+ }
+
+ /**
+ * Unschedule a scheduled job
+ */
+ public void unscheduleJob(final ScheduledJobInfoImpl info) {
+ synchronized ( this.scheduledJobs ) {
+ if ( this.scheduledJobs.remove(info.getName()) != null ) {
+ this.stopScheduledJob(info);
+ }
+ }
+ }
+
+ /**
+ * Remove a scheduled job
+ */
+ public void removeJob(final ScheduledJobInfoImpl info) {
+ this.unscheduleJob(info);
+ this.scheduledJobHandler.remove(info);
+ }
+
+ /**
+ * Start a scheduled job
+ * @param info The scheduling info
+ */
+ private void startScheduledJob(final ScheduledJobInfoImpl info) {
+ if ( this.active.get() ) {
+ if ( !info.isSuspended() ) {
+ this.configuration.getAuditLogger().debug("SCHEDULED OK name={}, topic={}, properties={} : {}",
+ new Object[] {info.getName(),
+ info.getJobTopic(),
+ info.getJobProperties()},
+ info.getSchedules());
+ int index = 0;
+ for(final ScheduleInfo si : info.getSchedules()) {
+ final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
+ ScheduleOptions options = null;
+ switch ( si.getType() ) {
+ case DAILY:
+ case WEEKLY:
+ case HOURLY:
+ case MONTHLY:
+ case YEARLY:
+ case CRON:
+ options = this.scheduler.EXPR(((ScheduleInfoImpl)si).getCronExpression());
+
+ break;
+ case DATE:
+ options = this.scheduler.AT(((ScheduleInfoImpl)si).getNextScheduledExecution());
+ break;
+ }
+ // Create configuration for scheduled job
+ final Map<String, Serializable> config = new HashMap<String, Serializable>();
+ config.put(PROPERTY_READ_JOB, info);
+ config.put(PROPERTY_SCHEDULE_INDEX, index);
+ this.scheduler.schedule(this, options.name(name).config(config).canRunConcurrently(false));
+ index++;
+ }
+ } else {
+ this.configuration.getAuditLogger().debug("SCHEDULED SUSPENDED name={}, topic={}, properties={} : {}",
+ new Object[] {info.getName(),
+ info.getJobTopic(),
+ info.getJobProperties(),
+ info.getSchedules()});
+ }
+ }
+ }
+
+ /**
+ * Stop a scheduled job
+ * @param info The scheduling info
+ */
+ private void stopScheduledJob(final ScheduledJobInfoImpl info) {
+ final Scheduler localScheduler = this.scheduler;
+ if ( localScheduler != null ) {
+ this.configuration.getAuditLogger().debug("SCHEDULED STOP name={}, topic={}, properties={} : {}",
+ new Object[] {info.getName(),
+ info.getJobTopic(),
+ info.getJobProperties(),
+ info.getSchedules()});
+ for(int index = 0; index<info.getSchedules().size(); index++) {
+ final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
+ localScheduler.unschedule(name);
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.sling.commons.scheduler.Job#execute(org.apache.sling.commons.scheduler.JobContext)
+ */
+ @Override
+ public void execute(final JobContext context) {
+ if ( !active.get() ) {
+ // not active anymore, simply return
+ return;
+ }
+ final ScheduledJobInfoImpl info = (ScheduledJobInfoImpl) context.getConfiguration().get(PROPERTY_READ_JOB);
+
+ if ( info.isSuspended() ) {
+ return;
+ }
+
+ this.jobManager.addJob(info.getJobTopic(), info.getJobProperties());
+ final int index = (Integer)context.getConfiguration().get(PROPERTY_SCHEDULE_INDEX);
+ final Iterator<ScheduleInfo> iter = info.getSchedules().iterator();
+ ScheduleInfo si = iter.next();
+ for(int i=0; i<index; i++) {
+ si = iter.next();
+ }
+ // if scheduled once (DATE), remove from schedule
+ if ( si.getType() == ScheduleType.DATE ) {
+ if ( index == 0 && info.getSchedules().size() == 1 ) {
+ // remove
+ this.scheduledJobHandler.remove(info);
+ } else {
+ // update schedule list
+ final List<ScheduleInfo> infos = new ArrayList<ScheduleInfo>();
+ for(final ScheduleInfo i : info.getSchedules() ) {
+ if ( i != si ) { // no need to use equals
+ infos.add(i);
+ }
+ }
+ info.update(infos);
+ this.scheduledJobHandler.updateSchedule(info.getName(), infos);
+ }
+ }
+ }
+
+ /**
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ public void handleEvent(final Event event) {
+ if ( ResourceHelper.BUNDLE_EVENT_STARTED.equals(event.getTopic())
+ || ResourceHelper.BUNDLE_EVENT_UPDATED.equals(event.getTopic()) ) {
+ this.scheduledJobHandler.bundleEvent();
+ }
+ }
+
+ /**
+ * Helper method which just logs the exception in debug mode.
+ * @param e
+ */
+ private void ignoreException(final Exception e) {
+ if ( this.logger.isDebugEnabled() ) {
+ this.logger.debug("Ignored exception " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Create a schedule builder for a currently scheduled job
+ */
+ public JobBuilder.ScheduleBuilder createJobBuilder(final ScheduledJobInfoImpl info) {
+ final JobBuilder.ScheduleBuilder sb = new JobScheduleBuilderImpl(info.getJobTopic(),
+ info.getJobProperties(), info.getName(), this);
+ return (info.isSuspended() ? sb.suspend() : sb);
+ }
+
+ private enum Operation {
+ LESS,
+ LESS_OR_EQUALS,
+ EQUALS,
+ GREATER_OR_EQUALS,
+ GREATER
+ }
+
+ /**
+ * Check if the job matches the template
+ */
+ private boolean match(final ScheduledJobInfoImpl job, final Map<String, Object> template) {
+ if ( template != null ) {
+ for(final Map.Entry<String, Object> current : template.entrySet()) {
+ final String key = current.getKey();
+ final char firstChar = key.length() > 0 ? key.charAt(0) : 0;
+ final String propName;
+ final Operation op;
+ if ( firstChar == '=' ) {
+ propName = key.substring(1);
+ op = Operation.EQUALS;
+ } else if ( firstChar == '<' ) {
+ final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
+ if ( secondChar == '=' ) {
+ op = Operation.LESS_OR_EQUALS;
+ propName = key.substring(2);
+ } else {
+ op = Operation.LESS;
+ propName = key.substring(1);
+ }
+ } else if ( firstChar == '>' ) {
+ final char secondChar = key.length() > 1 ? key.charAt(1) : 0;
+ if ( secondChar == '=' ) {
+ op = Operation.GREATER_OR_EQUALS;
+ propName = key.substring(2);
+ } else {
+ op = Operation.GREATER;
+ propName = key.substring(1);
+ }
+ } else {
+ propName = key;
+ op = Operation.EQUALS;
+ }
+ final Object value = current.getValue();
+
+ if ( op == Operation.EQUALS ) {
+ if ( !value.equals(job.getJobProperties().get(propName)) ) {
+ return false;
+ }
+ } else {
+ if ( value instanceof Comparable ) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final int result = ((Comparable)value).compareTo(job.getJobProperties().get(propName));
+ if ( op == Operation.LESS && result > -1 ) {
+ return false;
+ } else if ( op == Operation.LESS_OR_EQUALS && result > 0 ) {
+ return false;
+ } else if ( op == Operation.GREATER_OR_EQUALS && result < 0 ) {
+ return false;
+ } else if ( op == Operation.GREATER && result < 1 ) {
+ return false;
+ }
+ } else {
+ // if the value is not comparable we simply don't match
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get all scheduled jobs
+ */
+ public Collection<ScheduledJobInfo> getScheduledJobs(final String topic,
+ final long limit,
+ final Map<String, Object>... templates) {
+ final List<ScheduledJobInfo> jobs = new ArrayList<ScheduledJobInfo>();
+ long count = 0;
+ synchronized ( this.scheduledJobs ) {
+ for(final ScheduledJobInfoImpl job : this.scheduledJobs.values() ) {
+ boolean add = true;
+ if ( topic != null && !topic.equals(job.getJobTopic()) ) {
+ add = false;
+ }
+ if ( add && templates != null && templates.length != 0 ) {
+ add = false;
+ for (Map<String,Object> template : templates) {
+ add = this.match(job, template);
+ if ( add ) {
+ break;
+ }
+ }
+ }
+ if ( add ) {
+ jobs.add(job);
+ count++;
+ if ( limit > 0 && count == limit ) {
+ break;
+ }
+ }
+ }
+ }
+ return jobs;
+ }
+
+ /**
+ * Change the suspended flag for a scheduled job
+ * @param info The schedule info
+ * @param flag The corresponding flag
+ */
+ public void setSuspended(final ScheduledJobInfoImpl info, final boolean flag) {
+ final ResourceResolver resolver = configuration.createResourceResolver();
+ try {
+ final StringBuilder sb = new StringBuilder(this.configuration.getScheduledJobsPath(true));
+ sb.append(ResourceHelper.filterName(info.getName()));
+ final String path = sb.toString();
+
+ final Resource eventResource = resolver.getResource(path);
+ if ( eventResource != null ) {
+ final ModifiableValueMap mvm = eventResource.adaptTo(ModifiableValueMap.class);
+ if ( flag ) {
+ mvm.put(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED, Boolean.TRUE);
+ } else {
+ mvm.remove(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED);
+ }
+ resolver.commit();
+ }
+ if ( flag ) {
+ this.stopScheduledJob(info);
+ } else {
+ this.startScheduledJob(info);
+ }
+ } catch (final PersistenceException pe) {
+ // we ignore the exception if removing fails
+ ignoreException(pe);
+ } finally {
+ resolver.close();
+ }
+ }
+
+ /**
+ * Add a scheduled job
+ * @param topic The job topic
+ * @param properties The job properties
+ * @param scheduleName The schedule name
+ * @param isSuspended Whether it is suspended
+ * @param scheduleInfos The scheduling information
+ * @param errors Optional list to contain potential errors
+ * @return A new job info or {@code null}
+ */
+ public ScheduledJobInfo addScheduledJob(final String topic,
+ final Map<String, Object> properties,
+ final String scheduleName,
+ final boolean isSuspended,
+ final List<ScheduleInfoImpl> scheduleInfos,
+ final List<String> errors) {
+ final List<String> msgs = new ArrayList<String>();
+ if ( scheduleName == null || scheduleName.length() == 0 ) {
+ msgs.add("Schedule name not specified");
+ }
+ final String errorMessage = Utility.checkJob(topic, properties);
+ if ( errorMessage != null ) {
+ msgs.add(errorMessage);
+ }
+ if ( scheduleInfos.size() == 0 ) {
+ msgs.add("No schedule defined for " + scheduleName);
+ }
+ for(final ScheduleInfoImpl info : scheduleInfos) {
+ info.check(msgs);
+ }
+ if ( msgs.size() == 0 ) {
+ try {
+ final ScheduledJobInfo info = this.scheduledJobHandler.addOrUpdateJob(topic, properties, scheduleName, isSuspended, scheduleInfos);
+ if ( info != null ) {
+ return info;
+ }
+ msgs.add("Unable to persist scheduled job.");
+ } catch ( final PersistenceException pe) {
+ msgs.add("Unable to persist scheduled job: " + scheduleName);
+ logger.warn("Unable to persist scheduled job", pe);
+ }
+ } else {
+ for(final String msg : msgs) {
+ logger.warn(msg);
+ }
+ }
+ if ( errors != null ) {
+ errors.addAll(msgs);
+ }
+ return null;
+ }
+
+ public void maintenance() {
+ this.scheduledJobHandler.maintenance();
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.observation.ResourceChangeListener#onChange(java.util.List)
+ */
+ @Override
+ public void onChange(List<ResourceChange> changes) {
+ for(final ResourceChange change : changes ) {
+ if ( change.getPath() != null && change.getPath().startsWith(this.configuration.getScheduledJobsPath(true)) ) {
+ if ( change.getType() == ResourceChange.ChangeType.REMOVED ) {
+ // removal
+ logger.debug("Remove scheduled job {}", change.getPath());
+ this.scheduledJobHandler.handleRemove(change.getPath());
+ } else {
+ // add or update
+ logger.debug("Add or update scheduled job {}, event {}", change.getPath(), change.getType());
+ this.scheduledJobHandler.handleAddUpdate(change.getPath());
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java
new file mode 100644
index 0000000..deb3955
--- /dev/null
+++ b/src/main/java/org/apache/sling/event/impl/jobs/scheduling/ScheduledJobHandler.java
@@ -0,0 +1,545 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.event.impl.jobs.scheduling;
+
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
+import org.apache.sling.event.impl.support.Environment;
+import org.apache.sling.event.impl.support.ResourceHelper;
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.ScheduleInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class ScheduledJobHandler implements Runnable {
+
+ public static final class Holder {
+ public Calendar created;
+ public ScheduledJobInfoImpl info;
+ public long read;
+ }
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** The job manager configuration. */
+ private final JobManagerConfiguration configuration;
+
+ /** The job scheduler. */
+ private final JobSchedulerImpl jobScheduler;
+
+ /** The map of all scheduled jobs, key is the filtered schedule name */
+ private final Map<String, Holder> scheduledJobs = new HashMap<String, Holder>();
+
+ private final AtomicLong lastBundleActivity = new AtomicLong();
+
+ private final AtomicBoolean isRunning = new AtomicBoolean(true);
+
+ /** A local queue for serializing the event processing. */
+ private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
+
+ /**
+ * @param configuration Current job manager configuration
+ */
+ public ScheduledJobHandler(final JobManagerConfiguration configuration,
+ final JobSchedulerImpl jobScheduler) {
+ this.configuration = configuration;
+ this.jobScheduler = jobScheduler;
+ final Thread t = new Thread(this, "Apache Sling Scheduled Job Handler Thread");
+ t.setDaemon(true);
+ t.start();
+
+ this.addFullScan();
+ }
+
+ /**
+ * Add a task/runnable to the queue
+ */
+ private void addTask(final Runnable r) {
+ try {
+ this.queue.put(r);
+ } catch (final InterruptedException e) {
+ this.ignoreException(e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ /**
+ * Add a full scan to the task queue
+ */
+ private void addFullScan() {
+ this.addTask(new Runnable() {
+ @Override
+ public void run() {
+ scan();
+ }
+ });
+ }
+
+ public void deactivate() {
+ this.isRunning.set(false);
+ this.queue.clear();
+ // put a NOP runnable to wake up the queue
+ this.addTask(new Runnable() {
+ @Override
+ public void run() {
+ // do nothing
+ }
+ });
+ }
+
+ @Override
+ public void run() {
+ while ( this.isRunning.get() ) {
+ Runnable r = null;
+ try {
+ r = this.queue.take();
+ } catch (final InterruptedException e) {
+ this.ignoreException(e);
+ Thread.currentThread().interrupt();
+ this.isRunning.set(false);
+ }
+ if ( this.isRunning.get() && r != null) {
+ r.run();
+ }
+ }
+ }
+
+ private void scan() {
+ final ResourceResolver resolver = configuration.createResourceResolver();
+ if ( resolver != null ) {
+ try {
+ logger.debug("Scanning for scheduled jobs...");
+ final String path = this.configuration.getScheduledJobsPath(false);
+ final Resource startResource = resolver.getResource(path);
+ if ( startResource != null ) {
+ final Map<String, Holder> newScheduledJobs = new HashMap<String, Holder>();
+ synchronized ( this.scheduledJobs ) {
+ for(final Resource rsrc : startResource.getChildren()) {
+ if ( !isRunning.get() ) {
+ break;
+ }
+ handleAddOrUpdate(newScheduledJobs, rsrc);
+ }
+ if ( isRunning.get() ) {
+ for(final Holder h : this.scheduledJobs.values()) {
+ if ( h.info != null ) {
+ this.jobScheduler.unscheduleJob(h.info);
+ }
+ }
+ this.scheduledJobs.clear();
+ this.scheduledJobs.putAll(newScheduledJobs);
+ }
+ }
+ }
+ logger.debug("Finished scanning for scheduled jobs...");
+ } finally {
+ resolver.close();
+ }
+ }
+ }
+
+ /**
+ * Read a scheduled job from the resource
+ * @return The job or <code>null</code>
+ */
+ private Map<String, Object> readScheduledJob(final Resource eventResource) {
+ try {
+ final ValueMap vm = ResourceHelper.getValueMap(eventResource);
+ final Map<String, Object> properties = ResourceHelper.cloneValueMap(vm);
+
+ @SuppressWarnings("unchecked")
+ final List<Exception> readErrorList = (List<Exception>) properties.remove(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST);
+ if ( readErrorList != null ) {
+ for(final Exception e : readErrorList) {
+ logger.warn("Unable to read scheduled job from " + eventResource.getPath(), e);
+ }
+ } else {
+ return properties;
+ }
+ } catch (final InstantiationException ie) {
+ // something happened with the resource in the meantime
+ this.ignoreException(ie);
+ }
+ return null;
+ }
+
+ /**
+ * Write a scheduled job to the resource tree.
+ * @throws PersistenceException
+ */
+ public ScheduledJobInfoImpl addOrUpdateJob(
+ final String jobTopic,
+ final Map<String, Object> jobProperties,
+ final String scheduleName,
+ final boolean suspend,
+ final List<ScheduleInfoImpl> scheduleInfos)
+ throws PersistenceException {
+ final Map<String, Object> properties = this.writeScheduledJob(jobTopic, jobProperties, scheduleName, suspend, scheduleInfos);
+
+ final String key = ResourceHelper.filterName(scheduleName);
+ synchronized ( this.scheduledJobs ) {
+ final Holder h = this.scheduledJobs.remove(key);
+ if ( h != null && h.info != null ) {
+ this.jobScheduler.unscheduleJob(h.info);
+ }
+ final Holder holder = new Holder();
+ holder.created = (Calendar) properties.get(Job.PROPERTY_JOB_CREATED);
+ holder.read = System.currentTimeMillis();
+ holder.info = this.addOrUpdateScheduledJob(properties, h == null ? null : h.info);
+
+ this.jobScheduler.scheduleJob(holder.info);
+ return holder.info;
+ }
+ }
+
+ private Map<String, Object> writeScheduledJob(final String jobTopic,
+ final Map<String, Object> jobProperties,
+ final String scheduleName,
+ final boolean suspend,
+ final List<ScheduleInfoImpl> scheduleInfos)
+ throws PersistenceException {
+ final ResourceResolver resolver = this.configuration.createResourceResolver();
+ try {
+ // create properties
+ final Map<String, Object> properties = new HashMap<String, Object>();
+
+ if ( jobProperties != null ) {
+ for(final Map.Entry<String, Object> entry : jobProperties.entrySet() ) {
+ final String propName = entry.getKey();
+ if ( !ResourceHelper.ignoreProperty(propName) ) {
+ properties.put(propName, entry.getValue());
+ }
+ }
+ }
+
+ properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
+ properties.put(Job.PROPERTY_JOB_CREATED, Calendar.getInstance());
+ properties.put(Job.PROPERTY_JOB_CREATED_INSTANCE, Environment.APPLICATION_ID);
+
+ // put scheduler name and scheduler info
+ properties.put(ResourceHelper.PROPERTY_SCHEDULE_NAME, scheduleName);
+ final String[] infoArray = new String[scheduleInfos.size()];
+ int index = 0;
+ for(final ScheduleInfoImpl info : scheduleInfos) {
+ infoArray[index] = info.getSerializedString();
+ index++;
+ }
+ properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, infoArray);
+ if ( suspend ) {
+ properties.put(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED, Boolean.TRUE);
+ }
+
+ // create path and resource
+ properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, ResourceHelper.RESOURCE_TYPE_SCHEDULED_JOB);
+
+ final String path = this.configuration.getScheduledJobsPath(true) + ResourceHelper.filterName(scheduleName);
+
+ // update existing resource
+ final Resource existingInfo = resolver.getResource(path);
+ if ( existingInfo != null ) {
+ resolver.delete(existingInfo);
+ logger.debug("Updating scheduled job {} at {}", properties, path);
+ } else {
+ logger.debug("Storing new scheduled job {} at {}", properties, path);
+ }
+ ResourceHelper.getOrCreateResource(resolver,
+ path,
+ properties);
+ // put back real schedule infos
+ properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, scheduleInfos);
+
+ return properties;
+ } finally {
+ resolver.close();
+ }
+ }
+
+ private ScheduledJobInfoImpl addOrUpdateScheduledJob(
+ final Map<String, Object> properties,
+ final ScheduledJobInfoImpl oldInfo) {
+ properties.remove(ResourceResolver.PROPERTY_RESOURCE_TYPE);
+ properties.remove(Job.PROPERTY_JOB_CREATED);
+ properties.remove(Job.PROPERTY_JOB_CREATED_INSTANCE);
+
+ final String jobTopic = (String) properties.remove(ResourceHelper.PROPERTY_JOB_TOPIC);
+ final String schedulerName = (String) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_NAME);
+
+ final ScheduledJobInfoImpl info;
+ if ( oldInfo == null ) {
+ info = new ScheduledJobInfoImpl(jobScheduler, schedulerName);
+ } else {
+ info = oldInfo;
+ }
+ info.update(jobTopic, properties);
+
+ return info;
+ }
+
+ /**
+ * A bundle event occurred which means we can try loading jobs that previously
+ * failed because of missing classes.
+ */
+ public void bundleEvent() {
+ this.lastBundleActivity.set(System.currentTimeMillis());
+ this.addTask(new Runnable() {
+ @Override
+ public void run() {
+ final Map<String, Holder> updateJobs = new HashMap<String, ScheduledJobHandler.Holder>();
+ synchronized ( scheduledJobs ) {
+ for(final Map.Entry<String, Holder> entry : scheduledJobs.entrySet()) {
+ if ( entry.getValue().info == null && entry.getValue().read < lastBundleActivity.get() ) {
+ updateJobs.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ if ( !updateJobs.isEmpty() && isRunning.get() ) {
+ ResourceResolver resolver = configuration.createResourceResolver();
+ if ( resolver != null ) {
+ try {
+ for(final Map.Entry<String, Holder> entry : updateJobs.entrySet()) {
+ final String path = configuration.getScheduledJobsPath(true) + entry.getKey();
+ final Resource rsrc = resolver.getResource(path);
+ if ( !isRunning.get() ) {
+ break;
+ }
+ if ( rsrc != null ) {
+ synchronized ( scheduledJobs ) {
+ handleAddOrUpdate(scheduledJobs, rsrc);
+ }
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle observation event for removing a scheduled job
+ * @param path The path to the job
+ */
+ public void handleRemove(final String path) {
+ this.addTask(new Runnable() {
+ @Override
+ public void run() {
+ if ( isRunning.get() ) {
+ final String scheduleKey = ResourceHelper.filterName(ResourceUtil.getName(path));
+ if ( scheduleKey != null ) {
+ synchronized ( scheduledJobs ) {
+ final Holder h = scheduledJobs.remove(scheduleKey);
+ if ( h != null && h.info != null ) {
+ jobScheduler.unscheduleJob(h.info);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle observation event for adding or updating a scheduled job
+ * @param path The path to the job
+ */
+ public void handleAddUpdate(final String path) {
+ this.addTask(new Runnable() {
+ @Override
+ public void run() {
+ if ( isRunning.get() ) {
+ final ResourceResolver resolver = configuration.createResourceResolver();
+ if ( resolver != null ) {
+ try {
+ final Resource rsrc = resolver.getResource(path);
+ if ( rsrc != null ) {
+ synchronized ( scheduledJobs ) {
+ handleAddOrUpdate(scheduledJobs, rsrc);
+ }
+ }
+ } finally {
+ resolver.close();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Handle add or update of a resource
+ * @param newScheduledJobs The map to store the jobs
... 10243 lines suppressed ...
[sling-org-apache-sling-event-api] 09/32: [maven-release-plugin] prepare for next development iteration
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit c647075c2c7e1d38041c1d0bad516a4616c12091
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Apr 12 08:23:20 2017 +0000
[maven-release-plugin] prepare for next development iteration
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1791089 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pom.xml b/pom.xml
index f1c5cf1..35b17cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
<artifactId>org.apache.sling.event.api</artifactId>
<packaging>bundle</packaging>
- <version>1.0.0</version>
+ <version>1.0.1-SNAPSHOT</version>
<name>Apache Sling Event API</name>
<description>
@@ -37,9 +37,9 @@
</description>
<scm>
- <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.event.api-1.0.0</connection>
- <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.event.api-1.0.0</developerConnection>
- <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.event.api-1.0.0</url>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/event/api</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/api</url>
</scm>
<properties>
[sling-org-apache-sling-event-api] 26/32: SLING-10676 - remove SECURITY.md which is not needed
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 25071e871031192553ff190006184b475c8cc97d
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri Jul 30 10:43:33 2021 +0200
SLING-10676 - remove SECURITY.md which is not needed
---
SECURITY.md | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index 2e2f930..0000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Security Policy for Apache Sling modules
-
-This module is part of the [Apache Sling Project](https://sling.apache.org), a
-project of the [Apache Software Foundation](https://apache.org) (ASF).
-
-It follows the ASF's [vulnerability handling process](https://apache.org/security/#vulnerability-handling) and
-provides its own [security information page](http://sling.apache.org/project-information/security.html).
-
-## Reporting a Vulnerability
-
-To report a new vulnerability you have discovered in an Apache Sling module,
-please follow the instructions on the
-[project's security page](http://sling.apache.org/project-information/security.html) .
\ No newline at end of file
[sling-org-apache-sling-event-api] 24/32: SLING-10676 - add or update SECURITY.md
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 923c84d92b47d60621a3eb29c4a407d12b0a09ec
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Wed Jul 28 16:11:20 2021 +0200
SLING-10676 - add or update SECURITY.md
---
SECURITY.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..e69de29
[sling-org-apache-sling-event-api] 32/32: Merge pull request #3 from andrewmkhoury/SLING-11831
Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-event-api.git
commit 3c014ecd673a25a45ae09fd36f9a4a1d043d585f
Merge: d151fdf e014be0
Author: Dan Klco <kl...@users.noreply.github.com>
AuthorDate: Tue Apr 18 19:56:23 2023 -0400
Merge pull request #3 from andrewmkhoury/SLING-11831
SLING-11831 - Allow setting job properties for custom job state
.../org/apache/sling/event/jobs/consumer/JobExecutionContext.java | 7 +++++++
.../java/org/apache/sling/event/jobs/consumer/package-info.java | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)