1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.nio.charset.Charset;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.Calendar;
23 import java.util.GregorianCalendar;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.SortedMap;
28 import java.util.TreeMap;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.apache.logging.log4j.Level;
33 import org.apache.logging.log4j.LoggingException;
34 import org.apache.logging.log4j.core.Layout;
35 import org.apache.logging.log4j.core.LogEvent;
36 import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
37 import org.apache.logging.log4j.core.config.Configuration;
38 import org.apache.logging.log4j.core.layout.internal.ExcludeChecker;
39 import org.apache.logging.log4j.core.layout.internal.IncludeChecker;
40 import org.apache.logging.log4j.core.layout.internal.ListChecker;
41 import org.apache.logging.log4j.core.config.Node;
42 import org.apache.logging.log4j.core.config.plugins.Plugin;
43 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
44 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
45 import org.apache.logging.log4j.core.config.plugins.PluginElement;
46 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
47 import org.apache.logging.log4j.core.net.Facility;
48 import org.apache.logging.log4j.core.net.Priority;
49 import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
50 import org.apache.logging.log4j.core.pattern.PatternConverter;
51 import org.apache.logging.log4j.core.pattern.PatternFormatter;
52 import org.apache.logging.log4j.core.pattern.PatternParser;
53 import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
54 import org.apache.logging.log4j.core.util.NetUtils;
55 import org.apache.logging.log4j.core.util.Patterns;
56 import org.apache.logging.log4j.message.Message;
57 import org.apache.logging.log4j.message.MessageCollectionMessage;
58 import org.apache.logging.log4j.message.StructuredDataCollectionMessage;
59 import org.apache.logging.log4j.message.StructuredDataId;
60 import org.apache.logging.log4j.message.StructuredDataMessage;
61 import org.apache.logging.log4j.util.ProcessIdUtil;
62 import org.apache.logging.log4j.util.StringBuilders;
63 import org.apache.logging.log4j.util.Strings;
64
65
66
67
68
69
70 @Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
71 public final class Rfc5424Layout extends AbstractStringLayout {
72
73
74
75
76 public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
77
78
79
80 public static final String DEFAULT_ID = "Audit";
81
82
83
84 public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
85
86
87
88 public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]");
89
90
91
92
93 public static final String DEFAULT_MDCID = "mdc";
94
95 private static final String LF = "\n";
96 private static final int TWO_DIGITS = 10;
97 private static final int THREE_DIGITS = 100;
98 private static final int MILLIS_PER_MINUTE = 60000;
99 private static final int MINUTES_PER_HOUR = 60;
100 private static final String COMPONENT_KEY = "RFC5424-Converter";
101
102 private final Facility facility;
103 private final String defaultId;
104 private final int enterpriseNumber;
105 private final boolean includeMdc;
106 private final String mdcId;
107 private final StructuredDataId mdcSdId;
108 private final String localHostName;
109 private final String appName;
110 private final String messageId;
111 private final String configName;
112 private final String mdcPrefix;
113 private final String eventPrefix;
114 private final List<String> mdcExcludes;
115 private final List<String> mdcIncludes;
116 private final List<String> mdcRequired;
117 private final ListChecker listChecker;
118 private final boolean includeNewLine;
119 private final String escapeNewLine;
120 private final boolean useTlsMessageFormat;
121
122 private long lastTimestamp = -1;
123 private String timestamppStr;
124
125 private final List<PatternFormatter> exceptionFormatters;
126 private final Map<String, FieldFormatter> fieldFormatters;
127 private final String procId;
128
129 private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final int ein,
130 final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId,
131 final String mdcPrefix, final String eventPrefix, final String appName, final String messageId,
132 final String excludes, final String includes, final String required, final Charset charset,
133 final String exceptionPattern, final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) {
134 super(charset);
135 final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class);
136 exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern);
137 this.facility = facility;
138 this.defaultId = id == null ? DEFAULT_ID : id;
139 this.enterpriseNumber = ein;
140 this.includeMdc = includeMDC;
141 this.includeNewLine = includeNL;
142 this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
143 this.mdcId = mdcId != null ? mdcId : id == null ? DEFAULT_MDCID : id;
144 this.mdcSdId = new StructuredDataId(this.mdcId, enterpriseNumber, null, null);
145 this.mdcPrefix = mdcPrefix;
146 this.eventPrefix = eventPrefix;
147 this.appName = appName;
148 this.messageId = messageId;
149 this.useTlsMessageFormat = useTLSMessageFormat;
150 this.localHostName = NetUtils.getLocalHostname();
151 ListChecker checker = null;
152 if (excludes != null) {
153 final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
154 if (array.length > 0) {
155 mdcExcludes = new ArrayList<>(array.length);
156 for (final String str : array) {
157 mdcExcludes.add(str.trim());
158 }
159 checker = new ExcludeChecker(mdcExcludes);
160 } else {
161 mdcExcludes = null;
162 }
163 } else {
164 mdcExcludes = null;
165 }
166 if (includes != null) {
167 final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
168 if (array.length > 0) {
169 mdcIncludes = new ArrayList<>(array.length);
170 for (final String str : array) {
171 mdcIncludes.add(str.trim());
172 }
173 checker = new IncludeChecker(mdcIncludes);
174 } else {
175 mdcIncludes = null;
176 }
177 } else {
178 mdcIncludes = null;
179 }
180 if (required != null) {
181 final String[] array = required.split(Patterns.COMMA_SEPARATOR);
182 if (array.length > 0) {
183 mdcRequired = new ArrayList<>(array.length);
184 for (final String str : array) {
185 mdcRequired.add(str.trim());
186 }
187 } else {
188 mdcRequired = null;
189 }
190
191 } else {
192 mdcRequired = null;
193 }
194 this.listChecker = checker != null ? checker : ListChecker.NOOP_CHECKER;
195 final String name = config == null ? null : config.getName();
196 configName = Strings.isNotEmpty(name) ? name : null;
197 this.fieldFormatters = createFieldFormatters(loggerFields, config);
198 this.procId = ProcessIdUtil.getProcessId();
199 }
200
201 private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields,
202 final Configuration config) {
203 final Map<String, FieldFormatter> sdIdMap = new HashMap<>(loggerFields == null ? 0 : loggerFields.length);
204 if (loggerFields != null) {
205 for (final LoggerFields loggerField : loggerFields) {
206 final StructuredDataId key = loggerField.getSdId() == null ? mdcSdId : loggerField.getSdId();
207 final Map<String, List<PatternFormatter>> sdParams = new HashMap<>();
208 final Map<String, String> fields = loggerField.getMap();
209 if (!fields.isEmpty()) {
210 final PatternParser fieldParser = createPatternParser(config, null);
211
212 for (final Map.Entry<String, String> entry : fields.entrySet()) {
213 final List<PatternFormatter> formatters = fieldParser.parse(entry.getValue());
214 sdParams.put(entry.getKey(), formatters);
215 }
216 final FieldFormatter fieldFormatter = new FieldFormatter(sdParams,
217 loggerField.getDiscardIfAllFieldsAreEmpty());
218 sdIdMap.put(key.toString(), fieldFormatter);
219 }
220 }
221 }
222 return sdIdMap.size() > 0 ? sdIdMap : null;
223 }
224
225
226
227
228
229
230
231
232 private static PatternParser createPatternParser(final Configuration config,
233 final Class<? extends PatternConverter> filterClass) {
234 if (config == null) {
235 return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class, filterClass);
236 }
237 PatternParser parser = config.getComponent(COMPONENT_KEY);
238 if (parser == null) {
239 parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class);
240 config.addComponent(COMPONENT_KEY, parser);
241 parser = config.getComponent(COMPONENT_KEY);
242 }
243 return parser;
244 }
245
246
247
248
249
250
251
252
253
254
255 @Override
256 public Map<String, String> getContentFormat() {
257 final Map<String, String> result = new HashMap<>();
258 result.put("structured", "true");
259 result.put("formatType", "RFC5424");
260 return result;
261 }
262
263
264
265
266
267
268
269 @Override
270 public String toSerializable(final LogEvent event) {
271 final StringBuilder buf = getStringBuilder();
272 appendPriority(buf, event.getLevel());
273 appendTimestamp(buf, event.getTimeMillis());
274 appendSpace(buf);
275 appendHostName(buf);
276 appendSpace(buf);
277 appendAppName(buf);
278 appendSpace(buf);
279 appendProcessId(buf);
280 appendSpace(buf);
281 appendMessageId(buf, event.getMessage());
282 appendSpace(buf);
283 appendStructuredElements(buf, event);
284 appendMessage(buf, event);
285 if (useTlsMessageFormat) {
286 return new TlsSyslogFrame(buf.toString()).toString();
287 }
288 return buf.toString();
289 }
290
291 private void appendPriority(final StringBuilder buffer, final Level logLevel) {
292 buffer.append('<');
293 buffer.append(Priority.getPriority(facility, logLevel));
294 buffer.append(">1 ");
295 }
296
297 private void appendTimestamp(final StringBuilder buffer, final long milliseconds) {
298 buffer.append(computeTimeStampString(milliseconds));
299 }
300
301 private void appendSpace(final StringBuilder buffer) {
302 buffer.append(' ');
303 }
304
305 private void appendHostName(final StringBuilder buffer) {
306 buffer.append(localHostName);
307 }
308
309 private void appendAppName(final StringBuilder buffer) {
310 if (appName != null) {
311 buffer.append(appName);
312 } else if (configName != null) {
313 buffer.append(configName);
314 } else {
315 buffer.append('-');
316 }
317 }
318
319 private void appendProcessId(final StringBuilder buffer) {
320 buffer.append(getProcId());
321 }
322
323 private void appendMessageId(final StringBuilder buffer, final Message message) {
324 final boolean isStructured = message instanceof StructuredDataMessage;
325 final String type = isStructured ? ((StructuredDataMessage) message).getType() : null;
326 if (type != null) {
327 buffer.append(type);
328 } else if (messageId != null) {
329 buffer.append(messageId);
330 } else {
331 buffer.append('-');
332 }
333 }
334
335 private void appendMessage(final StringBuilder buffer, final LogEvent event) {
336 final Message message = event.getMessage();
337
338 final String text = (message instanceof StructuredDataMessage || message instanceof MessageCollectionMessage)
339 ? message.getFormat() : message.getFormattedMessage();
340
341 if (text != null && text.length() > 0) {
342 buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
343 }
344
345 if (exceptionFormatters != null && event.getThrown() != null) {
346 final StringBuilder exception = new StringBuilder(LF);
347 for (final PatternFormatter formatter : exceptionFormatters) {
348 formatter.format(event, exception);
349 }
350 buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
351 }
352 if (includeNewLine) {
353 buffer.append(LF);
354 }
355 }
356
357 private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
358 final Message message = event.getMessage();
359 final boolean isStructured = message instanceof StructuredDataMessage ||
360 message instanceof StructuredDataCollectionMessage;
361
362 if (!isStructured && (fieldFormatters != null && fieldFormatters.isEmpty()) && !includeMdc) {
363 buffer.append('-');
364 return;
365 }
366
367 final Map<String, StructuredDataElement> sdElements = new HashMap<>();
368 final Map<String, String> contextMap = event.getContextData().toMap();
369
370 if (mdcRequired != null) {
371 checkRequired(contextMap);
372 }
373
374 if (fieldFormatters != null) {
375 for (final Map.Entry<String, FieldFormatter> sdElement : fieldFormatters.entrySet()) {
376 final String sdId = sdElement.getKey();
377 final StructuredDataElement elem = sdElement.getValue().format(event);
378 sdElements.put(sdId, elem);
379 }
380 }
381
382 if (includeMdc && contextMap.size() > 0) {
383 final String mdcSdIdStr = mdcSdId.toString();
384 final StructuredDataElement union = sdElements.get(mdcSdIdStr);
385 if (union != null) {
386 union.union(contextMap);
387 sdElements.put(mdcSdIdStr, union);
388 } else {
389 final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, mdcPrefix, false);
390 sdElements.put(mdcSdIdStr, formattedContextMap);
391 }
392 }
393
394 if (isStructured) {
395 if (message instanceof MessageCollectionMessage) {
396 for (final StructuredDataMessage data : ((StructuredDataCollectionMessage)message)) {
397 addStructuredData(sdElements, data);
398 }
399 } else {
400 addStructuredData(sdElements, (StructuredDataMessage) message);
401 }
402 }
403
404 if (sdElements.isEmpty()) {
405 buffer.append('-');
406 return;
407 }
408
409 for (final Map.Entry<String, StructuredDataElement> entry : sdElements.entrySet()) {
410 formatStructuredElement(entry.getKey(), entry.getValue(), buffer, listChecker);
411 }
412 }
413
414 private void addStructuredData(final Map<String, StructuredDataElement> sdElements, final StructuredDataMessage data) {
415 final Map<String, String> map = data.getData();
416 final StructuredDataId id = data.getId();
417 final String sdId = getId(id);
418
419 if (sdElements.containsKey(sdId)) {
420 final StructuredDataElement union = sdElements.get(id.toString());
421 union.union(map);
422 sdElements.put(sdId, union);
423 } else {
424 final StructuredDataElement formattedData = new StructuredDataElement(map, eventPrefix, false);
425 sdElements.put(sdId, formattedData);
426 }
427 }
428
429 private String escapeNewlines(final String text, final String replacement) {
430 if (null == replacement) {
431 return text;
432 }
433 return NEWLINE_PATTERN.matcher(text).replaceAll(replacement);
434 }
435
436 protected String getProcId() {
437 return procId;
438 }
439
440 protected List<String> getMdcExcludes() {
441 return mdcExcludes;
442 }
443
444 protected List<String> getMdcIncludes() {
445 return mdcIncludes;
446 }
447
448 private String computeTimeStampString(final long now) {
449 long last;
450 synchronized (this) {
451 last = lastTimestamp;
452 if (now == lastTimestamp) {
453 return timestamppStr;
454 }
455 }
456
457 final StringBuilder buffer = new StringBuilder();
458 final Calendar cal = new GregorianCalendar();
459 cal.setTimeInMillis(now);
460 buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
461 buffer.append('-');
462 pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
463 buffer.append('-');
464 pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
465 buffer.append('T');
466 pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
467 buffer.append(':');
468 pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
469 buffer.append(':');
470 pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
471 buffer.append('.');
472 pad(cal.get(Calendar.MILLISECOND), THREE_DIGITS, buffer);
473
474 int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
475 if (tzmin == 0) {
476 buffer.append('Z');
477 } else {
478 if (tzmin < 0) {
479 tzmin = -tzmin;
480 buffer.append('-');
481 } else {
482 buffer.append('+');
483 }
484 final int tzhour = tzmin / MINUTES_PER_HOUR;
485 tzmin -= tzhour * MINUTES_PER_HOUR;
486 pad(tzhour, TWO_DIGITS, buffer);
487 buffer.append(':');
488 pad(tzmin, TWO_DIGITS, buffer);
489 }
490 synchronized (this) {
491 if (last == lastTimestamp) {
492 lastTimestamp = now;
493 timestamppStr = buffer.toString();
494 }
495 }
496 return buffer.toString();
497 }
498
499 private void pad(final int val, int max, final StringBuilder buf) {
500 while (max > 1) {
501 if (val < max) {
502 buf.append('0');
503 }
504 max = max / TWO_DIGITS;
505 }
506 buf.append(Integer.toString(val));
507 }
508
509 private void formatStructuredElement(final String id, final StructuredDataElement data,
510 final StringBuilder sb, final ListChecker checker) {
511 if ((id == null && defaultId == null) || data.discard()) {
512 return;
513 }
514
515 sb.append('[');
516 sb.append(id);
517 if (!mdcSdId.toString().equals(id)) {
518 appendMap(data.getPrefix(), data.getFields(), sb, ListChecker.NOOP_CHECKER);
519 } else {
520 appendMap(data.getPrefix(), data.getFields(), sb, checker);
521 }
522 sb.append(']');
523 }
524
525 private String getId(final StructuredDataId id) {
526 final StringBuilder sb = new StringBuilder();
527 if (id == null || id.getName() == null) {
528 sb.append(defaultId);
529 } else {
530 sb.append(id.getName());
531 }
532 int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
533 if (ein < 0) {
534 ein = enterpriseNumber;
535 }
536 if (ein >= 0) {
537 sb.append('@').append(ein);
538 }
539 return sb.toString();
540 }
541
542 private void checkRequired(final Map<String, String> map) {
543 for (final String key : mdcRequired) {
544 final String value = map.get(key);
545 if (value == null) {
546 throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
547 }
548 }
549 }
550
551 private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb,
552 final ListChecker checker) {
553 final SortedMap<String, String> sorted = new TreeMap<>(map);
554 for (final Map.Entry<String, String> entry : sorted.entrySet()) {
555 if (checker.check(entry.getKey()) && entry.getValue() != null) {
556 sb.append(' ');
557 if (prefix != null) {
558 sb.append(prefix);
559 }
560 final String safeKey = escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine);
561 final String safeValue = escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine);
562 StringBuilders.appendKeyDqValue(sb, safeKey, safeValue);
563 }
564 }
565 }
566
567 private String escapeSDParams(final String value) {
568 return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
569 }
570
571 @Override
572 public String toString() {
573 final StringBuilder sb = new StringBuilder();
574 sb.append("facility=").append(facility.name());
575 sb.append(" appName=").append(appName);
576 sb.append(" defaultId=").append(defaultId);
577 sb.append(" enterpriseNumber=").append(enterpriseNumber);
578 sb.append(" newLine=").append(includeNewLine);
579 sb.append(" includeMDC=").append(includeMdc);
580 sb.append(" messageId=").append(messageId);
581 return sb.toString();
582 }
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608 @PluginFactory
609 public static Rfc5424Layout createLayout(
610
611 @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
612 @PluginAttribute("id") final String id,
613 @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER)
614 final int enterpriseNumber,
615 @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC,
616 @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId,
617 @PluginAttribute("mdcPrefix") final String mdcPrefix,
618 @PluginAttribute("eventPrefix") final String eventPrefix,
619 @PluginAttribute(value = "newLine") final boolean newLine,
620 @PluginAttribute("newLineEscape") final String escapeNL,
621 @PluginAttribute("appName") final String appName,
622 @PluginAttribute("messageId") final String msgId,
623 @PluginAttribute("mdcExcludes") final String excludes,
624 @PluginAttribute("mdcIncludes") String includes,
625 @PluginAttribute("mdcRequired") final String required,
626 @PluginAttribute("exceptionPattern") final String exceptionPattern,
627
628 @PluginAttribute(value = "useTlsMessageFormat") final boolean useTlsMessageFormat,
629 @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
630 @PluginConfiguration final Configuration config) {
631
632 if (includes != null && excludes != null) {
633 LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
634 includes = null;
635 }
636
637 return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId,
638 mdcPrefix, eventPrefix, appName, msgId, excludes, includes, required, StandardCharsets.UTF_8,
639 exceptionPattern, useTlsMessageFormat, loggerFields);
640 }
641
642 private class FieldFormatter {
643
644 private final Map<String, List<PatternFormatter>> delegateMap;
645 private final boolean discardIfEmpty;
646
647 public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) {
648 this.discardIfEmpty = discardIfEmpty;
649 this.delegateMap = fieldMap;
650 }
651
652 public StructuredDataElement format(final LogEvent event) {
653 final Map<String, String> map = new HashMap<>(delegateMap.size());
654
655 for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) {
656 final StringBuilder buffer = new StringBuilder();
657 for (final PatternFormatter formatter : entry.getValue()) {
658 formatter.format(event, buffer);
659 }
660 map.put(entry.getKey(), buffer.toString());
661 }
662 return new StructuredDataElement(map, eventPrefix, discardIfEmpty);
663 }
664 }
665
666 private class StructuredDataElement {
667
668 private final Map<String, String> fields;
669 private final boolean discardIfEmpty;
670 private final String prefix;
671
672 public StructuredDataElement(final Map<String, String> fields, final String prefix,
673 final boolean discardIfEmpty) {
674 this.discardIfEmpty = discardIfEmpty;
675 this.fields = fields;
676 this.prefix = prefix;
677 }
678
679 boolean discard() {
680 if (discardIfEmpty == false) {
681 return false;
682 }
683 boolean foundNotEmptyValue = false;
684 for (final Map.Entry<String, String> entry : fields.entrySet()) {
685 if (Strings.isNotEmpty(entry.getValue())) {
686 foundNotEmptyValue = true;
687 break;
688 }
689 }
690 return !foundNotEmptyValue;
691 }
692
693 void union(final Map<String, String> addFields) {
694 this.fields.putAll(addFields);
695 }
696
697 Map<String, String> getFields() {
698 return this.fields;
699 }
700
701 String getPrefix() {
702 return prefix;
703 }
704 }
705
706 public Facility getFacility() {
707 return facility;
708 }
709 }