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.juneau.rest.stats;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.util.*;
023import java.util.concurrent.atomic.*;
024
025import org.apache.juneau.commons.collections.*;
026import org.apache.juneau.cp.*;
027
028/**
029 * Represents an entry in {@link ThrownStore}.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ExecutionStatistics">REST method execution statistics</a>
033 * </ul>
034 */
035public class ThrownStats implements Cloneable {
036   /**
037    * Builder class.
038    */
039   public static class Builder {
040
041      final BeanStore beanStore;
042      Throwable throwable;
043      long hash;
044      List<String> stackTrace;
045      ThrownStats causedBy;
046
047      BeanCreator<ThrownStats> creator;
048
049      /**
050       * Constructor.
051       *
052       * @param beanStore The bean store to use for creating beans.
053       */
054      protected Builder(BeanStore beanStore) {
055         this.beanStore = beanStore;
056         this.creator = beanStore.createBean(ThrownStats.class).builder(Builder.class, this);
057      }
058
059      /**
060       * Create a new {@link ThrownStats} using this builder.
061       *
062       * @return A new {@link ThrownStats}
063       */
064      public ThrownStats build() {
065         return creator.run();
066      }
067
068      /**
069       * Specifies the caused-by exception.
070       *
071       * @param value The new value for this setting.
072       * @return This object.
073       */
074      public Builder causedBy(ThrownStats value) {
075         causedBy = value;
076         return this;
077      }
078
079      /**
080       * Specifies the calculated hash.
081       *
082       * @param value The new value for this setting.
083       * @return This object.
084       */
085      public Builder hash(long value) {
086         hash = value;
087         return this;
088      }
089
090      /**
091       * Specifies the normalized stacktrace.
092       *
093       * @param value The new value for this setting.
094       * @return This object.
095       */
096      public Builder stackTrace(List<String> value) {
097         stackTrace = value;
098         return this;
099      }
100
101      /**
102       * Specifies the thrown exception.
103       *
104       * @param value The new value for this setting.
105       * @return This object.
106       */
107      public Builder throwable(Throwable value) {
108         throwable = value;
109         return this;
110      }
111
112      /**
113       * Specifies a subclass of {@link ThrownStats} to create when the {@link #build()} method is called.
114       *
115       * @param value The new value for this setting.
116       * @return This object.
117       */
118      public Builder type(Class<? extends ThrownStats> value) {
119         creator.type(value == null ? ThrownStats.class : value);
120         return this;
121      }
122   }
123
124   /**
125    * Static creator.
126    *
127    * @param beanStore The bean store to use for creating beans.
128    * @return A new builder for this object.
129    */
130   public static Builder create(BeanStore beanStore) {
131      return new Builder(beanStore);
132   }
133
134   private final long guid;
135   private final long hash;
136   private final Class<?> thrownClass;
137   private final String firstMessage;
138   private final List<String> stackTrace;
139   private final Optional<ThrownStats> causedBy;
140
141   private final AtomicInteger count;
142   private final AtomicLong firstOccurrence, lastOccurrence;
143
144   /**
145    * Copy constructor.
146    */
147   private ThrownStats(ThrownStats x) {
148      this.guid = x.guid;
149      this.thrownClass = x.thrownClass;
150      this.firstMessage = x.firstMessage;
151      this.stackTrace = u(copyOf(x.stackTrace));
152      this.causedBy = opt(x.causedBy.isPresent() ? x.causedBy.get().clone() : null);
153      this.hash = x.hash;
154      this.count = new AtomicInteger(x.count.get());
155      this.firstOccurrence = new AtomicLong(x.firstOccurrence.get());
156      this.lastOccurrence = new AtomicLong(x.lastOccurrence.get());
157   }
158
159   /**
160    * Constructor.
161    *
162    * @param builder The builder for this object.
163    */
164   protected ThrownStats(Builder builder) {
165      this.guid = new Random().nextLong();
166      this.thrownClass = builder.throwable.getClass();
167      this.firstMessage = builder.throwable.getMessage();
168      this.stackTrace = u(copyOf(builder.stackTrace));
169      this.causedBy = opt(builder.causedBy);
170      this.hash = builder.hash;
171      this.count = new AtomicInteger(0);
172      long ct = System.currentTimeMillis();
173      this.firstOccurrence = new AtomicLong(ct);
174      this.lastOccurrence = new AtomicLong(ct);
175   }
176
177   @Override /* Overridden from Object */
178   public ThrownStats clone() {
179      return new ThrownStats(this);
180   }
181
182   /**
183    * Returns the stats on the caused-by exception.
184    *
185    * @return The stats on the caused-by exception, never <jk>null</jk>.
186    */
187   public Optional<ThrownStats> getCausedBy() { return causedBy; }
188
189   /**
190    * Returns the number of times this exception occurred at a specific location in code.
191    *
192    * @return The number of times this exception occurred at a specific location in code.
193    */
194   public int getCount() { return count.intValue(); }
195
196   /**
197    * Returns the message of the first exception at a specific location in code.
198    *
199    * @return The message of the first exception at a specific location in code.
200    */
201   public String getFirstMessage() { return firstMessage; }
202
203   /**
204    * Returns the UTC time of the first occurrence of this exception at a specific location in code.
205    *
206    * @return The UTC time of the first occurrence of this exception at a specific location in code.
207    */
208   public long getFirstOccurrence() { return firstOccurrence.longValue(); }
209
210   /**
211    * Returns a globally unique ID for this object.
212    *
213    * <p>
214    * A random long generated during the creation of this object.
215    * Allows this object to be differentiated from other similar objects in multi-node environments so that
216    * statistics can be reliably stored in a centralized location.
217    *
218    * @return The globally unique ID for this object.
219    */
220   public long getGuid() { return guid; }
221
222   /**
223    * Returns a hash of this exception that can typically be used to uniquely identify it.
224    *
225    * @return A hash of this exception.
226    */
227   public long getHash() { return hash; }
228
229   /**
230    * Returns the UTC time of the last occurrence of this exception at a specific location in code.
231    *
232    * @return The UTC time of the last occurrence of this exception at a specific location in code.
233    */
234   public long getLastOccurrence() { return lastOccurrence.longValue(); }
235
236   /**
237    * Returns the stack trace of the first exception at a specific location in code.
238    *
239    * @return The stack trace of the first exception at a specific location in code.
240    */
241   public List<String> getStackTrace() { return stackTrace; }
242
243   /**
244    * Returns the exception class.
245    *
246    * @return The exception class.
247    */
248   public Class<?> getThrownClass() { return thrownClass; }
249
250   /**
251    * Increments the occurrence count of this exception.
252    *
253    * @return This object.
254    */
255   public ThrownStats increment() {
256      count.incrementAndGet();
257      lastOccurrence.set(System.currentTimeMillis());
258      causedBy.ifPresent(ThrownStats::increment);
259      return this;
260   }
261
262   protected FluentMap<String,Object> properties() {
263      // @formatter:off
264      return filteredBeanPropertyMap()
265         .a("causedBy", causedBy.orElse(null))
266         .a("count", getCount())
267         .a("firstMessage", firstMessage)
268         .a("firstOccurrence", getFirstOccurrence())
269         .a("guid", guid)
270         .a("hash", hash)
271         .a("lastOccurrence", getLastOccurrence())
272         .a("stackTrace", stackTrace)
273         .a("thrownClass", thrownClass);
274      // @formatter:on
275   }
276
277   @Override /* Overridden from Object */
278   public String toString() {
279      return r(properties());
280   }
281}