View Javadoc

1   package com.atlassian.scheduler.tenancy;
2   
3   import com.atlassian.scheduler.SchedulerService;
4   import com.atlassian.scheduler.SchedulerServiceException;
5   import com.atlassian.scheduler.config.JobConfig;
6   import com.atlassian.scheduler.config.JobId;
7   import com.atlassian.scheduler.core.DelegatingSchedulerService;
8   import com.atlassian.scheduler.core.LifecycleAwareSchedulerService;
9   import com.atlassian.scheduler.core.RunningJob;
10  import com.atlassian.tenancy.api.TenantAccessor;
11  import com.google.common.collect.Iterables;
12  
13  import javax.annotation.Nonnull;
14  import java.util.Collection;
15  import java.util.concurrent.TimeUnit;
16  
17  /**
18   * Decorates a lifecycle-aware scheduler service with awareness of application tenancy.
19   * <p>
20   * Any attempt to schedule a job with no tenant available results in an exception.
21   * </p><p>
22   * Implemention note: This provides the full {@link LifecycleAwareSchedulerService} interface
23   * rather than the {@link SchedulerService} subset so that the raw, non-tenant-aware delegate
24   * can be completely hidden away at construction time without losing the ability to control
25   * its lifecycle.  For example, in JIRA (which uses {@code PicoContainer}, instead of registering
26   * a {@code FooSchedulerService} directly as the {@code LifecycleAwareSchedulerService}
27   * implementation, we might register something like this instead:
28   * </p>
29   * <pre><code>
30   * // Registrations:
31   * //   FooConfiguration =&gt; JiraFooConfiguration
32   * //   RunDetailsDao =&gt; OfbizRunDetailsDao
33   * //   TenantAccessor =&gt; DefaultJiraTenantAccessor
34   * public class JiraFooSchedulerService extends TenantAwareSchedulerService
35   * {
36   *     public JiraFooSchedulerService(FooConfiguration config, RunDetailsDao runDetailsDao,
37   *              TenantAccessor tenantAccessor)
38   *     {
39   *         super(new FooSchedulerService(config, runDetailsDao), tenantAccessor);
40   *     }
41   * }
42   * </code></pre>
43   * <p>
44   * It is not, of course, absolutely necessary to do this.  We could register {@code FooSchedulerService} directly
45   * and use an explicit constructor specification for {@code TenantAwareSchedulerService} that will use
46   * {@code FooSchedulerService} as the parameter's key instead of the {@code LifecycleAwareSchedulerService}
47   * key that Pico would find by reflection.  However, there doesn't seem to be any real benefit to the approach
48   * unless there are legitimate reasons to access the {@code FooSchedulerService} directly, which would bypass
49   * the tenant-awareness.
50   * </p>
51   *
52   * @since v1.7.0
53   */
54  public class TenantAwareSchedulerService extends DelegatingSchedulerService implements LifecycleAwareSchedulerService {
55      private final LifecycleAwareSchedulerService delegate;
56      private final TenantAccessor tenantAccessor;
57  
58      public TenantAwareSchedulerService(LifecycleAwareSchedulerService delegate, TenantAccessor tenantAccessor) {
59          super(delegate);
60          this.delegate = delegate;
61          this.tenantAccessor = tenantAccessor;
62      }
63  
64      @Override
65      public void scheduleJob(JobId jobId, JobConfig jobConfig) throws SchedulerServiceException {
66          assertTenantAvailable();
67          super.scheduleJob(jobId, jobConfig);
68      }
69  
70      @Nonnull
71      @Override
72      public JobId scheduleJobWithGeneratedId(JobConfig jobConfig) throws SchedulerServiceException {
73          assertTenantAvailable();
74          return super.scheduleJobWithGeneratedId(jobConfig);
75      }
76  
77      @Override
78      public void start() throws SchedulerServiceException {
79          delegate.start();
80      }
81  
82      @Override
83      public void standby() throws SchedulerServiceException {
84          delegate.standby();
85      }
86  
87      @Override
88      public void shutdown() {
89          delegate.shutdown();
90      }
91  
92      @Override
93      @Nonnull
94      public Collection<RunningJob> getLocallyRunningJobs() {
95          return delegate.getLocallyRunningJobs();
96      }
97  
98      @Override
99      public boolean waitUntilIdle(long timeout, TimeUnit units) throws InterruptedException {
100         return delegate.waitUntilIdle(timeout, units);
101     }
102 
103     @Override
104     @Nonnull
105     public State getState() {
106         return delegate.getState();
107     }
108 
109     private void assertTenantAvailable() {
110         if (Iterables.isEmpty(tenantAccessor.getAvailableTenants())) {
111             throw new IllegalStateException("You are not allowed to schedule jobs before a tenant is available.");
112         }
113     }
114 }