001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.broker.jmx; 018 019import org.apache.activemq.broker.region.policy.SlowConsumerEntry; 020import org.apache.activemq.broker.scheduler.Job; 021import org.apache.activemq.command.ActiveMQBlobMessage; 022import org.apache.activemq.command.ActiveMQBytesMessage; 023import org.apache.activemq.command.ActiveMQMapMessage; 024import org.apache.activemq.command.ActiveMQMessage; 025import org.apache.activemq.command.ActiveMQObjectMessage; 026import org.apache.activemq.command.ActiveMQStreamMessage; 027import org.apache.activemq.command.ActiveMQTextMessage; 028import org.fusesource.hawtbuf.UTF8Buffer; 029 030import javax.jms.DeliveryMode; 031import javax.jms.JMSException; 032import javax.management.openmbean.ArrayType; 033import javax.management.openmbean.CompositeData; 034import javax.management.openmbean.CompositeDataSupport; 035import javax.management.openmbean.CompositeType; 036import javax.management.openmbean.OpenDataException; 037import javax.management.openmbean.OpenType; 038import javax.management.openmbean.SimpleType; 039import javax.management.openmbean.TabularDataSupport; 040import javax.management.openmbean.TabularType; 041import java.io.IOException; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.Date; 045import java.util.HashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049 050public final class OpenTypeSupport { 051 052 public interface OpenTypeFactory { 053 CompositeType getCompositeType() throws OpenDataException; 054 055 Map<String, Object> getFields(Object o) throws OpenDataException; 056 } 057 058 private static final Map<Class, AbstractOpenTypeFactory> OPEN_TYPE_FACTORIES = new HashMap<Class, AbstractOpenTypeFactory>(); 059 060 public abstract static class AbstractOpenTypeFactory implements OpenTypeFactory { 061 062 private CompositeType compositeType; 063 private final List<String> itemNamesList = new ArrayList<String>(); 064 private final List<String> itemDescriptionsList = new ArrayList<String>(); 065 private final List<OpenType> itemTypesList = new ArrayList<OpenType>(); 066 067 public CompositeType getCompositeType() throws OpenDataException { 068 if (compositeType == null) { 069 init(); 070 compositeType = createCompositeType(); 071 } 072 return compositeType; 073 } 074 075 protected void init() throws OpenDataException { 076 } 077 078 protected CompositeType createCompositeType() throws OpenDataException { 079 String[] itemNames = itemNamesList.toArray(new String[itemNamesList.size()]); 080 String[] itemDescriptions = itemDescriptionsList.toArray(new String[itemDescriptionsList.size()]); 081 OpenType[] itemTypes = itemTypesList.toArray(new OpenType[itemTypesList.size()]); 082 return new CompositeType(getTypeName(), getDescription(), itemNames, itemDescriptions, itemTypes); 083 } 084 085 protected abstract String getTypeName(); 086 087 protected void addItem(String name, String description, OpenType type) { 088 itemNamesList.add(name); 089 itemDescriptionsList.add(description); 090 itemTypesList.add(type); 091 } 092 093 protected String getDescription() { 094 return getTypeName(); 095 } 096 097 public Map<String, Object> getFields(Object o) throws OpenDataException { 098 Map<String, Object> rc = new HashMap<String, Object>(); 099 return rc; 100 } 101 } 102 103 static class MessageOpenTypeFactory extends AbstractOpenTypeFactory { 104 protected TabularType stringPropertyTabularType; 105 protected TabularType booleanPropertyTabularType; 106 protected TabularType bytePropertyTabularType; 107 protected TabularType shortPropertyTabularType; 108 protected TabularType intPropertyTabularType; 109 protected TabularType longPropertyTabularType; 110 protected TabularType floatPropertyTabularType; 111 protected TabularType doublePropertyTabularType; 112 113 @Override 114 protected String getTypeName() { 115 return ActiveMQMessage.class.getName(); 116 } 117 118 @Override 119 protected void init() throws OpenDataException { 120 super.init(); 121 addItem("JMSCorrelationID", "JMSCorrelationID", SimpleType.STRING); 122 addItem("JMSDestination", "JMSDestination", SimpleType.STRING); 123 addItem("JMSMessageID", "JMSMessageID", SimpleType.STRING); 124 addItem("JMSReplyTo", "JMSReplyTo", SimpleType.STRING); 125 addItem("JMSType", "JMSType", SimpleType.STRING); 126 addItem("JMSDeliveryMode", "JMSDeliveryMode", SimpleType.STRING); 127 addItem("JMSExpiration", "JMSExpiration", SimpleType.LONG); 128 addItem("JMSPriority", "JMSPriority", SimpleType.INTEGER); 129 addItem("JMSRedelivered", "JMSRedelivered", SimpleType.BOOLEAN); 130 addItem("JMSTimestamp", "JMSTimestamp", SimpleType.DATE); 131 addItem(CompositeDataConstants.JMSXGROUP_ID, "Message Group ID", SimpleType.STRING); 132 addItem(CompositeDataConstants.JMSXGROUP_SEQ, "Message Group Sequence Number", SimpleType.INTEGER); 133 addItem(CompositeDataConstants.JMSXUSER_ID, "The user that sent the message", SimpleType.STRING); 134 addItem(CompositeDataConstants.BROKER_PATH, "Brokers traversed", SimpleType.STRING); 135 addItem(CompositeDataConstants.ORIGINAL_DESTINATION, "Original Destination Before Senting To DLQ", SimpleType.STRING); 136 addItem(CompositeDataConstants.PROPERTIES, "User Properties Text", SimpleType.STRING); 137 138 // now lets expose the type safe properties 139 stringPropertyTabularType = createTabularType(String.class, SimpleType.STRING); 140 booleanPropertyTabularType = createTabularType(Boolean.class, SimpleType.BOOLEAN); 141 bytePropertyTabularType = createTabularType(Byte.class, SimpleType.BYTE); 142 shortPropertyTabularType = createTabularType(Short.class, SimpleType.SHORT); 143 intPropertyTabularType = createTabularType(Integer.class, SimpleType.INTEGER); 144 longPropertyTabularType = createTabularType(Long.class, SimpleType.LONG); 145 floatPropertyTabularType = createTabularType(Float.class, SimpleType.FLOAT); 146 doublePropertyTabularType = createTabularType(Double.class, SimpleType.DOUBLE); 147 148 addItem(CompositeDataConstants.STRING_PROPERTIES, "User String Properties", stringPropertyTabularType); 149 addItem(CompositeDataConstants.BOOLEAN_PROPERTIES, "User Boolean Properties", booleanPropertyTabularType); 150 addItem(CompositeDataConstants.BYTE_PROPERTIES, "User Byte Properties", bytePropertyTabularType); 151 addItem(CompositeDataConstants.SHORT_PROPERTIES, "User Short Properties", shortPropertyTabularType); 152 addItem(CompositeDataConstants.INT_PROPERTIES, "User Integer Properties", intPropertyTabularType); 153 addItem(CompositeDataConstants.LONG_PROPERTIES, "User Long Properties", longPropertyTabularType); 154 addItem(CompositeDataConstants.FLOAT_PROPERTIES, "User Float Properties", floatPropertyTabularType); 155 addItem(CompositeDataConstants.DOUBLE_PROPERTIES, "User Double Properties", doublePropertyTabularType); 156 } 157 158 @Override 159 public Map<String, Object> getFields(Object o) throws OpenDataException { 160 ActiveMQMessage m = (ActiveMQMessage)o; 161 Map<String, Object> rc = super.getFields(o); 162 rc.put("JMSCorrelationID", m.getJMSCorrelationID()); 163 rc.put("JMSDestination", "" + m.getJMSDestination()); 164 rc.put("JMSMessageID", m.getJMSMessageID()); 165 rc.put("JMSReplyTo",toString(m.getJMSReplyTo())); 166 rc.put("JMSType", m.getJMSType()); 167 rc.put("JMSDeliveryMode", m.getJMSDeliveryMode() == DeliveryMode.PERSISTENT ? "PERSISTENT" : "NON-PERSISTENT"); 168 rc.put("JMSExpiration", Long.valueOf(m.getJMSExpiration())); 169 rc.put("JMSPriority", Integer.valueOf(m.getJMSPriority())); 170 rc.put("JMSRedelivered", Boolean.valueOf(m.getJMSRedelivered())); 171 rc.put("JMSTimestamp", new Date(m.getJMSTimestamp())); 172 rc.put(CompositeDataConstants.JMSXGROUP_ID, m.getGroupID()); 173 rc.put(CompositeDataConstants.JMSXGROUP_SEQ, m.getGroupSequence()); 174 rc.put(CompositeDataConstants.JMSXUSER_ID, m.getUserID()); 175 rc.put(CompositeDataConstants.BROKER_PATH, Arrays.toString(m.getBrokerPath())); 176 rc.put(CompositeDataConstants.ORIGINAL_DESTINATION, toString(m.getOriginalDestination())); 177 try { 178 rc.put(CompositeDataConstants.PROPERTIES, "" + m.getProperties()); 179 } catch (IOException e) { 180 rc.put(CompositeDataConstants.PROPERTIES, ""); 181 } 182 183 try { 184 rc.put(CompositeDataConstants.STRING_PROPERTIES, createTabularData(m, stringPropertyTabularType, String.class)); 185 } catch (IOException e) { 186 rc.put(CompositeDataConstants.STRING_PROPERTIES, new TabularDataSupport(stringPropertyTabularType)); 187 } 188 try { 189 rc.put(CompositeDataConstants.BOOLEAN_PROPERTIES, createTabularData(m, booleanPropertyTabularType, Boolean.class)); 190 } catch (IOException e) { 191 rc.put(CompositeDataConstants.BOOLEAN_PROPERTIES, new TabularDataSupport(booleanPropertyTabularType)); 192 } 193 try { 194 rc.put(CompositeDataConstants.BYTE_PROPERTIES, createTabularData(m, bytePropertyTabularType, Byte.class)); 195 } catch (IOException e) { 196 rc.put(CompositeDataConstants.BYTE_PROPERTIES, new TabularDataSupport(bytePropertyTabularType)); 197 } 198 try { 199 rc.put(CompositeDataConstants.SHORT_PROPERTIES, createTabularData(m, shortPropertyTabularType, Short.class)); 200 } catch (IOException e) { 201 rc.put(CompositeDataConstants.SHORT_PROPERTIES, new TabularDataSupport(shortPropertyTabularType)); 202 } 203 try { 204 rc.put(CompositeDataConstants.INT_PROPERTIES, createTabularData(m, intPropertyTabularType, Integer.class)); 205 } catch (IOException e) { 206 rc.put(CompositeDataConstants.INT_PROPERTIES, new TabularDataSupport(intPropertyTabularType)); 207 } 208 try { 209 rc.put(CompositeDataConstants.LONG_PROPERTIES, createTabularData(m, longPropertyTabularType, Long.class)); 210 } catch (IOException e) { 211 rc.put(CompositeDataConstants.LONG_PROPERTIES, new TabularDataSupport(longPropertyTabularType)); 212 } 213 try { 214 rc.put(CompositeDataConstants.FLOAT_PROPERTIES, createTabularData(m, floatPropertyTabularType, Float.class)); 215 } catch (IOException e) { 216 rc.put(CompositeDataConstants.FLOAT_PROPERTIES, new TabularDataSupport(floatPropertyTabularType)); 217 } 218 try { 219 rc.put(CompositeDataConstants.DOUBLE_PROPERTIES, createTabularData(m, doublePropertyTabularType, Double.class)); 220 } catch (IOException e) { 221 rc.put(CompositeDataConstants.DOUBLE_PROPERTIES, new TabularDataSupport(doublePropertyTabularType)); 222 } 223 return rc; 224 } 225 226 protected String toString(Object value) { 227 if (value == null) { 228 return null; 229 } 230 return value.toString(); 231 } 232 233 234 protected <T> TabularType createTabularType(Class<T> type, OpenType openType) throws OpenDataException { 235 String typeName = "java.util.Map<java.lang.String, " + type.getName() + ">"; 236 String[] keyValue = new String[]{"key", "value"}; 237 OpenType[] openTypes = new OpenType[]{SimpleType.STRING, openType}; 238 CompositeType rowType = new CompositeType(typeName, typeName, keyValue, keyValue, openTypes); 239 return new TabularType(typeName, typeName, rowType, new String[]{"key"}); 240 } 241 242 protected TabularDataSupport createTabularData(ActiveMQMessage m, TabularType type, Class valueType) throws IOException, OpenDataException { 243 TabularDataSupport answer = new TabularDataSupport(type); 244 Set<Map.Entry<String,Object>> entries = m.getProperties().entrySet(); 245 for (Map.Entry<String, Object> entry : entries) { 246 Object value = entry.getValue(); 247 if (value instanceof UTF8Buffer && valueType.equals(String.class)) { 248 String actual = value.toString(); 249 CompositeDataSupport compositeData = createTabularRowValue(type, entry.getKey(), actual); 250 answer.put(compositeData); 251 } 252 if (valueType.isInstance(value)) { 253 CompositeDataSupport compositeData = createTabularRowValue(type, entry.getKey(), value); 254 answer.put(compositeData); 255 } 256 } 257 return answer; 258 } 259 260 protected CompositeDataSupport createTabularRowValue(TabularType type, String key, Object value) throws OpenDataException { 261 Map<String,Object> fields = new HashMap<String, Object>(); 262 fields.put("key", key); 263 fields.put("value", value); 264 return new CompositeDataSupport(type.getRowType(), fields); 265 } 266 } 267 268 static class ByteMessageOpenTypeFactory extends MessageOpenTypeFactory { 269 270 271 @Override 272 protected String getTypeName() { 273 return ActiveMQBytesMessage.class.getName(); 274 } 275 276 @Override 277 protected void init() throws OpenDataException { 278 super.init(); 279 addItem(CompositeDataConstants.BODY_LENGTH, "Body length", SimpleType.LONG); 280 addItem(CompositeDataConstants.BODY_PREVIEW, "Body preview", new ArrayType(1, SimpleType.BYTE)); 281 } 282 283 @Override 284 public Map<String, Object> getFields(Object o) throws OpenDataException { 285 ActiveMQBytesMessage m = (ActiveMQBytesMessage)o; 286 m.setReadOnlyBody(true); 287 Map<String, Object> rc = super.getFields(o); 288 long length = 0; 289 try { 290 length = m.getBodyLength(); 291 rc.put(CompositeDataConstants.BODY_LENGTH, Long.valueOf(length)); 292 } catch (JMSException e) { 293 rc.put(CompositeDataConstants.BODY_LENGTH, Long.valueOf(0)); 294 } 295 try { 296 byte preview[] = new byte[(int)Math.min(length, 255)]; 297 m.readBytes(preview); 298 m.reset(); 299 300 // This is whack! Java 1.5 JMX spec does not support primitive 301 // arrays! 302 // In 1.6 it seems it is supported.. but until then... 303 Byte data[] = new Byte[preview.length]; 304 for (int i = 0; i < data.length; i++) { 305 data[i] = new Byte(preview[i]); 306 } 307 308 rc.put(CompositeDataConstants.BODY_PREVIEW, data); 309 } catch (JMSException e) { 310 rc.put(CompositeDataConstants.BODY_PREVIEW, new Byte[] {}); 311 } 312 return rc; 313 } 314 315 } 316 317 static class MapMessageOpenTypeFactory extends MessageOpenTypeFactory { 318 319 @Override 320 protected String getTypeName() { 321 return ActiveMQMapMessage.class.getName(); 322 } 323 324 @Override 325 protected void init() throws OpenDataException { 326 super.init(); 327 addItem(CompositeDataConstants.CONTENT_MAP, "Content map", SimpleType.STRING); 328 } 329 330 @Override 331 public Map<String, Object> getFields(Object o) throws OpenDataException { 332 ActiveMQMapMessage m = (ActiveMQMapMessage)o; 333 Map<String, Object> rc = super.getFields(o); 334 try { 335 rc.put(CompositeDataConstants.CONTENT_MAP, "" + m.getContentMap()); 336 } catch (JMSException e) { 337 rc.put(CompositeDataConstants.CONTENT_MAP, ""); 338 } 339 return rc; 340 } 341 } 342 343 static class ObjectMessageOpenTypeFactory extends MessageOpenTypeFactory { 344 @Override 345 protected String getTypeName() { 346 return ActiveMQObjectMessage.class.getName(); 347 } 348 349 @Override 350 protected void init() throws OpenDataException { 351 super.init(); 352 } 353 354 @Override 355 public Map<String, Object> getFields(Object o) throws OpenDataException { 356 Map<String, Object> rc = super.getFields(o); 357 return rc; 358 } 359 } 360 361 static class StreamMessageOpenTypeFactory extends MessageOpenTypeFactory { 362 @Override 363 protected String getTypeName() { 364 return ActiveMQStreamMessage.class.getName(); 365 } 366 367 @Override 368 protected void init() throws OpenDataException { 369 super.init(); 370 } 371 372 @Override 373 public Map<String, Object> getFields(Object o) throws OpenDataException { 374 Map<String, Object> rc = super.getFields(o); 375 return rc; 376 } 377 } 378 379 static class TextMessageOpenTypeFactory extends MessageOpenTypeFactory { 380 381 @Override 382 protected String getTypeName() { 383 return ActiveMQTextMessage.class.getName(); 384 } 385 386 @Override 387 protected void init() throws OpenDataException { 388 super.init(); 389 addItem(CompositeDataConstants.MESSAGE_TEXT, CompositeDataConstants.MESSAGE_TEXT, SimpleType.STRING); 390 } 391 392 @Override 393 public Map<String, Object> getFields(Object o) throws OpenDataException { 394 ActiveMQTextMessage m = (ActiveMQTextMessage)o; 395 Map<String, Object> rc = super.getFields(o); 396 try { 397 rc.put(CompositeDataConstants.MESSAGE_TEXT, "" + m.getText()); 398 } catch (JMSException e) { 399 rc.put(CompositeDataConstants.MESSAGE_TEXT, ""); 400 } 401 return rc; 402 } 403 } 404 405 406 static class JobOpenTypeFactory extends AbstractOpenTypeFactory { 407 408 @Override 409 protected String getTypeName() { 410 return Job.class.getName(); 411 } 412 413 @Override 414 protected void init() throws OpenDataException { 415 super.init(); 416 addItem("jobId", "jobId", SimpleType.STRING); 417 addItem("cronEntry", "Cron entry", SimpleType.STRING); 418 addItem("start", "start time", SimpleType.STRING); 419 addItem("delay", "initial delay", SimpleType.LONG); 420 addItem("next", "next time", SimpleType.STRING); 421 addItem("period", "period between jobs", SimpleType.LONG); 422 addItem("repeat", "number of times to repeat", SimpleType.INTEGER); 423 } 424 425 @Override 426 public Map<String, Object> getFields(Object o) throws OpenDataException { 427 Job job = (Job) o; 428 Map<String, Object> rc = super.getFields(o); 429 rc.put("jobId", job.getJobId()); 430 rc.put("cronEntry", "" + job.getCronEntry()); 431 rc.put("start", job.getStartTime()); 432 rc.put("delay", job.getDelay()); 433 rc.put("next", job.getNextExecutionTime()); 434 rc.put("period", job.getPeriod()); 435 rc.put("repeat", job.getRepeat()); 436 return rc; 437 } 438 } 439 440 static class ActiveMQBlobMessageOpenTypeFactory extends MessageOpenTypeFactory { 441 442 @Override 443 protected String getTypeName() { 444 return ActiveMQBlobMessage.class.getName(); 445 } 446 447 @Override 448 protected void init() throws OpenDataException { 449 super.init(); 450 addItem(CompositeDataConstants.MESSAGE_URL, "Body Url", SimpleType.STRING); 451 } 452 453 @Override 454 public Map<String, Object> getFields(Object o) throws OpenDataException { 455 ActiveMQBlobMessage m = (ActiveMQBlobMessage)o; 456 Map<String, Object> rc = super.getFields(o); 457 try { 458 rc.put(CompositeDataConstants.MESSAGE_URL, "" + m.getURL().toString()); 459 } catch (JMSException e) { 460 rc.put(CompositeDataConstants.MESSAGE_URL, ""); 461 } 462 return rc; 463 } 464 } 465 466 static class SlowConsumerEntryOpenTypeFactory extends AbstractOpenTypeFactory { 467 @Override 468 protected String getTypeName() { 469 return SlowConsumerEntry.class.getName(); 470 } 471 472 @Override 473 protected void init() throws OpenDataException { 474 super.init(); 475 addItem("subscription", "the subscription view", SimpleType.OBJECTNAME); 476 addItem("slowCount", "number of times deemed slow", SimpleType.INTEGER); 477 addItem("markCount", "number of periods remaining slow", SimpleType.INTEGER); 478 } 479 480 @Override 481 public Map<String, Object> getFields(Object o) throws OpenDataException { 482 SlowConsumerEntry entry = (SlowConsumerEntry) o; 483 Map<String, Object> rc = super.getFields(o); 484 rc.put("subscription", entry.getSubscription()); 485 rc.put("slowCount", Integer.valueOf(entry.getSlowCount())); 486 rc.put("markCount", Integer.valueOf(entry.getMarkCount())); 487 return rc; 488 } 489 } 490 491 static class HealthStatusOpenTypeFactory extends AbstractOpenTypeFactory { 492 @Override 493 protected String getTypeName() { 494 return HealthStatus.class.getName(); 495 } 496 497 @Override 498 protected void init() throws OpenDataException { 499 super.init(); 500 addItem("healthId", "health check id", SimpleType.STRING); 501 addItem("level", "severity", SimpleType.STRING); 502 addItem("message", "severity", SimpleType.STRING); 503 addItem("resource", "event resource", SimpleType.STRING); 504 } 505 506 @Override 507 public Map<String, Object> getFields(Object o) throws OpenDataException { 508 HealthStatus event = (HealthStatus) o; 509 Map<String, Object> rc = super.getFields(o); 510 rc.put("healthId", event.getHealthId()); 511 rc.put("level", event.getLevel()); 512 rc.put("message", event.getMessage()); 513 rc.put("resource", event.getResource()); 514 return rc; 515 } 516 } 517 518 static { 519 OPEN_TYPE_FACTORIES.put(ActiveMQMessage.class, new MessageOpenTypeFactory()); 520 OPEN_TYPE_FACTORIES.put(ActiveMQBytesMessage.class, new ByteMessageOpenTypeFactory()); 521 OPEN_TYPE_FACTORIES.put(ActiveMQMapMessage.class, new MapMessageOpenTypeFactory()); 522 OPEN_TYPE_FACTORIES.put(ActiveMQObjectMessage.class, new ObjectMessageOpenTypeFactory()); 523 OPEN_TYPE_FACTORIES.put(ActiveMQStreamMessage.class, new StreamMessageOpenTypeFactory()); 524 OPEN_TYPE_FACTORIES.put(ActiveMQTextMessage.class, new TextMessageOpenTypeFactory()); 525 OPEN_TYPE_FACTORIES.put(Job.class, new JobOpenTypeFactory()); 526 OPEN_TYPE_FACTORIES.put(SlowConsumerEntry.class, new SlowConsumerEntryOpenTypeFactory()); 527 OPEN_TYPE_FACTORIES.put(ActiveMQBlobMessage.class, new ActiveMQBlobMessageOpenTypeFactory()); 528 OPEN_TYPE_FACTORIES.put(HealthStatus.class, new HealthStatusOpenTypeFactory()); 529 } 530 531 private OpenTypeSupport() { 532 } 533 534 public static OpenTypeFactory getFactory(Class<?> clazz) throws OpenDataException { 535 return OPEN_TYPE_FACTORIES.get(clazz); 536 } 537 538 public static CompositeData convert(Object message) throws OpenDataException { 539 OpenTypeFactory f = getFactory(message.getClass()); 540 if (f == null) { 541 throw new OpenDataException("Cannot create a CompositeData for type: " + message.getClass().getName()); 542 } 543 CompositeType ct = f.getCompositeType(); 544 Map<String, Object> fields = f.getFields(message); 545 return new CompositeDataSupport(ct, fields); 546 } 547 548}