You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2013/10/09 19:28:26 UTC

svn commit: r1530723 [1/2] - in /sling/trunk/bundles/extensions/event: ./ src/main/java/org/apache/sling/event/impl/dea/ src/main/java/org/apache/sling/event/impl/jobs/ src/main/java/org/apache/sling/event/impl/jobs/console/ src/main/java/org/apache/sl...

Author: cziegeler
Date: Wed Oct  9 17:28:25 2013
New Revision: 1530723

URL: http://svn.apache.org/r1530723
Log:
SLING-3139 : Provide a way to schedule jobs

Added:
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfoImpl.java
      - copied, changed from r1530653, sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfo.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java   (with props)
Removed:
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfo.java
Modified:
    sling/trunk/bundles/extensions/event/pom.xml
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/dea/DistributedEventReceiver.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobSchedulerImpl.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/MaintenanceTask.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/ScheduledJobInfoImpl.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/EventAdminBridge.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/JobStatusProviderImpl.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/queues/AbstractJobQueue.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/ScheduleInfo.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/TimedEventSender.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ResourceHelper.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobBuilder.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobManager.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduledJobInfo.java
    sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/consumer/JobConsumer.java
    sling/trunk/bundles/extensions/event/src/test/java/org/apache/sling/event/it/AbstractJobHandlingTest.java
    sling/trunk/bundles/extensions/event/src/test/java/org/apache/sling/event/it/JobHandlingTest.java
    sling/trunk/bundles/extensions/event/src/test/java/org/apache/sling/event/it/TimedJobsTest.java

Modified: sling/trunk/bundles/extensions/event/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/pom.xml?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/pom.xml (original)
+++ sling/trunk/bundles/extensions/event/pom.xml Wed Oct  9 17:28:25 2013
@@ -220,7 +220,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.scheduler</artifactId>
-            <version>2.1.0</version>
+            <version>2.4.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/dea/DistributedEventReceiver.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/dea/DistributedEventReceiver.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/dea/DistributedEventReceiver.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/dea/DistributedEventReceiver.java Wed Oct  9 17:28:25 2013
@@ -197,7 +197,7 @@ public class DistributedEventReceiver
         final String[] propNames = event.getPropertyNames();
         if ( propNames != null && propNames.length > 0 ) {
             for(final String propName : propNames) {
-                if ( !ResourceHelper.ignoreProperty(propName) || JobUtil.JOB_ID.equals(propName) ) { // special handling for job id
+                if ( !ResourceHelper.ignoreProperty(propName) || ResourceHelper.PROPERTY_JOB_ID.equals(propName) ) { // special handling for job id
                     properties.put(propName, event.getProperty(propName));
                 }
             }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobBuilderImpl.java Wed Oct  9 17:28:25 2013
@@ -18,14 +18,15 @@
  */
 package org.apache.sling.event.impl.jobs;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 
-import org.apache.sling.event.impl.support.ScheduleInfo;
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
 import org.apache.sling.event.jobs.Job;
 import org.apache.sling.event.jobs.JobBuilder;
 import org.apache.sling.event.jobs.ScheduledJobInfo;
-import org.slf4j.Logger;
 
 /**
  * Fluent builder API
@@ -36,18 +37,16 @@ public class JobBuilderImpl implements J
 
     private final JobManagerImpl jobManager;
 
-    private final Logger logger;
-
     private String name;
 
     private Map<String, Object> properties;
 
-    public JobBuilderImpl(final JobManagerImpl manager, final Logger logger, final String topic) {
+    public JobBuilderImpl(final JobManagerImpl manager, final String topic) {
         this.jobManager = manager;
         this.topic = topic;
-        this.logger = logger;
     }
 
+
     @Override
     public JobBuilder name(final String name) {
         this.name = name;
@@ -62,7 +61,12 @@ public class JobBuilderImpl implements J
 
     @Override
     public Job add() {
-        return this.jobManager.addJob(this.topic, this.name, this.properties);
+        return this.add(null);
+    }
+
+    @Override
+    public Job add(final List<String> errors) {
+        return this.jobManager.addJob(this.topic, this.name, this.properties, errors);
     }
 
     @Override
@@ -70,105 +74,75 @@ public class JobBuilderImpl implements J
         return new ScheduleBuilderImpl(name);
     }
 
-    public final class ScheduleBuilderImpl implements ScheduleBuilder {
+    public final class ScheduleBuilderImpl implements ScheduleBuilder,
+        WeekBuilder, DayBuilder, MinuteBuilder, DateBuilder, ScheduleBuilderAdder {
 
         private final String scheduleName;
 
         private boolean suspend = false;
 
+        private final List<ScheduleInfoImpl> schedules = new ArrayList<ScheduleInfoImpl>();
+
         public ScheduleBuilderImpl(final String name) {
             this.scheduleName = name;
         }
 
-        private boolean check() {
-            if ( this.scheduleName == null || this.scheduleName.length() == 0 ) {
-                logger.warn("Discarding scheduled job - schedule name not specified");
-                return false;
-            }
-            final String errorMessage = Utility.checkJob(topic, properties);
-            if ( errorMessage != null ) {
-                logger.warn("{}", errorMessage);
-                return false;
-            }
-            return true;
-        }
-
-        @Override
-        public boolean hourly(final int minutes) {
-            if ( check() ) {
-                if ( minutes > 0 ) {
-                    final ScheduleInfo info = ScheduleInfo.HOURLY(minutes);
-                    return jobManager.addScheduledJob(topic, name, properties, scheduleName, suspend, info);
-                }
-                logger.warn("Discarding scheduled job - minutes must be between 0 and 59 : {}", minutes);
-            }
-            return false;
-        }
-
-        @Override
-        public TimeBuilder daily() {
-            return new TimeBuilderImpl(ScheduledJobInfo.ScheduleType.DAILY, -1);
-        }
-
-        @Override
-        public TimeBuilder weekly(final int day) {
-            return new TimeBuilderImpl(ScheduledJobInfo.ScheduleType.WEEKLY, day);
-        }
-
-        @Override
-        public boolean at(final Date date) {
-            if ( check() ) {
-                if ( date != null && date.getTime() > System.currentTimeMillis() ) {
-                    final ScheduleInfo info = ScheduleInfo.AT(date);
-                    return jobManager.addScheduledJob(topic, name, properties, scheduleName, suspend, info);
-                }
-                logger.warn("Discarding scheduled job - date must be in the future : {}", date);
-            }
-            return false;
-        }
-
-        @Override
-        public ScheduleBuilder suspend(final boolean flag) {
-            this.suspend = flag;
-            return this;
-        }
-
-        public final class TimeBuilderImpl implements TimeBuilder {
-
-            private final ScheduledJobInfo.ScheduleType scheduleType;
-
-            private final int day;
-
-            public TimeBuilderImpl(ScheduledJobInfo.ScheduleType scheduleType, final int day) {
-                this.scheduleType = scheduleType;
-                this.day = day;
-            }
-
-            @Override
-            public boolean at(final int hour, final int minute) {
-                if ( check() ) {
-                    boolean valid = true;
-                    if ( scheduleType == ScheduledJobInfo.ScheduleType.WEEKLY ) {
-                        if ( day < 1 || day > 7 ) {
-                            valid = false;
-                            logger.warn("Discarding scheduled job - day must be between 1 and 7 : {}", day);
-                        }
-                    }
-                    if ( valid ) {
-                        if ( hour >= 0 && hour < 24 && minute >= 0 && minute < 60 ) {
-                            final ScheduleInfo info;
-                            if ( scheduleType == ScheduledJobInfo.ScheduleType.WEEKLY ) {
-                                info = ScheduleInfo.WEEKLY(this.day, hour, minute);
-                            } else {
-                                info = ScheduleInfo.DAYLY(hour, minute);
-                            }
-                            return jobManager.addScheduledJob(topic, name, properties, scheduleName, suspend, info);
-                        }
-                        logger.warn("Discarding scheduled job - wrong time information : {}…{}", hour, minute);
-                    }
-                }
-                return false;
-            }
+        @Override
+        public WeekBuilder weekly(final int day, final int hour, final int minute) {
+            schedules.add(ScheduleInfoImpl.WEEKLY(day, hour, minute));
+            return this;
+        }
+
+        @Override
+        public DayBuilder dayly(final int hour, final int minute) {
+            schedules.add(ScheduleInfoImpl.DAYLY(hour, minute));
+            return this;
+        }
+
+        @Override
+        public MinuteBuilder hourly(final int minute) {
+            schedules.add(ScheduleInfoImpl.HOURLY(minute));
+            return this;
+        }
+
+        @Override
+        public DateBuilder at(final Date date) {
+            schedules.add(ScheduleInfoImpl.AT(date));
+            return this;
+        }
+
+        @Override
+        public MinuteBuilder at(int minute) {
+            schedules.add(ScheduleInfoImpl.HOURLY(minute));
+            return this;
+        }
+
+        @Override
+        public DayBuilder at(int hour, int minute) {
+            schedules.add(ScheduleInfoImpl.DAYLY(hour, minute));
+            return this;
+        }
+
+        @Override
+        public WeekBuilder at(int day, int hour, int minute) {
+            schedules.add(ScheduleInfoImpl.WEEKLY(day, hour, minute));
+            return this;
+        }
+
+        @Override
+        public ScheduledJobInfo add() {
+            return this.add(null);
+        }
+
+        @Override
+        public ScheduledJobInfo add(final List<String> errors) {
+            return jobManager.addScheduledJob(topic, name, properties, scheduleName, suspend, schedules, errors);
+        }
+
+        @Override
+        public ScheduleBuilder suspend() {
+            this.suspend = true;
+            return this;
         }
     }
 }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobManagerImpl.java Wed Oct  9 17:28:25 2013
@@ -69,7 +69,7 @@ import org.apache.sling.event.impl.jobs.
 import org.apache.sling.event.impl.jobs.stats.TopicStatisticsImpl;
 import org.apache.sling.event.impl.support.Environment;
 import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.impl.support.ScheduleInfo;
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
 import org.apache.sling.event.jobs.Job;
 import org.apache.sling.event.jobs.JobBuilder;
 import org.apache.sling.event.jobs.JobManager;
@@ -517,10 +517,10 @@ public class JobManagerImpl
                 final ValueMap vm = ResourceHelper.getValueMap(resource);
 
                 // check job topic and job id
-                final String errorMessage = Utility.checkJobTopic(vm.get(JobUtil.PROPERTY_JOB_TOPIC));
-                final String jobId = vm.get(JobUtil.JOB_ID, String.class);
+                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(JobUtil.PROPERTY_JOB_TOPIC, String.class);
+                    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());
@@ -542,7 +542,7 @@ public class JobManagerImpl
                         }
                     }
                     job = new JobImpl(topic,
-                            (String)jobProperties.get(JobUtil.PROPERTY_JOB_NAME),
+                            (String)jobProperties.get(ResourceHelper.PROPERTY_JOB_NAME),
                             jobId,
                             jobProperties);
                 } else {
@@ -833,16 +833,7 @@ public class JobManagerImpl
      */
     @Override
     public Job addJob(final String topic, final String name, final Map<String, Object> properties) {
-        final String errorMessage = Utility.checkJob(topic, properties);
-        if ( errorMessage != null ) {
-            logger.warn("{}", errorMessage);
-            return null;
-        }
-        Job result = this.addJobInteral(topic, name, properties);
-        if ( result == null && name != null ) {
-            result = this.getJobByName(name);
-        }
-        return result;
+        return this.addJob(topic, name, properties, null);
     }
 
     /**
@@ -859,7 +850,7 @@ public class JobManagerImpl
             buf.append("//element(*,");
             buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
             buf.append(")[@");
-            buf.append(ISO9075.encode(JobUtil.PROPERTY_JOB_NAME));
+            buf.append(ISO9075.encode(ResourceHelper.PROPERTY_JOB_NAME));
             buf.append(" = '");
             buf.append(name);
             buf.append("']");
@@ -902,7 +893,7 @@ public class JobManagerImpl
             buf.append("//element(*,");
             buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
             buf.append(")[@");
-            buf.append(JobUtil.JOB_ID);
+            buf.append(ResourceHelper.PROPERTY_JOB_ID);
             buf.append(" = '");
             buf.append(id);
             buf.append("']");
@@ -977,7 +968,7 @@ public class JobManagerImpl
             buf.append("//element(*,");
             buf.append(ResourceHelper.RESOURCE_TYPE_JOB);
             buf.append(")[@");
-            buf.append(ISO9075.encode(JobUtil.PROPERTY_JOB_TOPIC));
+            buf.append(ISO9075.encode(ResourceHelper.PROPERTY_JOB_TOPIC));
             buf.append(" = '");
             buf.append(topic);
             buf.append("'");
@@ -1244,7 +1235,10 @@ public class JobManagerImpl
      * @param jobProperties The optional job properties
      * @return The persisted job or <code>null</code>.
      */
-    private Job addJobInteral(final String jobTopic, final String jobName, final Map<String, Object> jobProperties) {
+    private Job addJobInteral(final String jobTopic,
+            final String jobName,
+            final Map<String, Object> jobProperties,
+            final List<String> errors) {
         final QueueInfo info = this.queueConfigManager.getQueueInfo(jobTopic);
         if ( info.queueConfiguration.getType() == QueueConfiguration.Type.DROP ) {
             if ( logger.isDebugEnabled() ) {
@@ -1281,8 +1275,8 @@ public class JobManagerImpl
                         if ( configuration.isLocalJob(job.getResourcePath()) ) {
                             this.backgroundLoader.addJob(job);
                         }
+                        return job;
                     }
-                    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, jobName, jobProperties) + "'", re);
@@ -1294,6 +1288,9 @@ public class JobManagerImpl
                         resolver.close();
                     }
                 }
+                if ( errors != null ) {
+                    errors.add("Unable to persist new job.");
+                }
             }
         }
         return null;
@@ -1327,10 +1324,10 @@ public class JobManagerImpl
             }
         }
 
-        properties.put(JobUtil.JOB_ID, jobId);
-        properties.put(JobUtil.PROPERTY_JOB_TOPIC, jobTopic);
+        properties.put(ResourceHelper.PROPERTY_JOB_ID, jobId);
+        properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
         if ( jobName != null ) {
-            properties.put(JobUtil.PROPERTY_JOB_NAME, jobName);
+            properties.put(ResourceHelper.PROPERTY_JOB_NAME, jobName);
         }
         properties.put(Job.PROPERTY_JOB_QUEUE_NAME, info.queueConfiguration.getName());
         properties.put(Job.PROPERTY_JOB_RETRY_COUNT, 0);
@@ -1464,7 +1461,7 @@ public class JobManagerImpl
      */
     @Override
     public JobBuilder createJob(final String topic) {
-        return new JobBuilderImpl(this, this.logger, topic);
+        return new JobBuilderImpl(this, topic);
     }
 
     /**
@@ -1483,18 +1480,64 @@ public class JobManagerImpl
         return this.jobScheduler.getScheduledJob(name);
     }
 
-    public boolean addScheduledJob(final String topic,
+    public ScheduledJobInfo addScheduledJob(final String topic,
             final String jobName,
             final Map<String, Object> properties,
             final String scheduleName,
             final boolean isSuspended,
-            final ScheduleInfo scheduleInfo) {
-        try {
-            return this.jobScheduler.writeJob(topic, jobName, properties, scheduleName, isSuspended, scheduleInfo);
-        } catch ( final PersistenceException pe) {
-            logger.warn("Unable to persist scheduled job", pe);
+            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");
         }
-        return false;
+        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.jobScheduler.writeJob(topic, jobName, 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 Job addJob(final String topic, final String name,
+            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);
+            }
+            return null;
+        }
+        Job result = this.addJobInteral(topic, name, properties, errors);
+        if ( result == null && name != null ) {
+            result = this.getJobByName(name);
+        }
+        return result;
+    }
 }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobSchedulerImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobSchedulerImpl.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobSchedulerImpl.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/JobSchedulerImpl.java Wed Oct  9 17:28:25 2013
@@ -29,7 +29,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -47,16 +46,18 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 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.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
 import org.apache.sling.discovery.TopologyEventListener;
 import org.apache.sling.event.impl.support.Environment;
 import org.apache.sling.event.impl.support.ResourceHelper;
-import org.apache.sling.event.impl.support.ScheduleInfo;
+import org.apache.sling.event.impl.support.ScheduleInfoImpl;
 import org.apache.sling.event.jobs.Job;
 import org.apache.sling.event.jobs.JobBuilder;
 import org.apache.sling.event.jobs.JobUtil;
+import org.apache.sling.event.jobs.ScheduleInfo;
 import org.apache.sling.event.jobs.ScheduledJobInfo;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventHandler;
@@ -149,12 +150,7 @@ public class JobSchedulerImpl
         if ( this.active ) {
             final Collection<ScheduledJobInfo> jobs = this.getScheduledJobs();
             for(final ScheduledJobInfo info : jobs) {
-                try {
-                    logger.debug("Stopping scheduled job : {}", info.getName());
-                    this.scheduler.removeJob(((ScheduledJobInfoImpl)info).getSchedulerJobId());
-                } catch ( final NoSuchElementException nsee ) {
-                    this.ignoreException(nsee);
-                }
+                this.stopScheduledJob((ScheduledJobInfoImpl)info);
             }
         }
     }
@@ -190,27 +186,8 @@ public class JobSchedulerImpl
                 if ( event.getTopic().equals(TOPIC_READ_JOB) ) {
                     @SuppressWarnings("unchecked")
                     final Map<String, Object> properties = (Map<String, Object>) event.getProperty(PROPERTY_READ_JOB);
-                    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(JobUtil.PROPERTY_JOB_TOPIC);
-                    final String jobName = (String) properties.remove(JobUtil.PROPERTY_JOB_NAME);
-                    final String schedulerName = (String) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_NAME);
-                    final ScheduleInfo scheduleInfo = (ScheduleInfo) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_INFO);
-                    final boolean isSuspended = properties.remove(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED) != null;
-                    // and now schedule
-                    final String key = ResourceHelper.filterName(schedulerName);
-                    ScheduledJobInfoImpl info;
-                    synchronized ( this.scheduledJobs ) {
-                        info = this.scheduledJobs.get(key);
-                        if ( info == null ) {
-                            info = new ScheduledJobInfoImpl(this, jobTopic, jobName,
-                                    properties, schedulerName);
-                            this.scheduledJobs.put(key, info);
-                        }
-                        info.update(isSuspended, scheduleInfo);
-                    }
+                    final ScheduledJobInfoImpl info = this.addOrUpdateScheduledJob(properties);
+
                     if ( this.active ) {
                         this.startScheduledJob(info);
                     }
@@ -249,14 +226,7 @@ public class JobSchedulerImpl
                         info = this.scheduledJobs.remove(scheduleName);
                     }
                     if ( info != null && this.active ) {
-                        logger.debug("Stopping scheduled job : {}", info.getName());
-                        try {
-                            this.scheduler.removeJob(info.getSchedulerJobId());
-                        } catch (final NoSuchElementException nsee) {
-                            // this can happen if the job is scheduled on another node
-                            // so we can just ignore this
-                        }
-
+                        this.stopScheduledJob(info);
                     }
                 }
                 event = nextEvent;
@@ -264,6 +234,32 @@ public class JobSchedulerImpl
         }
     }
 
+    private ScheduledJobInfoImpl addOrUpdateScheduledJob(final Map<String, Object> properties) {
+        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 jobName = (String) properties.remove(ResourceHelper.PROPERTY_JOB_NAME);
+        final String schedulerName = (String) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_NAME);
+        @SuppressWarnings("unchecked")
+        final List<ScheduleInfo> scheduleInfos = (List<ScheduleInfo>) properties.remove(ResourceHelper.PROPERTY_SCHEDULE_INFO);
+        final boolean isSuspended = properties.remove(ResourceHelper.PROPERTY_SCHEDULE_SUSPENDED) != null;
+        // and now schedule
+        final String key = ResourceHelper.filterName(schedulerName);
+        ScheduledJobInfoImpl info;
+        synchronized ( this.scheduledJobs ) {
+            info = this.scheduledJobs.get(key);
+            if ( info == null ) {
+                info = new ScheduledJobInfoImpl(this, jobTopic, jobName,
+                        properties, schedulerName);
+                this.scheduledJobs.put(key, info);
+            }
+            info.update(isSuspended, scheduleInfos);
+        }
+        return info;
+    }
+
     private void startScheduledJob(final ScheduledJobInfoImpl info) {
         if ( !info.isSuspended() ) {
             // Create configuration for scheduled job
@@ -271,24 +267,35 @@ public class JobSchedulerImpl
             config.put(PROPERTY_READ_JOB, info);
 
             logger.debug("Adding scheduled job: {}", info.getName());
-            try {
-                switch ( info.getScheduleType() ) {
-                    case DAILY:
+            int index = 0;
+            for(final ScheduleInfo si : info.getSchedules()) {
+                final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
+                ScheduleOptions options = null;
+                switch ( si.getType() ) {
+                    case DAYLY:
                     case WEEKLY:
                     case HOURLY:
-                        this.scheduler.addJob(info.getSchedulerJobId(), this, config, info.getCronExpression(), false);
+                        options = this.scheduler.EXPR(((ScheduleInfoImpl)si).getCronExpression());
+
                         break;
                     case DATE:
-                        this.scheduler.fireJobAt(info.getSchedulerJobId(), this, config, info.getNextScheduledExecution());
+                        options = this.scheduler.AT(((ScheduleInfoImpl)si).getNextScheduledExecution());
                         break;
                 }
-            } catch (final Exception e) {
-                // we ignore it if scheduled fails...
-                this.ignoreException(e);
+                this.scheduler.schedule(this, options.name(name).config(config).canRunConcurrently(false));
+                index++;
             }
         }
     }
 
+    private void stopScheduledJob(final ScheduledJobInfoImpl info) {
+        logger.debug("Stopping scheduled job : {}", info.getName());
+        for(int index = 0; index<info.getSchedules().size(); index++) {
+            final String name = info.getSchedulerJobId() + "-" + String.valueOf(index);
+            this.scheduler.unschedule(name);
+        }
+    }
+
     /**
      * @see org.apache.sling.commons.scheduler.Job#execute(org.apache.sling.commons.scheduler.JobContext)
      */
@@ -297,12 +304,6 @@ public class JobSchedulerImpl
         final ScheduledJobInfoImpl info = (ScheduledJobInfoImpl) context.getConfiguration().get(PROPERTY_READ_JOB);
 
         this.jobManager.addJob(info.getJobTopic(), info.getJobName(), info.getJobProperties());
-
-        // is this job scheduled for a specific date?
-        if ( info.getScheduleType() == ScheduledJobInfo.ScheduleType.DATE ) {
-            // we can remove it from the resource tree
-            this.unschedule(info);
-        }
     }
 
     public void unschedule(final ScheduledJobInfoImpl info) {
@@ -500,13 +501,13 @@ public class JobSchedulerImpl
      * Write a schedule job to the resource tree.
      * @throws PersistenceException
      */
-    public boolean writeJob(
+    public ScheduledJobInfoImpl writeJob(
             final String jobTopic,
             final String jobName,
             final Map<String, Object> jobProperties,
             final String scheduleName,
             final boolean suspend,
-            final ScheduleInfo scheduleInfo)
+            final List<ScheduleInfoImpl> scheduleInfos)
     throws PersistenceException {
         ResourceResolver resolver = null;
         try {
@@ -524,16 +525,22 @@ public class JobSchedulerImpl
                 }
             }
 
-            properties.put(JobUtil.PROPERTY_JOB_TOPIC, jobTopic);
+            properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, jobTopic);
             if ( jobName != null ) {
-                properties.put(JobUtil.PROPERTY_JOB_NAME, jobName);
+                properties.put(ResourceHelper.PROPERTY_JOB_NAME, jobName);
             }
             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);
-            properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, scheduleInfo.getSerializedString());
+            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);
             }
@@ -558,7 +565,10 @@ public class JobSchedulerImpl
             ResourceHelper.getOrCreateResource(resolver,
                     path,
                     properties);
-            return true;
+            // put back real schedule infos
+            properties.put(ResourceHelper.PROPERTY_SCHEDULE_INFO, scheduleInfos);
+
+            return this.addOrUpdateScheduledJob(properties);
         } catch ( final LoginException le ) {
             // we ignore this
             this.ignoreException(le);
@@ -567,7 +577,7 @@ public class JobSchedulerImpl
                 resolver.close();
             }
         }
-        return false;
+        return null;
     }
 
     /**
@@ -600,12 +610,18 @@ public class JobSchedulerImpl
         }
     }
 
+    /**
+     * Create a schedule builder for a currently scheduled job
+     */
     public JobBuilder.ScheduleBuilder createJobBuilder(final ScheduledJobInfoImpl info) {
         final JobBuilder builder = this.jobManager.createJob(info.getJobTopic()).name(info.getJobTopic()).properties(info.getJobProperties());
         final JobBuilder.ScheduleBuilder sb = builder.schedule(info.getName());
-        return sb.suspend(info.isSuspended());
+        return (info.isSuspended() ? sb.suspend() : sb);
     }
 
+    /**
+     * Get all scheduled jobs
+     */
     public Collection<ScheduledJobInfo> getScheduledJobs() {
         final List<ScheduledJobInfo> jobs = new ArrayList<ScheduledJobInfo>();
         synchronized ( this.scheduledJobs ) {
@@ -616,6 +632,9 @@ public class JobSchedulerImpl
         return jobs;
     }
 
+    /**
+     * Get a scheduled job with the given name
+     */
     public ScheduledJobInfo getScheduledJob(final String name) {
         synchronized ( this.scheduledJobs ) {
             return this.scheduledJobs.get(ResourceHelper.filterName(name));

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/MaintenanceTask.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/MaintenanceTask.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/MaintenanceTask.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/MaintenanceTask.java Wed Oct  9 17:28:25 2013
@@ -710,7 +710,7 @@ public class MaintenanceTask {
 
             properties.put(JobImpl.PROPERTY_BRIDGED_EVENT, true);
             final String topic = (String)properties.remove("slingevent:topic");
-            properties.put(JobUtil.PROPERTY_JOB_TOPIC, topic);
+            properties.put(ResourceHelper.PROPERTY_JOB_TOPIC, topic);
 
             properties.remove(Job.PROPERTY_JOB_QUEUE_NAME);
             properties.remove(Job.PROPERTY_JOB_TARGET_INSTANCE);
@@ -752,7 +752,7 @@ public class MaintenanceTask {
             properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, ResourceHelper.RESOURCE_TYPE_JOB);
 
             final String jobId = this.configuration.getUniqueId(topic);
-            properties.put(JobUtil.JOB_ID, jobId);
+            properties.put(ResourceHelper.PROPERTY_JOB_ID, jobId);
             properties.remove(Job.PROPERTY_JOB_STARTED_TIME);
 
             final String newPath = this.configuration.getUniquePath(targetId, topic, jobId, vm);

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/ScheduledJobInfoImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/ScheduledJobInfoImpl.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/ScheduledJobInfoImpl.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/ScheduledJobInfoImpl.java Wed Oct  9 17:28:25 2013
@@ -19,14 +19,17 @@
 package org.apache.sling.event.impl.jobs;
 
 import java.io.Serializable;
-import java.util.Calendar;
+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.ScheduleInfo;
+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;
 
 public class ScheduledJobInfoImpl implements ScheduledJobInfo, Serializable {
@@ -43,7 +46,7 @@ public class ScheduledJobInfoImpl implem
 
     private final JobSchedulerImpl jobScheduler;
 
-    private ScheduleInfo scheduleInfo;
+    private List<ScheduleInfo> scheduleInfos;
 
     private AtomicBoolean isSuspended;
 
@@ -60,8 +63,8 @@ public class ScheduledJobInfoImpl implem
     }
 
     public void update(final boolean isSuspended,
-            final ScheduleInfo scheduleInfo) {
-        this.scheduleInfo = scheduleInfo;
+            final List<ScheduleInfo> scheduleInfos) {
+        this.scheduleInfos = Collections.unmodifiableList(scheduleInfos);
         this.isSuspended = new AtomicBoolean(isSuspended);
     }
 
@@ -74,11 +77,11 @@ public class ScheduledJobInfoImpl implem
     }
 
     /**
-     * @see org.apache.sling.event.jobs.ScheduledJobInfo#getScheduleType()
+     * @see org.apache.sling.event.jobs.ScheduledJobInfo#getSchedules()
      */
     @Override
-    public ScheduleType getScheduleType() {
-        return this.scheduleInfo.getScheduleType();
+    public Collection<ScheduleInfo> getSchedules() {
+        return this.scheduleInfos;
     }
 
     /**
@@ -86,56 +89,14 @@ public class ScheduledJobInfoImpl implem
      */
     @Override
     public Date getNextScheduledExecution() {
-        final Calendar now = Calendar.getInstance();
-        switch ( this.scheduleInfo.getScheduleType() ) {
-            case DATE : return this.scheduleInfo.getAt();
-            case DAILY : final Calendar next = Calendar.getInstance();
-                         next.set(Calendar.HOUR_OF_DAY, this.getHourOfDay());
-                         next.set(Calendar.MINUTE, this.getMinuteOfHour());
-                         if ( next.before(now) ) {
-                             next.add(Calendar.DAY_OF_WEEK, 1);
-                         }
-                         return next.getTime();
-            case WEEKLY : final Calendar nextW = Calendar.getInstance();
-                          nextW.set(Calendar.HOUR_OF_DAY, this.getHourOfDay());
-                          nextW.set(Calendar.MINUTE, this.getMinuteOfHour());
-                          nextW.set(Calendar.DAY_OF_WEEK, this.getDayOfWeek());
-                          if ( nextW.before(now) ) {
-                              nextW.add(Calendar.WEEK_OF_YEAR, 1);
-                          }
-                          return nextW.getTime();
-            case HOURLY : final Calendar nextH = Calendar.getInstance();
-                          nextH.set(Calendar.MINUTE, this.getMinuteOfHour());
-                          if ( nextH.before(now) ) {
-                              nextH.add(Calendar.HOUR_OF_DAY, 1);
-                          }
-                          return nextH.getTime();
+        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 null;
-    }
-
-    /**
-     * @see org.apache.sling.event.jobs.ScheduledJobInfo#getDayOfWeek()
-     */
-    @Override
-    public int getDayOfWeek() {
-        return this.scheduleInfo.getDayOfWeek();
-    }
-
-    /**
-     * @see org.apache.sling.event.jobs.ScheduledJobInfo#getHourOfDay()
-     */
-    @Override
-    public int getHourOfDay() {
-        return this.scheduleInfo.getHourOfDay();
-    }
-
-    /**
-     * @see org.apache.sling.event.jobs.ScheduledJobInfo#getMinuteOfHour()
-     */
-    @Override
-    public int getMinuteOfHour() {
-        return this.scheduleInfo.getMinuteOfHour();
+        return result;
     }
 
     /**
@@ -212,32 +173,4 @@ public class ScheduledJobInfoImpl implem
     public String getSchedulerJobId() {
         return Job.class.getName() + ":" + this.scheduleName;
     }
-
-    /**
-     * If the job is scheduled daily or weekly, return the cron expression
-     */
-    public String getCronExpression() {
-        if ( this.scheduleInfo.getScheduleType() == ScheduleType.DAILY ) {
-            final StringBuilder sb = new StringBuilder("0 ");
-            sb.append(this.scheduleInfo.getMinuteOfHour());
-            sb.append(' ');
-            sb.append(this.scheduleInfo.getHourOfDay());
-            sb.append(" * * *");
-            return sb.toString();
-        } else if ( this.scheduleInfo.getScheduleType() == ScheduleType.WEEKLY ) {
-            final StringBuilder sb = new StringBuilder("0 ");
-            sb.append(this.scheduleInfo.getMinuteOfHour());
-            sb.append(' ');
-            sb.append(this.scheduleInfo.getHourOfDay());
-            sb.append(" * * ");
-            sb.append(this.scheduleInfo.getDayOfWeek());
-            return sb.toString();
-        } else if ( this.scheduleInfo.getScheduleType() == ScheduleType.HOURLY ) {
-            final StringBuilder sb = new StringBuilder("0 ");
-            sb.append(this.scheduleInfo.getMinuteOfHour());
-            sb.append(" * * * *");
-            return sb.toString();
-        }
-        return null;
-    }
 }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/Utility.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/Utility.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/Utility.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/Utility.java Wed Oct  9 17:28:25 2013
@@ -25,6 +25,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
 
+import org.apache.sling.event.impl.support.ResourceHelper;
 import org.apache.sling.event.jobs.Job;
 import org.apache.sling.event.jobs.JobUtil;
 import org.apache.sling.event.jobs.consumer.JobConsumer;
@@ -169,9 +170,9 @@ public abstract class Utility {
         final Map<String, Object> eventProps = new HashMap<String, Object>();
         eventProps.putAll(((JobImpl)job).getProperties());
         if ( job.getName() != null ) {
-            eventProps.put(JobUtil.PROPERTY_JOB_NAME, job.getName());
+            eventProps.put(ResourceHelper.PROPERTY_JOB_NAME, job.getName());
         }
-        eventProps.put(JobUtil.JOB_ID, job.getId());
+        eventProps.put(ResourceHelper.PROPERTY_JOB_ID, job.getId());
         eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER);
         return new Event(job.getTopic(), eventProps);
     }
@@ -185,9 +186,9 @@ public abstract class Utility {
             sb.append(", properties=");
             boolean first = true;
             for(final String propName : properties.keySet()) {
-                if ( propName.equals(JobUtil.JOB_ID)
-                    || propName.equals(JobUtil.PROPERTY_JOB_NAME)
-                    || propName.equals(JobUtil.PROPERTY_JOB_TOPIC) ) {
+                if ( propName.equals(ResourceHelper.PROPERTY_JOB_ID)
+                    || propName.equals(ResourceHelper.PROPERTY_JOB_NAME)
+                    || propName.equals(ResourceHelper.PROPERTY_JOB_TOPIC) ) {
                    continue;
                 }
                 if ( first ) {

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/console/WebConsolePlugin.java Wed Oct  9 17:28:25 2013
@@ -44,6 +44,7 @@ import org.apache.sling.event.impl.jobs.
 import org.apache.sling.event.impl.jobs.TopologyCapabilities;
 import org.apache.sling.event.impl.jobs.config.InternalQueueConfiguration;
 import org.apache.sling.event.impl.jobs.config.QueueConfigurationManager;
+import org.apache.sling.event.impl.support.ResourceHelper;
 import org.apache.sling.event.jobs.JobManager;
 import org.apache.sling.event.jobs.JobUtil;
 import org.apache.sling.event.jobs.Queue;
@@ -183,9 +184,9 @@ public class WebConsolePlugin extends Ht
 
     private Event getTestEvent(final String id) {
         final Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(JobUtil.PROPERTY_JOB_TOPIC, SLING_WEBCONSOLE_TEST_JOB_TOPIC);
+        props.put(ResourceHelper.PROPERTY_JOB_TOPIC, SLING_WEBCONSOLE_TEST_JOB_TOPIC);
         if ( id != null ) {
-            props.put(JobUtil.PROPERTY_JOB_NAME, id);
+            props.put(ResourceHelper.PROPERTY_JOB_NAME, id);
         }
 
         return new Event(JobUtil.TOPIC_JOB, props);

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/EventAdminBridge.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/EventAdminBridge.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/EventAdminBridge.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/EventAdminBridge.java Wed Oct  9 17:28:25 2013
@@ -35,6 +35,7 @@ import org.apache.sling.event.EventUtil;
 import org.apache.sling.event.impl.jobs.JobImpl;
 import org.apache.sling.event.impl.jobs.Utility;
 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.JobManager;
 import org.apache.sling.event.jobs.JobUtil;
@@ -154,8 +155,8 @@ public class EventAdminBridge
                         this.ignoreException(ie);
                     }
                 } else {
-                    final String jobTopic = (String)event.getProperty(JobUtil.PROPERTY_JOB_TOPIC);
-                    final String jobName = (String)event.getProperty(JobUtil.PROPERTY_JOB_NAME);
+                    final String jobTopic = (String)event.getProperty(ResourceHelper.PROPERTY_JOB_TOPIC);
+                    final String jobName = (String)event.getProperty(ResourceHelper.PROPERTY_JOB_NAME);
 
                     final Map<String, Object> props =  new EventPropertiesMap(event);
                     props.put(JobImpl.PROPERTY_BRIDGED_EVENT, Boolean.TRUE);
@@ -187,7 +188,7 @@ public class EventAdminBridge
                 logger.debug("Handling local job {}", EventUtil.toString(event));
             }
             // check job topic
-            final String errorMessage = Utility.checkJobTopic(event.getProperty(JobUtil.PROPERTY_JOB_TOPIC));
+            final String errorMessage = Utility.checkJobTopic(event.getProperty(ResourceHelper.PROPERTY_JOB_TOPIC));
             if ( errorMessage == null ) {
                 try {
                     this.writeQueue.put(event);

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/JobStatusProviderImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/JobStatusProviderImpl.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/JobStatusProviderImpl.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/deprecated/JobStatusProviderImpl.java Wed Oct  9 17:28:25 2013
@@ -29,9 +29,9 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.event.JobStatusProvider;
 import org.apache.sling.event.JobsIterator;
+import org.apache.sling.event.impl.support.ResourceHelper;
 import org.apache.sling.event.jobs.JobManager;
 import org.apache.sling.event.jobs.JobManager.QueryType;
-import org.apache.sling.event.jobs.JobUtil;
 import org.apache.sling.event.jobs.Queue;
 import org.osgi.service.event.Event;
 
@@ -57,9 +57,9 @@ public class JobStatusProviderImpl
     public boolean removeJob(final String topic, final String jobId) {
         if ( jobId != null && topic != null ) {
             final Event job = this.jobManager.findJob(topic,
-                    Collections.singletonMap(JobUtil.PROPERTY_JOB_NAME, (Object)jobId));
+                    Collections.singletonMap(ResourceHelper.PROPERTY_JOB_NAME, (Object)jobId));
             if ( job != null ) {
-                return this.removeJob((String)job.getProperty(JobUtil.JOB_ID));
+                return this.removeJob((String)job.getProperty(ResourceHelper.PROPERTY_JOB_ID));
             }
         }
         return true;
@@ -81,9 +81,9 @@ public class JobStatusProviderImpl
     public void forceRemoveJob(final String topic, final String jobId) {
         if ( jobId != null && topic != null ) {
             final Event job = this.jobManager.findJob(topic,
-                    Collections.singletonMap(JobUtil.PROPERTY_JOB_NAME, (Object)jobId));
+                    Collections.singletonMap(ResourceHelper.PROPERTY_JOB_NAME, (Object)jobId));
             if ( job != null ) {
-                this.forceRemoveJob((String)job.getProperty(JobUtil.JOB_ID));
+                this.forceRemoveJob((String)job.getProperty(ResourceHelper.PROPERTY_JOB_ID));
             }
         }
     }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/queues/AbstractJobQueue.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/queues/AbstractJobQueue.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/queues/AbstractJobQueue.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/queues/AbstractJobQueue.java Wed Oct  9 17:28:25 2013
@@ -39,6 +39,7 @@ import org.apache.sling.event.impl.jobs.
 import org.apache.sling.event.impl.jobs.deprecated.JobStatusNotifier;
 import org.apache.sling.event.impl.jobs.stats.StatisticsImpl;
 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.JobUtil;
 import org.apache.sling.event.jobs.Queue;
@@ -276,7 +277,7 @@ public abstract class AbstractJobQueue
      */
     @Override
     public boolean sendAcknowledge(final Event job) {
-        final String jobId = (String)job.getProperty(JobUtil.JOB_ID);
+        final String jobId = (String)job.getProperty(ResourceHelper.PROPERTY_JOB_ID);
         final JobHandler ack;
         synchronized ( this.startedJobsLists ) {
             ack = this.startedJobsLists.remove(jobId);
@@ -350,7 +351,7 @@ public abstract class AbstractJobQueue
      */
     @Override
     public boolean finishedJob(final Event job, final boolean shouldReschedule) {
-        final String location = (String)job.getProperty(JobUtil.JOB_ID);
+        final String location = (String)job.getProperty(ResourceHelper.PROPERTY_JOB_ID);
         return this.finishedJob(location, shouldReschedule ? JobStatus.FAILED : JobStatus.SUCCEEDED, false);
     }
 

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/ScheduleInfo.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/ScheduleInfo.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/ScheduleInfo.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/ScheduleInfo.java Wed Oct  9 17:28:25 2013
@@ -67,7 +67,7 @@ final class ScheduleInfo implements Seri
         }
 
         final String id = (String)event.getProperty(EventUtil.PROPERTY_TIMED_EVENT_ID);
-        final String jId = (String)event.getProperty(JobUtil.PROPERTY_JOB_NAME);
+        final String jId = (String)event.getProperty(ResourceHelper.PROPERTY_JOB_NAME);
 
         this.jobId = getJobId(topic, id, jId);
     }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/TimedEventSender.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/TimedEventSender.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/TimedEventSender.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/jobs/timed/TimedEventSender.java Wed Oct  9 17:28:25 2013
@@ -22,6 +22,7 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
+import java.util.Date;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -29,7 +30,6 @@ import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -53,6 +53,7 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.commons.scheduler.Job;
 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.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
@@ -143,11 +144,7 @@ public class TimedEventSender
         final Scheduler localScheduler = this.scheduler;
         if ( localScheduler != null ) {
             for(final String id : this.startedSchedulerJobs ) {
-                try {
-                    localScheduler.removeJob(id);
-                } catch ( final NoSuchElementException nsee ) {
-                    this.ignoreException(nsee);
-                }
+                localScheduler.unschedule(id);
             }
         }
         this.startedSchedulerJobs.clear();
@@ -227,12 +224,7 @@ public class TimedEventSender
                     final String jobId = ResourceUtil.getName(path);
                     this.startedSchedulerJobs.remove(jobId);
                     logger.debug("Stopping job with id : {}", jobId);
-                    try {
-                        this.scheduler.removeJob(jobId);
-                    } catch (final NoSuchElementException nsee) {
-                        // this can happen if the job is scheduled on another node
-                        // so we can just ignore this
-                    }
+                    this.scheduler.unschedule(jobId);
                     event = null;
 
                 } else if ( !Utility.TOPIC_STOPPED.equals(event.getTopic()) ) {
@@ -275,12 +267,7 @@ public class TimedEventSender
                     this.logger.debug("Stopping timed event " + event.getProperty(EventUtil.PROPERTY_TIMED_EVENT_TOPIC) + "(" + scheduleInfo.jobId + ")");
                 }
                 this.startedSchedulerJobs.remove(scheduleInfo.jobId);
-                try {
-                    localScheduler.removeJob(scheduleInfo.jobId);
-                } catch (final NoSuchElementException nsee) {
-                    // this can happen if the job is scheduled on another node
-                    // so we can just ignore this
-                }
+                localScheduler.unschedule(scheduleInfo.jobId);
                 return true;
             }
 
@@ -298,29 +285,28 @@ public class TimedEventSender
             config.put(JOB_CONFIG, properties);
             config.put(JOB_SCHEDULE_INFO, scheduleInfo);
 
-            try {
-                if ( scheduleInfo.expression != null ) {
-                    if ( this.logger.isDebugEnabled() ) {
-                        this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with cron expression " + scheduleInfo.expression);
-                    }
-                    localScheduler.addJob(scheduleInfo.jobId, this, config, scheduleInfo.expression, false);
-                } else if ( scheduleInfo.period != null ) {
-                    if ( this.logger.isDebugEnabled() ) {
-                        this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with period " + scheduleInfo.period);
-                    }
-                    localScheduler.addPeriodicJob(scheduleInfo.jobId, this, config, scheduleInfo.period, false);
-                } else {
-                    // then it must be date
-                    if ( this.logger.isDebugEnabled() ) {
-                        this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with date " + scheduleInfo.date);
-                    }
-                    localScheduler.fireJobAt(scheduleInfo.jobId, this, config, scheduleInfo.date);
+            final ScheduleOptions options;
+            if ( scheduleInfo.expression != null ) {
+                if ( this.logger.isDebugEnabled() ) {
+                    this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with cron expression " + scheduleInfo.expression);
                 }
-                this.startedSchedulerJobs.add(scheduleInfo.jobId);
-                return true;
-            } catch (final Exception e) {
-                this.ignoreException(e);
+                options = localScheduler.EXPR(scheduleInfo.expression);
+            } else if ( scheduleInfo.period != null ) {
+                if ( this.logger.isDebugEnabled() ) {
+                    this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with period " + scheduleInfo.period);
+                }
+                final Date startDate = new Date(System.currentTimeMillis() + scheduleInfo.period * 1000);
+                options = localScheduler.AT(startDate, -1, scheduleInfo.period);
+            } else {
+                // then it must be date
+                if ( this.logger.isDebugEnabled() ) {
+                    this.logger.debug("Adding timed event " + config.get(JOB_TOPIC) + "(" + scheduleInfo.jobId + ")" + " with date " + scheduleInfo.date);
+                }
+                options = localScheduler.AT(scheduleInfo.date);
             }
+            localScheduler.schedule(this, options.canRunConcurrently(false).name(scheduleInfo.jobId).config(config));
+            this.startedSchedulerJobs.add(scheduleInfo.jobId);
+            return true;
         } else {
             this.logger.error("No scheduler available to start timed event " + event);
         }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ResourceHelper.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ResourceHelper.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ResourceHelper.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ResourceHelper.java Wed Oct  9 17:28:25 2013
@@ -38,6 +38,7 @@ import org.apache.sling.event.impl.jobs.
 import org.apache.sling.event.impl.jobs.deprecated.JobStatusNotifier;
 import org.apache.sling.event.jobs.Job;
 import org.apache.sling.event.jobs.JobUtil;
+import org.apache.sling.event.jobs.ScheduleInfo;
 import org.apache.sling.event.jobs.consumer.JobConsumer;
 import org.osgi.service.event.EventConstants;
 
@@ -60,13 +61,17 @@ public abstract class ResourceHelper {
     public static final String PROPERTY_SCHEDULE_INFO = "slingevent:scheduleInfo";
     public static final String PROPERTY_SCHEDULE_SUSPENDED = "slingevent:scheduleSuspended";
 
+    public static final String PROPERTY_JOB_ID = "slingevent:eventId";
+    public static final String PROPERTY_JOB_NAME = "event.job.id";
+    public static final String PROPERTY_JOB_TOPIC = "event.job.topic";
+
     /** List of ignored properties to write to the repository. */
     @SuppressWarnings("deprecation")
     private static final String[] IGNORE_PROPERTIES = new String[] {
         EventUtil.PROPERTY_DISTRIBUTE,
         EventUtil.PROPERTY_APPLICATION,
         EventConstants.EVENT_TOPIC,
-        JobUtil.JOB_ID,
+        ResourceHelper.PROPERTY_JOB_ID,
         JobUtil.PROPERTY_JOB_PARALLEL,
         JobUtil.PROPERTY_JOB_RUN_LOCAL,
         JobUtil.PROPERTY_JOB_QUEUE_ORDERED,
@@ -159,11 +164,22 @@ public abstract class ResourceHelper {
             final Map<String, Object> result = new HashMap<String, Object>(vm);
             for(final Map.Entry<String, Object> entry : result.entrySet()) {
                 if ( entry.getKey().equals(PROPERTY_SCHEDULE_INFO) ) {
-                    final ScheduleInfo info = ScheduleInfo.deserialize(entry.getValue().toString());
-                    if ( info == null ) {
+                    final String[] infoArray = vm.get(entry.getKey(), String[].class);
+                    if ( infoArray == null || infoArray.length == 0 ) {
                         hasReadError.add(new Exception("Unable to deserialize property '" + entry.getKey() + "'"));
                     } else {
-                        entry.setValue(info);
+                        final List<ScheduleInfo> infos = new ArrayList<ScheduleInfo>();
+                        for(final String i : infoArray) {
+                            final ScheduleInfoImpl info = ScheduleInfoImpl.deserialize(i);
+                            if ( info != null ) {
+                                infos.add(info);
+                            }
+                        }
+                        if ( infos.size() < infoArray.length ) {
+                            hasReadError.add(new Exception("Unable to deserialize property '" + entry.getKey() + "'"));
+                        } else {
+                            entry.setValue(infos);
+                        }
                     }
                 }
                 if ( entry.getValue() instanceof InputStream ) {

Copied: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfoImpl.java (from r1530653, sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfo.java)
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfoImpl.java?p2=sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfoImpl.java&p1=sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfo.java&r1=1530653&r2=1530723&rev=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfo.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/impl/support/ScheduleInfoImpl.java Wed Oct  9 17:28:25 2013
@@ -19,31 +19,33 @@
 package org.apache.sling.event.impl.support;
 
 import java.io.Serializable;
+import java.util.Calendar;
 import java.util.Date;
+import java.util.List;
 
-import org.apache.sling.event.jobs.ScheduledJobInfo.ScheduleType;
+import org.apache.sling.event.jobs.ScheduleInfo;
 
-public class ScheduleInfo implements Serializable {
+public class ScheduleInfoImpl implements ScheduleInfo, Serializable {
 
     private static final long serialVersionUID = 1L;
 
     /** Serialization version. */
     private static final String VERSION = "1";
 
-    public static ScheduleInfo HOURLY(final int minutes) {
-        return new ScheduleInfo(ScheduleType.HOURLY, -1, -1, minutes, null);
+    public static ScheduleInfoImpl HOURLY(final int minutes) {
+        return new ScheduleInfoImpl(ScheduleType.HOURLY, -1, -1, minutes, null);
     }
 
-    public static ScheduleInfo AT(final Date at) {
-        return new ScheduleInfo(ScheduleType.DATE, -1, -1, -1, at);
+    public static ScheduleInfoImpl AT(final Date at) {
+        return new ScheduleInfoImpl(ScheduleType.DATE, -1, -1, -1, at);
     }
 
-    public static ScheduleInfo WEEKLY(final int day, final int hour, final int minute) {
-        return new ScheduleInfo(ScheduleType.WEEKLY, day, hour, minute, null);
+    public static ScheduleInfoImpl WEEKLY(final int day, final int hour, final int minute) {
+        return new ScheduleInfoImpl(ScheduleType.WEEKLY, day, hour, minute, null);
     }
 
-    public static ScheduleInfo DAYLY(final int hour, final int minute) {
-        return new ScheduleInfo(ScheduleType.DAILY, -1, hour, minute, null);
+    public static ScheduleInfoImpl DAYLY(final int hour, final int minute) {
+        return new ScheduleInfoImpl(ScheduleType.DAYLY, -1, hour, minute, null);
     }
 
     private final ScheduleType scheduleType;
@@ -56,7 +58,7 @@ public class ScheduleInfo implements Ser
 
     private final Date at;
 
-    private ScheduleInfo(final ScheduleType scheduleType,
+    private ScheduleInfoImpl(final ScheduleType scheduleType,
             final int dayOfWeek,
             final int hourOfDay,
             final int minuteOfHour,
@@ -68,11 +70,11 @@ public class ScheduleInfo implements Ser
         this.at = at;
     }
 
-    public static ScheduleInfo deserialize(final String s) {
+    public static ScheduleInfoImpl deserialize(final String s) {
         final String[] parts = s.split(":");
         if ( parts.length == 6 && parts[0].equals(VERSION) ) {
             try {
-                return new ScheduleInfo(ScheduleType.valueOf(parts[1]),
+                return new ScheduleInfoImpl(ScheduleType.valueOf(parts[1]),
                         Integer.parseInt(parts[2]),
                         Integer.parseInt(parts[3]),
                         Integer.parseInt(parts[4]),
@@ -103,23 +105,109 @@ public class ScheduleInfo implements Ser
         return sb.toString();
     }
 
-    public Date getAt() {
-        return this.at;
+    @Override
+    public ScheduleType getType() {
+        return this.scheduleType;
     }
 
-    public ScheduleType getScheduleType() {
-        return this.scheduleType;
+    @Override
+    public Date getAt() {
+        return this.at;
     }
 
+    @Override
     public int getDayOfWeek() {
         return this.dayOfWeek;
     }
 
+    @Override
     public int getHourOfDay() {
         return this.hourOfDay;
     }
 
+    @Override
     public int getMinuteOfHour() {
         return this.minuteOfHour;
     }
+
+    public void check(final List<String> errors) {
+        switch ( this.scheduleType ) {
+        case DAYLY : if ( hourOfDay < 0 || hourOfDay > 23 || minuteOfHour < 0 || minuteOfHour > 59 ) {
+                         errors.add("Wrong time information : " + minuteOfHour + ":" + minuteOfHour);
+                     }
+                     break;
+        case DATE :  if ( at == null || at.getTime() <= System.currentTimeMillis() + 2000 ) {
+                         errors.add("Date must be in the future : " + at);
+                     }
+                     break;
+        case HOURLY : if ( minuteOfHour < 0 || minuteOfHour > 59 ) {
+                          errors.add("Minute must be between 0 and 59 : " + minuteOfHour);
+                      }
+                      break;
+        case WEEKLY : if ( hourOfDay < 0 || hourOfDay > 23 || minuteOfHour < 0 || minuteOfHour > 59 ) {
+                          errors.add("Wrong time information : " + minuteOfHour + ":" + minuteOfHour);
+                      }
+                      if ( dayOfWeek < 1 || dayOfWeek > 7 ) {
+                          errors.add("Day must be between 1 and 7 : " + dayOfWeek);
+                      }
+                      break;
+        }
+    }
+
+    public Date getNextScheduledExecution() {
+        final Calendar now = Calendar.getInstance();
+        switch ( this.scheduleType ) {
+            case DATE : return this.at;
+            case DAYLY : final Calendar next = Calendar.getInstance();
+                         next.set(Calendar.HOUR_OF_DAY, this.hourOfDay);
+                         next.set(Calendar.MINUTE, this.minuteOfHour);
+                         if ( next.before(now) ) {
+                             next.add(Calendar.DAY_OF_WEEK, 1);
+                         }
+                         return next.getTime();
+            case WEEKLY : final Calendar nextW = Calendar.getInstance();
+                          nextW.set(Calendar.HOUR_OF_DAY, this.hourOfDay);
+                          nextW.set(Calendar.MINUTE, this.minuteOfHour);
+                          nextW.set(Calendar.DAY_OF_WEEK, this.dayOfWeek);
+                          if ( nextW.before(now) ) {
+                              nextW.add(Calendar.WEEK_OF_YEAR, 1);
+                          }
+                          return nextW.getTime();
+            case HOURLY : final Calendar nextH = Calendar.getInstance();
+                          nextH.set(Calendar.MINUTE, this.minuteOfHour);
+                          if ( nextH.before(now) ) {
+                              nextH.add(Calendar.HOUR_OF_DAY, 1);
+                          }
+                          return nextH.getTime();
+        }
+        return null;
+    }
+
+    /**
+     * If the job is scheduled daily or weekly, return the cron expression
+     */
+    public String getCronExpression() {
+        if ( this.scheduleType == ScheduleType.DAYLY ) {
+            final StringBuilder sb = new StringBuilder("0 ");
+            sb.append(String.valueOf(this.minuteOfHour));
+            sb.append(' ');
+            sb.append(String.valueOf(this.hourOfDay));
+            sb.append(" * * *");
+            return sb.toString();
+        } else if ( this.scheduleType == ScheduleType.WEEKLY ) {
+            final StringBuilder sb = new StringBuilder("0 ");
+            sb.append(String.valueOf(this.minuteOfHour));
+            sb.append(' ');
+            sb.append(String.valueOf(this.hourOfDay));
+            sb.append(" * * ");
+            sb.append(String.valueOf(this.dayOfWeek));
+            return sb.toString();
+        } else if ( this.scheduleType == ScheduleType.HOURLY ) {
+            final StringBuilder sb = new StringBuilder("0 ");
+            sb.append(String.valueOf(this.minuteOfHour));
+            sb.append(" * * * *");
+            return sb.toString();
+        }
+        return null;
+    }
 }

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobBuilder.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobBuilder.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobBuilder.java Wed Oct  9 17:28:25 2013
@@ -19,6 +19,7 @@
 package org.apache.sling.event.jobs;
 
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 
 import aQute.bnd.annotation.ProviderType;
@@ -44,12 +45,20 @@ public interface JobBuilder {
 
     /**
      * Add the job.
-     * @see JobManager#addJob(String, Map)
      * @return The job or <code>null</code>
+     * @see JobManager#addJob(String, Map)
      */
     Job add();
 
     /**
+     * Add the job.
+     * @param errors Optional list which will be filled with error messages.
+     * @return The job or <code>null</code>
+     * @see JobManager#addJob(String, Map)
+     */
+    Job add(final List<String> errors);
+
+    /**
      * Schedule the job
      * If a job scheduler with the same name already exists, it is updated
      * with the new information.
@@ -59,46 +68,83 @@ public interface JobBuilder {
      */
     ScheduleBuilder schedule(final String name);
 
+    public interface ScheduleBuilderAdder {
+
+        /**
+         * Finally add the job to the schedule
+         * @return Returns the info object if the job could be scheduled, <code>null</code>otherwise.
+         */
+        ScheduledJobInfo add();
+
+        /**
+         * Finally add the job to the schedule
+         * @param errors Optional list which will be filled with error messages.
+         * @return Returns the info object if the job could be scheduled, <code>null</code>otherwise.
+         */
+        ScheduledJobInfo add(final List<String> errors);
+    }
+
     /**
      * This is a builder interface for creating schedule information
      */
     public interface ScheduleBuilder {
 
         /**
-         * Suspend this scheduling by default
+         * Suspend this scheduling by default.
+         * Invoking this method several times has the same effect as calling it just once.
          */
-        ScheduleBuilder suspend(final boolean flag);
+        ScheduleBuilder suspend();
 
         /**
          * Schedule the job hourly at the given minute.
          * If the minutes argument is less than 0 or higher than 59, the job can't be scheduled.
-         * @param minutes Between 0 and 59.
-         * @return <code>true</code> if the job could be scheduled, <code>false</code>otherwise.
+         * @param minute Between 0 and 59.
          */
-        boolean hourly(final int minutes);
+        MinuteBuilder hourly(final int minute);
 
         /**
-         * Schedule the job daily, the time needs to be specified in addition.
+         * Schedule the job daily at the given time.
+         * If a value less than zero for hour or minute is specified or a value higher than 23 for hour or
+         * a value higher than 59 for minute than the job can't be scheduled.
+         * @param hour  Hour of the day ranging from 0 to 23.
+         * @param minute Minute of the hour ranging from 0 to 59.
          */
-        TimeBuilder daily();
+        DayBuilder dayly(final int hour, final int minute);
 
         /**
          * Schedule the job weekly, the time needs to be specified in addition.
-         * If a value lower than 1 or higher than 7 is used, the job can't be scheduled.
+         * If a value lower than 1 or higher than 7 is used for the day, the job can't be scheduled.
+         * If a value less than zero for hour or minute is specified or a value higher than 23 for hour or
+         * a value higher than 59 for minute than the job can't be scheduled.
          * @param day Day of the week, 1:Sunday, 2:Monday, ... 7:Saturday.
+         * @param hour  Hour of the day ranging from 0 to 23.
+         * @param minute Minute of the hour ranging from 0 to 59.
          */
-        TimeBuilder weekly(final int day);
+        WeekBuilder weekly(final int day, final int hour, final int minute);
 
         /**
          * Schedule the job for a specific date.
          * If no date or a a date in the past is provided, the job can't be scheduled.
          * @param date The date
-         * @return <code>true</code> if the job could be scheduled, <code>false</code>otherwise.
          */
-        boolean at(final Date date);
+        DateBuilder at(final Date date);
     }
 
-    public interface TimeBuilder {
+    public interface WeekBuilder extends ScheduleBuilderAdder {
+
+        /**
+         * Schedule the job for the given day, hour and minute.
+         * If a value lower than 1 or higher than 7 is used for the day, the job can't be scheduled.
+         * If a value less than zero for hour or minute is specified or a value higher than 23 for hour or
+         * a value higher than 59 for minute than the job can't be scheduled.
+         * @param day Day of the week, 1:Sunday, 2:Monday, ... 7:Saturday.
+         * @param hour  Hour of the day ranging from 0 to 23.
+         * @param minute Minute of the hour ranging from 0 to 59.
+         */
+        WeekBuilder at(final int day, final int hour, final int minute);
+    }
+
+    public interface DayBuilder extends ScheduleBuilderAdder  {
 
         /**
          * Schedule the job for the given hour and minute.
@@ -106,8 +152,27 @@ public interface JobBuilder {
          * a value higher than 59 for minute than the job can't be scheduled.
          * @param hour  Hour of the day ranging from 0 to 23.
          * @param minute Minute of the hour ranging from 0 to 59.
-         * @return <code>true</code> if the job could be scheduled, <code>false</code>otherwise.
          */
-        boolean at(final int hour, final int minute);
+        DayBuilder at(final int hour, final int minute);
+    }
+
+    public interface DateBuilder extends ScheduleBuilderAdder {
+
+        /**
+         * Schedule the job for a specific date.
+         * If no date or a a date in the past is provided, the job can't be scheduled.
+         * @param date The date
+         */
+        DateBuilder at(final Date date);
+    }
+
+    public interface MinuteBuilder extends ScheduleBuilderAdder {
+
+        /**
+         * Schedule the job hourly at the given minute.
+         * If the minutes argument is less than 0 or higher than 59, the job can't be scheduled.
+         * @param minute Between 0 and 59.
+         */
+        MinuteBuilder at(final int minute);
     }
 }
\ No newline at end of file

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobManager.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobManager.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobManager.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/JobManager.java Wed Oct  9 17:28:25 2013
@@ -88,6 +88,9 @@ public interface JobManager {
      * The returned job object is a snapshot of the job state taken at the time of creation. Updates
      * to the job state are not reflected and the client needs to get a new job object using the job id.
      *
+     * If the queue for processing this job is configured to drop the job, <code>null</code> is returned
+     * as well.
+     *
      * @param topic The required job topic.
      * @param properties Optional job properties. The properties must be serializable.
      * @return The new job - or <code>null</code> if the job could not be created.
@@ -113,6 +116,9 @@ public interface JobManager {
      * The returned job object is a snapshot of the job state taken at the time of creation. Updates
      * to the job state are not reflected and the client needs to get a new job object using the job id.
      *
+     * If the queue for processing this job is configured to drop the job, <code>null</code> is returned
+     * as well.
+     *
      * @param topic The required job topic.
      * @param name  Optional unique job name
      * @param properties Optional job properties. The properties must be serializable.

Added: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java?rev=1530723&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java (added)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java Wed Oct  9 17:28:25 2013
@@ -0,0 +1,67 @@
+/*
+ * 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.jobs;
+
+import java.util.Date;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * Scheduling information.
+ * @since 1.3
+ */
+@ProviderType
+public interface ScheduleInfo {
+
+    enum ScheduleType {
+        DATE,         // scheduled for a date
+        HOURLY,       // scheduled hourly
+        DAYLY,        // scheduled once a day
+        WEEKLY        // scheduled once a week
+    }
+
+    /**
+     * Return the scheduling type
+     * @return The scheduling type
+     */
+    ScheduleType getType();
+
+    /**
+     * Return the scheduled execution date for a schedule of type date.
+     */
+    Date getAt();
+
+    /**
+     * If the job is scheduled weekly, returns the day of the week
+     * @return The day of the week (from 1 to 7) or -1
+     */
+    int getDayOfWeek();
+
+    /**
+     * Return the hour of the day for daily and weekly scheduled jobs
+     * @return The hour of the day (from 0 to 23) or -1
+     */
+    int getHourOfDay();
+
+    /**
+     * Return the minute of the hour for daily, weekly and hourly scheduled jobs.
+     * @return The minute of the hour (from 0 to 59) or -1
+     */
+    int getMinuteOfHour();
+}

Propchange: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduleInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduledJobInfo.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduledJobInfo.java?rev=1530723&r1=1530722&r2=1530723&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduledJobInfo.java (original)
+++ sling/trunk/bundles/extensions/event/src/main/java/org/apache/sling/event/jobs/ScheduledJobInfo.java Wed Oct  9 17:28:25 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.event.jobs;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.Map;
 
@@ -30,13 +31,6 @@ import aQute.bnd.annotation.ProviderType
 @ProviderType
 public interface ScheduledJobInfo {
 
-    enum ScheduleType {
-        DATE,         // scheduled for a date
-        HOURLY,       // scheduled hourly
-        DAILY,        // scheduled once a day
-        WEEKLY        // scheduled once a week
-    }
-
     /**
      * Return the unique scheduling name.
      * @return The unique name
@@ -44,10 +38,10 @@ public interface ScheduledJobInfo {
     String getName();
 
     /**
-     * Return the scheduling type
-     * @return The scheduling type
+     * Get all schedules for this job
+     * @return A non null and non empty list of schedules.
      */
-    ScheduleType getScheduleType();
+    Collection<ScheduleInfo> getSchedules();
 
     /**
      * Return the next scheduled execution date.
@@ -55,24 +49,6 @@ public interface ScheduledJobInfo {
     Date getNextScheduledExecution();
 
     /**
-     * If the job is scheduled weekly, returns the day of the week
-     * @return The day of the week (from 1 to 7) or -1
-     */
-    int getDayOfWeek();
-
-    /**
-     * Return the hour of the day for daily and weekly scheduled jobs
-     * @return The hour of the day (from 0 to 23) or -1
-     */
-    int getHourOfDay();
-
-    /**
-     * Return the minute of the hour for daily, weekly and hourly scheduled jobs.
-     * @return The minute of the hour (from 0 to 59) or -1
-     */
-    int getMinuteOfHour();
-
-    /**
      * Return the job topic.
      * @return The job topic
      */