View Javadoc
1   /*
2    * Copyright 2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.openehealth.ipf.commons.ihe.core.payload;
17  
18  import org.apache.commons.io.FileUtils;
19  import org.apache.commons.io.IOUtils;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import java.io.*;
24  import java.util.concurrent.atomic.AtomicInteger;
25  import java.util.concurrent.atomic.AtomicLong;
26  import java.util.stream.Collectors;
27  import java.util.stream.Stream;
28  
29  import static java.util.Objects.requireNonNull;
30  
31  
32  /**
33   * Base class for interceptors which store incoming and outgoing payload
34   * into files with user-defined name patterns, or to the regular Java log.
35   * <p>
36   * File name patterns can contain absolute and relative paths and must correspond to the
37   * <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html">SpEL</a>
38   * syntax, using square brackets for referencing placeholder parameters.
39   * In the base version, the following parameters are supported
40   * (this set can be extended in derived classes):
41   * <ul>
42   * <li><tt>sequenceId</tt>&nbsp;&mdash; internally generated sequential ID
43   * as a 12-digit positive long int, zero-padded.</li>
44   * <li><tt>processId</tt>&nbsp;&mdash; process ID consisting from the OS process
45   * number and the host name, e.g. <tt>"12345-myhostname"</tt>.</li>
46   * <li><tt>date('format_spec')</tt>&nbsp;&mdash; current date and time, formatted
47   * using {@link java.text.SimpleDateFormat} according to the given specification.</li>
48   * </ul>
49   * <br>
50   * Example of a file name pattern:<br>
51   * <tt>C:/IPF-LOGS/[processId]/[date('yyyyMMdd-HH00')]/[sequenceId]-server-output.txt</tt>
52   * <br>
53   * After a pre-configured count of failed trials to create a file, the logger will be switched off.
54   * <p>
55   * As an alternative to SpEL, the user can provide another {@link ExpressionResolver expression resolver}.
56   * <p>
57   * Furthermore, the behavior of this class is regulated application-widely by the following Boolean
58   * system properties:
59   * <ul>
60   * <li><tt>org.openehealth.ipf.commons.ihe.core.payload.PayloadLoggerBase.CONSOLE</tt>&nbsp;&mdash;
61   * when set to <code>true</code>, then the message payload will be logged using regular
62   * Java logging mechanisms  (level DEBUG) instead of being written into files whose names
63   * are created from the pattern.</li>
64   * <li><tt>org.openehealth.ipf.commons.ihe.core.payload.PayloadLoggerBase.DISABLED</tt>&nbsp;&mdash;
65   * when set to <code>true</code>, then no logging will be performed at all.</li>
66   * </ul>
67   *
68   * @author Dmytro Rud
69   */
70  abstract public class PayloadLoggerBase<T extends PayloadLoggingContext> {
71      private static final transient Logger LOG = LoggerFactory.getLogger(PayloadLoggerBase.class);
72  
73      private static final AtomicLong SEQUENCE_ID_GENERATOR = new AtomicLong(0L);
74  
75      // CXF message property
76      public static final String SEQUENCE_ID_PROPERTY_NAME =
77              PayloadLoggerBase.class.getName() + ".sequence.id";
78  
79      // Java system properties
80      public static final String PROPERTY_CONSOLE = PayloadLoggerBase.class.getName() + ".CONSOLE";
81      public static final String PROPERTY_DISABLED = PayloadLoggerBase.class.getName() + ".DISABLED";
82  
83      private boolean enabled = true;
84  
85      private int errorCountLimit = -1;
86      private AtomicInteger errorCount = new AtomicInteger(0);
87  
88      private ExpressionResolver resolver;
89  
90      protected static Long getNextSequenceId() {
91          return SEQUENCE_ID_GENERATOR.getAndIncrement();
92      }
93  
94      protected void doLogPayload(T context, String charsetName, String... payloadPieces) {
95          // check whether we can process
96          if (!canProcess()) {
97              return;
98          }
99          if ((errorCountLimit >= 0) && (errorCount.get() >= errorCountLimit)) {
100             LOG.warn("Error count limit has bean reached, reset the counter to enable further trials");
101             return;
102         }
103 
104         if (Boolean.getBoolean(PROPERTY_CONSOLE)) {
105             // use regular Java logging
106             if (LOG.isDebugEnabled()) {
107                 String output = Stream.of(payloadPieces).collect(Collectors.joining());
108                 LOG.debug(output);
109             }
110         } else {
111             // compute the file path and write payload pieces into this file
112             String path = resolver.resolveExpression(context);
113             Writer writer = null;
114             try {
115                 FileOutputStream outputStream = FileUtils.openOutputStream(new File(path), true);
116                 writer = (charsetName != null) ?
117                         new OutputStreamWriter(outputStream, charsetName) :
118                         new OutputStreamWriter(outputStream);
119                 for (String payloadPiece : payloadPieces) {
120                     writer.write(payloadPiece);
121                 }
122                 errorCount.set(0);
123             } catch (IOException e) {
124                 errorCount.incrementAndGet();
125                 LOG.warn("Cannot write into " + path, e);
126             } finally {
127                 IOUtils.closeQuietly(writer);
128             }
129         }
130     }
131 
132 
133     public boolean canProcess() {
134         if ((!enabled) || Boolean.getBoolean(PROPERTY_DISABLED)) {
135             LOG.trace("Message payload logging is disabled");
136             return false;
137         }
138         return true;
139     }
140 
141 
142     /**
143      * Resets count of occurred errors, can be used e.g. via JMX.
144      */
145     public void resetErrorCount() {
146         errorCount.set(0);
147     }
148 
149     /**
150      * @return <code>true</code> if this logging interceptor instance is enabled.
151      */
152     public boolean isEnabled() {
153         return enabled;
154     }
155 
156     /**
157      * @param enabled <code>true</code> when this logging interceptor instance should be enabled.
158      */
159     public void setEnabled(boolean enabled) {
160         this.enabled = enabled;
161     }
162 
163     /**
164      * @return <code>true</code> if this logging interceptor instance is enabled.
165      * @deprecated use {@link #isEnabled()}
166      */
167     @Deprecated
168     public boolean isLocallyEnabled() {
169         return isEnabled();
170     }
171 
172     /**
173      * @param locallyEnabled <code>true</code> when this logging interceptor instance should be enabled.
174      * @deprecated use {@link #setEnabled(boolean)}
175      */
176     @Deprecated
177     public void setLocallyEnabled(boolean locallyEnabled) {
178         setEnabled(locallyEnabled);
179     }
180 
181     /**
182      * @return <code>true</code> when logging interceptors are generally enabled.
183      * @see #isLocallyEnabled()
184      * @deprecated use environment variable {@link #PROPERTY_DISABLED}
185      */
186     public static boolean isGloballyEnabled() {
187         return !Boolean.getBoolean(PROPERTY_DISABLED);
188     }
189 
190     /**
191      * @param globallyEnabled <code>true</code> when logging interceptors shall be generally enabled.
192      * @see #setLocallyEnabled(boolean)
193      * @deprecated use environment variable {@link #PROPERTY_DISABLED}
194      */
195     public static void setGloballyEnabled(boolean globallyEnabled) {
196         System.setProperty(PROPERTY_DISABLED, Boolean.toString(!globallyEnabled));
197     }
198 
199     /**
200      * @return maximal allowed count of file creation errors,
201      * negative value (the default) means "no limit".
202      */
203     public int getErrorCountLimit() {
204         return errorCountLimit;
205     }
206 
207     /**
208      * Configures maximal allowed count of file creation errors.
209      *
210      * @param errorCountLimit maximal allowed count of file creation errors,
211      *                        negative value (the default) means "no limit".
212      */
213     public void setErrorCountLimit(int errorCountLimit) {
214         this.errorCountLimit = errorCountLimit;
215     }
216 
217     public ExpressionResolver getExpressionResolver() {
218         return resolver;
219     }
220 
221     public void setExpressionResolver(ExpressionResolver resolver) {
222         this.resolver = requireNonNull(resolver);
223     }
224 }