1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework.action.async;
35
36 import info.magnolia.cms.security.User;
37 import info.magnolia.context.Context;
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.i18nsystem.SimpleTranslator;
40 import info.magnolia.module.scheduler.CommandJob;
41 import info.magnolia.module.scheduler.SchedulerConsts;
42 import info.magnolia.module.scheduler.SchedulerModule;
43 import info.magnolia.objectfactory.Components;
44 import info.magnolia.ui.api.action.CommandActionDefinition;
45 import info.magnolia.ui.api.app.SubAppContext;
46 import info.magnolia.ui.api.context.UiContext;
47 import info.magnolia.ui.api.message.Message;
48 import info.magnolia.ui.api.message.MessageType;
49 import info.magnolia.ui.framework.message.MessagesManager;
50 import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
51
52 import java.util.Calendar;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.concurrent.atomic.AtomicInteger;
56
57 import javax.inject.Inject;
58 import javax.inject.Provider;
59 import javax.jcr.RepositoryException;
60
61 import org.apache.commons.lang3.StringUtils;
62 import org.apache.commons.lang3.exception.ExceptionUtils;
63 import org.quartz.JobBuilder;
64 import org.quartz.JobDetail;
65 import org.quartz.JobExecutionContext;
66 import org.quartz.Scheduler;
67 import org.quartz.SchedulerException;
68 import org.quartz.Trigger;
69 import org.quartz.TriggerBuilder;
70 import org.quartz.TriggerListener;
71 import org.quartz.listeners.TriggerListenerSupport;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75
76
77
78
79
80 public class DefaultAsyncActionExecutor<D extends CommandActionDefinition> implements AsyncActionExecutor {
81
82 private static AtomicInteger idx = new AtomicInteger();
83
84 private final D definition;
85 private final Provider<SchedulerModule> schedulerModuleProvider;
86 private final User user;
87 private final UiContext uiContext;
88 private final String catalogName;
89 private final SimpleTranslator i18n;
90 private final String commandName;
91 private static final Logger log = LoggerFactory.getLogger(DefaultAsyncActionExecutor.class);
92
93 @Inject
94 public DefaultAsyncActionExecutor(final D definition, final Provider<SchedulerModule> schedulerModuleProvider, final Context context,
95 final UiContext uiContext, final SimpleTranslator i18n) {
96 this.definition = definition;
97 this.schedulerModuleProvider = schedulerModuleProvider;
98 this.user = context.getUser();
99 this.uiContext = uiContext;
100 this.i18n = i18n;
101 this.commandName = definition.getCommand();
102 this.catalogName = definition.getCatalog();
103 }
104
105 @Override
106 public boolean execute(JcrItemAdapter item, Map<String, Object> params) throws Exception {
107 Calendar cal = Calendar.getInstance();
108
109 cal.add(Calendar.SECOND, definition.getDelay());
110
111
112 int timeToWait = definition.getTimeToWait();
113
114 String jobName = "UI Action triggered execution of [" + (StringUtils.isNotEmpty(catalogName) ? (catalogName + ":") : "") + commandName + "] by user [" + StringUtils.defaultIfEmpty(user.getName(), "") + "].";
115
116 if (definition.isParallel()) {
117 jobName += " (" + idx.getAndIncrement() + ")";
118 }
119
120 Trigger trigger = TriggerBuilder.newTrigger()
121 .withIdentity(jobName, SchedulerConsts.SCHEDULER_GROUP_NAME)
122 .startAt(cal.getTime())
123 .build();
124
125
126 final JobDetail jd = JobBuilder.newJob()
127 .withIdentity(jobName, SchedulerConsts.SCHEDULER_GROUP_NAME)
128 .ofType(info.magnolia.module.scheduler.CommandJob.class)
129 .build();
130 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND, commandName);
131 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND_CATALOG, catalogName);
132 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_PARAMS, params);
133
134 Scheduler scheduler = schedulerModuleProvider.get().getScheduler();
135 TriggerListener triggerListener = getListener(jobName, item);
136 scheduler.getListenerManager().addTriggerListener(triggerListener);
137 try {
138 scheduler.scheduleJob(jd, trigger);
139 } catch (SchedulerException e) {
140 throw new ParallelExecutionException(e);
141 }
142
143
144 Thread.sleep(definition.getDelay() * 1000 + 100);
145 int timeToSleep = 500;
146
147 while (timeToWait > 0) {
148 List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
149 if (isJobRunning(jobs, jobName)) {
150 Thread.sleep(timeToSleep);
151 } else {
152 break;
153 }
154 timeToWait -= timeToSleep;
155 }
156
157 boolean isRunningInBackground = (timeToWait == 0);
158
159 if (!isRunningInBackground && triggerListener instanceof DefaultAsyncActionExecutor.CommandActionTriggerListener) {
160 CommandActionTriggerListener commandActionTriggerListener = (CommandActionTriggerListener) triggerListener;
161
162 if (commandActionTriggerListener.getException() != null) {
163 throw commandActionTriggerListener.getException();
164 }
165 }
166 return isRunningInBackground;
167 }
168
169 protected TriggerListener getListener(String jobName, JcrItemAdapter item) throws RepositoryException {
170 return new CommandActionTriggerListener(definition, jobName + "_trigger", uiContext, i18n, item.getJcrItem().getPath(), user.getName());
171 }
172
173 private boolean isJobRunning(List<JobExecutionContext> jobs, String jobName) {
174 for (JobExecutionContext job : jobs) {
175 if (StringUtils.equals(job.getJobDetail().getKey().getName(), jobName)) {
176 return true;
177 }
178 }
179 return false;
180 }
181
182
183
184
185
186
187 public static class CommandActionTriggerListener<D extends CommandActionDefinition> extends TriggerListenerSupport {
188
189 private final D definition;
190 private final String name;
191 private final SimpleTranslator i18n;
192 private final String successMessageTitle;
193 private final String successMessage;
194 private final String errorMessageTitle;
195 private final String errorMessage;
196 private final String userName;
197 private Exception exception = null;
198
199
200
201
202 @Deprecated
203 public CommandActionTriggerListener(D definition, String triggerName, UiContext uiContext, SimpleTranslator i18n, String path) {
204 this(definition, triggerName, uiContext, i18n, path, MgnlContext.getUser().getName());
205 }
206
207 @Inject
208 public CommandActionTriggerListener(D definition, String triggerName, UiContext uiContext, SimpleTranslator i18n, String path, String userName) {
209 this.definition = definition;
210 this.name = triggerName;
211 this.i18n = i18n;
212
213 String appName = uiContext instanceof SubAppContext ? ((SubAppContext) uiContext).getSubAppDescriptor().getLabel() : null;
214 this.successMessageTitle = i18n.translate("ui-framework.abstractcommand.asyncaction.successTitle", definition.getLabel());
215 this.successMessage = i18n.translate("ui-framework.abstractcommand.asyncaction.successMessage", definition.getLabel(), appName, path);
216 this.errorMessageTitle = i18n.translate("ui-framework.abstractcommand.asyncaction.errorTitle", definition.getLabel());
217 this.errorMessage = i18n.translate("ui-framework.abstractcommand.asyncaction.errorMessage", definition.getLabel(), appName, path);
218 this.userName = userName;
219 }
220
221 @Override
222 public String getName() {
223 return name;
224 }
225
226 @Override
227 public void triggerComplete(final Trigger trigger, final JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
228 if (!definition.isNotifyUser()) {
229 return;
230 }
231 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
232 @Override
233 public void doExec() {
234
235 MessagesManager messagesManager = Components.getComponent(MessagesManager.class);
236
237 CommandJob.JobResult result = (CommandJob.JobResult) jobExecutionContext.getResult();
238 exception = result.getException();
239 if (result.isSuccess()) {
240 messagesManager.sendMessage(userName, new Message(MessageType.INFO, successMessageTitle, successMessage));
241 } else {
242 Message msg = new Message(MessageType.WARNING, errorMessageTitle, errorMessage);
243 msg.setView("ui-admincentral:longRunning");
244 msg.addProperty("exception", ExceptionUtils.getMessage(result.getException()));
245 msg.addProperty("comment", i18n.translate("ui-framework.abstractcommand.asyncaction.errorComment"));
246 messagesManager.sendMessage(userName, msg);
247 }
248 }
249 });
250 try {
251
252
253
254
255
256 jobExecutionContext.getScheduler().getListenerManager().removeTriggerListener(getName());
257 } catch (SchedulerException e) {
258 log.warn("Failed to remove trigger listener {}", getName(), e);
259 }
260 }
261
262 public Exception getException() {
263 return exception;
264 }
265 }
266 }