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