├── .gitignore
├── .mvn
├── jvm.config
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── LICENSE
├── README.adoc
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
└── java
│ └── org
│ └── springframework
│ └── analytics
│ ├── metrics
│ ├── AggregateCounter.java
│ ├── AggregateCounterReader.java
│ ├── AggregateCounterRepository.java
│ ├── AggregateCounterResolution.java
│ ├── AggregateCounterWriter.java
│ ├── FieldValueCounter.java
│ ├── FieldValueCounterReader.java
│ ├── FieldValueCounterRepository.java
│ ├── FieldValueCounterWriter.java
│ ├── MetricUtils.java
│ ├── memory
│ │ ├── InMemoryAggregateCounter.java
│ │ ├── InMemoryAggregateCounterRepository.java
│ │ └── InMemoryFieldValueCounterRepository.java
│ └── redis
│ │ ├── AggregateKeyGenerator.java
│ │ ├── RedisAggregateCounterRepository.java
│ │ ├── RedisFieldValueCounterRepository.java
│ │ └── RedisMetricRepository.java
│ ├── rest
│ ├── controller
│ │ ├── AggregateCounterController.java
│ │ ├── CounterController.java
│ │ ├── FieldValueCounterController.java
│ │ ├── NoSuchMetricException.java
│ │ └── package-info.java
│ ├── domain
│ │ ├── AggregateCounterResource.java
│ │ ├── CounterResource.java
│ │ ├── Delta.java
│ │ ├── FieldValueCounterResource.java
│ │ ├── Metric.java
│ │ ├── MetricResource.java
│ │ └── package-info.java
│ └── package-info.java
│ └── retry
│ ├── LoggingRecoveryCallback.java
│ ├── RedisRetryTemplate.java
│ └── StringRedisRetryTemplate.java
└── test
└── java
└── org
└── springframework
└── analytics
├── rest
└── controller
│ ├── AggregateCounterControllerTests.java
│ ├── CounterControllerTests.java
│ └── FieldValueCounterControllerTests.java
└── test
└── support
├── AbstractExternalResourceTestSupport.java
├── CounterService.java
├── DefaultCounterService.java
└── RedisTestSupport.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .#*
3 | *#
4 | *.sw*
5 | _site/
6 | .factorypath
7 | .gradletasknamecache
8 | .DS_Store
9 | /application.yml
10 | /application.properties
11 | asciidoctor.css
12 | atlassian-ide-plugin.xml
13 | bin/
14 | build/
15 | dump.rdb
16 | out
17 | spring-shell.log
18 | target/
19 | test-output
20 |
21 | # Eclipse artifacts, including WTP generated manifests
22 | .classpath
23 | .project
24 | .settings/
25 | .springBeans
26 | spring-*/src/main/java/META-INF/MANIFEST.MF
27 |
28 | # IDEA artifacts and output dirs
29 | *.iml
30 | *.ipr
31 | *.iws
32 | .idea/*
33 | rebel.xml
34 |
--------------------------------------------------------------------------------
/.mvn/jvm.config:
--------------------------------------------------------------------------------
1 | -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spring-attic/spring-analytics/880ec3475c2a266b67ad459d63f3bc0179860665/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | # spring-analytics is no longer actively maintained by VMware, Inc.
2 |
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spring-analytics
2 | Common libraries for adding 'nosql' style analytics to your application
3 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | #
58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59 | # for the new JDKs provided by Oracle.
60 | #
61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62 | #
63 | # Apple JDKs
64 | #
65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66 | fi
67 |
68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69 | #
70 | # Apple JDKs
71 | #
72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73 | fi
74 |
75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76 | #
77 | # Oracle JDKs
78 | #
79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80 | fi
81 |
82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83 | #
84 | # Apple JDKs
85 | #
86 | export JAVA_HOME=`/usr/libexec/java_home`
87 | fi
88 | ;;
89 | esac
90 |
91 | if [ -z "$JAVA_HOME" ] ; then
92 | if [ -r /etc/gentoo-release ] ; then
93 | JAVA_HOME=`java-config --jre-home`
94 | fi
95 | fi
96 |
97 | if [ -z "$M2_HOME" ] ; then
98 | ## resolve links - $0 may be a link to maven's home
99 | PRG="$0"
100 |
101 | # need this for relative symlinks
102 | while [ -h "$PRG" ] ; do
103 | ls=`ls -ld "$PRG"`
104 | link=`expr "$ls" : '.*-> \(.*\)$'`
105 | if expr "$link" : '/.*' > /dev/null; then
106 | PRG="$link"
107 | else
108 | PRG="`dirname "$PRG"`/$link"
109 | fi
110 | done
111 |
112 | saveddir=`pwd`
113 |
114 | M2_HOME=`dirname "$PRG"`/..
115 |
116 | # make it fully qualified
117 | M2_HOME=`cd "$M2_HOME" && pwd`
118 |
119 | cd "$saveddir"
120 | # echo Using m2 at $M2_HOME
121 | fi
122 |
123 | # For Cygwin, ensure paths are in UNIX format before anything is touched
124 | if $cygwin ; then
125 | [ -n "$M2_HOME" ] &&
126 | M2_HOME=`cygpath --unix "$M2_HOME"`
127 | [ -n "$JAVA_HOME" ] &&
128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129 | [ -n "$CLASSPATH" ] &&
130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131 | fi
132 |
133 | # For Migwn, ensure paths are in UNIX format before anything is touched
134 | if $mingw ; then
135 | [ -n "$M2_HOME" ] &&
136 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
137 | [ -n "$JAVA_HOME" ] &&
138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139 | # TODO classpath?
140 | fi
141 |
142 | if [ -z "$JAVA_HOME" ]; then
143 | javaExecutable="`which javac`"
144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145 | # readlink(1) is not available as standard on Solaris 10.
146 | readLink=`which readlink`
147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148 | if $darwin ; then
149 | javaHome="`dirname \"$javaExecutable\"`"
150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151 | else
152 | javaExecutable="`readlink -f \"$javaExecutable\"`"
153 | fi
154 | javaHome="`dirname \"$javaExecutable\"`"
155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156 | JAVA_HOME="$javaHome"
157 | export JAVA_HOME
158 | fi
159 | fi
160 | fi
161 |
162 | if [ -z "$JAVACMD" ] ; then
163 | if [ -n "$JAVA_HOME" ] ; then
164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165 | # IBM's JDK on AIX uses strange locations for the executables
166 | JAVACMD="$JAVA_HOME/jre/sh/java"
167 | else
168 | JAVACMD="$JAVA_HOME/bin/java"
169 | fi
170 | else
171 | JAVACMD="`which java`"
172 | fi
173 | fi
174 |
175 | if [ ! -x "$JAVACMD" ] ; then
176 | echo "Error: JAVA_HOME is not defined correctly." >&2
177 | echo " We cannot execute $JAVACMD" >&2
178 | exit 1
179 | fi
180 |
181 | if [ -z "$JAVA_HOME" ] ; then
182 | echo "Warning: JAVA_HOME environment variable is not set."
183 | fi
184 |
185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186 |
187 | # For Cygwin, switch paths to Windows format before running java
188 | if $cygwin; then
189 | [ -n "$M2_HOME" ] &&
190 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
191 | [ -n "$JAVA_HOME" ] &&
192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193 | [ -n "$CLASSPATH" ] &&
194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195 | fi
196 |
197 | # traverses directory structure from process work directory to filesystem root
198 | # first directory with .mvn subdirectory is considered project base directory
199 | find_maven_basedir() {
200 | local basedir=$(pwd)
201 | local wdir=$(pwd)
202 | while [ "$wdir" != '/' ] ; do
203 | if [ -d "$wdir"/.mvn ] ; then
204 | basedir=$wdir
205 | break
206 | fi
207 | wdir=$(cd "$wdir/.."; pwd)
208 | done
209 | echo "${basedir}"
210 | }
211 |
212 | # concatenates all lines of a file
213 | concat_lines() {
214 | if [ -f "$1" ]; then
215 | echo "$(tr -s '\n' ' ' < "$1")"
216 | fi
217 | }
218 |
219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
221 |
222 | # Provide a "standardized" way to retrieve the CLI args that will
223 | # work with both Windows and non-Windows executions.
224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
225 | export MAVEN_CMD_LINE_ARGS
226 |
227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
228 |
229 | exec "$JAVACMD" \
230 | $MAVEN_OPTS \
231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
233 | ${WRAPPER_LAUNCHER} "$@"
234 |
235 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | set MAVEN_CMD_LINE_ARGS=%*
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 |
121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123 |
124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
125 | if ERRORLEVEL 1 goto error
126 | goto end
127 |
128 | :error
129 | set ERROR_CODE=1
130 |
131 | :end
132 | @endlocal & set ERROR_CODE=%ERROR_CODE%
133 |
134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
138 | :skipRcPost
139 |
140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
142 |
143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
144 |
145 | exit /B %ERROR_CODE%
146 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.springframework.analytics
6 | spring-analytics
7 | 2.0.1.BUILD-SNAPSHOT
8 | spring-analytics
9 | Contains APIs for interacting with analytics
10 | https://github.com/spring-projects/spring-analytics
11 | jar
12 |
13 |
14 | Pivotal Software, Inc.
15 | http://www.spring.io
16 |
17 |
18 |
19 | The Apache License, Version 2.0
20 | http://www.apache.org/licenses/LICENSE-2.0.txt
21 |
22 |
23 |
24 | https://github.com/spring-projects/spring-analytics
25 | scm:git:git://github.com/spring-projects/spring-analytics.git
26 | scm:git:ssh://git@github.com:spring-projects/spring-analytics.git
27 |
28 |
29 |
30 | mpollack
31 | Mark Pollack
32 | mpollack at pivotal.io
33 | Pivotal Software, Inc.
34 | http://www.spring.io
35 |
36 | lead
37 |
38 |
39 |
40 |
41 | 3.0.0
42 |
43 |
44 |
45 | 2.0.3.RELEASE
46 |
47 |
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-dependencies
52 | 2.0.3.RELEASE
53 | pom
54 | import
55 |
56 |
57 |
58 |
59 |
60 | GitHub
61 | https://github.com/SpringSource/spring-data-build/issues
62 |
63 |
64 |
65 |
66 | org.springframework.boot
67 | spring-boot-starter-data-redis
68 | true
69 |
70 |
71 | org.springframework.retry
72 | spring-retry
73 |
74 |
75 | joda-time
76 | joda-time
77 |
78 |
79 | org.springframework.boot
80 | spring-boot-starter-actuator
81 |
82 |
83 | org.springframework.boot
84 | spring-boot-starter-logging
85 |
86 |
87 | org.springframework.hateoas
88 | spring-hateoas
89 |
90 |
91 | org.springframework.data
92 | spring-data-commons
93 |
94 |
95 | org.springframework.boot
96 | spring-boot-starter-test
97 | test
98 |
99 |
100 | org.springframework.data
101 | spring-data-redis
102 | true
103 |
104 |
105 | org.springframework.boot
106 | spring-boot-starter-web
107 | test
108 |
109 |
110 | org.springframework.plugin
111 | spring-plugin-core
112 | test
113 |
114 |
115 | com.jayway.jsonpath
116 | json-path
117 | test
118 |
119 |
120 |
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-compiler-plugin
126 | 3.5.1
127 |
128 | 1.8
129 | 1.8
130 |
131 |
132 |
133 | org.apache.maven.plugins
134 | maven-surefire-plugin
135 | 2.19.1
136 |
137 |
138 | **/*Tests.java
139 |
140 |
141 | **/Abstract*.java
142 |
143 |
144 |
145 |
146 | org.apache.maven.plugins
147 | maven-javadoc-plugin
148 | 2.10.4
149 |
150 |
151 | javadoc
152 |
153 | jar
154 |
155 | package
156 |
157 |
158 |
159 | true
160 |
161 |
162 |
163 | org.apache.maven.plugins
164 | maven-source-plugin
165 | 3.0.1
166 |
167 |
168 | attach-sources
169 |
170 | jar
171 |
172 | package
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | spring
182 |
183 |
184 |
185 | true
186 |
187 | spring-snapshots
188 | Spring Snapshots
189 | http://repo.spring.io/libs-snapshot-local
190 |
191 |
192 |
193 | false
194 |
195 | spring-milestones
196 | Spring Milestones
197 | http://repo.spring.io/libs-milestone-local
198 |
199 |
200 |
201 | false
202 |
203 | spring-releases
204 | Spring Releases
205 | http://repo.spring.io/release
206 |
207 |
208 |
209 | false
210 |
211 | spring-libs-release
212 | Spring Libs Release
213 | http://repo.spring.io/libs-release
214 |
215 |
216 |
217 | false
218 |
219 | spring-milestone-release
220 | Spring Milestone Release
221 | http://repo.spring.io/libs-milestone
222 |
223 |
224 |
225 |
226 |
227 | true
228 |
229 | spring-snapshots
230 | Spring Snapshots
231 | http://repo.spring.io/libs-snapshot-local
232 |
233 |
234 |
235 | false
236 |
237 | spring-milestones
238 | Spring Milestones
239 | http://repo.spring.io/libs-milestone-local
240 |
241 |
242 |
243 |
244 |
245 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/AggregateCounter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 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.springframework.analytics.metrics;
17 |
18 | import java.util.Arrays;
19 |
20 | import org.joda.time.Interval;
21 |
22 | /**
23 | * Represents the data returned from an aggregate count query.
24 | *
25 | * @author Luke Taylor
26 | * @author Ilayaperumal Gopinathan
27 | */
28 | public class AggregateCounter {
29 |
30 | private final String name;
31 |
32 | private final Interval interval;
33 |
34 | private final long[] counts;
35 |
36 | private final AggregateCounterResolution resolution;
37 |
38 | public AggregateCounter(String name, Interval interval, long[] counts, AggregateCounterResolution resolution) {
39 | this.name = name;
40 | this.interval = interval;
41 | this.counts = counts.clone();
42 | this.resolution = resolution;
43 | }
44 |
45 | /**
46 | * @return the total number of counts in the interval.
47 | */
48 | public int getTotal() {
49 | int total = 0;
50 | for (int i = 0; i < counts.length; i++) {
51 | total += counts[i];
52 | }
53 | return total;
54 | }
55 |
56 | public String getName() {
57 | return name;
58 | }
59 |
60 | public Interval getInterval() {
61 | return interval;
62 | }
63 |
64 | public long[] getCounts() {
65 | return counts;
66 | }
67 |
68 | public AggregateCounterResolution getResolution() {
69 | return resolution;
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return "AggregateCount{" +
75 | "name='" + name +
76 | "', interval=" + interval +
77 | ", counts=" + Arrays.toString(counts) +
78 | ", resolution=" + resolution +
79 | '}';
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/AggregateCounterReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 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.springframework.analytics.metrics;
17 |
18 | import org.joda.time.DateTime;
19 | import org.joda.time.Interval;
20 |
21 | import java.util.List;
22 |
23 | /**
24 | * Contains operations to find AggreageCounters.
25 | *
26 | * @author Luke Taylor
27 | * @author Ilayaperumal Gopinathan
28 | */
29 | public interface AggregateCounterReader {
30 |
31 | /**
32 | * Query function which returns the last 'n' points for a particular resolution.
33 | *
34 | * @param name the counter to query
35 | * @param nCounts the number of data points to return
36 | * @param resolution the resolution at which the data should be returned (minute, hour, day, month)
37 | * @return an object containing an indexed array of the aggregate counts for the given query.
38 | */
39 | AggregateCounter getCounts(String name, int nCounts, AggregateCounterResolution resolution);
40 |
41 | /**
42 | * Query function to allow the counts for a specific interval to be retrieved for the given resolution.
43 | *
44 | *
45 | * @param name the counter to query
46 | * @param interval the time interval to return data for. Includes start and end.
47 | * @param resolution the resolution at which the data should be returned (minute, hour, day, month)
48 | * @return an object containing an indexed array of the aggregate counts for the given query.
49 | */
50 | AggregateCounter getCounts(String name, Interval interval, AggregateCounterResolution resolution);
51 |
52 | /**
53 | * Queries by requesting a number of points, ending on the given date (inclusive).
54 | * The date may be null to use the current time, thus returning the last nCounts point
55 | * at the given resolution.
56 | *
57 | * @param name the counter to query
58 | * @param end the end of the query interval (inclusive). Cannot be null.
59 | * @param nCounts the number of data points to return
60 | * @param resolution the resolution at which the data should be returned (minute, hour, day, month)
61 | * @return an object containing an indexed array of the counts .
62 | */
63 | AggregateCounter getCounts(String name, int nCounts, DateTime end, AggregateCounterResolution resolution);
64 |
65 | /**
66 | * Retrieve a single counter by name.
67 | * @param name the counter to query
68 | * @return an object containing an indexed array of the counts .
69 | */
70 | AggregateCounter findOne(String name);
71 |
72 | /**
73 | * List the names of all available aggregate counters.
74 | * @return the names of all available aggregate counters.
75 | */
76 | List list();
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/AggregateCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 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.springframework.analytics.metrics;
17 |
18 | /**
19 | * A repository to interact with Aggregate Counters.
20 | *
21 | * @author Ilayaperumal Gopinathan
22 | */
23 | public interface AggregateCounterRepository extends AggregateCounterReader, AggregateCounterWriter {
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/AggregateCounterResolution.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 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.springframework.analytics.metrics;
17 |
18 | import org.joda.time.DateTime;
19 | import org.joda.time.Days;
20 | import org.joda.time.Hours;
21 | import org.joda.time.Minutes;
22 | import org.joda.time.Months;
23 | import org.joda.time.ReadablePeriod;
24 | import org.joda.time.Years;
25 |
26 | /**
27 | * The resolution options available for querying an aggregate counter.
28 | *
29 | * @author Luke Taylor
30 | */
31 | public enum AggregateCounterResolution {
32 | minute(Minutes.minutes(1)),
33 | hour(Hours.hours(1)),
34 | day(Days.days(1)),
35 | month(Months.months(1)),
36 | year(Years.years(1));
37 |
38 | public final ReadablePeriod unitPeriod;
39 |
40 | private AggregateCounterResolution(ReadablePeriod unitPeriod) {
41 | this.unitPeriod = unitPeriod;
42 | }
43 |
44 | /**
45 | * Subtracts this resolution a given number of times from a supplied date.
46 | *
47 | * @param dt the date to subtract from
48 | * @param n the number of periods of this resolution to subtract
49 | * @return the resulting date in the past.
50 | */
51 | public DateTime minus(DateTime dt, int n) {
52 | DateTime start = dt;
53 | for (int i = 0; i < n; i++) {
54 | start = start.minus(unitPeriod);
55 | }
56 | return start;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/AggregateCounterWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 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.springframework.analytics.metrics;
17 |
18 | import org.joda.time.DateTime;
19 |
20 | /**
21 | * Contains operations to modify/reset AggregateCounter instances.
22 | *
23 | * @author Ilayaperumal Gopinathan
24 | */
25 | public interface AggregateCounterWriter {
26 |
27 | /**
28 | * Increments the named counter by a specific amount for the given instant.
29 | * @param name the name of the counter
30 | * @param amount the amount to increment
31 | * @param dateTime the time of the event
32 | * @return the total count
33 | */
34 | long increment(String name, long amount, DateTime dateTime);
35 |
36 | /**
37 | * Reset the given AggregateCounter.
38 | *
39 | * @param name the AggregateCounter name
40 | */
41 | void reset(String name);
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/FieldValueCounter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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.springframework.analytics.metrics;
17 |
18 | import java.util.Map;
19 | import java.util.concurrent.ConcurrentHashMap;
20 |
21 | import org.springframework.util.Assert;
22 |
23 | /**
24 | * Represents the data stored in a Counter for multiple field-value pairs. Operations on it are expected to increment or
25 | * decrement the value associated with a specific field name. The name property is a friendly user assigned name, and
26 | * should be unique.
27 | *
28 | * @author Mark Pollack
29 | * @author Ilayaperumal Gopinathan
30 | */
31 | public final class FieldValueCounter {
32 |
33 | private final String name;
34 |
35 | private final Map fieldValueCounts;
36 |
37 | public FieldValueCounter(String name) {
38 | Assert.notNull(name);
39 | this.name = name;
40 | this.fieldValueCounts = new ConcurrentHashMap<>();
41 | }
42 |
43 | public FieldValueCounter(String name, Map fieldValueCounts) {
44 | Assert.notNull(name);
45 | Assert.notNull(fieldValueCounts);
46 | this.name = name;
47 | this.fieldValueCounts = fieldValueCounts;
48 | }
49 |
50 | public String getName() {
51 | return name;
52 | }
53 |
54 | public Map getFieldValueCounts() {
55 | return this.fieldValueCounts;
56 | }
57 |
58 | @Override
59 | public String toString() {
60 | return "FieldValueCounter [name=" + name + ", fieldValueCounts=" + fieldValueCounts + "]";
61 | }
62 |
63 | @Override
64 | public int hashCode() {
65 | final int prime = 31;
66 | int result = 1;
67 | result = prime * result + ((name == null) ? 0 : name.hashCode());
68 | return result;
69 | }
70 |
71 | @Override
72 | public boolean equals(Object obj) {
73 | if (this == obj) {
74 | return true;
75 | }
76 | if (obj == null) {
77 | return false;
78 | }
79 | if (!(obj instanceof FieldValueCounter)) {
80 | return false;
81 | }
82 | FieldValueCounter other = (FieldValueCounter) obj;
83 | if (name == null) {
84 | if (other.name != null) {
85 | return false;
86 | }
87 | }
88 | else if (!name.equals(other.name)) {
89 | return false;
90 | }
91 | return true;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/FieldValueCounterReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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 |
17 | package org.springframework.analytics.metrics;
18 |
19 | import java.util.Collection;
20 |
21 | /**
22 | * Contains operations to find FieldValueCounters.
23 | *
24 | * @author Eric Bottard
25 | */
26 | public interface FieldValueCounterReader {
27 |
28 | /**
29 | * Retrieve a single counter by name.
30 | * @param name the name of the counter
31 | * @return the counter
32 | */
33 | FieldValueCounter findOne(String name);
34 |
35 | /**
36 | * List the names of all available counters.
37 | * @return the names of all available counters.
38 | */
39 | Collection list();
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/FieldValueCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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 |
17 | package org.springframework.analytics.metrics;
18 |
19 | /**
20 | * Convenience interface that allows both reading and writing operations on FieldValueCounters.
21 | *
22 | * @author Eric Bottard
23 | */
24 | public interface FieldValueCounterRepository extends FieldValueCounterReader, FieldValueCounterWriter {
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/FieldValueCounterWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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.springframework.analytics.metrics;
17 |
18 | /**
19 | * Contains operations to modify and reset FieldValueCounter instances.
20 | *
21 | * The name is the id and should be unique.
22 | *
23 | * @author Mark Pollack
24 | * @author Ilayaperumal Gopinathan
25 | */
26 | public interface FieldValueCounterWriter {
27 |
28 | /**
29 | * Increment the FieldValueCounter for a given field name by score, creating missing counters.
30 | *
31 | * @param name the FieldValueCounter name
32 | * @param fieldName the name of the field
33 | * @param score the incremental value
34 | * @throws IllegalArgumentException in case the given name is null
35 | */
36 | void increment(String name, String fieldName, double score);
37 |
38 | /**
39 | * Decrement the FieldValueCounter for a given field name by score, creating missing counters.
40 | *
41 | * @param name the FieldValueCounter name
42 | * @param fieldName the name of the field
43 | * @param score the decremental value
44 | * @throws IllegalArgumentException in case the given name is null
45 | */
46 | void decrement(String name, String fieldName, double score);
47 |
48 | /**
49 | * Reset the given FieldValueCounter.
50 | *
51 | * @param name the FieldValueCounter name
52 | */
53 | void reset(String name);
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/MetricUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 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 |
17 | package org.springframework.analytics.metrics;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * Utility class, primarily to avoid exposing mutable objects beyond the core package.
23 | *
24 | * For internal use only.
25 | *
26 | * @author Luke Taylor
27 | * @author Eric Bottard
28 | */
29 | public final class MetricUtils {
30 |
31 | /**
32 | * Concatenate {@code size} many values from the passed in arrays, starting at offset {@code start}.
33 | * @param arrays arrays to concatenate
34 | * @param start starting offset position
35 | * @param size number of elements
36 | * @return concatenated array
37 | */
38 | public static long[] concatArrays(List arrays, int start, int size) {
39 | long[] result = new long[size];
40 |
41 | // How many values we still need to move over
42 | int howManyLeft = size;
43 |
44 | // Where in the resulting array we're currently bulk-writing
45 | int targetPosition = 0;
46 |
47 | // Where we're copying *from*, in (one of) the source array.
48 | // Typically 0, except maybe for the first array in the list
49 | int from = start;
50 |
51 | for (int i = 0; i < arrays.size() && howManyLeft > 0; i++) {
52 | long[] current = arrays.get(i);
53 |
54 | // Can't copy more than the current source array size, or the grand total pointer
55 | int howManyThisRound = Math.min(current.length - from, howManyLeft);
56 | System.arraycopy(current, from, result, targetPosition, howManyThisRound);
57 | from = 0;
58 | howManyLeft -= howManyThisRound;
59 | targetPosition += howManyThisRound;
60 | }
61 |
62 | // If this is non-zero here, means we were asked to copy more than what we were provided
63 | if (howManyLeft > 0) {
64 | throw new ArrayIndexOutOfBoundsException(
65 | String.format("Not enough data, short of %d elements", howManyLeft));
66 | }
67 | return result;
68 | }
69 |
70 | /**
71 | * Return the sum of values in the array
72 | * @param array the array to sum
73 | * @return the sum
74 | */
75 | public static long sum(long[] array) {
76 | if (array == null) {
77 | return 0L;
78 | }
79 | long sum = 0;
80 | for (int i = 0; i < array.length; i++) {
81 | sum += array[i];
82 | }
83 | return sum;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/memory/InMemoryAggregateCounter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2016 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 |
17 | package org.springframework.analytics.metrics.memory;
18 |
19 | import java.util.ArrayList;
20 | import java.util.HashMap;
21 | import java.util.List;
22 | import java.util.Map;
23 |
24 | import org.joda.time.Chronology;
25 | import org.joda.time.DateTime;
26 | import org.joda.time.Days;
27 | import org.joda.time.Duration;
28 | import org.joda.time.Interval;
29 | import org.joda.time.Months;
30 | import org.joda.time.Years;
31 |
32 | import org.springframework.analytics.metrics.AggregateCounter;
33 | import org.springframework.analytics.metrics.AggregateCounterResolution;
34 | import org.springframework.analytics.metrics.MetricUtils;
35 | import org.springframework.util.Assert;
36 |
37 | import static java.lang.Math.toIntExact;
38 |
39 | /**
40 | * A counter that tracks integral values but also remembers how its value was distributed over time.
41 | *
42 | *
43 | * This core class only holds data structures. Depending on backing stores, logic for computing totals may be
44 | * implemented in a specialization of this class or at the repository level.
45 | *
46 | *
47 | * @author Luke Taylor
48 | * @author Eric Bottard
49 | * @author Ilayaperumal Gopinathan
50 | */
51 | class InMemoryAggregateCounter {
52 |
53 | private final String name;
54 |
55 | private long value;
56 |
57 | private Map monthCountsByYear = new HashMap();
58 |
59 | private Map dayCountsByYear = new HashMap();
60 |
61 | private Map hourCountsByDay = new HashMap();
62 |
63 | private Map minuteCountsByDay = new HashMap();
64 |
65 | public InMemoryAggregateCounter(String name, long value) {
66 | this.name = name;
67 | this.value = value;
68 | }
69 |
70 | public InMemoryAggregateCounter(String name) {
71 | this.name = name;
72 | this.value = 0l;
73 | }
74 |
75 | public AggregateCounter getCounts(int nCounts, DateTime endDate, AggregateCounterResolution resolution) {
76 | Assert.notNull(endDate, "endDate must not be null");
77 | return getCounts(new Interval(resolution.minus(endDate, nCounts-1), endDate), resolution);
78 | }
79 |
80 | public AggregateCounter getCounts(Interval interval, AggregateCounterResolution resolution) {
81 | DateTime start = interval.getStart();
82 | DateTime end = interval.getEnd();
83 | Chronology c = interval.getChronology();
84 |
85 | long[] counts;
86 | if (resolution == AggregateCounterResolution.minute) {
87 | List days = accumulateDayCounts(minuteCountsByDay, start, end, 60 * 24);
88 |
89 | counts = MetricUtils.concatArrays(days, interval.getStart().getMinuteOfDay(),
90 | interval.toPeriod().toStandardMinutes().getMinutes() + 1);
91 | }
92 | else if (resolution == AggregateCounterResolution.hour) {
93 | List days = accumulateDayCounts(hourCountsByDay, start, end, 24);
94 |
95 | counts = MetricUtils.concatArrays(days, interval.getStart().getHourOfDay(),
96 | interval.toPeriod().toStandardHours().getHours() + 1);
97 | }
98 | else if (resolution == AggregateCounterResolution.day) {
99 | DateTime startDay = new DateTime(c.dayOfYear().roundFloor(start.getMillis()));
100 | DateTime endDay = new DateTime(c.dayOfYear().roundFloor(end.plusDays(1).getMillis()));
101 | int nDays = Days.daysBetween(startDay, endDay).getDays();
102 | DateTime cursor = new DateTime(c.year().roundFloor(interval.getStartMillis()));
103 | List yearDays = new ArrayList();
104 | DateTime endYear = new DateTime(c.year().roundCeiling(end.getMillis()));
105 |
106 | while (cursor.isBefore(endYear)) {
107 | long[] dayCounts = dayCountsByYear.get(cursor.getYear());
108 | if (dayCounts == null) {
109 | // Querying where we have no data
110 | dayCounts = new long[daysInYear(cursor.getYear())];
111 | }
112 | yearDays.add(dayCounts);
113 | cursor = cursor.plusYears(1);
114 | }
115 |
116 | counts = MetricUtils.concatArrays(yearDays, startDay.getDayOfYear() - 1, nDays);
117 |
118 | }
119 | else if (resolution == AggregateCounterResolution.month) {
120 | DateTime startMonth = new DateTime(c.monthOfYear().roundFloor(interval.getStartMillis()));
121 | DateTime endMonth = new DateTime(c.monthOfYear().roundFloor(end.plusMonths(1).getMillis()));
122 | int nMonths = Months.monthsBetween(startMonth, endMonth).getMonths();
123 | DateTime cursor = new DateTime(c.year().roundFloor(interval.getStartMillis()));
124 | List yearMonths = new ArrayList();
125 | DateTime endYear = new DateTime(c.year().roundCeiling(end.getMillis()));
126 |
127 | while (cursor.isBefore(endYear)) {
128 | long[] monthCounts = monthCountsByYear.get(cursor.getYear());
129 | if (monthCounts == null) {
130 | monthCounts = new long[12];
131 | }
132 | yearMonths.add(monthCounts);
133 | cursor = cursor.plusYears(1);
134 | }
135 |
136 | counts = MetricUtils.concatArrays(yearMonths, startMonth.getMonthOfYear() - 1 , nMonths);
137 | }
138 | else if (resolution == AggregateCounterResolution.year) {
139 | DateTime startYear = new DateTime(interval.getStart().getYear(), 1, 1, 0, 0);
140 | DateTime endYear = new DateTime(end.getYear() + 1, 1, 1, 0, 0);
141 | int nYears = Years.yearsBetween(startYear, endYear).getYears();
142 | counts = new long[nYears];
143 |
144 | for (int i = 0; i < nYears; i++) {
145 | long[] monthCounts = monthCountsByYear.get(startYear.plusYears(i).getYear());
146 | counts[i] = MetricUtils.sum(monthCounts);
147 | }
148 |
149 | }
150 | else {
151 | throw new IllegalStateException("Shouldn't happen. Unhandled resolution: " + resolution);
152 | }
153 | return new AggregateCounter(this.name, interval, counts, resolution);
154 | }
155 |
156 | private static List accumulateDayCounts(Map fromDayCounts, DateTime start, DateTime end,
157 | int subSize) {
158 | List days = new ArrayList();
159 | Duration step = Duration.standardDays(1);
160 | long[] emptySubArray = new long[subSize];
161 | end = end.plusDays(1); // Need to account for an interval which crosses days
162 |
163 | for (DateTime now = start; now.isBefore(end); now = now.plus(step)) {
164 | int countsByDayKey = now.getYear() * 1000 + now.getDayOfYear();
165 | long[] dayCounts = fromDayCounts.get(countsByDayKey);
166 |
167 | if (dayCounts == null) {
168 | // Use an empty array if we don't have data
169 | dayCounts = emptySubArray;
170 | }
171 | days.add(dayCounts);
172 | }
173 | return days;
174 | }
175 |
176 | private int daysInYear(int year) {
177 | Duration d = new Duration(new DateTime(year, 1, 1, 0, 0), new DateTime(year + 1, 1, 1, 0, 0));
178 | return toIntExact(d.getStandardDays());
179 | }
180 |
181 | synchronized long increment(long amount) {
182 | return this.value += amount;
183 | }
184 |
185 | synchronized long increment(long amount, DateTime dateTime) {
186 | int year = dateTime.getYear();
187 | int month = dateTime.getMonthOfYear();
188 | int day = dateTime.getDayOfYear();
189 | int hour = dateTime.getHourOfDay();
190 | int minute = dateTime.getMinuteOfDay();
191 |
192 | long[] monthCounts = monthCountsByYear.get(year);
193 | long[] dayCounts = dayCountsByYear.get(year);
194 |
195 | if (monthCounts == null) {
196 | monthCounts = new long[12];
197 | monthCountsByYear.put(year, monthCounts);
198 | dayCounts = new long[daysInYear(year)];
199 | dayCountsByYear.put(year, dayCounts);
200 | }
201 |
202 | int countsByDayKey = year * 1000 + day;
203 | long[] hourCounts = hourCountsByDay.get(countsByDayKey);
204 |
205 | if (hourCounts == null) {
206 | hourCounts = new long[24];
207 | hourCountsByDay.put(countsByDayKey, hourCounts);
208 | }
209 |
210 | long[] minuteCounts = minuteCountsByDay.get(countsByDayKey);
211 |
212 | if (minuteCounts == null) {
213 | minuteCounts = new long[60 * 24];
214 | minuteCountsByDay.put(countsByDayKey, minuteCounts);
215 | }
216 |
217 | minuteCounts[minute] += amount;
218 | monthCounts[month-1] += amount;
219 | dayCounts[day-1] += amount;
220 | hourCounts[hour] += amount;
221 |
222 | return increment(amount);
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/memory/InMemoryAggregateCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011-2016 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 |
17 | package org.springframework.analytics.metrics.memory;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.concurrent.ConcurrentHashMap;
24 |
25 | import org.joda.time.DateTime;
26 | import org.joda.time.Interval;
27 |
28 | import org.springframework.analytics.metrics.AggregateCounter;
29 | import org.springframework.analytics.metrics.AggregateCounterRepository;
30 | import org.springframework.analytics.metrics.AggregateCounterResolution;
31 |
32 | /**
33 | * In-memory aggregate counter with minute resolution.
34 | *
35 | * Note that the data is permanently accumulated, so will grow steadily in size until the host process is restarted.
36 | *
37 | * @author Luke Taylor
38 | * @author Eric Bottard
39 | * @author Ilayaperumal Gopinathan
40 | */
41 | public class InMemoryAggregateCounterRepository implements AggregateCounterRepository {
42 |
43 | private Map aggregates = new ConcurrentHashMap();
44 |
45 | public long increment(String name) {
46 | return increment(name, 1L, DateTime.now());
47 | }
48 |
49 | @Override
50 | public void reset(String name) {
51 | aggregates.remove(name);
52 | }
53 |
54 | @Override
55 | public long increment(String name, long amount, DateTime dateTime) {
56 | InMemoryAggregateCounter counter = getOrCreate(name);
57 | return counter.increment(amount, dateTime);
58 | }
59 |
60 | @Override
61 | public AggregateCounter getCounts(String name, int nCounts, AggregateCounterResolution resolution) {
62 | return getOrCreate(name).getCounts(nCounts, new DateTime(), resolution);
63 | }
64 |
65 | @Override
66 | public AggregateCounter getCounts(String name, Interval interval, AggregateCounterResolution resolution) {
67 | return getOrCreate(name).getCounts(interval, resolution);
68 | }
69 |
70 | @Override
71 | public AggregateCounter findOne(String name) {
72 | return getCounts(name, 1000, new DateTime(), AggregateCounterResolution.minute);
73 | }
74 |
75 | @Override
76 | public List list() {
77 | List list = new ArrayList<>();
78 | list.addAll(aggregates.keySet());
79 | Collections.sort(list);
80 | return list;
81 | }
82 |
83 | @Override
84 | public AggregateCounter getCounts(String name, int nCounts, DateTime end, AggregateCounterResolution resolution) {
85 | return getOrCreate(name).getCounts(nCounts, end, resolution);
86 | }
87 |
88 | private synchronized InMemoryAggregateCounter getOrCreate(String name) {
89 | InMemoryAggregateCounter c = aggregates.get(name);
90 | if (c == null) {
91 | c = new InMemoryAggregateCounter(name);
92 | aggregates.put(name, c);
93 | }
94 | return c;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/memory/InMemoryFieldValueCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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.springframework.analytics.metrics.memory;
17 |
18 | import java.util.Collection;
19 | import java.util.Map;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ConcurrentMap;
22 |
23 | import org.springframework.analytics.metrics.FieldValueCounter;
24 | import org.springframework.analytics.metrics.FieldValueCounterRepository;
25 | import org.springframework.util.Assert;
26 |
27 | /**
28 | * Memory backed implementation of FieldValueCounterRepository that uses a ConcurrentMap
29 | *
30 | * @author Mark Pollack
31 | * @author Ilayaperumal Gopinathan
32 | *
33 | */
34 | public class InMemoryFieldValueCounterRepository implements FieldValueCounterRepository {
35 |
36 | private final ConcurrentMap map = new ConcurrentHashMap<>();
37 |
38 | @Override
39 | public void increment(String name, String fieldName, double score) {
40 | modifyFieldValue(name, fieldName, score);
41 | }
42 |
43 | @Override
44 | public void decrement(String name, String fieldName, double score) {
45 | modifyFieldValue(name, fieldName, -score);
46 | }
47 |
48 | @Override
49 | public void reset(String name) {
50 | map.remove(name);
51 | }
52 |
53 | private void modifyFieldValue(String name, String fieldName, double delta) {
54 | FieldValueCounter counter = getOrCreate(name);
55 | Map data = counter.getFieldValueCounts();
56 | double count = data.containsKey(fieldName) ? data.get(fieldName) : 0;
57 | data.put(fieldName, count + delta);
58 | save(counter);
59 | }
60 |
61 | private FieldValueCounter getOrCreate(String name) {
62 | FieldValueCounter result = findOne(name);
63 | if (result == null) {
64 | synchronized (map) {
65 | result = create(name);
66 | }
67 | result = save(result);
68 | }
69 | return result;
70 | }
71 |
72 | private FieldValueCounter save(FieldValueCounter fieldValueCounter) {
73 | map.put(fieldValueCounter.getName(), fieldValueCounter);
74 | return fieldValueCounter;
75 | }
76 |
77 | @Override
78 | public FieldValueCounter findOne(String name) {
79 | Assert.notNull(name, "The name of the metric must not be null");
80 | return map.get(name);
81 | }
82 |
83 | @Override
84 | public Collection list() {
85 | return map.keySet();
86 | }
87 |
88 | private FieldValueCounter create(String name) {
89 | return new FieldValueCounter(name);
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/redis/AggregateKeyGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2016 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 |
17 | package org.springframework.analytics.metrics.redis;
18 |
19 | import org.joda.time.DateTime;
20 | import org.joda.time.ReadableDateTime;
21 | import org.joda.time.format.DateTimeFormat;
22 | import org.joda.time.format.DateTimeFormatter;
23 |
24 | import org.springframework.util.Assert;
25 |
26 | /**
27 | * Utility class used to generate keys for a named Aggregate Counter.
28 | *
29 | * The general format is
30 | *
One total value
31 | *
One years hash with a field per year eg. { 2010: value, 2011: value }
32 | *
One hash per year with a field per month { 01: value, ...}
33 | *
One hash per month with a field per day
34 | *
One hash per day with a field per hour
35 | *
One hash per hour with a field per minute
36 | *
37 | *
38 | * @author Mark Pollack
39 | * @author Luke Taylor
40 | */
41 | /* default */class AggregateKeyGenerator {
42 |
43 | final static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMddHHmm");
44 |
45 | public final static String SEPARATOR = ".";
46 |
47 | // keys
48 | private final String hourKey;
49 |
50 | private final String dayKey;
51 |
52 | private final String monthKey;
53 |
54 | private final String yearKey;
55 |
56 | private final String yearsKey;
57 |
58 | private final String totalKey;
59 |
60 | // time
61 | private final String year;
62 |
63 | private final String minute;
64 |
65 | private final String hour;
66 |
67 | private final String day;
68 |
69 | private final String month;
70 |
71 | private final String repoPrefix;
72 |
73 | private final String counterName;
74 |
75 | public AggregateKeyGenerator(String repoPrefix, String counterName) {
76 | this(repoPrefix, counterName, new DateTime());
77 | }
78 |
79 | public AggregateKeyGenerator(String repoPrefix, String counterName, ReadableDateTime dateTime) {
80 | Assert.notNull(counterName, "Counter name name can not be null");
81 | Assert.notNull(dateTime, "DateTime can not be null");
82 | this.repoPrefix = repoPrefix;
83 | this.counterName = counterName;
84 | String timeStamp = dateTimeFormatter.print(dateTime);
85 | totalKey = key("total");
86 | hourKey = key(timeStamp.substring(0, 10));
87 | dayKey = key(timeStamp.substring(0, 8));
88 | monthKey = key(timeStamp.substring(0, 6));
89 | yearKey = key(timeStamp.substring(0, 4));
90 | yearsKey = key("years");
91 |
92 | minute = timeStamp.substring(10, 12);
93 | hour = timeStamp.substring(8, 10);
94 | day = timeStamp.substring(6, 8);
95 | month = timeStamp.substring(4, 6);
96 | year = timeStamp.substring(0, 4);
97 | }
98 |
99 | public String getYearsKey() {
100 | return yearsKey;
101 | }
102 |
103 | public String getTotalKey() {
104 | return totalKey;
105 | }
106 |
107 | private String key(String suffix) {
108 | return repoPrefix + SEPARATOR + counterName + SEPARATOR + suffix;
109 | }
110 |
111 | public String getHourKey() {
112 | return hourKey;
113 | }
114 |
115 | public String getDayKey() {
116 | return dayKey;
117 | }
118 |
119 | public String getMonthKey() {
120 | return monthKey;
121 | }
122 |
123 | public String getYearKey() {
124 | return yearKey;
125 | }
126 |
127 | public String getMinute() {
128 | return minute;
129 | }
130 |
131 | public String getHour() {
132 | return hour;
133 | }
134 |
135 | public String getDay() {
136 | return day;
137 | }
138 |
139 | public String getMonth() {
140 | return month;
141 | }
142 |
143 | public String getYear() {
144 | return this.year;
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/redis/RedisAggregateCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2016 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 |
17 | package org.springframework.analytics.metrics.redis;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Set;
24 |
25 | import org.joda.time.Chronology;
26 | import org.joda.time.DateTime;
27 | import org.joda.time.Days;
28 | import org.joda.time.Duration;
29 | import org.joda.time.Interval;
30 | import org.joda.time.Months;
31 | import org.joda.time.MutableDateTime;
32 | import org.joda.time.ReadableDateTime;
33 | import org.joda.time.Years;
34 |
35 | import org.springframework.analytics.metrics.AggregateCounter;
36 | import org.springframework.analytics.metrics.AggregateCounterRepository;
37 | import org.springframework.analytics.metrics.AggregateCounterResolution;
38 | import org.springframework.analytics.metrics.MetricUtils;
39 | import org.springframework.analytics.retry.RedisRetryTemplate;
40 | import org.springframework.data.redis.connection.RedisConnectionFactory;
41 | import org.springframework.data.redis.core.HashOperations;
42 | import org.springframework.data.redis.core.SetOperations;
43 | import org.springframework.data.redis.serializer.GenericToStringSerializer;
44 | import org.springframework.data.redis.serializer.StringRedisSerializer;
45 | import org.springframework.retry.RetryOperations;
46 | import org.springframework.util.Assert;
47 |
48 | /**
49 | * Redis implementation of {@link AggregateCounterRepository}.
50 | *
51 | * @author Eric Bottard
52 | * @author Luke Taylor
53 | * @author Ilayaperumal Gopinathan
54 | */
55 | public class RedisAggregateCounterRepository implements AggregateCounterRepository {
56 |
57 | private static final String AGGREGATE_COUNTER_KEY_PREFIX = "aggregate-counters";
58 |
59 | private final RedisRetryTemplate redisTemplate;
60 |
61 | protected HashOperations hashOperations;
62 |
63 | protected SetOperations setOperations;
64 |
65 | public RedisAggregateCounterRepository(RedisConnectionFactory redisConnectionFactory, RetryOperations retryOperations) {
66 | redisTemplate = new RedisRetryTemplate();
67 | redisTemplate.setConnectionFactory(redisConnectionFactory);
68 | redisTemplate.setKeySerializer(new StringRedisSerializer());
69 | redisTemplate.setValueSerializer(new StringRedisSerializer());
70 | redisTemplate.setHashKeySerializer(new StringRedisSerializer());
71 | redisTemplate.setHashValueSerializer(new GenericToStringSerializer(Long.class));
72 | redisTemplate.setRetryOperations(retryOperations);
73 | redisTemplate.afterPropertiesSet();
74 | hashOperations = redisTemplate.opsForHash();
75 | setOperations = redisTemplate.opsForSet();
76 | }
77 |
78 | long increment(String name) {
79 | return increment(name, 1, new DateTime());
80 | }
81 |
82 | @Override
83 | public long increment(String name, long amount, DateTime dateTime) {
84 | final AggregateKeyGenerator akg = new AggregateKeyGenerator(
85 | AGGREGATE_COUNTER_KEY_PREFIX, name, dateTime);
86 |
87 | String bookkeepingKey = bookkeepingKeyFor(name);
88 |
89 | if (!this.setOperations.isMember(AGGREGATE_COUNTER_KEY_PREFIX, name)) {
90 | this.setOperations.add(AGGREGATE_COUNTER_KEY_PREFIX, name);
91 | }
92 | doIncrementHash(akg.getYearsKey(), akg.getYear(), amount, bookkeepingKey);
93 | doIncrementHash(akg.getYearKey(), akg.getMonth(), amount, bookkeepingKey);
94 | doIncrementHash(akg.getMonthKey(), akg.getDay(), amount, bookkeepingKey);
95 | doIncrementHash(akg.getDayKey(), akg.getHour(), amount, bookkeepingKey);
96 | doIncrementHash(akg.getHourKey(), akg.getMinute(), amount, bookkeepingKey);
97 |
98 | return redisTemplate.boundValueOps(getMetricKey(name)).increment(amount);
99 | }
100 |
101 | /**
102 | * Provides the key for a named metric. By default this prepends the name to the metricPrefix value.
103 | *
104 | * @param metricName the name of the metric
105 | * @return the redis key under which the metric is stored
106 | */
107 | protected String getMetricKey(String metricName) {
108 | return AGGREGATE_COUNTER_KEY_PREFIX + AggregateKeyGenerator.SEPARATOR + metricName;
109 | }
110 |
111 | /**
112 | * Return the key under which are stored the names of the other keys used for the given counter.
113 | */
114 | private String bookkeepingKeyFor(String counterName) {
115 | return "metric_meta.aggregatecounters." + counterName;
116 | }
117 |
118 | /**
119 | * Internally increments the given hash key, keeping track of created hash for a given counter, so they can be
120 | * cleaned up when needed.
121 | */
122 | private void doIncrementHash(String key, String hashKey, long amount, String bookkeepingKey) {
123 | long newValue = hashOperations.increment(key, hashKey, amount);
124 | // TODO: the following test does not necessarily mean that the hash
125 | // is new, just that the key inside that hash is new. So we end up
126 | // calling add more than needed
127 | if (newValue == amount) {
128 | setOperations.add(bookkeepingKey, key);
129 | }
130 | }
131 |
132 | @Override
133 | public AggregateCounter getCounts(String name, int nCounts, AggregateCounterResolution resolution) {
134 | return getCounts(name, nCounts, new DateTime(), resolution);
135 | }
136 |
137 | @Override
138 | public AggregateCounter getCounts(String name, int nCounts, DateTime endDate, AggregateCounterResolution resolution) {
139 | Assert.notNull(endDate, "endDate cannot be null");
140 | return getCounts(name, new Interval(resolution.minus(endDate, nCounts - 1), endDate), resolution);
141 | }
142 |
143 | /**
144 | * For each query, we need to convert the interval into two variations. One is the start and end points rounded to
145 | * the resolution (used to calculate the number of entries to be returned from the query). The second is the start
146 | * and end buckets we have to retrieve which may contain entries for the interval. For example, when querying
147 | * at day resolution, the number of entries is the number of Joda time days between the start (rounded down to a
148 | * day boundary) and the end plus one day (also rounded down). However, we need load the data from the buckets
149 | * from the month the start day occurs in to the month end day occurs in. These are then concatenated, using the
150 | * start day as the start index into the first array, and writing the total number of entries in sequence from that
151 | * point into the combined result counts array.
152 | */
153 | @Override
154 | public AggregateCounter getCounts(String name, Interval interval, AggregateCounterResolution resolution) {
155 |
156 | DateTime end = interval.getEnd();
157 | Chronology c = interval.getChronology();
158 |
159 | long[] counts;
160 |
161 | if (resolution == AggregateCounterResolution.minute) {
162 | // Iterate through each hour in the interval and load the minutes for it
163 | MutableDateTime dt = new MutableDateTime(interval.getStart());
164 | dt.setRounding(c.hourOfDay());
165 | Duration step = Duration.standardHours(1);
166 | List hours = new ArrayList();
167 | while (dt.isBefore(end) || dt.isEqual(end)) {
168 | hours.add(getMinCountsForHour(name, dt));
169 | dt.add(step);
170 | }
171 | counts = MetricUtils.concatArrays(hours, interval.getStart().getMinuteOfHour(),
172 | interval.toPeriod().toStandardMinutes().getMinutes() + 1);
173 |
174 | }
175 | else if (resolution == AggregateCounterResolution.hour) {
176 | DateTime cursor = new DateTime(c.dayOfMonth().roundFloor(interval.getStart().getMillis()));
177 | List days = new ArrayList();
178 | Duration step = Duration.standardHours(24);
179 | while (cursor.isBefore(end)) {
180 | days.add(getHourCountsForDay(name, cursor));
181 | cursor = cursor.plus(step);
182 | }
183 |
184 | counts = MetricUtils.concatArrays(days, interval.getStart().getHourOfDay(),
185 | interval.toPeriod().toStandardHours().getHours() + 1);
186 |
187 | }
188 | else if (resolution == AggregateCounterResolution.day) {
189 | DateTime startDay = new DateTime(c.dayOfYear().roundFloor(interval.getStart().getMillis()));
190 | DateTime endDay = new DateTime(c.dayOfYear().roundFloor(end.plusDays(1).getMillis()));
191 | int nDays = Days.daysBetween(startDay, endDay).getDays();
192 | DateTime cursor = new DateTime(c.monthOfYear().roundFloor(interval.getStart().getMillis()));
193 | List months = new ArrayList();
194 | DateTime endMonth = new DateTime(c.monthOfYear().roundCeiling(interval.getEnd().plusMonths(1).getMillis()));
195 | while (cursor.isBefore(endMonth)) {
196 | months.add(getDayCountsForMonth(name, cursor));
197 | cursor = cursor.plusMonths(1);
198 | }
199 |
200 | counts = MetricUtils.concatArrays(months, interval.getStart().getDayOfMonth() - 1, nDays);
201 | }
202 | else if (resolution == AggregateCounterResolution.month) {
203 | DateTime startMonth = new DateTime(c.monthOfYear().roundFloor(interval.getStartMillis()));
204 | DateTime endMonth = new DateTime(c.monthOfYear().roundFloor(end.plusMonths(1).getMillis()));
205 | int nMonths = Months.monthsBetween(startMonth, endMonth).getMonths();
206 | DateTime cursor = new DateTime(c.year().roundFloor(interval.getStartMillis()));
207 | List years = new ArrayList();
208 | DateTime endYear = new DateTime(c.year().roundCeiling(interval.getEnd().plusYears(1).getMillis()));
209 | while (cursor.isBefore(endYear)) {
210 | years.add(getMonthCountsForYear(name, cursor));
211 | cursor = cursor.plusYears(1);
212 | }
213 |
214 | counts = MetricUtils.concatArrays(years, interval.getStart().getMonthOfYear() - 1, nMonths);
215 | }
216 | else if (resolution == AggregateCounterResolution.year) {
217 | DateTime startYear = new DateTime(interval.getStart().getYear(), 1, 1, 0, 0);
218 | DateTime endYear = new DateTime(end.getYear() + 1, 1, 1, 0, 0);
219 | int nYears = Years.yearsBetween(startYear, endYear).getYears();
220 | Map yearCounts = getYearCounts(name);
221 | counts = new long[nYears];
222 |
223 | for (int i = 0; i < nYears; i++) {
224 | int year = startYear.plusYears(i).getYear();
225 | Long count = yearCounts.get(Integer.toString(year));
226 | if (count == null) {
227 | count = 0L;
228 | }
229 | counts[i] = count;
230 | }
231 | }
232 | else {
233 | throw new IllegalStateException("Shouldn't happen. Unhandled resolution: " + resolution);
234 | }
235 | return new AggregateCounter(name, interval, counts, resolution);
236 | }
237 |
238 | @Override
239 | public List list() {
240 | Set aggregateCounters = this.setOperations.members(AGGREGATE_COUNTER_KEY_PREFIX);
241 | List list = new ArrayList();
242 | if (aggregateCounters != null && !aggregateCounters.isEmpty()) {
243 | list.addAll(aggregateCounters);
244 | Collections.sort(list);
245 | }
246 | return list;
247 | }
248 |
249 | @Override
250 | public AggregateCounter findOne(String name) {
251 | return getCounts(name, 1000, new DateTime(), AggregateCounterResolution.minute);
252 | }
253 |
254 | private Map getYearCounts(String name) {
255 | AggregateKeyGenerator akg = new AggregateKeyGenerator(
256 | AGGREGATE_COUNTER_KEY_PREFIX, name, new DateTime());
257 | return getEntries(akg.getYearsKey());
258 | }
259 |
260 | private long[] getMonthCountsForYear(String name, DateTime year) {
261 | AggregateKeyGenerator akg = new AggregateKeyGenerator(
262 | AGGREGATE_COUNTER_KEY_PREFIX, name, year);
263 | return convertToArray(getEntries(akg.getYearKey()), year.monthOfYear().getMaximumValue(), true); // Months in this year
264 | }
265 |
266 | private long[] getDayCountsForMonth(String name, DateTime month) {
267 | AggregateKeyGenerator akg = new AggregateKeyGenerator(
268 | AGGREGATE_COUNTER_KEY_PREFIX, name, month.withTimeAtStartOfDay());
269 | return convertToArray(getEntries(akg.getMonthKey()), month.dayOfMonth().getMaximumValue(), true); // Days in this month
270 | }
271 |
272 | private long[] getHourCountsForDay(String name, DateTime day) {
273 | AggregateKeyGenerator akg = new AggregateKeyGenerator(
274 | AGGREGATE_COUNTER_KEY_PREFIX, name, day.withTimeAtStartOfDay());
275 | return convertToArray(getEntries(akg.getDayKey()), 24, false);
276 | }
277 |
278 | private long[] getMinCountsForHour(String name, ReadableDateTime dateTime) {
279 | return getMinCountsForHour(name, dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth(),
280 | dateTime.getHourOfDay());
281 | }
282 |
283 | private long[] getMinCountsForHour(String name, int year, int month, int day, int hour) {
284 | DateTime dt = new DateTime().withYear(year).withMonthOfYear(month).withDayOfMonth(day).withHourOfDay(hour);
285 | AggregateKeyGenerator akg = new AggregateKeyGenerator(
286 | AGGREGATE_COUNTER_KEY_PREFIX, name, dt);
287 | return convertToArray(getEntries(akg.getHourKey()), 60, false);
288 | }
289 |
290 | private Map getEntries(String key) {
291 | return hashOperations.entries(key);
292 | }
293 |
294 | /**
295 | * Will convert a (possibly sparse) map whose keys are String versions of numbers between 0 and size, to an array.
296 | */
297 | private long[] convertToArray(Map map, int size, boolean unitOffset) {
298 | long[] values = new long[size];
299 | // Some joda fields (e.g. days of month are unit offset)
300 | int arrayOffset = unitOffset ? -1 : 0;
301 | for (Map.Entry cursor : map.entrySet()) {
302 | int offset = Integer.parseInt(cursor.getKey()) + arrayOffset;
303 | values[offset] = cursor.getValue();
304 | }
305 | return values;
306 | }
307 |
308 | @Override
309 | public void reset(String id) {
310 | redisTemplate.delete(getMetricKey(id));
311 | String metricMetaKey = bookkeepingKeyFor(id);
312 | Set otherKeys = setOperations.members(metricMetaKey);
313 | otherKeys.add(metricMetaKey);
314 | redisTemplate.delete(otherKeys);
315 | Set members = this.setOperations.members(AGGREGATE_COUNTER_KEY_PREFIX);
316 | if (members.contains(id)) {
317 | this.setOperations.remove(AGGREGATE_COUNTER_KEY_PREFIX, id);
318 | }
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/redis/RedisFieldValueCounterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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.springframework.analytics.metrics.redis;
17 |
18 | import java.util.Collection;
19 | import java.util.Collections;
20 | import java.util.HashMap;
21 | import java.util.HashSet;
22 | import java.util.Iterator;
23 | import java.util.Map;
24 | import java.util.Set;
25 |
26 | import org.springframework.analytics.metrics.FieldValueCounter;
27 | import org.springframework.analytics.metrics.FieldValueCounterRepository;
28 | import org.springframework.analytics.retry.StringRedisRetryTemplate;
29 | import org.springframework.data.redis.connection.RedisConnectionFactory;
30 | import org.springframework.data.redis.core.ZSetOperations;
31 | import org.springframework.retry.RetryOperations;
32 | import org.springframework.util.Assert;
33 |
34 | public class RedisFieldValueCounterRepository implements FieldValueCounterRepository {
35 |
36 | private final String metricPrefix;
37 |
38 | private final StringRedisRetryTemplate redisTemplate;
39 |
40 | public RedisFieldValueCounterRepository(RedisConnectionFactory connectionFactory, RetryOperations retryOperations) {
41 | this(connectionFactory, "fieldvaluecounters.", retryOperations);
42 | }
43 |
44 | public RedisFieldValueCounterRepository(RedisConnectionFactory connectionFactory, String metricPrefix,
45 | RetryOperations retryOperations) {
46 | Assert.notNull(connectionFactory);
47 | Assert.hasText(metricPrefix, "metric prefix cannot be empty");
48 | this.metricPrefix = metricPrefix;
49 | redisTemplate = new StringRedisRetryTemplate(connectionFactory, retryOperations);
50 | // avoids proxy
51 | redisTemplate.setExposeConnection(true);
52 | redisTemplate.afterPropertiesSet();
53 | }
54 |
55 | @Override
56 | public FieldValueCounter findOne(String name) {
57 | Assert.notNull(name, "The name of the FieldValueCounter must not be null");
58 | String metricKey = getMetricKey(name);
59 | if (redisTemplate.hasKey(metricKey)) {
60 | Map values = getZSetData(metricKey);
61 | FieldValueCounter c = new FieldValueCounter(name, values);
62 | return c;
63 | }
64 | else {
65 | return null;
66 | }
67 | }
68 |
69 | @Override
70 | public Collection list() {
71 | Set keys = redisTemplate.keys(getMetricKey("*"));
72 | if (keys != null && !keys.isEmpty()) {
73 | Set names = new HashSet<>(keys.size());
74 | for (String key : keys) {
75 | names.add(getCounterName(key));
76 | }
77 | return names;
78 | }
79 | return Collections.EMPTY_SET;
80 | }
81 |
82 | @Override
83 | public void increment(String counterName, String fieldName, double score) {
84 | redisTemplate.boundZSetOps(getMetricKey(counterName)).incrementScore(fieldName, score);
85 | }
86 |
87 | @Override
88 | public void decrement(String counterName, String fieldName, double score) {
89 | redisTemplate.boundZSetOps(getMetricKey(counterName)).incrementScore(fieldName, -score);
90 | }
91 |
92 | @Override
93 | public void reset(String counterName) {
94 | redisTemplate.delete(getMetricKey(counterName));
95 | }
96 |
97 |
98 | /**
99 | * Provides the key for a named metric. By default this prepends the name to the metricPrefix value.
100 | *
101 | * @param metricName the name of the metric
102 | * @return the redis key under which the metric is stored
103 | */
104 | protected String getMetricKey(String metricName) {
105 | return metricPrefix + metricName;
106 | }
107 |
108 | /**
109 | * Provides the name of a counter stored under a given key. This operation is the reverse of {@link #getMetricKey(String)}.
110 | */
111 | private String getCounterName(String redisKey) {
112 | return redisKey.substring(metricPrefix.length());
113 | }
114 |
115 | protected Map getZSetData(String counterKey) {
116 | Set> rangeWithScore = this.redisTemplate
117 | .boundZSetOps(counterKey).rangeWithScores(0, -1);
118 | Map values = new HashMap(
119 | rangeWithScore.size());
120 | for (Iterator> iterator = rangeWithScore.iterator(); iterator
121 | .hasNext();) {
122 | ZSetOperations.TypedTuple typedTuple = iterator.next();
123 | values.put(typedTuple.getValue(), typedTuple.getScore());
124 | }
125 | return values;
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/metrics/redis/RedisMetricRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2018 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 |
17 | package org.springframework.analytics.metrics.redis;
18 |
19 | import org.springframework.analytics.rest.domain.Delta;
20 | import org.springframework.analytics.rest.domain.Metric;
21 | import org.springframework.data.redis.connection.RedisConnectionFactory;
22 | import org.springframework.data.redis.core.BoundZSetOperations;
23 | import org.springframework.data.redis.core.RedisOperations;
24 | import org.springframework.data.redis.core.RedisTemplate;
25 | import org.springframework.data.redis.core.StringRedisTemplate;
26 | import org.springframework.data.redis.serializer.GenericToStringSerializer;
27 | import org.springframework.data.redis.serializer.StringRedisSerializer;
28 | import org.springframework.util.Assert;
29 |
30 | import java.util.*;
31 |
32 | /**
33 | * Redis implementation of metric information. Metric values are stored
34 | * as zset values plus a regular hash value for the timestamp, both against a key composed
35 | * of the metric name prefixed with a constant (default "spring.metrics."). If you have
36 | * multiple metrics repositories all point at the same instance of Redis, it may be useful
37 | * to change the prefix to be unique (but not if you want them to contribute to the same
38 | * metrics).
39 | *
40 | * @author Dave Syer
41 | */
42 | public class RedisMetricRepository {
43 |
44 | private static final String DEFAULT_METRICS_PREFIX = "spring.metrics.";
45 |
46 | private static final String DEFAULT_KEY = "keys.spring.metrics";
47 |
48 | private String prefix = DEFAULT_METRICS_PREFIX;
49 |
50 | private String key = DEFAULT_KEY;
51 |
52 | private BoundZSetOperations zSetOperations;
53 |
54 | private final RedisOperations redisOperations;
55 |
56 | /**
57 | * Create a RedisMetricRepository with a default prefix to apply to all metric names.
58 | * If multiple repositories share a redis instance they will feed into the same global
59 | * metrics.
60 | * @param redisConnectionFactory the redis connection factory
61 | */
62 | public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory) {
63 | this(redisConnectionFactory, null);
64 | }
65 |
66 | /**
67 | * Create a RedisMetricRepository with a prefix to apply to all metric names (ideally
68 | * unique to this repository or to a logical repository contributed to by multiple
69 | * instances, where they all see the same values). Recommended constructor for general
70 | * purpose use.
71 | * @param redisConnectionFactory the redis connection factory
72 | * @param prefix the prefix to set for all metrics keys
73 | */
74 | public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
75 | String prefix) {
76 | this(redisConnectionFactory, prefix, null);
77 | }
78 |
79 | /**
80 | * Allows user to set the prefix and key to use to store the index of other keys. The
81 | * redis store will hold a zset under the key just so the metric names can be
82 | * enumerated. Read operations, especially {@link #findAll()} and {@link #count()},
83 | * will only be accurate if the key is unique to the prefix of this repository.
84 | * @param redisConnectionFactory the redis connection factory
85 | * @param prefix the prefix to set for all metrics keys
86 | * @param key the key to set
87 | */
88 | public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
89 | String prefix, String key) {
90 | if (prefix == null) {
91 | prefix = DEFAULT_METRICS_PREFIX;
92 | if (key == null) {
93 | key = DEFAULT_KEY;
94 | }
95 | }
96 | else if (key == null) {
97 | key = "keys." + prefix;
98 | }
99 | Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
100 | this.redisOperations = stringTemplate(redisConnectionFactory);
101 | if (!prefix.endsWith(".")) {
102 | prefix = prefix + ".";
103 | }
104 | this.prefix = prefix;
105 | if (key.endsWith(".")) {
106 | key = key.substring(0, key.length() - 1);
107 | }
108 | this.key = key;
109 | this.zSetOperations = this.redisOperations.boundZSetOps(this.key);
110 | }
111 |
112 | static RedisTemplate createRedisTemplate(
113 | RedisConnectionFactory connectionFactory, Class valueClass) {
114 | RedisTemplate redisTemplate = new RedisTemplate();
115 | redisTemplate.setKeySerializer(new StringRedisSerializer());
116 | redisTemplate.setValueSerializer(new GenericToStringSerializer(valueClass));
117 |
118 | // avoids proxy
119 | redisTemplate.setExposeConnection(true);
120 |
121 | redisTemplate.setConnectionFactory(connectionFactory);
122 | redisTemplate.afterPropertiesSet();
123 | return redisTemplate;
124 | }
125 |
126 | static RedisOperations stringTemplate(
127 | RedisConnectionFactory redisConnectionFactory) {
128 | return new StringRedisTemplate(redisConnectionFactory);
129 | }
130 |
131 | public Metric> findOne(String metricName) {
132 | String redisKey = keyFor(metricName);
133 | String raw = this.redisOperations.opsForValue().get(redisKey);
134 | return deserialize(redisKey, raw, this.zSetOperations.score(redisKey));
135 | }
136 |
137 | public Iterable> findAll() {
138 |
139 | // This set is sorted
140 | Set keys = this.zSetOperations.range(0, -1);
141 | Iterator keysIt = keys.iterator();
142 |
143 | List> result = new ArrayList>(keys.size());
144 | List values = this.redisOperations.opsForValue().multiGet(keys);
145 | for (String v : values) {
146 | String key = keysIt.next();
147 | Metric> value = deserialize(key, v, this.zSetOperations.score(key));
148 | if (value != null) {
149 | result.add(value);
150 | }
151 | }
152 | return result;
153 |
154 | }
155 |
156 | public long count() {
157 | return this.zSetOperations.size();
158 | }
159 |
160 | public void increment(Delta> delta) {
161 | String name = delta.getName();
162 | String key = keyFor(name);
163 | trackMembership(key);
164 | double value = this.zSetOperations.incrementScore(key,
165 | delta.getValue().doubleValue());
166 | String raw = serialize(new Metric(name, value, delta.getTimestamp()));
167 | this.redisOperations.opsForValue().set(key, raw);
168 | }
169 |
170 | public void set(Metric> value) {
171 | String name = value.getName();
172 | String key = keyFor(name);
173 | trackMembership(key);
174 | this.zSetOperations.add(key, value.getValue().doubleValue());
175 | String raw = serialize(value);
176 | this.redisOperations.opsForValue().set(key, raw);
177 | }
178 |
179 | public void reset(String metricName) {
180 | String key = keyFor(metricName);
181 | if (this.zSetOperations.remove(key) == 1) {
182 | this.redisOperations.delete(key);
183 | }
184 | }
185 |
186 | private Metric> deserialize(String redisKey, String v, Double value) {
187 | if (redisKey == null || v == null || !redisKey.startsWith(this.prefix)) {
188 | return null;
189 | }
190 | Date timestamp = new Date(Long.valueOf(v));
191 | return new Metric(nameFor(redisKey), value, timestamp);
192 | }
193 |
194 | private String serialize(Metric> entity) {
195 | return String.valueOf(entity.getTimestamp().getTime());
196 | }
197 |
198 | private String keyFor(String name) {
199 | return this.prefix + name;
200 | }
201 |
202 | private String nameFor(String redisKey) {
203 | return redisKey.substring(this.prefix.length());
204 | }
205 |
206 | private void trackMembership(String redisKey) {
207 | this.zSetOperations.incrementScore(redisKey, 0.0D);
208 | }
209 |
210 | }
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/controller/AggregateCounterController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2017 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 |
17 | package org.springframework.analytics.rest.controller;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Date;
21 | import java.util.LinkedList;
22 | import java.util.List;
23 |
24 | import org.joda.time.DateTime;
25 | import org.joda.time.Interval;
26 | import org.joda.time.ReadablePeriod;
27 |
28 | import org.springframework.analytics.metrics.AggregateCounter;
29 | import org.springframework.analytics.metrics.AggregateCounterRepository;
30 | import org.springframework.analytics.metrics.AggregateCounterResolution;
31 | import org.springframework.analytics.rest.domain.AggregateCounterResource;
32 | import org.springframework.data.domain.Page;
33 | import org.springframework.data.domain.PageImpl;
34 | import org.springframework.data.domain.Pageable;
35 | import org.springframework.data.web.PagedResourcesAssembler;
36 | import org.springframework.format.annotation.DateTimeFormat;
37 | import org.springframework.hateoas.ExposesResourceFor;
38 | import org.springframework.hateoas.PagedResources;
39 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
40 | import org.springframework.http.HttpStatus;
41 | import org.springframework.http.MediaType;
42 | import org.springframework.web.bind.annotation.PathVariable;
43 | import org.springframework.web.bind.annotation.RequestMapping;
44 | import org.springframework.web.bind.annotation.RequestMethod;
45 | import org.springframework.web.bind.annotation.RequestParam;
46 | import org.springframework.web.bind.annotation.ResponseBody;
47 | import org.springframework.web.bind.annotation.ResponseStatus;
48 | import org.springframework.web.bind.annotation.RestController;
49 |
50 | import static java.lang.Math.toIntExact;
51 |
52 | /**
53 | * Allows interaction with Aggregate Counters.
54 | *
55 | * @author Ilayaperumal Gopinathan
56 | * @author Eric Bottard
57 | */
58 | @RestController
59 | @RequestMapping("/metrics/aggregate-counters")
60 | @ExposesResourceFor(AggregateCounterResource.class)
61 | public class AggregateCounterController {
62 |
63 | private final AggregateCounterRepository repository;
64 |
65 | public AggregateCounterController(AggregateCounterRepository repository) {
66 | this.repository = repository;
67 | }
68 |
69 | private DeepResourceAssembler deepAssembler = new DeepResourceAssembler();
70 |
71 | private ShallowResourceAssembler shallowAssembler = new ShallowResourceAssembler();
72 |
73 | /**
74 | * Retrieve information about a specific aggregate counter.
75 | *
76 | * @param name counter name
77 | * @return {@link AggregateCounterResource}
78 | *
79 | */
80 | @RequestMapping(value = "/{name}", method = RequestMethod.GET)
81 | public AggregateCounterResource display(@PathVariable("name") String name) {
82 | AggregateCounter counter = repository.findOne(name);
83 | if (counter == null) {
84 | throw new NoSuchMetricException(name);
85 | }
86 | return deepAssembler.toResource(counter);
87 | }
88 |
89 | /**
90 | * Delete (reset) a specific counter.
91 | *
92 | * @param name counter name
93 | */
94 | @RequestMapping(value = "/{name}", method = RequestMethod.DELETE)
95 | @ResponseStatus(HttpStatus.OK)
96 | protected void delete(@PathVariable("name") String name) {
97 | AggregateCounter counter = repository.findOne(name);
98 | if (counter == null) {
99 | throw new NoSuchMetricException(name);
100 | }
101 | repository.reset(name);
102 | }
103 |
104 | /**
105 | * List Counters that match the given criteria.
106 | *
107 | * @param pageable {@link Pageable}
108 | * @param pagedAssembler {@link PagedResourcesAssembler}
109 | * @param detailed detailed info
110 | * @param from from date
111 | * @param to to date
112 | * @param resolution {@link AggregateCounterResolution}
113 | * @return list counters
114 | */
115 | @RequestMapping(value = "", method = RequestMethod.GET)
116 | public PagedResources list(
117 | Pageable pageable, PagedResourcesAssembler pagedAssembler,
118 | @RequestParam(value = "detailed", defaultValue = "false") boolean detailed,
119 | @RequestParam(value = "from", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime from,
120 | @RequestParam(value = "to", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime to,
121 | @RequestParam(value = "resolution", defaultValue = "hour") AggregateCounterResolution resolution) {
122 | List names = new ArrayList<>(repository.list());
123 | long count = names.size();
124 | long pageEnd = Math.min(count, pageable.getOffset() + pageable.getPageSize());
125 | Page aggregateCounterPage = new PageImpl<>(names.subList( toIntExact(pageable.getOffset()), toIntExact(pageEnd)), pageable, names.size());
126 | PagedResources resources = pagedAssembler.toResource(aggregateCounterPage, shallowAssembler);
127 | if (detailed) {
128 | to = providedOrDefaultToValue(to);
129 | from = providedOrDefaultFromValue(from, to, resolution);
130 | Interval interval = new Interval(from, to);
131 | List aggregateCounts = new LinkedList<>();
132 | for (AggregateCounterResource aggregateCounterResource : resources) {
133 | AggregateCounter aggregateCount = repository.getCounts(aggregateCounterResource.getName(), interval, resolution);
134 | aggregateCounts.add(deepAssembler.toResource(aggregateCount));
135 | }
136 | return new PagedResources<>(aggregateCounts, resources.getMetadata());
137 | }
138 | return resources;
139 | }
140 |
141 | /**
142 | * Retrieve counts for a given time interval, using some precision.
143 | *
144 | * @param name the name of the aggregate counter we want to retrieve data from
145 | * @param from the start-time for the interval, default depends on the resolution (e.g. go back 1 day for hourly
146 | * buckets)
147 | * @param to the end-time for the interval, default "now"
148 | * @param resolution the size of buckets to aggregate, e.g. hourly, daily, etc. (default "hour")
149 | * @return counts
150 | */
151 | @ResponseBody
152 | @RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
153 | public AggregateCounterResource display(
154 | @PathVariable("name") String name,
155 | @RequestParam(value = "from", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime from,
156 | @RequestParam(value = "to", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime to,
157 | @RequestParam(value = "resolution", defaultValue = "hour") AggregateCounterResolution resolution) {
158 | to = providedOrDefaultToValue(to);
159 | from = providedOrDefaultFromValue(from, to, resolution);
160 | AggregateCounter aggregate = repository.getCounts(name, new Interval(from, to), resolution);
161 | return deepAssembler.toResource(aggregate);
162 | }
163 |
164 | /**
165 | * Return a default value for the interval end if none has been provided.
166 | */
167 | private DateTime providedOrDefaultToValue(DateTime to) {
168 | if (to == null) {
169 | to = new DateTime();
170 | }
171 | return to;
172 | }
173 |
174 | /**
175 | * Return a default value for the interval start if none has been provided.
176 | */
177 | private DateTime providedOrDefaultFromValue(DateTime from, DateTime to,
178 | AggregateCounterResolution resolution) {
179 | if (from != null) {
180 | return from;
181 | }
182 | switch (resolution) {
183 | case minute:
184 | return to.minusMinutes(59);
185 | case hour:
186 | return to.minusHours(23);
187 | case day:
188 | return to.minusDays(6);
189 | case month:
190 | return to.minusMonths(11);
191 | case year:
192 | return to.minusYears(4);
193 | default:
194 | throw new IllegalStateException(
195 | "Shouldn't happen. Unhandled resolution: " + resolution);
196 | }
197 | }
198 |
199 | /**
200 | * Knows how to assemble {@link AggregateCounterResource} out of simple String names
201 | */
202 | private static class ShallowResourceAssembler
203 | extends ResourceAssemblerSupport {
204 |
205 | private ShallowResourceAssembler() {
206 | super(AggregateCounterController.class, AggregateCounterResource.class);
207 | }
208 |
209 | @Override
210 | public AggregateCounterResource toResource(String name) {
211 | return createResourceWithId(name, name);
212 | }
213 |
214 | @Override
215 | protected AggregateCounterResource instantiateResource(String name) {
216 | return new AggregateCounterResource(name);
217 | }
218 | }
219 |
220 | private static class DeepResourceAssembler
221 | extends ResourceAssemblerSupport {
222 |
223 | private DeepResourceAssembler() {
224 | super(AggregateCounterController.class, AggregateCounterResource.class);
225 | }
226 |
227 | @Override
228 | public AggregateCounterResource toResource(AggregateCounter entity) {
229 | return createResourceWithId(entity.getName(), entity);
230 | }
231 |
232 | @Override
233 | protected AggregateCounterResource instantiateResource(AggregateCounter entity) {
234 | AggregateCounterResource result = new AggregateCounterResource(
235 | entity.getName());
236 | ReadablePeriod increment = entity.getResolution().unitPeriod;
237 | DateTime end = entity.getInterval().getEnd();
238 | int i = 0;
239 | for (DateTime when = entity.getInterval().getStart(); !when
240 | .isAfter(end); when = when.plus(increment)) {
241 | result.addValue(new Date(when.getMillis()), entity.getCounts()[i++]);
242 | }
243 | return result;
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/controller/CounterController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2017 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 |
17 | package org.springframework.analytics.rest.controller;
18 |
19 | import org.springframework.analytics.rest.domain.Metric;
20 | import org.springframework.analytics.metrics.redis.RedisMetricRepository;
21 | import org.springframework.analytics.rest.domain.CounterResource;
22 | import org.springframework.analytics.rest.domain.MetricResource;
23 | import org.springframework.data.domain.Page;
24 | import org.springframework.data.domain.PageImpl;
25 | import org.springframework.data.domain.Pageable;
26 | import org.springframework.data.web.PagedResourcesAssembler;
27 | import org.springframework.hateoas.ExposesResourceFor;
28 | import org.springframework.hateoas.PagedResources;
29 | import org.springframework.hateoas.ResourceAssembler;
30 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
31 | import org.springframework.http.HttpStatus;
32 | import org.springframework.util.Assert;
33 | import org.springframework.web.bind.annotation.*;
34 |
35 | import java.util.ArrayList;
36 | import java.util.List;
37 |
38 | import static java.lang.Math.toIntExact;
39 |
40 | /**
41 | * Allows interaction with Counters.
42 | *
43 | * @author Eric Bottard
44 | * @author Mark Fisher
45 | * @author Ilayaperumal Gopinathan
46 | */
47 | @RestController
48 | @RequestMapping("/metrics/counters")
49 | @ExposesResourceFor(CounterResource.class)
50 | public class CounterController {
51 |
52 | public static final String COUNTER_PREFIX = "counter.";
53 |
54 | private final RedisMetricRepository metricRepository;
55 |
56 | private final ResourceAssembler, CounterResource> counterResourceAssembler =
57 | new DeepCounterResourceAssembler();
58 |
59 | protected final ResourceAssembler, ? extends MetricResource> shallowResourceAssembler =
60 | new ShallowMetricResourceAssembler();
61 |
62 | /**
63 | * Create a {@link CounterController} that delegates to the provided {@link RedisMetricRepository}.
64 | *
65 | * @param metricRepository the {@link RedisMetricRepository} used by this controller
66 | */
67 | public CounterController(RedisMetricRepository metricRepository) {
68 | Assert.notNull(metricRepository, "metricRepository must not be null");
69 | this.metricRepository = metricRepository;
70 | }
71 |
72 | /**
73 | * List Counters that match the given criteria.
74 | *
75 | * @param pageable {@link Pageable}
76 | * @param pagedAssembler {@link PagedResourcesAssembler}
77 | * @param detailed details
78 | * @return {@link PagedResources}
79 | */
80 | @RequestMapping(value = "", method = RequestMethod.GET)
81 | public PagedResources extends MetricResource> list(
82 | Pageable pageable,
83 | PagedResourcesAssembler> pagedAssembler,
84 | @RequestParam(value = "detailed", defaultValue = "false") boolean detailed) {
85 | /* Page */ Iterable> metrics = metricRepository.findAll(/* pageable */);
86 | List> content = filterCounters(metrics);
87 | long count = content.size();
88 | long pageEnd = Math.min(count, pageable.getOffset() + pageable.getPageSize());
89 | Page counterPage = new PageImpl<>(content.subList(toIntExact(pageable.getOffset()), toIntExact(pageEnd)),
90 | pageable, content.size());
91 | ResourceAssembler, ? extends MetricResource> assemblerToUse =
92 | detailed ? counterResourceAssembler : shallowResourceAssembler;
93 | return pagedAssembler.toResource(counterPage, assemblerToUse);
94 | }
95 |
96 | /**
97 | * Retrieve information about a specific counter.
98 | *
99 | * @param name name
100 | * @return counter information
101 | */
102 | @RequestMapping(value = "/{name}", method = RequestMethod.GET)
103 | public CounterResource display(@PathVariable("name") String name) {
104 | Metric c = findCounter(name);
105 | return counterResourceAssembler.toResource(c);
106 | }
107 |
108 | /**
109 | * Delete (reset) a specific counter.
110 | *
111 | * @param name to delete
112 | */
113 | @RequestMapping(value = "/{name}", method = RequestMethod.DELETE)
114 | @ResponseStatus(HttpStatus.OK)
115 | protected void delete(@PathVariable("name") String name) {
116 | Metric c = findCounter(name);
117 | metricRepository.reset(c.getName());
118 | }
119 |
120 | /**
121 | * Find a given counter, taking care of name conversion between the Spring Boot domain and our domain.
122 | *
123 | * @param name name
124 | * @return counter
125 | * @throws NoSuchMetricException if the counter does not exist
126 | */
127 | private Metric findCounter(@PathVariable("name") String name) {
128 | @SuppressWarnings("unchecked")
129 | Metric c = (Metric) metricRepository.findOne(COUNTER_PREFIX + name);
130 | if (c == null) {
131 | throw new NoSuchMetricException(name);
132 | }
133 | return c;
134 | }
135 |
136 |
137 | /**
138 | * Filter the list of Boot metrics to only return those that are counters.
139 | */
140 | @SuppressWarnings("unchecked")
141 | private List> filterCounters(Iterable> input) {
142 | List> result = new ArrayList<>();
143 | for (Metric> metric : input) {
144 | if (metric.getName().startsWith(COUNTER_PREFIX)) {
145 | result.add((Metric) metric);
146 | }
147 | }
148 | return result;
149 | }
150 |
151 | /**
152 | * Base class for a ResourceAssembler that builds shallow resources for metrics
153 | * (exposing only their names, and hence their "self" rel).
154 | *
155 | * @author Eric Bottard
156 | */
157 | static class ShallowMetricResourceAssembler extends
158 | ResourceAssemblerSupport, MetricResource> {
159 |
160 | public ShallowMetricResourceAssembler() {
161 | super(CounterController.class, MetricResource.class);
162 | }
163 |
164 | @Override
165 | public MetricResource toResource(Metric entity) {
166 | return createResourceWithId(entity.getName().substring(COUNTER_PREFIX.length()), entity);
167 | }
168 |
169 | @Override
170 | protected MetricResource instantiateResource(Metric entity) {
171 | return new MetricResource(entity.getName().substring(COUNTER_PREFIX.length()));
172 | }
173 |
174 | }
175 |
176 | /**
177 | * Knows how to assemble {@link CounterResource}s out of counter {@link Metric}s.
178 | *
179 | * @author Eric Bottard
180 | */
181 | static class DeepCounterResourceAssembler extends
182 | ResourceAssemblerSupport, CounterResource> {
183 |
184 | public DeepCounterResourceAssembler() {
185 | super(CounterController.class, CounterResource.class);
186 | }
187 |
188 | @Override
189 | public CounterResource toResource(Metric entity) {
190 | return createResourceWithId(entity.getName().substring(COUNTER_PREFIX.length()), entity);
191 | }
192 |
193 | @Override
194 | protected CounterResource instantiateResource(Metric entity) {
195 | return new CounterResource(entity.getName().substring(COUNTER_PREFIX.length()), entity.getValue().longValue());
196 | }
197 |
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/controller/FieldValueCounterController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 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 |
17 | package org.springframework.analytics.rest.controller;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import org.springframework.analytics.metrics.FieldValueCounter;
23 | import org.springframework.analytics.metrics.FieldValueCounterRepository;
24 | import org.springframework.analytics.rest.domain.FieldValueCounterResource;
25 | import org.springframework.analytics.rest.domain.MetricResource;
26 | import org.springframework.data.domain.Page;
27 | import org.springframework.data.domain.PageImpl;
28 | import org.springframework.data.domain.Pageable;
29 | import org.springframework.data.web.PagedResourcesAssembler;
30 | import org.springframework.hateoas.ExposesResourceFor;
31 | import org.springframework.hateoas.PagedResources;
32 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
33 | import org.springframework.http.HttpStatus;
34 | import org.springframework.web.bind.annotation.PathVariable;
35 | import org.springframework.web.bind.annotation.RequestMapping;
36 | import org.springframework.web.bind.annotation.RequestMethod;
37 | import org.springframework.web.bind.annotation.ResponseStatus;
38 | import org.springframework.web.bind.annotation.RestController;
39 |
40 | import static java.lang.Math.toIntExact;
41 |
42 | /**
43 | * Allows interaction with Field Value Counters.
44 | *
45 | * @author Eric Bottard
46 | * @author Ilayaperumal Gopinathan
47 | */
48 | @RestController
49 | @RequestMapping("/metrics/field-value-counters")
50 | @ExposesResourceFor(FieldValueCounterResource.class)
51 | public class FieldValueCounterController {
52 |
53 | private final FieldValueCounterRepository repository;
54 |
55 | public FieldValueCounterController(FieldValueCounterRepository repository) {
56 | this.repository = repository;
57 | }
58 |
59 | private DeepResourceAssembler deepAssembler = new DeepResourceAssembler();
60 |
61 | private ShallowResourceAssembler shallowAssembler = new ShallowResourceAssembler();
62 |
63 | /**
64 | * Retrieve information about a specific counter.
65 | *
66 | * @param name name
67 | * @return counter information
68 | */
69 | @RequestMapping(value = "/{name}", method = RequestMethod.GET)
70 | public FieldValueCounterResource display(@PathVariable("name") String name) {
71 | FieldValueCounter counter = repository.findOne(name);
72 | if (counter == null) {
73 | throw new NoSuchMetricException(name);
74 | }
75 | return deepAssembler.toResource(counter);
76 | }
77 |
78 | /**
79 | * Delete (reset) a specific counter.
80 | *
81 | * @param name name
82 | */
83 | @RequestMapping(value = "/{name}", method = RequestMethod.DELETE)
84 | @ResponseStatus(HttpStatus.OK)
85 | protected void delete(@PathVariable("name") String name) {
86 | FieldValueCounter counter = repository.findOne(name);
87 | if (counter == null) {
88 | throw new NoSuchMetricException(name);
89 | }
90 | repository.reset(name);
91 | }
92 |
93 | /**
94 | * List Counters that match the given criteria.
95 | *
96 | * @param pageable {@link Pageable}
97 | * @param pagedAssembler {@link PagedResourcesAssembler}
98 | * @return counters
99 | */
100 | @RequestMapping(value = "", method = RequestMethod.GET)
101 | public PagedResources extends MetricResource> list(Pageable pageable,
102 | PagedResourcesAssembler pagedAssembler) {
103 | List names = new ArrayList<>(repository.list());
104 | long count = names.size();
105 | long pageEnd = Math.min(count, pageable.getOffset() + pageable.getPageSize());
106 | Page fieldValueCounterPage = new PageImpl<>(names.subList(toIntExact(pageable.getOffset()), toIntExact(pageEnd)), pageable, names.size());
107 | return pagedAssembler.toResource(fieldValueCounterPage, shallowAssembler);
108 | }
109 |
110 | /**
111 | * Knows how to assemble {@link MetricResource} out of simple String names
112 | */
113 | private static class ShallowResourceAssembler extends
114 | ResourceAssemblerSupport {
115 |
116 | private ShallowResourceAssembler() {
117 | super(FieldValueCounterController.class, MetricResource.class);
118 | }
119 |
120 | @Override
121 | public MetricResource toResource(String name) {
122 | return createResourceWithId(name, name);
123 | }
124 |
125 | @Override
126 | protected MetricResource instantiateResource(String name) {
127 | return new MetricResource(name);
128 | }
129 | }
130 |
131 | /**
132 | * Knows how to assemble {@link FieldValueCounterResource} out of {@link FieldValueCounter}.
133 | *
134 | * @author Eric Bottard
135 | */
136 | private static class DeepResourceAssembler extends
137 | ResourceAssemblerSupport {
138 |
139 | private DeepResourceAssembler() {
140 | super(FieldValueCounterController.class, FieldValueCounterResource.class);
141 | }
142 |
143 | @Override
144 | public FieldValueCounterResource toResource(FieldValueCounter entity) {
145 | return createResourceWithId(entity.getName(), entity);
146 | }
147 |
148 | @Override
149 | protected FieldValueCounterResource instantiateResource(FieldValueCounter entity) {
150 | return new FieldValueCounterResource(entity.getName(), entity.getFieldValueCounts());
151 | }
152 |
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/controller/NoSuchMetricException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 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.springframework.analytics.rest.controller;
17 |
18 | import org.springframework.http.HttpStatus;
19 | import org.springframework.web.bind.annotation.ResponseStatus;
20 |
21 | /**
22 | * Exception thrown when the specified metric cannot be found.
23 | * @author Dave Syer
24 | */
25 | @SuppressWarnings("serial")
26 | @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such metric")
27 | public class NoSuchMetricException extends RuntimeException {
28 |
29 | public NoSuchMetricException(String string) {
30 | super(string);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/controller/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Package for REST controller classes.
3 | */
4 | package org.springframework.analytics.rest.controller;
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/AggregateCounterResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016-2018 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 |
17 | package org.springframework.analytics.rest.domain;
18 |
19 | import com.fasterxml.jackson.annotation.JsonProperty;
20 |
21 | import javax.xml.bind.annotation.XmlElement;
22 | import javax.xml.bind.annotation.XmlRootElement;
23 | import java.util.Date;
24 | import java.util.SortedMap;
25 | import java.util.TreeMap;
26 |
27 | /**
28 | * The REST representation of an Aggregate Count.
29 | *
30 | * @author Eric Bottard
31 | */
32 | @XmlRootElement(name = "aggregate-counts")
33 | public class AggregateCounterResource extends MetricResource {
34 |
35 | @JsonProperty("counts")
36 | @XmlElement(name = "counts", type = TreeMap.class)
37 | private SortedMap values = new TreeMap();
38 |
39 | /**
40 | * No-arg constructor for serialization frameworks.
41 | */
42 | protected AggregateCounterResource() {
43 | }
44 |
45 | public AggregateCounterResource(String name) {
46 | super(name);
47 | }
48 |
49 | /**
50 | * Add a data point to the set.
51 | *
52 | * @param when date
53 | * @param value to set
54 | */
55 | public void addValue(Date when, long value) {
56 | values.put(when, value);
57 | }
58 |
59 | /**
60 | * @return a date-sorted view of counts.
61 | */
62 | public SortedMap getValues() {
63 | return values;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/CounterResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 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 |
17 | package org.springframework.analytics.rest.domain;
18 |
19 | /**
20 | * The REST representation of a Counter.
21 | *
22 | * @author Eric Bottard
23 | */
24 | public class CounterResource extends MetricResource {
25 |
26 | /**
27 | * The value for the counter.
28 | */
29 | private long value;
30 |
31 |
32 | /**
33 | * No-arg constructor for serialization frameworks.
34 | */
35 | protected CounterResource() {
36 |
37 | }
38 |
39 | public CounterResource(String name, long value) {
40 | super(name);
41 | this.value = value;
42 | }
43 |
44 | /**
45 | * Return the value for the counter.
46 | *
47 | * @return counter value
48 | */
49 | public long getValue() {
50 | return value;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/Delta.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2018 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 |
17 | package org.springframework.analytics.rest.domain;
18 |
19 | import java.util.Date;
20 |
21 | /**
22 | * A value object representing an increment in a metric value (usually a counter).
23 | *
24 | * @param the value type
25 | * @author Dave Syer
26 | */
27 | public class Delta extends Metric {
28 |
29 | public Delta(String name, T value, Date timestamp) {
30 | super(name, value, timestamp);
31 | }
32 |
33 | public Delta(String name, T value) {
34 | super(name, value);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/FieldValueCounterResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 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 |
17 | package org.springframework.analytics.rest.domain;
18 |
19 | import com.fasterxml.jackson.annotation.JsonProperty;
20 |
21 | import java.util.Comparator;
22 | import java.util.Map;
23 | import java.util.TreeMap;
24 |
25 | /**
26 | * The REST representation of a Field Value Counter.
27 | *
28 | * @author Eric Bottard
29 | */
30 | public class FieldValueCounterResource extends MetricResource {
31 |
32 | /**
33 | * The values for the counter.
34 | */
35 | private Map values;
36 |
37 |
38 | /**
39 | * No-arg constructor for serialization frameworks.
40 | */
41 | protected FieldValueCounterResource() {
42 |
43 | }
44 |
45 | public FieldValueCounterResource(String name, Map values) {
46 | super(name);
47 | setValues(values);
48 | }
49 |
50 | /**
51 | * Return the values for the counter.
52 | *
53 | * @return counter values
54 | */
55 | public Map getValues() {
56 | return values;
57 | }
58 |
59 | @JsonProperty
60 | public void setValues(Map values) {
61 | this.values = new TreeMap<>(new ByDecreasingValueComparator(values));
62 | this.values.putAll(values);
63 | }
64 |
65 | /**
66 | * A comparator on map keys that orders them according to their mapped value.
67 | *
68 | * @author Eric Bottard
69 | */
70 | private static class ByDecreasingValueComparator implements Comparator {
71 |
72 | private final Map values;
73 |
74 | private ByDecreasingValueComparator(Map values) {
75 | this.values = values;
76 | }
77 |
78 | @Override
79 | public int compare(String k1, String k2) {
80 | int byValue = values.get(k1).compareTo(values.get(k2));
81 | return byValue == 0 ? k1.compareTo(k2) : -byValue;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/Metric.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2018 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 |
17 | package org.springframework.analytics.rest.domain;
18 |
19 | import org.springframework.util.Assert;
20 | import org.springframework.util.ObjectUtils;
21 |
22 | import java.util.Date;
23 |
24 | /**
25 | * Immutable class that can be used to hold any arbitrary system measurement value (a
26 | * named numeric value with a timestamp). For example a metric might record the number of
27 | * active connections to a server, or the temperature of a meeting room.
28 | *
29 | * @param the value type
30 | * @author Dave Syer
31 | */
32 | public class Metric {
33 |
34 | private final String name;
35 |
36 | private final T value;
37 |
38 | private final Date timestamp;
39 |
40 | /**
41 | * Create a new {@link Metric} instance for the current time.
42 | * @param name the name of the metric
43 | * @param value the value of the metric
44 | */
45 | public Metric(String name, T value) {
46 | this(name, value, new Date());
47 | }
48 |
49 | /**
50 | * Create a new {@link Metric} instance.
51 | * @param name the name of the metric
52 | * @param value the value of the metric
53 | * @param timestamp the timestamp for the metric
54 | */
55 | public Metric(String name, T value, Date timestamp) {
56 | Assert.notNull(name, "Name must not be null");
57 | this.name = name;
58 | this.value = value;
59 | this.timestamp = timestamp;
60 | }
61 |
62 | /**
63 | * Returns the name of the metric.
64 | * @return the name
65 | */
66 | public String getName() {
67 | return this.name;
68 | }
69 |
70 | /**
71 | * Returns the value of the metric.
72 | * @return the value
73 | */
74 | public T getValue() {
75 | return this.value;
76 | }
77 |
78 | public Date getTimestamp() {
79 | return this.timestamp;
80 | }
81 |
82 | @Override
83 | public String toString() {
84 | return "Metric [name=" + this.name + ", value=" + this.value + ", timestamp="
85 | + this.timestamp + "]";
86 | }
87 |
88 | /**
89 | * Create a new {@link Metric} with an incremented value.
90 | * @param amount the amount that the new metric will differ from this one
91 | * @return a new {@link Metric} instance
92 | */
93 | public Metric increment(int amount) {
94 | return new Metric(this.getName(),
95 | Long.valueOf(this.getValue().longValue() + amount));
96 | }
97 |
98 | /**
99 | * Create a new {@link Metric} with a different value.
100 | * @param the metric value type
101 | * @param value the value of the new metric
102 | * @return a new {@link Metric} instance
103 | */
104 | public Metric set(S value) {
105 | return new Metric(this.getName(), value);
106 | }
107 |
108 | @Override
109 | public int hashCode() {
110 | final int prime = 31;
111 | int result = 1;
112 | result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
113 | result = prime * result + ObjectUtils.nullSafeHashCode(this.timestamp);
114 | result = prime * result + ObjectUtils.nullSafeHashCode(this.value);
115 | return result;
116 | }
117 |
118 | @Override
119 | public boolean equals(Object obj) {
120 | if (this == obj) {
121 | return true;
122 | }
123 | if (obj == null) {
124 | return false;
125 | }
126 | if (obj instanceof Metric) {
127 | Metric> other = (Metric>) obj;
128 | boolean rtn = true;
129 | rtn = rtn && ObjectUtils.nullSafeEquals(this.name, other.name);
130 | rtn = rtn && ObjectUtils.nullSafeEquals(this.timestamp, other.timestamp);
131 | rtn = rtn && ObjectUtils.nullSafeEquals(this.value, other.value);
132 | return rtn;
133 | }
134 | return super.equals(obj);
135 | }
136 |
137 | }
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/MetricResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | package org.springframework.analytics.rest.domain;
17 |
18 | import org.springframework.hateoas.PagedResources;
19 | import org.springframework.hateoas.ResourceSupport;
20 |
21 | /**
22 | * Base class for REST representations of named metrics. Can be used when a just a shallow representation
23 | * of a metric is expected.
24 | *
25 | * @author Eric Bottard
26 | */
27 | public class MetricResource extends ResourceSupport {
28 |
29 | private String name;
30 |
31 | /**
32 | * No arg constructor for serialization frameworks.
33 | */
34 | protected MetricResource() {
35 |
36 | }
37 |
38 | public MetricResource(String name) {
39 | this.name = name;
40 | }
41 |
42 | /**
43 | * Return the name of the metric.
44 | *
45 | * @return name of the metric
46 | */
47 | public String getName() {
48 | return name;
49 | }
50 |
51 | /**
52 | * Dedicated subclass to workaround type erasure.
53 | *
54 | * @author Eric Bottard
55 | */
56 | public static class Page extends PagedResources {
57 |
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/domain/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Package for REST domain classes.
3 | */
4 | package org.springframework.analytics.rest.domain;
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/rest/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Package for REST controller/domain classes.
3 | */
4 | package org.springframework.analytics.rest;
--------------------------------------------------------------------------------
/src/main/java/org/springframework/analytics/retry/LoggingRecoveryCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 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.springframework.analytics.retry;
17 |
18 | import org.apache.commons.logging.Log;
19 | import org.apache.commons.logging.LogFactory;
20 |
21 | import org.springframework.retry.RecoveryCallback;
22 | import org.springframework.retry.RetryContext;
23 |
24 | /**
25 | * A callback that logs information about the failure to execute the redis operation after retries have
26 | * been exhausted
27 | *
28 | * @author Mark Pollack
29 | */
30 | public class LoggingRecoveryCallback implements RecoveryCallback