1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package jgroup.relacs.config;
20
21 import java.io.Externalizable;
22 import java.io.IOException;
23 import java.io.ObjectInput;
24 import java.io.ObjectOutput;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.NoSuchElementException;
32 import java.util.StringTokenizer;
33
34 import jgroup.core.ConfigurationException;
35 import jgroup.core.MemberId;
36 import jgroup.core.View;
37 import jgroup.core.arm.DistributionScheme;
38 import jgroup.core.arm.RecoveryStrategy;
39 import jgroup.relacs.types.ViewImpl;
40
41 import org.apache.log4j.Logger;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.Node;
44 import org.w3c.dom.NodeList;
45
46
47
48
49
50
51
52
53
54 public class AppConfig
55 implements Externalizable, ConfigurationObject
56 {
57
58 private static final long serialVersionUID = 1335688016655019665L;
59
60
61
62
63
64
65
66 private static final Logger log = Logger.getLogger(AppConfig.class);
67
68
69
70
71
72
73
74 private static final String NAME_ATTRIB = "name";
75 private static final String GROUP_ATTRIB = "group";
76
77 private static final String CLASS_TAG_NAME = "Class";
78 private static final String EXEC_JVM_ATTRIB = "inExecJVM";
79 private static final String ARGS_ATTRIB = "args";
80
81 private static final String PARAM_TAG_NAME = "Param";
82 private static final String VALUE_ATTRIB = "value";
83
84 private static final String LAYERSTACK_TAG_NAME = "LayerStack";
85 private static final String ORDER_ATTRIB = "order";
86
87 private static final String SERVICE_TAG_NAME = "Service";
88 private static final String AUTO_ATTRIB = "auto";
89
90 private static final String RECOVERYSTRATEGY_TAG_NAME = "RecoveryStrategy";
91 private static final String REDUNDANCY_TAG_NAME = "Redundancy";
92 private static final String MINIMAL_ATTRIB = "minimal";
93 private static final String INITIAL_ATTRIB = "initial";
94
95
96
97
98
99
100
101
102
103
104 private final static int REDUNDANCY_LOWER_BOUND = 1;
105 private final static int REDUNDANCY_UPPER_BOUND = 20;
106
107
108
109
110
111
112 private static final int APPLICATION_HASH_SIZE = 37;
113
114
115
116
117
118
119
120 private static Map<String,AppConfig> appMap =
121 new HashMap<String,AppConfig>(APPLICATION_HASH_SIZE);
122
123
124 private static Map<String,AppConfig> appClazzMap =
125 new HashMap<String,AppConfig>(APPLICATION_HASH_SIZE);
126
127
128 private static Map<Integer,AppConfig> appGroupMap =
129 new HashMap<Integer,AppConfig>(APPLICATION_HASH_SIZE);
130
131
132
133
134
135
136
137 private transient RecoveryStrategy recoveryStrategy = null;
138
139
140 private static DistributionScheme dScheme = null;
141
142
143
144
145
146
147
148 private String regName;
149
150
151 private int groupId;
152
153
154 private ClassData classData;
155
156
157 private List<String> serviceSet;
158
159
160 private Map<String,String> paramMap;
161
162
163
164
165
166
167
168 private boolean inExecJVM;
169
170
171 private Class<?> strategyClass;
172
173
174 private int initialRedundancy = -1;
175
176
177 private int minimalRedundancy = -1;
178
179
180 private View view;
181
182
183 private HostSet viewHosts = new HostSet();
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public AppConfig(String regName, String className, String args, int groupId, boolean inExecJVM)
209
210 {
211 this.regName = regName;
212 this.groupId = groupId;
213 this.view = new ViewImpl(groupId);
214 StringTokenizer argTokens = new StringTokenizer(args);
215 String[] classArgs = new String[argTokens.countTokens()];
216 for (int i = 0; argTokens.hasMoreTokens(); i++)
217 classArgs[i] = argTokens.nextToken();
218 classData = new ClassData(className, classArgs);
219 this.inExecJVM = inExecJVM;
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242 public static AppConfig addApplication(String regname, String classname, String args,
243 int group, boolean inExecJVM)
244 throws ConfigurationException
245 {
246
247
248
249
250
251 if (appClazzMap.containsKey(classname))
252 throw new ConfigurationException("Class name already defined: " + classname);
253 AppConfig app = new AppConfig(regname, classname, args, group, inExecJVM);
254
255
256
257
258
259
260 appMap.put(regname, app);
261 appClazzMap.put(classname, app);
262 appGroupMap.put(new Integer(group), app);
263 return app;
264 }
265
266
267
268
269
270
271
272
273 public static void addApplications(AppConfig[] apps)
274 throws ConfigurationException
275 {
276 for (int i = 0; i < apps.length; i++) {
277 AppConfig app = apps[i];
278 String classname = app.getClassName();
279 if (appClazzMap.containsKey(classname))
280 throw new ConfigurationException("Class name already defined: " + classname);
281 appMap.put(app.getRegistryName(), app);
282 appClazzMap.put(classname, app);
283 appGroupMap.put(new Integer(app.getGroupId()), app);
284 }
285 }
286
287
288
289
290
291 public static AppConfig[] getApplications()
292 {
293 return (AppConfig[]) appMap.values().toArray(new AppConfig[appMap.size()]);
294 }
295
296
297
298
299
300
301
302
303
304
305
306 public static AppConfig getApplication(String name)
307 {
308 AppConfig app = (AppConfig) appMap.get(name);
309 if (app == null) {
310 app = (AppConfig) appClazzMap.get(name);
311 if (app == null)
312 throw new NullPointerException("Unknown application: " + name);
313 }
314 return app;
315 }
316
317
318
319
320
321
322
323
324
325
326 public static AppConfig getApplication(Object appObj)
327 {
328 if (appObj == null)
329 throw new NullPointerException("No application object specified");
330 AppConfig app = (AppConfig) appClazzMap.get(appObj.getClass().getName());
331 if (app == null)
332 throw new NullPointerException("Unknown application: " + appObj.getClass().getName());
333 return app;
334 }
335
336
337
338
339
340
341
342
343
344
345 public static AppConfig getApplication(Class cl)
346 {
347 AppConfig app = (AppConfig) appClazzMap.get(cl.getName());
348 if (app == null)
349 throw new NullPointerException("Unknown application: " + cl.getName());
350 return app;
351 }
352
353
354
355
356
357
358
359
360
361
362 public static AppConfig getApplication(int gid)
363 {
364 AppConfig app = (AppConfig) appGroupMap.get(new Integer(gid));
365 if (app == null)
366 throw new NullPointerException("Unknown application for group: " + gid);
367 return app;
368 }
369
370
371
372
373
374
375 public static void setDistributionScheme(DistributionScheme distScheme)
376 {
377 dScheme = distScheme;
378 }
379
380
381
382
383
384
385
386
387
388 public void parse(Element elm)
389 throws ConfigurationException
390 {
391
392
393
394 String regname = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
395 int group = ConfigParser.getIntAttrib(elm, GROUP_ATTRIB, true);
396 AppConfig app = null;
397
398
399
400
401
402 NodeList tagList = elm.getChildNodes();
403
404
405 for (int i = 1; i <= tagList.getLength(); i++) {
406 Node node = tagList.item(i);
407 if (node != null && !node.getNodeName().startsWith("#")) {
408 elm = (Element)node;
409
410 if (ConfigParser.checkTag(elm, CLASS_TAG_NAME)) {
411 String classname = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
412 boolean inExecJVM = ConfigParser.getBooleanAttrib(elm, EXEC_JVM_ATTRIB, false);
413 String args = ConfigParser.getAttrib(elm, ARGS_ATTRIB, false);
414
415 app = AppConfig.addApplication(regname, classname, args, group, inExecJVM);
416
417 } else if (ConfigParser.checkTag(elm, PARAM_TAG_NAME)) {
418 String pName = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
419 String pValue = ConfigParser.getAttrib(elm, VALUE_ATTRIB, true);
420 app.addParam(pName, pValue);
421
422 } else if (ConfigParser.checkTag(elm, LAYERSTACK_TAG_NAME)) {
423 if (!parseServiceSet(app, elm)) {
424
425
426
427
428 String stackOrder = ConfigParser.getAttrib(elm, ORDER_ATTRIB, true);
429 app.setServiceSet(stackOrder);
430 }
431
432 } else if (ConfigParser.checkTag(elm, RECOVERYSTRATEGY_TAG_NAME)) {
433 parseRecoveryStrategy(app, elm);
434 }
435 }
436 }
437 }
438
439
440
441
442
443
444 private boolean parseServiceSet(AppConfig app, Element elm)
445 throws ConfigurationException
446 {
447 NodeList serviceList = elm.getChildNodes();
448 if (serviceList.getLength() == 0) {
449 return false;
450 }
451
452
453 for (int i = 1; i <= serviceList.getLength(); i++) {
454 Node node = serviceList.item(i);
455 if (node != null && !node.getNodeName().startsWith("#")) {
456 elm = (Element)node;
457 if (ConfigParser.checkTag(elm, SERVICE_TAG_NAME)) {
458 String serviceName = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
459 boolean auto = ConfigParser.getBooleanAttrib(elm, AUTO_ATTRIB, false);
460 if (auto)
461 app.addParam(serviceName + "." + AUTO_ATTRIB, Boolean.toString(auto));
462 app.addService(serviceName);
463 parseServiceParams(serviceName, app, elm);
464 }
465 }
466 }
467 return true;
468 }
469
470
471 private void parseServiceParams(String serviceName, AppConfig app, Element elm)
472 throws ConfigurationException
473 {
474 NodeList paramList = elm.getChildNodes();
475
476
477 for (int i = 1; i <= paramList.getLength(); i++) {
478 Node node = paramList.item(i);
479 if (node != null && !node.getNodeName().startsWith("#")) {
480 elm = (Element)node;
481 if (ConfigParser.checkTag(elm, PARAM_TAG_NAME)) {
482 String pName = serviceName + "." + ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
483 String pValue = ConfigParser.getAttrib(elm, VALUE_ATTRIB, true);
484 app.addParam(pName, pValue);
485 }
486 }
487 }
488 }
489
490
491 private void parseRecoveryStrategy(AppConfig app, Element elm)
492 throws ConfigurationException
493 {
494 int initial = 0, minimal = 0;
495
496 String strategy = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
497
498 NodeList redundancyList = elm.getChildNodes();
499
500
501 for (int i=1; i <= redundancyList.getLength(); i++) {
502 Node node = redundancyList.item(i);
503 if (node != null && !node.getNodeName().startsWith("#")) {
504 elm = (Element)node;
505 if (ConfigParser.checkTag(elm, REDUNDANCY_TAG_NAME)) {
506 initial = ConfigParser.getIntAttrib(elm, INITIAL_ATTRIB, true);
507 minimal = ConfigParser.getIntAttrib(elm, MINIMAL_ATTRIB, true);
508 }
509 }
510 }
511 app.setRecoveryStrategy(strategy);
512
513
514
515
516 String minimalStr = System.getProperty("minimal.redundancy");
517 String initialStr = System.getProperty("initial.redundancy");
518 if (minimalStr != null && minimalStr.length() > 0 && !minimalStr.equals("default")) {
519 minimal = Integer.parseInt(minimalStr);
520 }
521 if (initialStr != null && initialStr.length() > 0 && !initialStr.equals("default")) {
522 initial = Integer.parseInt(initialStr);
523 }
524 app.setRedundancy(minimal, initial);
525 }
526
527
528
529
530
531
532
533
534
535
536 public String getRegistryName()
537 {
538 return regName;
539 }
540
541
542
543
544
545
546
547
548 public ClassData getClassData()
549 {
550 return classData;
551 }
552
553
554
555
556
557
558
559 public void setProperties(Map<String,String> properties)
560 {
561 if (properties == null) {
562
563
564
565
566 properties = new HashMap<String,String>(3);
567 }
568 if (!properties.containsKey("minimal.redundancy"))
569 properties.put("minimal.redundancy", Integer.toString(getMinimalRedundancy()));
570 if (!properties.containsKey("initial.redundancy"))
571 properties.put("initial.redundancy", Integer.toString(getInitialRedundancy()));
572 classData.setProperties(properties);
573 }
574
575
576 public int getGroupId()
577 {
578 return groupId;
579 }
580
581
582
583
584
585
586
587
588 public String getClassName()
589 {
590 return classData.getName();
591 }
592
593
594
595
596
597
598
599
600 public String[] getClassArgs()
601 {
602 return classData.getArgs();
603 }
604
605
606
607
608
609
610
611
612 public boolean startInExecJVM()
613 {
614 return inExecJVM;
615 }
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631 public String getParam(String name)
632 throws NoSuchElementException
633 {
634 if (paramMap == null) {
635 throw new NoSuchElementException("The application has no associated parameters");
636 }
637 String strval = (String) paramMap.get(name);
638 if (strval == null) {
639 throw new NoSuchElementException("No parameter named '" + name
640 + "' found to be associated with application: " + regName);
641 }
642 return strval;
643 }
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659 public String getParam(String name, String defaultVal)
660 {
661 if (paramMap == null) {
662 return defaultVal;
663 }
664 String strval = (String) paramMap.get(name);
665 if (strval == null) {
666 return defaultVal;
667 }
668 return strval;
669 }
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 public int getIntParam(String name)
686 throws NoSuchElementException
687 {
688 return Integer.valueOf(getParam(name)).intValue();
689 }
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707 public int getIntParam(String name, int defaultValue)
708 {
709 if (paramMap == null) {
710 return defaultValue;
711 }
712 String strval = (String) paramMap.get(name);
713 if (strval == null) {
714 return defaultValue;
715 }
716 return Integer.valueOf(strval).intValue();
717 }
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732 public boolean getBooleanParam(String name)
733 {
734 if (paramMap == null) {
735 return false;
736 }
737 String strval = (String) paramMap.get(name);
738 if (strval == null) {
739 return false;
740 }
741 return Boolean.valueOf(strval).booleanValue();
742 }
743
744
745
746
747
748
749
750
751
752
753
754 public String[] getServiceSet()
755 {
756 if (serviceSet == null) {
757 throw new IllegalStateException("Service set not yet initialized.");
758 }
759 return (String[]) serviceSet.toArray(new String[serviceSet.size()]);
760 }
761
762
763
764
765
766
767
768
769
770
771
772 public boolean hasService(String service)
773 {
774 if (serviceSet == null) {
775 throw new IllegalStateException("Service set not yet initialized.");
776 }
777 return serviceSet.contains(service);
778 }
779
780
781 public RecoveryStrategy getRecoveryStrategy()
782 {
783 if (strategyClass == null)
784 throw new IllegalStateException("RecoveryStrategy not set");
785 if (recoveryStrategy == null) {
786 try {
787 initRecoveryStrategy();
788 } catch (ConfigurationException e) {
789 throw new IllegalStateException(e.getMessage());
790 }
791 }
792 return recoveryStrategy;
793 }
794
795
796
797
798
799
800
801
802
803
804
805
806 public void setRedundancy(int minimal, int initial)
807 throws ConfigurationException
808 {
809 if (initial < minimal)
810 throw new ConfigurationException("Initial redundancy is less than the minimal redundancy.");
811
812 if (initial >= REDUNDANCY_LOWER_BOUND && initial <= REDUNDANCY_UPPER_BOUND)
813 initialRedundancy = initial;
814 else if (initial > REDUNDANCY_UPPER_BOUND)
815 throw new ConfigurationException("Initial redundancy is too high.");
816 else
817 throw new ConfigurationException("Initial redundancy is too low.");
818
819 if (minimal >= REDUNDANCY_LOWER_BOUND && minimal <= REDUNDANCY_UPPER_BOUND)
820 minimalRedundancy = minimal;
821 else if (minimal > REDUNDANCY_UPPER_BOUND)
822 throw new ConfigurationException("Minimal redundancy is too high.");
823 else
824 throw new ConfigurationException("Minimal redundancy is too low.");
825 }
826
827 public int getInitialRedundancy()
828 {
829 if (initialRedundancy == -1)
830 throw new IllegalStateException("Inital redundancy is not set: " + this);
831 return initialRedundancy;
832 }
833
834
835 public int getMinimalRedundancy()
836 {
837 if (minimalRedundancy == -1)
838 throw new IllegalStateException("Minimal redundancy is not set");
839 return minimalRedundancy;
840 }
841
842
843 public int getCurrentGroupSize()
844 {
845 return view.size();
846 }
847
848
849
850
851
852 public boolean needsRecovery()
853 {
854 RecoveryStrategy rs = getRecoveryStrategy();
855 return rs.needsRecovery();
856 }
857
858
859
860
861
862
863
864
865 public synchronized void viewChange(View view)
866 {
867 if (!this.view.equals(view)) {
868 this.view = view;
869
870
871 viewHosts.removeAllHosts();
872 MemberId[] members = view.getMembers();
873 for (int i = 0; i < members.length; i++) {
874 Host host = DistributedSystemConfig.getHost(members[i]);
875
876 host.viewChange(this);
877 viewHosts.addHost(host);
878 }
879 }
880 }
881
882
883
884
885
886
887 public View getView()
888 {
889 return view;
890 }
891
892
893
894
895
896 public HostSet getViewHosts()
897 {
898 return viewHosts;
899 }
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914 public void setRecoveryStrategy(String strategy)
915 throws ConfigurationException
916 {
917
918
919
920
921 strategy = (strategy.indexOf(".") != -1) ? strategy : "jgroup.arm.recovery." + strategy;
922 try {
923 strategyClass = Class.forName(strategy);
924 } catch (ClassNotFoundException e) {
925 throw new ConfigurationException("Unsupported recovery strategy specified: " + strategy);
926 }
927 }
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942 public void setServiceSet(String stackOrder)
943 throws ConfigurationException
944 {
945 String[] stack = stackOrder.split(":");
946 for (int i = 0; i < stack.length; i++) {
947 addService(stack[i]);
948 }
949 }
950
951
952
953
954
955
956
957
958
959
960 public void addService(String serviceName)
961 throws ConfigurationException
962 {
963 if (serviceSet == null) {
964 serviceSet = new ArrayList<String>();
965 }
966 serviceSet.add(serviceName);
967 }
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982 public void addParam(String name, String value)
983 throws ConfigurationException
984 {
985 if (paramMap == null) {
986 paramMap = new HashMap<String,String>();
987 } else {
988 if (paramMap.containsKey(name)) {
989 throw new ConfigurationException("Multiply defined parameter name: " + name);
990 }
991 }
992 paramMap.put(name, value);
993 }
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016 private void initRecoveryStrategy()
1017 throws ConfigurationException
1018 {
1019 if (dScheme == null) {
1020 throw new IllegalStateException("Cannot initialize recovery strategy "
1021 + "without providing a distribution scheme.");
1022 }
1023 try {
1024
1025
1026
1027
1028 if (recoveryStrategy != null)
1029 return;
1030
1031
1032
1033
1034
1035
1036
1037 Constructor<?> c = strategyClass.getConstructor( new Class[] {} );
1038 recoveryStrategy = (RecoveryStrategy) c.newInstance( new Object[] {} );
1039
1040 recoveryStrategy.initialize(dScheme, this);
1041
1042 } catch (NoSuchMethodException e) {
1043 throw new ConfigurationException(strategyClass + " does not implement a non-argument constructor.");
1044 } catch (IllegalArgumentException e) {
1045 throw new ConfigurationException(strategyClass + " does not implement a non-argument constructor.");
1046 } catch (IllegalAccessException e) {
1047 throw new ConfigurationException(strategyClass + " does not provide an accessible constructor.");
1048 } catch (InstantiationException e) {
1049 throw new ConfigurationException(strategyClass + " represents an abstract class.");
1050 } catch (InvocationTargetException e) {
1051 throw new ConfigurationException(strategyClass + " has thrown an exception.", e.getTargetException());
1052 }
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063 public int hashCode()
1064 {
1065 return regName.hashCode() ^ classData.hashCode() ^ groupId;
1066 }
1067
1068
1069
1070
1071
1072 public boolean equals(Object obj)
1073 {
1074 if (!(obj instanceof AppConfig)) {
1075 return false;
1076 }
1077 AppConfig app = (AppConfig) obj;
1078 return (regName.equals(app.regName) &&
1079 classData.equals(app.classData) &&
1080 groupId == app.groupId);
1081 }
1082
1083
1084
1085
1086
1087 public String toString()
1088 {
1089 return toString(false);
1090 }
1091
1092
1093
1094
1095
1096
1097
1098 public String toString(boolean full)
1099 {
1100 StringBuilder buf = new StringBuilder();
1101 if (full) {
1102 buf.append("[AppConfig: ");
1103 buf.append(regName);
1104 buf.append(", gid=");
1105 buf.append(groupId);
1106 buf.append(": ");
1107 }
1108 buf.append(classData.getShortName());
1109 buf.append(": ");
1110 if (full) {
1111 String[] srSet = getServiceSet();
1112 for (int i = 0; i < srSet.length; i++) {
1113 buf.append((i != 0) ? ":" : "");
1114 buf.append(srSet[i]);
1115 }
1116 buf.append("; ");
1117 if (strategyClass != null)
1118 buf.append(strategyClass.getName());
1119 buf.append(", ");
1120 }
1121 buf.append("{");
1122 buf.append(initialRedundancy);
1123 buf.append(", ");
1124 buf.append(minimalRedundancy);
1125 buf.append("}, viewHosts=");
1126 buf.append(viewHosts);
1127 if (full)
1128 buf.append("]");
1129 return buf.toString();
1130 }
1131
1132
1133
1134
1135
1136
1137
1138
1139 public AppConfig() { }
1140
1141
1142
1143
1144
1145 @SuppressWarnings("unchecked")
1146 public void readExternal(ObjectInput in)
1147 throws IOException, ClassNotFoundException
1148 {
1149 regName = in.readUTF();
1150 groupId = in.readInt();
1151 classData = (ClassData) in.readObject();
1152 serviceSet = (List) in.readObject();
1153 paramMap = (Map) in.readObject();
1154 inExecJVM = in.readBoolean();
1155 strategyClass = (Class) in.readObject();
1156 initialRedundancy = in.readInt();
1157 minimalRedundancy = in.readInt();
1158 view = new ViewImpl();
1159 view.readExternal(in);
1160 viewChange(view);
1161 if (log.isDebugEnabled())
1162 log.debug("Received AppConfig object: " + toString());
1163
1164
1165
1166
1167
1168 appMap.put(regName, this);
1169 appClazzMap.put(classData.getName(), this);
1170 appGroupMap.put(new Integer(groupId), this);
1171 }
1172
1173
1174
1175
1176
1177 public void writeExternal(ObjectOutput out)
1178 throws IOException
1179 {
1180 out.writeUTF(regName);
1181 out.writeInt(groupId);
1182 out.writeObject(classData);
1183 out.writeObject(serviceSet);
1184 out.writeObject(paramMap);
1185 out.writeBoolean(inExecJVM);
1186 out.writeObject(strategyClass);
1187 out.writeInt(initialRedundancy);
1188 out.writeInt(minimalRedundancy);
1189 view.writeExternal(out);
1190 if (log.isDebugEnabled())
1191 log.debug("Sending AppConfig object: " + toString());
1192 }
1193
1194 }