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.JobDetail;
64 import org.quartz.JobExecutionContext;
65 import org.quartz.Scheduler;
66 import org.quartz.SchedulerException;
67 import org.quartz.SimpleTrigger;
68 import org.quartz.Trigger;
69 import org.quartz.TriggerListener;
70 import org.quartz.listeners.TriggerListenerSupport;
71
72
73
74
75
76
77 public class DefaultAsyncActionExecutor<D extends CommandActionDefinition> implements AsyncActionExecutor {
78
79 private static AtomicInteger idx = new AtomicInteger();
80
81 private final D definition;
82 private final Provider<SchedulerModule> schedulerModuleProvider;
83 private final User user;
84 private final UiContext uiContext;
85 private final String catalogName;
86 private final SimpleTranslator i18n;
87 private final String commandName;
88
89 @Inject
90 public DefaultAsyncActionExecutor(final D definition, final Provider<SchedulerModule> schedulerModuleProvider, final Context context,
91 final UiContext uiContext, final SimpleTranslator i18n) {
92 this.definition = definition;
93 this.schedulerModuleProvider = schedulerModuleProvider;
94 this.user = context.getUser();
95 this.uiContext = uiContext;
96 this.i18n = i18n;
97 this.commandName = definition.getCommand();
98 this.catalogName = definition.getCatalog();
99 }
100
101 @Override
102 public boolean execute(JcrItemAdapter item, Map<String, Object> params) throws Exception {
103 Calendar cal = Calendar.getInstance();
104
105 cal.add(Calendar.SECOND, definition.getDelay());
106
107
108 int timeToWait = definition.getTimeToWait();
109
110 String jobName = "UI Action triggered execution of [" + (StringUtils.isNotEmpty(catalogName) ? (catalogName + ":") : "") + commandName + "] by user [" + StringUtils.defaultIfEmpty(user.getName(), "") + "].";
111
112 if (definition.isParallel()) {
113 jobName += " (" + idx.getAndIncrement() + ")";
114 }
115 SimpleTrigger trigger = new SimpleTrigger(jobName, SchedulerConsts.SCHEDULER_GROUP_NAME, cal.getTime());
116 trigger.addTriggerListener(jobName + "_trigger");
117
118 final JobDetail jd = new JobDetail(jobName, SchedulerConsts.SCHEDULER_GROUP_NAME, info.magnolia.module.scheduler.CommandJob.class);
119 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND, commandName);
120 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND_CATALOG, catalogName);
121 jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_PARAMS, params);
122
123 Scheduler scheduler = schedulerModuleProvider.get().getScheduler();
124 TriggerListener triggerListener = getListener(jobName, item);
125 scheduler.addTriggerListener(triggerListener);
126 try {
127 scheduler.scheduleJob(jd, trigger);
128 }
129 catch (SchedulerException e) {
130 throw new ParallelExecutionException(e);
131 }
132
133
134 Thread.sleep(definition.getDelay() * 1000 + 100);
135 int timeToSleep = 500;
136
137 while (timeToWait > 0) {
138 List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
139 if (isJobRunning(jobs, jobName)) {
140 Thread.sleep(timeToSleep);
141 } else {
142 break;
143 }
144 timeToWait -= timeToSleep;
145 }
146
147 boolean isRunningInBackground = (timeToWait == 0);
148
149 if (!isRunningInBackground && triggerListener instanceof DefaultAsyncActionExecutor.CommandActionTriggerListener) {
150 CommandActionTriggerListener commandActionTriggerListener = (CommandActionTriggerListener) triggerListener;
151
152 if (commandActionTriggerListener.getException() != null) {
153 throw commandActionTriggerListener.getException();
154 }
155 }
156 return isRunningInBackground;
157 }
158
159 protected TriggerListener getListener(String jobName, JcrItemAdapter item) throws RepositoryException {
160 return new CommandActionTriggerListener(definition, jobName + "_trigger", uiContext, i18n, item.getJcrItem().getPath());
161 }
162
163 private boolean isJobRunning(List<JobExecutionContext> jobs, String jobName) {
164 for (JobExecutionContext job : jobs) {
165 if (job.getJobDetail().getName().equals(jobName)) {
166 return true;
167 }
168 }
169 return false;
170 }
171
172
173
174
175 public class CommandActionTriggerListener extends TriggerListenerSupport {
176
177 private final D definition;
178 private final String name;
179 private final SimpleTranslator i18n;
180 private final String successMessageTitle;
181 private final String successMessage;
182 private final String errorMessageTitle;
183 private final String errorMessage;
184 private Exception exception = null;
185
186 @Inject
187 public CommandActionTriggerListener(D definition, String triggerName, UiContext uiContext, SimpleTranslator i18n, String path) {
188 this.definition = definition;
189 this.name = triggerName;
190 this.i18n = i18n;
191
192 String appName = uiContext instanceof SubAppContext ? ((SubAppContext) uiContext).getSubAppDescriptor().getLabel() : null;
193 this.successMessageTitle = i18n.translate("ui-framework.abstractcommand.asyncaction.successTitle", definition.getLabel());
194 this.successMessage = i18n.translate("ui-framework.abstractcommand.asyncaction.successMessage", definition.getLabel(), appName, path);
195 this.errorMessageTitle = i18n.translate("ui-framework.abstractcommand.asyncaction.errorTitle", definition.getLabel());
196 this.errorMessage = i18n.translate("ui-framework.abstractcommand.asyncaction.errorMessage", definition.getLabel(), appName, path);
197 }
198
199 @Override
200 public String getName() {
201 return name;
202 }
203
204 @Override
205 public void triggerComplete(final Trigger trigger, final JobExecutionContext jobExecutionContext, int i) {
206 if (!definition.isNotifyUser()) {
207 return;
208 }
209 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
210 @Override
211 public void doExec() {
212
213 MessagesManager messagesManager = Components.getComponent(MessagesManager.class);
214
215 CommandJob.JobResult result = (CommandJob.JobResult) jobExecutionContext.getResult();
216 exception = result.getException();
217 if (result.isSuccess()) {
218 messagesManager.sendMessage(user.getName(), new Message(MessageType.INFO, successMessageTitle, successMessage));
219 } else {
220 Message msg = new Message(MessageType.WARNING, errorMessageTitle, errorMessage);
221 msg.setView("ui-admincentral:longRunning");
222 msg.addProperty("exception", ExceptionUtils.getMessage(result.getException()));
223 msg.addProperty("comment", i18n.translate("ui-framework.abstractcommand.asyncaction.errorComment"));
224 messagesManager.sendMessage(user.getName(), msg);
225 }
226 }
227 });
228 }
229
230 public Exception getException() {
231 return exception;
232 }
233 }
234 }