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.module;
35
36 import info.magnolia.cms.core.SystemProperty;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.event.EventBus;
39 import info.magnolia.event.SystemEventBus;
40 import info.magnolia.jcr.node2bean.Node2BeanException;
41 import info.magnolia.jcr.node2bean.Node2BeanProcessor;
42 import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl;
43 import info.magnolia.jcr.util.NodeTypes;
44 import info.magnolia.jcr.util.NodeUtil;
45 import info.magnolia.jcr.wrapper.SystemNodeWrapper;
46 import info.magnolia.module.delta.Condition;
47 import info.magnolia.module.delta.Delta;
48 import info.magnolia.module.delta.Task;
49 import info.magnolia.module.delta.TaskExecutionException;
50 import info.magnolia.module.model.ModuleDefinition;
51 import info.magnolia.module.model.RepositoryDefinition;
52 import info.magnolia.module.model.Version;
53 import info.magnolia.module.model.reader.DependencyChecker;
54 import info.magnolia.module.model.reader.ModuleDefinitionReader;
55 import info.magnolia.module.ui.ModuleManagerNullUI;
56 import info.magnolia.module.ui.ModuleManagerUI;
57 import info.magnolia.module.ui.ModuleManagerWebUI;
58 import info.magnolia.objectfactory.ClassFactory;
59 import info.magnolia.objectfactory.Classes;
60 import info.magnolia.objectfactory.Components;
61 import info.magnolia.objectfactory.MgnlInstantiationException;
62 import info.magnolia.repository.DefaultRepositoryManager;
63 import info.magnolia.repository.Provider;
64 import info.magnolia.repository.RepositoryConstants;
65 import info.magnolia.repository.RepositoryManager;
66 import info.magnolia.repository.definition.WorkspaceMappingDefinition;
67
68 import java.util.ArrayList;
69 import java.util.Collection;
70 import java.util.Collections;
71 import java.util.HashMap;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.Set;
76
77 import javax.inject.Inject;
78 import javax.inject.Singleton;
79 import javax.jcr.Node;
80 import javax.jcr.RepositoryException;
81 import javax.jcr.Session;
82
83 import org.apache.commons.beanutils.BeanUtils;
84 import org.apache.commons.lang3.StringUtils;
85 import org.apache.commons.lang3.exception.ExceptionUtils;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
88
89 import com.google.common.collect.Sets;
90
91
92
93
94 @Singleton
95 public class ModuleManagerImpl implements ModuleManager {
96
97 private static final Logger log = LoggerFactory.getLogger(ModuleManagerImpl.class);
98
99
100
101 static final String MODULES_NODE = "modules";
102
103
104
105
106 private List<ModuleDefinition> orderedModuleDescriptors;
107
108 private ModuleManagementState state;
109
110
111 private final InstallContextImpl installContext;
112 private final ModuleRegistry registry;
113 private final DependencyChecker dependencyChecker;
114 private final Node2BeanProcessor nodeToBean;
115 private final RepositoryManager repositoryManager;
116 private final Set<ModuleDefinitionReader> moduleDefinitionReaders;
117
118
119
120
121 @Deprecated
122 public ModuleManagerImpl(InstallContextImpl installContext, ModuleDefinitionReader moduleDefinitionReader, ModuleRegistry moduleRegistry, DependencyChecker dependencyChecker, Node2BeanProcessor nodeToBean, RepositoryManager repositoryManager) {
123 this(installContext, Sets.newHashSet(moduleDefinitionReader), moduleRegistry, dependencyChecker, nodeToBean, repositoryManager);
124 }
125
126 @Inject
127 public ModuleManagerImpl(InstallContextImpl installContext, Set<ModuleDefinitionReader> moduleDefinitionReaders, ModuleRegistry moduleRegistry, DependencyChecker dependencyChecker, Node2BeanProcessor nodeToBean, RepositoryManager repositoryManager) {
128 this.installContext = installContext;
129 this.moduleDefinitionReaders = moduleDefinitionReaders;
130 this.registry = moduleRegistry;
131 this.dependencyChecker = dependencyChecker;
132 this.nodeToBean = nodeToBean;
133 this.repositoryManager = repositoryManager;
134 }
135
136 @Override
137 public List<ModuleDefinition> loadDefinitions() throws ModuleManagementException {
138 if (state != null) {
139 throw new IllegalStateException("ModuleManager was already initialized !");
140 }
141 Map<String, ModuleDefinition> moduleDefinitions = new HashMap<>();
142 for (ModuleDefinitionReader moduleDefinitionReader : moduleDefinitionReaders) {
143 moduleDefinitions.putAll(moduleDefinitionReader.readAll());
144 }
145
146 if (moduleDefinitions.isEmpty()) {
147 throw new ModuleManagementException("No module definition was found.");
148 }
149 log.debug("Loaded definitions: {}", moduleDefinitions);
150
151 dependencyChecker.checkDependencies(moduleDefinitions);
152 orderedModuleDescriptors = dependencyChecker.sortByDependencyLevel(moduleDefinitions);
153 for (ModuleDefinition moduleDefinition : orderedModuleDescriptors) {
154 registry.registerModuleDefinition(moduleDefinition.getName(), moduleDefinition);
155 }
156 return orderedModuleDescriptors;
157 }
158
159
160
161
162
163
164
165 @Override
166 public void checkForInstallOrUpdates() {
167
168 state = new ModuleManagementState();
169 int taskCount = 0;
170 for (ModuleDefinition module : orderedModuleDescriptors) {
171 installContext.setCurrentModule(module);
172 log.debug("Checking for installation or update [{}]", module);
173 final ModuleVersionHandler versionHandler = newVersionHandler(module);
174 registry.registerModuleVersionHandler(module.getName(), versionHandler);
175
176 final Version currentVersion = versionHandler.getCurrentlyInstalled(installContext);
177 final List<Delta> deltas = versionHandler.getDeltas(installContext, currentVersion);
178 if (deltas.size() > 0) {
179 state.addModule(module, currentVersion, deltas);
180 for (Delta delta : deltas) {
181 taskCount += delta.getTasks().size();
182 }
183 }
184 }
185
186
187 installContext.setCurrentModule(null);
188 installContext.setTotalTaskCount(taskCount);
189
190
191 if (!state.needsUpdateOrInstall()) {
192 loadModulesRepositories();
193 }
194
195
196 }
197
198 @Override
199 public ModuleManagementState getStatus() {
200 if (state == null) {
201 throw new IllegalStateException("ModuleManager was not initialized !");
202 }
203
204 return state;
205 }
206
207 @Override
208 public ModuleManagerUI getUI() {
209 if (SystemProperty.getBooleanProperty("magnolia.update.auto")) {
210 return new ModuleManagerNullUI(this);
211 }
212 return new ModuleManagerWebUI(this);
213 }
214
215 protected ModuleVersionHandler newVersionHandler(ModuleDefinition module) {
216 try {
217 final Class<? extends ModuleVersionHandler> versionHandlerClass = module.getVersionHandler();
218 if (versionHandlerClass != null) {
219 return Components.newInstance(versionHandlerClass);
220 }
221 return new DefaultModuleVersionHandler();
222 } catch (MgnlInstantiationException e) {
223 throw e;
224 }
225 }
226
227 @Override
228 public void performInstallOrUpdate() {
229 synchronized (installContext) {
230 if (state == null) {
231 throw new IllegalStateException("ModuleManager was not initialized !");
232 }
233 if (!state.needsUpdateOrInstall()) {
234 throw new IllegalStateException("ModuleManager has nothing to do !");
235 }
236 if (installContext.getStatus() != null) {
237 throw new IllegalStateException("ModuleManager.performInstallOrUpdate() was already started !");
238 }
239 installContext.setStatus(InstallStatus.inProgress);
240 }
241
242
243 boolean conditionsChecked = true;
244 for (ModuleAndDeltas moduleAndDeltas : state.getList()) {
245
246 installContext.setCurrentModule(moduleAndDeltas.getModule());
247 for (Delta delta : moduleAndDeltas.getDeltas()) {
248 final List<Condition> conditions = delta.getConditions();
249 for (Condition cond : conditions) {
250 if (!cond.check(installContext)) {
251 conditionsChecked = false;
252 installContext.warn(cond.getDescription());
253 }
254 }
255 }
256 }
257 installContext.setCurrentModule(null);
258 if (!conditionsChecked) {
259 installContext.setStatus(InstallStatus.stoppedConditionsNotMet);
260 return;
261 }
262
263 loadModulesRepositories();
264
265 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
266 @Override
267 public void doExec() {
268 final Iterator<ModuleAndDeltas> it = state.getList().iterator();
269 while (it.hasNext()) {
270 final ModuleAndDeltas moduleAndDeltas = it.next();
271 installOrUpdateModule(moduleAndDeltas, installContext);
272 it.remove();
273 }
274 }
275 }, true);
276
277
278 final InstallStatus status = installContext.isRestartNeeded() ? InstallStatus.installDoneRestartNeeded : InstallStatus.installDone;
279 installContext.setStatus(status);
280
281
282
283 System.gc();
284 }
285
286 @Override
287 public InstallContext getInstallContext() {
288 return installContext;
289 }
290
291 @Override
292 public void startModules() {
293
294 executeStartupTasks();
295
296
297
298 final ModuleLifecycleContextImplfecycleContextImpl">ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
299 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_STARTUP);
300 final Node modulesParentNode;
301 try {
302 final Session session = MgnlContext.getSystemContext().getJCRSession(RepositoryConstants.CONFIG);
303 modulesParentNode = NodeUtil.createPath(session.getRootNode(), MODULES_NODE, NodeTypes.Content.NAME);
304 } catch (RepositoryException e) {
305 throw new RuntimeException("Can't start module due to failing to load the /modules node.", e);
306 }
307 final Collection<Node> moduleNodes = new ArrayList<>();
308
309 for (ModuleDefinition moduleDefinition : orderedModuleDescriptors) {
310 final String moduleClassName = moduleDefinition.getClassName();
311 final String moduleName = moduleDefinition.getName();
312 log.info("Initializing module {}", moduleName);
313
314 try {
315 final Object moduleInstance;
316 if (moduleClassName != null) {
317 try {
318 final ClassFactory classFactory = Classes.getClassFactory();
319 final Class<?> moduleClass = classFactory.forName(moduleClassName);
320
321 moduleInstance = Components.newInstance(moduleClass);
322 } catch (Throwable t) {
323 log.error("Can't instantiate {} for module {} : {} : {}", moduleClassName, moduleName, t.getClass(), t.getMessage(), t);
324 continue;
325 }
326 registry.registerModuleInstance(moduleName, moduleInstance);
327 } else {
328 moduleInstance = null;
329 }
330
331 if (modulesParentNode.hasNode(moduleName)) {
332 moduleNodes.add(new SystemNodeWrapper(modulesParentNode.getNode(moduleName)));
333 }
334 } catch (Throwable th) {
335 log.error("Can't init module {}", moduleName, th);
336 }
337 }
338
339
340
341
342 EventBus systemEventBus = Components.getComponentWithAnnotation(EventBus.class, Components.named(SystemEventBus.NAME));
343 systemEventBus.fireEvent(new PopulateModulesEvent());
344
345 for (ModuleDefinition moduleDefinition : orderedModuleDescriptors) {
346 try {
347 startModule(registry.getModuleInstance(moduleDefinition.getName()), moduleDefinition, lifecycleContext);
348 } catch (Throwable th) {
349 log.error("Can't start module {}", moduleDefinition.getName(), th);
350 }
351 }
352 systemEventBus.fireEvent(new ModulesStartedEvent());
353 systemEventBus.addHandler(StartModuleEvent.class, event -> startModule(registry.getModuleInstance(event.getModuleName()), registry.getDefinition(event.getModuleName()), lifecycleContext));
354 systemEventBus.addHandler(StopModuleEvent.class, event -> stopModule(registry.getModuleInstance(event.getModuleName()), registry.getDefinition(event.getModuleName()), lifecycleContext));
355
356
357
358
359 System.gc();
360
361 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_MODULE_RESTART);
362 }
363
364
365
366
367
368 protected void executeStartupTasks() {
369 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
370 @Override
371 public void doExec() {
372 for (ModuleDefinition module : orderedModuleDescriptors) {
373 final ModuleVersionHandler versionHandler = registry.getVersionHandler(module.getName());
374 installContext.setCurrentModule(module);
375 final Delta startup = versionHandler.getStartupDelta(installContext);
376 applyDeltas(module, Collections.singletonList(startup), installContext);
377 }
378 }
379 }, false);
380 }
381
382 protected void startModule(Object moduleInstance, final ModuleDefinition moduleDefinition, final ModuleLifecycleContextImpl lifecycleContext) {
383 if (moduleInstance instanceof ModuleLifecycle) {
384 lifecycleContext.setCurrentModuleDefinition(moduleDefinition);
385 log.info("Starting module {}", moduleDefinition.getName());
386 ((ModuleLifecycle) moduleInstance).start(lifecycleContext);
387 }
388 }
389
390 protected void stopModule(Object moduleInstance, final ModuleDefinition moduleDefinition, final ModuleLifecycleContextImpl lifecycleContext) {
391 if (moduleInstance instanceof ModuleLifecycle) {
392 lifecycleContext.setCurrentModuleDefinition(moduleDefinition);
393 log.info("Stopping module {}", moduleDefinition.getName());
394 ((ModuleLifecycle) moduleInstance).stop(lifecycleContext);
395 }
396 }
397
398
399
400
401
402
403
404
405
406 @Deprecated
407 protected Map<String, Object> getModuleInstanceProperties(ModuleDefinition moduleDefinition) {
408 final Map<String, Object> moduleProperties = new HashMap<String, Object>();
409 final String moduleConfigPath = "/modules/" + moduleDefinition.getName() + "/config";
410 final String moduleNodePath = "/modules/" + moduleDefinition.getName();
411 try {
412 final Session session = MgnlContext.getSystemContext().getJCRSession(RepositoryConstants.CONFIG);
413 final Node moduleNode = session.nodeExists(moduleNodePath) ? new SystemNodeWrapper(session.getNode(moduleNodePath)) : null;
414 final Node configNode = session.nodeExists(moduleConfigPath) ? new SystemNodeWrapper(session.getNode(moduleConfigPath)) : null;
415 moduleProperties.put("moduleDefinition", moduleDefinition);
416 moduleProperties.put("name", moduleDefinition.getName());
417 moduleProperties.put("moduleNode", moduleNode);
418 moduleProperties.put("configNode", configNode);
419 } catch (RepositoryException e) {
420 log.error("Wasn't able to acquire module or module config node {}: {}", moduleConfigPath, e.getMessage(), e);
421 }
422 return moduleProperties;
423 }
424
425
426
427
428 @Deprecated
429 protected void populateModuleInstance(Object moduleInstance, Map<String, Object> moduleProperties) {
430
431 try {
432 BeanUtils.populate(moduleInstance, moduleProperties);
433 } catch (Throwable e) {
434 log.error("Can't initialize module {}: {}", moduleInstance, e.getMessage(), e);
435 }
436
437 Node content = (Node) moduleProperties.get("configNode");
438 if (content != null) {
439 try {
440 nodeToBean.setProperties(moduleInstance, content, true, new Node2BeanTransformerImpl(), Components.getComponentProvider());
441 } catch (Node2BeanException e) {
442 log.error("Wasn't able to configure module {}: {}", moduleInstance, e.getMessage(), e);
443 } catch (RepositoryException e) {
444 log.error("Can't read module configuration {}: {}", moduleInstance, e.getMessage(), e);
445 }
446 }
447 }
448
449 @Override
450 public void stopModules() {
451
452 final ModuleLifecycleContextImplfecycleContextImpl">ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
453 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_SHUTDOWN);
454 if (orderedModuleDescriptors != null) {
455
456 final ArrayList<ModuleDefinition> shutdownOrder = new ArrayList<ModuleDefinition>(orderedModuleDescriptors);
457 Collections.reverse(shutdownOrder);
458 for (ModuleDefinition md : shutdownOrder) {
459 Object module = registry.getModuleInstance(md.getName());
460 if (module instanceof ModuleLifecycle) {
461 stopModule(module, md, lifecycleContext);
462 }
463
464 }
465 }
466 }
467
468 protected void installOrUpdateModule(ModuleAndDeltas moduleAndDeltas, InstallContextImpl ctx) {
469 final ModuleDefinition moduleDef = moduleAndDeltas.getModule();
470 final List<Delta> deltas = moduleAndDeltas.getDeltas();
471 ctx.setCurrentModule(moduleDef);
472 log.debug("Install/update for {} is starting: {}", moduleDef, moduleAndDeltas);
473 applyDeltas(moduleDef, deltas, ctx);
474 log.debug("Install/update for {} has finished", moduleDef, moduleAndDeltas);
475 }
476
477
478
479
480
481
482 protected void applyDeltas(ModuleDefinition moduleDef, List<Delta> deltas, InstallContextImpl ctx) {
483 boolean success = true;
484 Task currentTask = null;
485 try {
486 for (Delta delta : deltas) {
487 final List<Task> tasks = delta.getTasks();
488 for (Task task : tasks) {
489 currentTask = task;
490 log.debug("Module {}, executing {}", moduleDef, currentTask);
491 task.execute(ctx);
492 ctx.incExecutedTaskCount();
493 }
494 }
495 } catch (TaskExecutionException e) {
496 ctx.error("Could not install or update " + moduleDef.getName() + " module. Task '" + currentTask.getName() + "' failed. (" + ExceptionUtils.getRootCauseMessage(e) + ")", e);
497 success = false;
498 } catch (RuntimeException e) {
499 ctx.error("Error while installing or updating " + moduleDef.getName() + " module. Task '" + (currentTask == null ? "<null>" : currentTask.getName()) + "' failed. (" + ExceptionUtils.getRootCauseMessage(e) + ")", e);
500 ctx.setStatus(InstallStatus.installFailed);
501 throw e;
502 } finally {
503
504 ctx.setCurrentModule(null);
505 }
506
507 saveChanges(success);
508 }
509
510
511
512
513
514
515 private void saveChanges(boolean persist) {
516
517 Collection<String> repositoryNames = repositoryManager.getWorkspaceNames();
518 for (String repoName : repositoryNames) {
519 log.debug("{} repository {}", (persist ? "Saving" : "Rolling back"), repoName);
520 try {
521 final Session session = MgnlContext.getJCRSession(repoName);
522
523 if (session.hasPendingChanges()) {
524 if (persist) {
525 boolean isMasterCluster = isMasterCluster();
526 boolean isClustered = isClustered(repoName);
527
528 if (!isClustered || isMasterCluster) {
529 session.save();
530 } else {
531 session.refresh(false);
532 log.info("Skipping bootstrapping of repository '{}' because it is clustered and node is not cluster master ", repoName);
533 }
534 } else {
535 session.refresh(false);
536 }
537 }
538 } catch (RepositoryException e) {
539 throw new RuntimeException(e);
540 }
541 }
542 }
543
544 private boolean isClustered(String repositoryName) {
545 return repositoryManager instanceof DefaultRepositoryManagera/repository/DefaultRepositoryManager.html#DefaultRepositoryManager">DefaultRepositoryManager && ((DefaultRepositoryManager) repositoryManager).isClusteredWorkspace(repositoryName);
546 }
547
548 private boolean isMasterCluster() {
549 return repositoryManager instanceof DefaultRepositoryManagera/repository/DefaultRepositoryManager.html#DefaultRepositoryManager">DefaultRepositoryManager && ((DefaultRepositoryManager) repositoryManager).isClusterMaster();
550 }
551
552
553
554
555
556
557 private void loadModulesRepositories() {
558 for (ModuleDefinition def : orderedModuleDescriptors) {
559
560 for (final RepositoryDefinition repDef : def.getRepositories()) {
561 final String repositoryName = repDef.getName();
562
563 final String nodetypeFile = repDef.getNodeTypeFile();
564
565 final List<String> wsList = repDef.getWorkspaces();
566 String[] workSpaces = wsList.toArray(new String[wsList.size()]);
567
568 loadRepository(repositoryName, nodetypeFile, workSpaces);
569 }
570 }
571 }
572
573
574
575
576 private void loadRepository(String repositoryNameFromModuleDescriptor, String nodeTypeFile, String[] workspaces) {
577
578 if (workspaces == null || workspaces.length == 0) {
579 log.error("Trying to register the repository {} without any workspace.", repositoryNameFromModuleDescriptor);
580 return;
581 }
582
583 final String DEFAULT_REPOSITORY_NAME = "magnolia";
584 String repositoryName = repositoryNameFromModuleDescriptor;
585
586 if (workspaces.length > 0) {
587
588 info.magnolia.repository.definition.RepositoryDefinition repositoryMapping = getRepositoryMapping(workspaces[0]);
589 if (repositoryMapping != null) {
590 repositoryName = repositoryMapping.getName();
591 }
592 }
593
594 info.magnolia.repository.definition.RepositoryDefinition rm = getRepositoryMapping(repositoryName);
595
596 if (rm == null) {
597
598 final info.magnolia.repository.definition.RepositoryDefinition defaultRepositoryMapping = getRepositoryMapping(DEFAULT_REPOSITORY_NAME);
599 final Map<String, String> defaultParameters = defaultRepositoryMapping.getParameters();
600
601 rm = new info.magnolia.repository.definition.RepositoryDefinition();
602 rm.setName(repositoryName);
603 rm.setProvider(defaultRepositoryMapping.getProvider());
604 rm.setLoadOnStartup(true);
605
606 final Map<String, String> parameters = new HashMap<>();
607 parameters.putAll(defaultParameters);
608
609
610 final String bindName = repositoryName + StringUtils.replace(defaultParameters.get("bindName"), "magnolia", "");
611 final String repositoryHome = StringUtils.substringBeforeLast(defaultParameters.get("configFile"), "/")
612 + "/"
613 + repositoryName;
614
615 parameters.put("repositoryHome", repositoryHome);
616 parameters.put("bindName", bindName);
617 parameters.put("customNodeTypes", nodeTypeFile);
618
619 rm.setParameters(parameters);
620
621 try {
622 repositoryManager.loadRepository(rm);
623 } catch (Exception e) {
624 log.error(e.getMessage(), e);
625 }
626 }
627
628 if (nodeTypeFile != null) {
629
630 registerNodeTypeFile(repositoryName, nodeTypeFile);
631
632 if (!DEFAULT_REPOSITORY_NAME.equals(repositoryName)) {
633 registerNodeTypeFile(DEFAULT_REPOSITORY_NAME, nodeTypeFile);
634 }
635 }
636
637 if (workspaces != null) {
638 for (String workspace : workspaces) {
639 if (!rm.getWorkspaces().contains(workspace)) {
640 log.debug("Loading new workspace: {}", workspace);
641
642 try {
643 repositoryManager.loadWorkspace(repositoryName, workspace);
644 } catch (RepositoryException e) {
645
646 log.error(e.getMessage(), e);
647 }
648 }
649 }
650 }
651
652 }
653
654
655
656
657
658
659 private info.magnolia.repository.definition.RepositoryDefinition getRepositoryMapping(String repositoryOrLogicalWorkspace) {
660 if (!repositoryManager.hasRepository(repositoryOrLogicalWorkspace)) {
661 WorkspaceMappingDefinition mapping = repositoryManager.getWorkspaceMapping(repositoryOrLogicalWorkspace);
662 repositoryOrLogicalWorkspace = mapping != null ? mapping.getRepositoryName() : repositoryOrLogicalWorkspace;
663 }
664 return repositoryManager.getRepositoryDefinition(repositoryOrLogicalWorkspace);
665 }
666
667
668
669
670
671
672
673 private void registerNodeTypeFile(String repositoryName, String nodeTypeFile) {
674 Provider provider = repositoryManager.getRepositoryProvider(repositoryName);
675 try {
676 provider.registerNodeTypes(nodeTypeFile);
677 } catch (RepositoryException e) {
678 log.error(e.getMessage(), e);
679 }
680 }
681 }