View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.terminationstate.impl;
22  
23  import org.opencastproject.serviceregistry.api.ServiceRegistry;
24  import org.opencastproject.serviceregistry.api.ServiceRegistryException;
25  import org.opencastproject.terminationstate.api.AbstractJobTerminationStateService;
26  import org.opencastproject.terminationstate.api.TerminationStateService;
27  import org.opencastproject.util.NotFoundException;
28  import org.opencastproject.util.OsgiUtil;
29  
30  import org.osgi.service.cm.ConfigurationException;
31  import org.osgi.service.component.ComponentContext;
32  import org.osgi.service.component.annotations.Activate;
33  import org.osgi.service.component.annotations.Component;
34  import org.osgi.service.component.annotations.Deactivate;
35  import org.osgi.service.component.annotations.Reference;
36  import org.quartz.Job;
37  import org.quartz.JobDetail;
38  import org.quartz.JobExecutionContext;
39  import org.quartz.JobExecutionException;
40  import org.quartz.Scheduler;
41  import org.quartz.SchedulerException;
42  import org.quartz.Trigger;
43  import org.quartz.TriggerUtils;
44  import org.quartz.impl.StdSchedulerFactory;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import java.util.Dictionary;
49  
50  @Component(
51      immediate = true,
52      service = TerminationStateService.class,
53      property = {
54          "service.description=Termination State Service",
55          "service.pid=org.opencastproject.terminationstate.impl.TerminationStateService",
56          "vendor.name=opencast",
57          "vendor.service=basic"
58      }
59  )
60  public final class TerminationStateServiceImpl extends AbstractJobTerminationStateService {
61    private static final Logger logger = LoggerFactory.getLogger(TerminationStateServiceImpl.class);
62  
63    public static final String CONFIG_JOB_POLLING_PERIOD = "job.polling.period";
64    private static final int DEFAULT_JOB_POLLING_PERIOD = 300; // secs
65  
66    protected static final String SCHEDULE_GROUP = AbstractJobTerminationStateService.class.getSimpleName();
67    private static final String SCHEDULE_JOB_POLLING = "JobPolling";
68    protected static final String SCHEDULE_JOB_POLLING_TRIGGER = "TriggerJobPolling";
69    private static final String SCHEDULE_JOB_PARAM_PARENT = "parent";
70  
71    private Scheduler scheduler;
72  
73    private int jobPollingPeriod = DEFAULT_JOB_POLLING_PERIOD;
74  
75  
76    @Activate
77    protected void activate(ComponentContext componentContext) {
78      try {
79        configure(componentContext.getProperties());
80      } catch (ConfigurationException e) {
81        logger.error("Unable to read configuration, using defaults", e);
82      }
83  
84      try {
85        scheduler = new StdSchedulerFactory().getScheduler();
86      } catch (SchedulerException e) {
87        logger.error("Cannot create quartz scheduler", e);
88      }
89    }
90  
91  
92    protected void configure(Dictionary config) throws ConfigurationException {
93      this.jobPollingPeriod = OsgiUtil.getOptCfgAsInt(config, CONFIG_JOB_POLLING_PERIOD)
94          .getOrElse(DEFAULT_JOB_POLLING_PERIOD);
95    }
96  
97    @Override
98    public void setState(TerminationState state) {
99      super.setState(state);
100 
101     if (getState() != TerminationState.NONE) {
102       // stop accepting new jobs, maintenance mode
103       try {
104         String host = getServiceRegistry().getRegistryHostname();
105         getServiceRegistry().setMaintenanceStatus(host, true);
106       } catch (ServiceRegistryException | NotFoundException e) {
107         logger.error("Cannot put this host into maintenance", e);
108       }
109       startJobPolling();
110     } else {
111       // termination terminated? unset maintenance
112       try {
113         String host = getServiceRegistry().getRegistryHostname();
114         getServiceRegistry().setMaintenanceStatus(host, false);
115       } catch (ServiceRegistryException | NotFoundException e) {
116         logger.error("Cannot take this host out of maintenance", e);
117       }
118     }
119   }
120 
121   protected void startJobPolling() {
122     try {
123       // create and set the job. To actually run it call schedule(..)
124       final JobDetail job = new JobDetail(SCHEDULE_GROUP, SCHEDULE_JOB_POLLING, CheckTerminationState.class);
125       job.getJobDataMap().put(SCHEDULE_JOB_PARAM_PARENT, this);
126       final Trigger trigger = TriggerUtils.makeSecondlyTrigger(jobPollingPeriod);
127       trigger.setGroup(SCHEDULE_GROUP);
128       trigger.setName(SCHEDULE_JOB_POLLING_TRIGGER);
129       scheduler.scheduleJob(job, trigger);
130       scheduler.start();
131       logger.info("Started polling if jobs are complete");
132     } catch (org.quartz.SchedulerException e) {
133       throw new RuntimeException(e);
134     }
135   }
136 
137   protected void stopJobPolling() {
138     try {
139       scheduler.deleteJob(SCHEDULE_GROUP, SCHEDULE_JOB_POLLING);
140     } catch (SchedulerException e) {
141       // ignore
142     }
143   }
144 
145   public static class CheckTerminationState implements Job {
146 
147     @Override
148     public void execute(JobExecutionContext context) throws JobExecutionException {
149       TerminationStateServiceImpl parent
150           = (TerminationStateServiceImpl) context.getJobDetail().getJobDataMap().get(SCHEDULE_JOB_PARAM_PARENT);
151 
152       if (parent.readyToTerminate()) {
153         logger.info("No jobs running, sent complete Lifecycle action");
154         parent.stopJobPolling();
155       } else if (parent.getState() == TerminationState.WAIT) {
156         logger.info("Jobs still running");
157       }
158     }
159   }
160 
161   /**
162    * Stop scheduled jobs and free resources
163    */
164   private void stop() {
165     try {
166       if (scheduler != null) {
167         this.scheduler.shutdown();
168       }
169     } catch (SchedulerException e) {
170       logger.error("Failed to stop scheduler", e);
171     }
172   }
173 
174   /**
175    * OSGI deactivate callback
176    */
177   @Deactivate
178   public void deactivate() {
179     stop();
180   }
181 
182   /** Methods below are used by test class */
183 
184   protected void setScheduler(Scheduler scheduler) {
185     this.scheduler = scheduler;
186   }
187 
188   @Reference
189   @Override
190   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
191     super.setServiceRegistry(serviceRegistry);
192   }
193 
194 }