1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.coprocessor;
21
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeSet;
31 import java.util.UUID;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.fs.Path;
39 import org.apache.hadoop.hbase.Abortable;
40 import org.apache.hadoop.hbase.Coprocessor;
41 import org.apache.hadoop.hbase.CoprocessorEnvironment;
42 import org.apache.hadoop.hbase.DoNotRetryIOException;
43 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
44 import org.apache.hadoop.hbase.HTableDescriptor;
45 import org.apache.hadoop.hbase.TableName;
46 import org.apache.hadoop.hbase.classification.InterfaceAudience;
47 import org.apache.hadoop.hbase.classification.InterfaceStability;
48 import org.apache.hadoop.hbase.client.Append;
49 import org.apache.hadoop.hbase.client.CoprocessorHConnection;
50 import org.apache.hadoop.hbase.client.Delete;
51 import org.apache.hadoop.hbase.client.Durability;
52 import org.apache.hadoop.hbase.client.Get;
53 import org.apache.hadoop.hbase.client.HConnection;
54 import org.apache.hadoop.hbase.client.HTable;
55 import org.apache.hadoop.hbase.client.HTableInterface;
56 import org.apache.hadoop.hbase.client.Increment;
57 import org.apache.hadoop.hbase.client.Put;
58 import org.apache.hadoop.hbase.client.Result;
59 import org.apache.hadoop.hbase.client.ResultScanner;
60 import org.apache.hadoop.hbase.client.Row;
61 import org.apache.hadoop.hbase.client.RowMutations;
62 import org.apache.hadoop.hbase.client.Scan;
63 import org.apache.hadoop.hbase.client.coprocessor.Batch;
64 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
65 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
66 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
67 import org.apache.hadoop.hbase.util.Bytes;
68 import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
69 import org.apache.hadoop.hbase.util.SortedList;
70 import org.apache.hadoop.hbase.util.VersionInfo;
71 import org.apache.hadoop.io.MultipleIOException;
72
73 import com.google.protobuf.Descriptors;
74 import com.google.protobuf.Message;
75 import com.google.protobuf.Service;
76 import com.google.protobuf.ServiceException;
77
78
79
80
81
82
83
84 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
85 @InterfaceStability.Evolving
86 public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
87 public static final String REGION_COPROCESSOR_CONF_KEY =
88 "hbase.coprocessor.region.classes";
89 public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
90 "hbase.coprocessor.regionserver.classes";
91 public static final String USER_REGION_COPROCESSOR_CONF_KEY =
92 "hbase.coprocessor.user.region.classes";
93 public static final String MASTER_COPROCESSOR_CONF_KEY =
94 "hbase.coprocessor.master.classes";
95 public static final String WAL_COPROCESSOR_CONF_KEY =
96 "hbase.coprocessor.wal.classes";
97 public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
98 public static final boolean DEFAULT_ABORT_ON_ERROR = true;
99 public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled";
100 public static final boolean DEFAULT_COPROCESSORS_ENABLED = true;
101 public static final String USER_COPROCESSORS_ENABLED_CONF_KEY =
102 "hbase.coprocessor.user.enabled";
103 public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true;
104
105 protected static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
106 protected Abortable abortable;
107
108 protected SortedList<E> coprocessors =
109 new SortedList<E>(new EnvironmentPriorityComparator());
110 protected Configuration conf;
111
112 protected String pathPrefix;
113 protected AtomicInteger loadSequence = new AtomicInteger();
114
115 public CoprocessorHost(Abortable abortable) {
116 this.abortable = abortable;
117 this.pathPrefix = UUID.randomUUID().toString();
118 }
119
120
121
122
123
124
125
126
127
128 private static Set<String> coprocessorNames =
129 Collections.synchronizedSet(new HashSet<String>());
130 public static Set<String> getLoadedCoprocessors() {
131 return coprocessorNames;
132 }
133
134
135
136
137
138
139
140
141 public Set<String> getCoprocessors() {
142 Set<String> returnValue = new TreeSet<String>();
143 for (CoprocessorEnvironment e: coprocessors) {
144 returnValue.add(e.getInstance().getClass().getSimpleName());
145 }
146 return returnValue;
147 }
148
149
150
151
152
153 protected void loadSystemCoprocessors(Configuration conf, String confKey) {
154 boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
155 DEFAULT_COPROCESSORS_ENABLED);
156 if (!coprocessorsEnabled) {
157 return;
158 }
159
160 Class<?> implClass = null;
161
162
163 String[] defaultCPClasses = conf.getStrings(confKey);
164 if (defaultCPClasses == null || defaultCPClasses.length == 0)
165 return;
166
167 int priority = Coprocessor.PRIORITY_SYSTEM;
168 for (String className : defaultCPClasses) {
169 className = className.trim();
170 if (findCoprocessor(className) != null) {
171
172 LOG.warn("Attempted duplicate loading of " + className + "; skipped");
173 continue;
174 }
175 ClassLoader cl = this.getClass().getClassLoader();
176 Thread.currentThread().setContextClassLoader(cl);
177 try {
178 implClass = cl.loadClass(className);
179
180
181 this.coprocessors.add(loadInstance(implClass, Coprocessor.PRIORITY_SYSTEM, conf));
182 LOG.info("System coprocessor " + className + " was loaded " +
183 "successfully with priority (" + priority++ + ").");
184 } catch (Throwable t) {
185
186 abortServer(className, t);
187 }
188 }
189 }
190
191
192
193
194
195
196
197
198
199 public E load(Path path, String className, int priority,
200 Configuration conf) throws IOException {
201 Class<?> implClass = null;
202 LOG.debug("Loading coprocessor class " + className + " with path " +
203 path + " and priority " + priority);
204
205 ClassLoader cl = null;
206 if (path == null) {
207 try {
208 implClass = getClass().getClassLoader().loadClass(className);
209 } catch (ClassNotFoundException e) {
210 throw new IOException("No jar path specified for " + className);
211 }
212 } else {
213 cl = CoprocessorClassLoader.getClassLoader(
214 path, getClass().getClassLoader(), pathPrefix, conf);
215 try {
216 implClass = cl.loadClass(className);
217 } catch (ClassNotFoundException e) {
218 throw new IOException("Cannot load external coprocessor class " + className, e);
219 }
220 }
221
222
223 Thread currentThread = Thread.currentThread();
224 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
225 try{
226
227 currentThread.setContextClassLoader(cl);
228 E cpInstance = loadInstance(implClass, priority, conf);
229 return cpInstance;
230 } finally {
231
232 currentThread.setContextClassLoader(hostClassLoader);
233 }
234 }
235
236
237
238
239
240
241
242 public void load(Class<?> implClass, int priority, Configuration conf)
243 throws IOException {
244 E env = loadInstance(implClass, priority, conf);
245 coprocessors.add(env);
246 }
247
248
249
250
251
252
253
254 public E loadInstance(Class<?> implClass, int priority, Configuration conf)
255 throws IOException {
256 if (!Coprocessor.class.isAssignableFrom(implClass)) {
257 throw new IOException("Configured class " + implClass.getName() + " must implement "
258 + Coprocessor.class.getName() + " interface ");
259 }
260
261
262 Coprocessor impl;
263 Object o = null;
264 try {
265 o = implClass.newInstance();
266 impl = (Coprocessor)o;
267 } catch (InstantiationException e) {
268 throw new IOException(e);
269 } catch (IllegalAccessException e) {
270 throw new IOException(e);
271 }
272
273 E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
274 if (env instanceof Environment) {
275 ((Environment)env).startup();
276 }
277
278
279 coprocessorNames.add(implClass.getName());
280 return env;
281 }
282
283
284
285
286 public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
287 int priority, int sequence, Configuration conf);
288
289 public void shutdown(CoprocessorEnvironment e) {
290 if (e instanceof Environment) {
291 if (LOG.isDebugEnabled()) {
292 LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
293 }
294 ((Environment)e).shutdown();
295 } else {
296 LOG.warn("Shutdown called on unknown environment: "+
297 e.getClass().getName());
298 }
299 }
300
301
302
303
304
305
306 public Coprocessor findCoprocessor(String className) {
307 for (E env: coprocessors) {
308 if (env.getInstance().getClass().getName().equals(className) ||
309 env.getInstance().getClass().getSimpleName().equals(className)) {
310 return env.getInstance();
311 }
312 }
313 return null;
314 }
315
316
317
318
319
320
321 public <T extends Coprocessor> List<T> findCoprocessors(Class<T> cls) {
322 ArrayList<T> ret = new ArrayList<T>();
323
324 for (E env: coprocessors) {
325 Coprocessor cp = env.getInstance();
326
327 if(cp != null) {
328 if (cls.isAssignableFrom(cp.getClass())) {
329 ret.add((T)cp);
330 }
331 }
332 }
333 return ret;
334 }
335
336
337
338
339
340
341 public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
342 for (E env: coprocessors) {
343 if (env.getInstance().getClass().getName().equals(className) ||
344 env.getInstance().getClass().getSimpleName().equals(className)) {
345 return env;
346 }
347 }
348 return null;
349 }
350
351
352
353
354
355
356 Set<ClassLoader> getExternalClassLoaders() {
357 Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
358 final ClassLoader systemClassLoader = this.getClass().getClassLoader();
359 for (E env : coprocessors) {
360 ClassLoader cl = env.getInstance().getClass().getClassLoader();
361 if (cl != systemClassLoader ){
362
363 externalClassLoaders.add(cl);
364 }
365 }
366 return externalClassLoaders;
367 }
368
369
370
371
372
373 static class EnvironmentPriorityComparator
374 implements Comparator<CoprocessorEnvironment> {
375 public int compare(final CoprocessorEnvironment env1,
376 final CoprocessorEnvironment env2) {
377 if (env1.getPriority() < env2.getPriority()) {
378 return -1;
379 } else if (env1.getPriority() > env2.getPriority()) {
380 return 1;
381 }
382 if (env1.getLoadSequence() < env2.getLoadSequence()) {
383 return -1;
384 } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
385 return 1;
386 }
387 return 0;
388 }
389 }
390
391
392
393
394 public static class Environment implements CoprocessorEnvironment {
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 class HTableWrapper implements HTableInterface {
411
412 private TableName tableName;
413 private HTable table;
414 private HConnection connection;
415
416 public HTableWrapper(TableName tableName, HConnection connection, ExecutorService pool)
417 throws IOException {
418 this.tableName = tableName;
419 this.table = new HTable(tableName, connection, pool);
420 this.connection = connection;
421 openTables.add(this);
422 }
423
424 void internalClose() throws IOException {
425 List<IOException> exceptions = new ArrayList<IOException>(2);
426 try {
427 table.close();
428 } catch (IOException e) {
429 exceptions.add(e);
430 }
431 try {
432
433 if (this.connection != null) {
434 this.connection.close();
435 }
436 } catch (IOException e) {
437 exceptions.add(e);
438 }
439 if (!exceptions.isEmpty()) {
440 throw MultipleIOException.createIOException(exceptions);
441 }
442 }
443
444 public Configuration getConfiguration() {
445 return table.getConfiguration();
446 }
447
448 public void close() throws IOException {
449 try {
450 internalClose();
451 } finally {
452 openTables.remove(this);
453 }
454 }
455
456 public Result getRowOrBefore(byte[] row, byte[] family)
457 throws IOException {
458 return table.getRowOrBefore(row, family);
459 }
460
461 public Result get(Get get) throws IOException {
462 return table.get(get);
463 }
464
465 public boolean exists(Get get) throws IOException {
466 return table.exists(get);
467 }
468
469 public Boolean[] exists(List<Get> gets) throws IOException{
470 return table.exists(gets);
471 }
472
473 public void put(Put put) throws IOException {
474 table.put(put);
475 }
476
477 public void put(List<Put> puts) throws IOException {
478 table.put(puts);
479 }
480
481 public void delete(Delete delete) throws IOException {
482 table.delete(delete);
483 }
484
485 public void delete(List<Delete> deletes) throws IOException {
486 table.delete(deletes);
487 }
488
489 public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
490 byte[] value, Put put) throws IOException {
491 return table.checkAndPut(row, family, qualifier, value, put);
492 }
493
494 public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
495 byte[] value, Delete delete) throws IOException {
496 return table.checkAndDelete(row, family, qualifier, value, delete);
497 }
498
499 public long incrementColumnValue(byte[] row, byte[] family,
500 byte[] qualifier, long amount) throws IOException {
501 return table.incrementColumnValue(row, family, qualifier, amount);
502 }
503
504 public long incrementColumnValue(byte[] row, byte[] family,
505 byte[] qualifier, long amount, Durability durability)
506 throws IOException {
507 return table.incrementColumnValue(row, family, qualifier, amount,
508 durability);
509 }
510
511 @Override
512 public Result append(Append append) throws IOException {
513 return table.append(append);
514 }
515
516 @Override
517 public Result increment(Increment increment) throws IOException {
518 return table.increment(increment);
519 }
520
521 public void flushCommits() throws IOException {
522 table.flushCommits();
523 }
524
525 public boolean isAutoFlush() {
526 return table.isAutoFlush();
527 }
528
529 public ResultScanner getScanner(Scan scan) throws IOException {
530 return table.getScanner(scan);
531 }
532
533 public ResultScanner getScanner(byte[] family) throws IOException {
534 return table.getScanner(family);
535 }
536
537 public ResultScanner getScanner(byte[] family, byte[] qualifier)
538 throws IOException {
539 return table.getScanner(family, qualifier);
540 }
541
542 public HTableDescriptor getTableDescriptor() throws IOException {
543 return table.getTableDescriptor();
544 }
545
546 @Override
547 public byte[] getTableName() {
548 return tableName.getName();
549 }
550
551 @Override
552 public TableName getName() {
553 return table.getName();
554 }
555
556 @Override
557 public void batch(List<? extends Row> actions, Object[] results)
558 throws IOException, InterruptedException {
559 table.batch(actions, results);
560 }
561
562
563
564
565
566
567 @Override
568 public Object[] batch(List<? extends Row> actions)
569 throws IOException, InterruptedException {
570 return table.batch(actions);
571 }
572
573 @Override
574 public <R> void batchCallback(List<? extends Row> actions, Object[] results,
575 Batch.Callback<R> callback) throws IOException, InterruptedException {
576 table.batchCallback(actions, results, callback);
577 }
578
579
580
581
582
583
584
585
586 @Override
587 public <R> Object[] batchCallback(List<? extends Row> actions,
588 Batch.Callback<R> callback) throws IOException, InterruptedException {
589 return table.batchCallback(actions, callback);
590 }
591
592 @Override
593 public Result[] get(List<Get> gets) throws IOException {
594 return table.get(gets);
595 }
596
597 @Override
598 public CoprocessorRpcChannel coprocessorService(byte[] row) {
599 return table.coprocessorService(row);
600 }
601
602 @Override
603 public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
604 byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
605 throws ServiceException, Throwable {
606 return table.coprocessorService(service, startKey, endKey, callable);
607 }
608
609 @Override
610 public <T extends Service, R> void coprocessorService(Class<T> service,
611 byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
612 throws ServiceException, Throwable {
613 table.coprocessorService(service, startKey, endKey, callable, callback);
614 }
615
616 @Override
617 public void mutateRow(RowMutations rm) throws IOException {
618 table.mutateRow(rm);
619 }
620
621 @Override
622 public void setAutoFlush(boolean autoFlush) {
623 table.setAutoFlush(autoFlush, autoFlush);
624 }
625
626 @Override
627 public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) {
628 table.setAutoFlush(autoFlush, clearBufferOnFail);
629 }
630
631 @Override
632 public void setAutoFlushTo(boolean autoFlush) {
633 table.setAutoFlushTo(autoFlush);
634 }
635
636 @Override
637 public long getWriteBufferSize() {
638 return table.getWriteBufferSize();
639 }
640
641 @Override
642 public void setWriteBufferSize(long writeBufferSize) throws IOException {
643 table.setWriteBufferSize(writeBufferSize);
644 }
645
646 @Override
647 public long incrementColumnValue(byte[] row, byte[] family,
648 byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
649 return table.incrementColumnValue(row, family, qualifier, amount, writeToWAL);
650 }
651
652 @Override
653 public <R extends Message> Map<byte[], R> batchCoprocessorService(
654 Descriptors.MethodDescriptor method, Message request, byte[] startKey,
655 byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
656 return table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype);
657 }
658
659 @Override
660 public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
661 Message request, byte[] startKey, byte[] endKey, R responsePrototype,
662 Callback<R> callback) throws ServiceException, Throwable {
663 table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype,
664 callback);
665 }
666
667 @Override
668 public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
669 CompareOp compareOp, byte[] value, RowMutations mutation) throws IOException {
670 return table.checkAndMutate(row, family, qualifier, compareOp, value, mutation);
671 }
672 }
673
674
675 public Coprocessor impl;
676
677 protected int priority = Coprocessor.PRIORITY_USER;
678
679 Coprocessor.State state = Coprocessor.State.UNINSTALLED;
680
681 protected List<HTableInterface> openTables =
682 Collections.synchronizedList(new ArrayList<HTableInterface>());
683 private int seq;
684 private Configuration conf;
685 private ClassLoader classLoader;
686
687
688
689
690
691
692 public Environment(final Coprocessor impl, final int priority,
693 final int seq, final Configuration conf) {
694 this.impl = impl;
695 this.classLoader = impl.getClass().getClassLoader();
696 this.priority = priority;
697 this.state = Coprocessor.State.INSTALLED;
698 this.seq = seq;
699 this.conf = conf;
700 }
701
702
703 public void startup() throws IOException {
704 if (state == Coprocessor.State.INSTALLED ||
705 state == Coprocessor.State.STOPPED) {
706 state = Coprocessor.State.STARTING;
707 Thread currentThread = Thread.currentThread();
708 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
709 try {
710 currentThread.setContextClassLoader(this.getClassLoader());
711 impl.start(this);
712 state = Coprocessor.State.ACTIVE;
713 } finally {
714 currentThread.setContextClassLoader(hostClassLoader);
715 }
716 } else {
717 LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
718 " because not inactive (state="+state.toString()+")");
719 }
720 }
721
722
723 protected void shutdown() {
724 if (state == Coprocessor.State.ACTIVE) {
725 state = Coprocessor.State.STOPPING;
726 Thread currentThread = Thread.currentThread();
727 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
728 try {
729 currentThread.setContextClassLoader(this.getClassLoader());
730 impl.stop(this);
731 state = Coprocessor.State.STOPPED;
732 } catch (IOException ioe) {
733 LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
734 } finally {
735 currentThread.setContextClassLoader(hostClassLoader);
736 }
737 } else {
738 LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
739 " because not active (state="+state.toString()+")");
740 }
741
742 for (HTableInterface table: openTables) {
743 try {
744 ((HTableWrapper)table).internalClose();
745 } catch (IOException e) {
746
747 LOG.warn("Failed to close " +
748 Bytes.toStringBinary(table.getTableName()), e);
749 }
750 }
751 }
752
753 @Override
754 public Coprocessor getInstance() {
755 return impl;
756 }
757
758 @Override
759 public ClassLoader getClassLoader() {
760 return classLoader;
761 }
762
763 @Override
764 public int getPriority() {
765 return priority;
766 }
767
768 @Override
769 public int getLoadSequence() {
770 return seq;
771 }
772
773
774 @Override
775 public int getVersion() {
776 return Coprocessor.VERSION;
777 }
778
779
780 @Override
781 public String getHBaseVersion() {
782 return VersionInfo.getVersion();
783 }
784
785 @Override
786 public Configuration getConfiguration() {
787 return conf;
788 }
789
790
791
792
793
794
795
796 @Override
797 public HTableInterface getTable(TableName tableName) throws IOException {
798 return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
799 }
800
801
802
803
804
805
806
807 @Override
808 public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
809 return new HTableWrapper(tableName, CoprocessorHConnection.getConnectionForEnvironment(this),
810 pool);
811 }
812 }
813
814 protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
815 abortServer(environment.getInstance().getClass().getName(), e);
816 }
817
818 protected void abortServer(final String coprocessorName, final Throwable e) {
819 String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
820 LOG.error(message, e);
821 if (abortable != null) {
822 abortable.abort(message, e);
823 } else {
824 LOG.warn("No available Abortable, process was not aborted");
825 }
826 }
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843 protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
844 throws IOException {
845 if (e instanceof IOException) {
846 throw (IOException)e;
847 }
848
849
850
851
852
853
854 if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
855
856 abortServer(env, e);
857 } else {
858 LOG.error("Removing coprocessor '" + env.toString() + "' from " +
859 "environment because it threw: " + e,e);
860 coprocessors.remove(env);
861 try {
862 shutdown(env);
863 } catch (Exception x) {
864 LOG.error("Uncaught exception when shutting down coprocessor '"
865 + env.toString() + "'", x);
866 }
867 throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
868 "' threw: '" + e + "' and has been removed from the active " +
869 "coprocessor set.", e);
870 }
871 }
872 }