├── settings.gradle ├── images └── facet_logo.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── facet.yml ├── src └── main │ ├── resources │ ├── example │ │ └── properties │ │ │ └── facet.yml │ └── log4j.yaml │ └── java │ └── run │ └── facet │ └── agent │ └── java │ ├── CircuitBreakerException.java │ ├── AppConfig.java │ ├── Exception.java │ ├── exception │ └── InstallException.java │ ├── Language.java │ ├── Sensor.java │ ├── Configuration.java │ ├── Toggle.java │ ├── Toggles.java │ ├── CircuitBreaker.java │ ├── Framework.java │ ├── Agent.java │ ├── Parameter.java │ ├── Annotation.java │ ├── Properties.java │ ├── LogInitializer.java │ ├── App.java │ ├── Frameworks.java │ ├── CircuitBreakers.java │ ├── BlockList.java │ ├── Facets.java │ ├── Signature.java │ ├── Facet.java │ ├── Method.java │ ├── WebRequest.java │ └── Transformer.java ├── .gitignore ├── .github └── workflows │ └── main.yml ├── LICENSE ├── CONTRIBUTING.md ├── db └── configuration │ ├── block_list │ └── default.json │ ├── circuit_breakers │ └── default.json │ └── frameworks │ └── spring.json ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── gradlew └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'facet-agent' -------------------------------------------------------------------------------- /images/facet_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facet-tech/agent-java/HEAD/images/facet_logo.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facet-tech/agent-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /facet.yml: -------------------------------------------------------------------------------- 1 | apiKey: andyTestApiKey 2 | workspaceId: WORKSPACE~N2IzODAyNzQtZGY5OC00OTE4LWEwM2UtZGVjYmRmZTkyMTA4 3 | name: demo 4 | environment: dev -------------------------------------------------------------------------------- /src/main/resources/example/properties/facet.yml: -------------------------------------------------------------------------------- 1 | workspaceId: WORKSPACE~N2IzODAyNzQtZGY5OC00OTE4LWEwM2UtZGVjYmRmZTkyMTA4 2 | name: demo 3 | environment: dev -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/CircuitBreakerException.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | public class CircuitBreakerException extends java.lang.Exception {} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | 7 | # Intellij IDEA 8 | .idea 9 | 10 | # MacOS 11 | .DS_Store -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/AppConfig.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan 8 | public class AppConfig {} 9 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Exception.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | public class Exception { 4 | private String className; 5 | 6 | public String getClassName() { 7 | return className; 8 | } 9 | 10 | public void setClassName(String className) { 11 | this.className = className; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/exception/InstallException.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java.exception; 2 | 3 | public class InstallException extends Exception{ 4 | public InstallException(String message) { 5 | super(message); 6 | } 7 | 8 | public InstallException(Exception e) { 9 | super(e); 10 | } 11 | 12 | public InstallException(String message, Exception e) { 13 | super(message,e); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Language.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | public class Language { 4 | private String name; 5 | private String version; 6 | 7 | public Language(String name, String version) { 8 | this.name = name; 9 | this.version = version; 10 | } 11 | 12 | public Language() {} 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | public String getVersion() { 23 | return version; 24 | } 25 | 26 | public void setVersion(String version) { 27 | this.version = version; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Sensor.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Sensor { 7 | private List annotations; 8 | private String returnType; 9 | 10 | public Sensor() { 11 | annotations = new ArrayList<>(); 12 | } 13 | 14 | public List getAnnotations() { 15 | return annotations; 16 | } 17 | 18 | public void setAnnotations(List annotations) { 19 | this.annotations = annotations; 20 | } 21 | 22 | public String getReturnType() { 23 | return returnType; 24 | } 25 | 26 | public void setReturnType(String returnType) { 27 | this.returnType = returnType; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Configuration.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | 4 | import java.util.Map; 5 | 6 | public class Configuration { 7 | String property; 8 | String id ; 9 | Map attribute; 10 | 11 | 12 | public String getProperty() { 13 | return property; 14 | } 15 | 16 | public void setProperty(String property) { 17 | this.property = property; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public void setId(String id) { 25 | this.id = id; 26 | } 27 | 28 | public Map getAttribute() { 29 | return attribute; 30 | } 31 | 32 | public void setAttribute(Map attribute) { 33 | this.attribute = attribute; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Toggle.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Toggle { 7 | public Method method; 8 | public Map parameterMapping; 9 | 10 | public Toggle(Method method) { 11 | this.method = method; 12 | this.parameterMapping = new HashMap<>(); 13 | } 14 | 15 | public Toggle() { 16 | parameterMapping = new HashMap<>(); 17 | } 18 | 19 | public Method getMethod() { 20 | return method; 21 | } 22 | 23 | public void setMethod(Method method) { 24 | this.method = method; 25 | } 26 | 27 | public Map getParameterMapping() { 28 | return parameterMapping; 29 | } 30 | 31 | public void setParameterMapping(Map parameterMapping) { 32 | this.parameterMapping = parameterMapping; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: java-agent 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@v2 14 | 15 | - uses: burrunan/gradle-cache-action@v1 16 | name: build 17 | env: 18 | ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.MAVEN_SIGNING_KEY_ID }} 19 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_SIGNING_KEY }} 20 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }} 21 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} 22 | with: 23 | job-id: gradleCache 24 | gradle-version: 6.7.1 25 | arguments: build signArchives publishToSonatype closeAndReleaseStagingRepository 26 | concurrent: true 27 | save-generated-gradle-jars: false 28 | save-local-build-cache: false 29 | save-gradle-dependencies-cache: false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Facet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/resources/log4j.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | status: warn 3 | name: YAMLConfigTest 4 | properties: 5 | property: 6 | name: filename 7 | value: test-yaml.log 8 | thresholdFilter: 9 | level: debug 10 | appenders: 11 | Console: 12 | name: STDOUT 13 | target: SYSTEM_OUT 14 | PatternLayout: 15 | Pattern: "%m%n" 16 | File: 17 | name: File 18 | fileName: ${filename} 19 | PatternLayout: 20 | Pattern: "%d %p %C{1.} [%t] %m%n" 21 | Filters: 22 | ThresholdFilter: 23 | level: error 24 | 25 | Loggers: 26 | logger: 27 | - 28 | name: org.apache.logging.log4j.test1 29 | level: debug 30 | additivity: false 31 | ThreadContextMapFilter: 32 | KeyValuePair: 33 | key: test 34 | value: 123 35 | AppenderRef: 36 | ref: STDOUT 37 | - 38 | name: org.apache.logging.log4j.test2 39 | level: debug 40 | additivity: false 41 | AppenderRef: 42 | ref: File 43 | Root: 44 | level: error 45 | AppenderRef: 46 | ref: STDOUT -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Toggles.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | @Component 9 | public class Toggles { 10 | private static Map toggle; 11 | 12 | private Toggles() { 13 | toggle = new HashMap<>(); 14 | } 15 | 16 | 17 | public static boolean isEnabled(String name) { 18 | if(!toggle.containsKey(name)) { 19 | return true; 20 | } else { 21 | return toggle.get(name); 22 | } 23 | } 24 | 25 | public String getToggleName(String className, String signature) { 26 | return className + "." + signature; 27 | } 28 | 29 | public void updateToggle(String className, String signature, boolean enabled) { 30 | String name = getToggleName(className,signature); 31 | if(toggle.containsKey(name)) { 32 | toggle.replace(name,enabled); 33 | } else { 34 | toggle.put(name,enabled); 35 | } 36 | } 37 | 38 | public Map getAll() { 39 | return toggle; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for contributing to the project! Please read our [code of conduct](./CODE_OF_CONDUCT.md) before you get started with the project setup. 4 | 5 | ## IntelliJ IDEA setup 6 | 7 | We recommend using IntelliJ IDEA for development on this project. Configure as follows: 8 | 9 | 1. Select `File > Open` and select `agent-java/build.gradle`. 10 | 1. Select `Open as Project`. 11 | 1. Wait for the builds, imports, and indexing to finish. This may take a few minutes due to the project's size and complexity. 12 | 1. Add Java 11 SDK: select `File > Project Structure... > Platform Settings > SDKs > Add New SDK`. 13 | 1. Configure project SDK and target language level: select `File > Project Structure... > Project Settings > Project`. 14 | 1. Set `Project SDK` to JDK 11 15 | 1. Set `Project language level` to 11 16 | 17 | ## Building 18 | 19 | `./gradlew clean shadowJar` 20 | 21 | Run the above command from the project root directory to build the java agent: 22 | 23 | The Java agent requires JDK 11 or higher to build; your JAVA_HOME must be set to this JDK version. 24 | 25 | After building, Java agent artifacts are located in the directory: `build/generated/libs/` 26 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/CircuitBreaker.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class CircuitBreaker { 7 | private int precedence; 8 | private List methodsToCreate; 9 | private Toggle toggle; 10 | private String returnType; 11 | 12 | public CircuitBreaker() { 13 | methodsToCreate = new ArrayList<>(); 14 | } 15 | 16 | public int getPrecedence() { 17 | return precedence; 18 | } 19 | 20 | public void setPrecedence(int precedence) { 21 | this.precedence = precedence; 22 | } 23 | 24 | public List getMethodsToCreate() { 25 | return methodsToCreate; 26 | } 27 | 28 | public void setMethodsToCreate(List methodsToCreate) { 29 | this.methodsToCreate = methodsToCreate; 30 | } 31 | 32 | public Toggle getToggle() { 33 | return toggle; 34 | } 35 | 36 | public void setToggle(Toggle toggle) { 37 | this.toggle = toggle; 38 | } 39 | 40 | public String getReturnType() { 41 | return returnType; 42 | } 43 | 44 | public void setReturnType(String returnType) { 45 | this.returnType = returnType; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Framework.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Framework { 7 | private String name; 8 | private String version; 9 | private List circuitBreakers; 10 | private List sensors; 11 | 12 | public Framework() { 13 | this.circuitBreakers = new ArrayList<>(); 14 | this.sensors = new ArrayList<>(); 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getVersion() { 26 | return version; 27 | } 28 | 29 | public void setVersion(String version) { 30 | this.version = version; 31 | } 32 | 33 | public List getCircuitBreakers() { 34 | return circuitBreakers; 35 | } 36 | 37 | public void setCircuitBreakers(List circuitBreakers) { 38 | this.circuitBreakers = circuitBreakers; 39 | } 40 | 41 | public List getSensors() { 42 | return sensors; 43 | } 44 | 45 | public void setSensors(List sensors) { 46 | this.sensors = sensors; 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Agent.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | import run.facet.agent.java.exception.InstallException; 6 | 7 | import java.lang.instrument.Instrumentation; 8 | import java.util.Objects; 9 | 10 | public class Agent { 11 | public static void premain(String args, Instrumentation instrumentation) { 12 | try { 13 | ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); 14 | Transformer transformer = ctx.getBean(Transformer.class); 15 | instrumentation.addTransformer(transformer); 16 | } catch (Throwable throwable) { 17 | System.err.println("An exception prevented the facet agent from starting."); 18 | printFriendlyException(throwable); 19 | } 20 | } 21 | 22 | public static void printFriendlyException(Throwable throwable) { 23 | Throwable rootCause = findRootCause(throwable); 24 | if(rootCause instanceof InstallException) { 25 | rootCause.printStackTrace(); 26 | } else { 27 | throwable.printStackTrace(); 28 | } 29 | } 30 | 31 | public static Throwable findRootCause(Throwable throwable) { 32 | Objects.requireNonNull(throwable); 33 | Throwable rootCause = throwable; 34 | while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { 35 | rootCause = rootCause.getCause(); 36 | } 37 | return rootCause; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /db/configuration/block_list/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "attribute": { 3 | "signature": [ 4 | "java", 5 | "sun", 6 | "jdk", 7 | "javax", 8 | "com/sun", 9 | "com/google", 10 | "ch", 11 | "net", 12 | "springfox", 13 | "io/swagger", 14 | "javassist", 15 | "com/fasterxml", 16 | "com/jayway", 17 | "com/intellij", 18 | "antlr", 19 | "io/micrometer", 20 | "groovyResetJarjarAsm", 21 | "com/mysql", 22 | "com/zaxxer", 23 | "org/aspectj", 24 | "org/ehcache", 25 | "org/jboss", 26 | "org/thymeleaf", 27 | "org/xml", 28 | "nonapi/io", 29 | "io/github", 30 | "org/springframework", 31 | "org/LatencyUtils", 32 | "org/aopalliance", 33 | "org/apache", 34 | "org/h2", 35 | "org/hibernate", 36 | "io/github", 37 | "org/slf4j", 38 | "org/yaml", 39 | "org/webjars", 40 | "org/terracotta", 41 | "org/mapstruct", 42 | "org/dom4j", 43 | "org/ietf", 44 | "run/facet", 45 | "org/jetbrains", 46 | "org/groovy", 47 | "io/lettuce", 48 | "com/rometools", 49 | "com/github", 50 | "io/netty", 51 | "org/attoparser", 52 | "org/jsoup", 53 | "org/modelmapper", 54 | "org/jsoup", 55 | "org/unbescape", 56 | "org/w3c", 57 | "org/xmlbeam", 58 | "reactor", 59 | "org/tuckey", 60 | "org/flywaydb", 61 | "io/pivotal", 62 | "org/HdrHistogram", 63 | "org/reactivestreams" 64 | ] 65 | }, 66 | "id": "JAVA_PACKAGE_PREFIX~", 67 | "property": "BLOCK_LIST~" 68 | } -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Parameter.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Parameter { 9 | private String name; 10 | private String className; 11 | private Type type; 12 | private List values; 13 | private String value; 14 | private int position; 15 | 16 | public enum Type { 17 | @JsonProperty("string") 18 | string, 19 | @JsonProperty("list") 20 | list 21 | } 22 | 23 | public Parameter() { 24 | values = new ArrayList<>(); 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public String getClassName() { 36 | return className; 37 | } 38 | 39 | public void setClassName(String className) { 40 | this.className = className; 41 | } 42 | 43 | public Type getType() { 44 | return type; 45 | } 46 | 47 | public void setType(Type type) { 48 | this.type = type; 49 | } 50 | 51 | public int getPosition() { 52 | return position; 53 | } 54 | 55 | public void setPosition(int position) { 56 | this.position = position; 57 | } 58 | 59 | public List getValues() { 60 | return values; 61 | } 62 | 63 | public void setValues(List values) { 64 | this.values = values; 65 | } 66 | 67 | public void addValue(Parameter value) { 68 | values.add(value); 69 | } 70 | 71 | public String getValue() { 72 | return value; 73 | } 74 | 75 | public void setValue(String value) { 76 | this.value = value; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Annotation.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import javassist.bytecode.AnnotationsAttribute; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Annotation { 9 | private String className; 10 | private List parameters; 11 | private String visibility; 12 | 13 | 14 | public Annotation() { 15 | parameters = new ArrayList<>(); 16 | } 17 | 18 | public String getClassName() { 19 | return className; 20 | } 21 | 22 | public void setClassName(String className) { 23 | this.className = className; 24 | } 25 | 26 | public List getParameters() { 27 | return parameters; 28 | } 29 | 30 | public void setParameters(List parameters) { 31 | this.parameters = parameters; 32 | } 33 | 34 | public void addParameter(Parameter parameter) { 35 | parameters.add(parameter); 36 | } 37 | 38 | public String getVisibility() { 39 | return visibility; 40 | } 41 | 42 | public String getVisibilityString(String visibility) { 43 | String visibilityString; 44 | switch (visibility) { 45 | case "run.facet.dependencies.javassist.bytecode.AnnotationsAttribute.visibleTag": 46 | visibilityString = AnnotationsAttribute.visibleTag; 47 | break; 48 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.invisibleTag": 49 | visibilityString = AnnotationsAttribute.invisibleTag; 50 | break; 51 | default: 52 | visibilityString = null; 53 | } 54 | return visibilityString; 55 | } 56 | 57 | public void setVisibility(String visibility) { 58 | this.visibility = visibility; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Properties.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 5 | import org.springframework.stereotype.Component; 6 | import run.facet.agent.java.exception.InstallException; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.net.URISyntaxException; 11 | 12 | @Component 13 | public class Properties { 14 | 15 | private File file; 16 | private ObjectMapper objectMapper; 17 | private final String facetYaml = "/facet.yml"; 18 | 19 | public Properties() throws InstallException { 20 | this.file = getFacetYaml(); 21 | objectMapper = new ObjectMapper(new YAMLFactory()); 22 | } 23 | 24 | public String getJarPath() throws InstallException { 25 | try { 26 | return new File(Properties.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); 27 | } catch (URISyntaxException e) { 28 | throw new InstallException("Unable to parse facet.jar path", e); 29 | } 30 | } 31 | 32 | public String getFacetYamlPath() throws InstallException { 33 | return getJarPath() + facetYaml; 34 | } 35 | 36 | public File getFacetYaml() throws InstallException { 37 | File file = new File(getFacetYamlPath()); 38 | if (!file.exists()) { 39 | throw new InstallException("facet.yaml not found, create file=[" + getFacetYamlPath() + "]"); 40 | } 41 | return file; 42 | } 43 | 44 | public Object getProperty(Class clazz) throws InstallException { 45 | try { 46 | return objectMapper.readValue(file, clazz); 47 | } catch (IOException e) { 48 | throw new InstallException("Unable to fetch properties, file=[" + file.getAbsolutePath() + "],clazz=[" + clazz.getName() + "]"); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/LogInitializer.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.apache.logging.log4j.Level; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import org.apache.logging.log4j.core.LoggerContext; 7 | import org.apache.logging.log4j.core.config.Configurator; 8 | import org.apache.logging.log4j.core.config.builder.api.*; 9 | import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import run.facet.agent.java.exception.InstallException; 14 | 15 | @Component 16 | public class LogInitializer { 17 | 18 | private final String logName = "/facet.log"; 19 | private Logger logger; 20 | 21 | @Autowired 22 | public LogInitializer(Properties properties) throws InstallException { 23 | ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); 24 | LoggerContext loggerContext = new LoggerContext("facet"); 25 | builder.setLoggerContext(loggerContext); 26 | AppenderComponentBuilder file = builder.newAppender("log", "File"); 27 | file.addAttribute("fileName", properties.getJarPath() + logName); 28 | 29 | LayoutComponentBuilder standard = builder.newLayout("PatternLayout"); 30 | standard.addAttribute("pattern", "%d [%t] %-5level: %msg%n"); 31 | file.add(standard); 32 | builder.add(file); 33 | 34 | 35 | RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.ALL); 36 | rootLogger.add(builder.newAppenderRef("log")); 37 | builder.add(rootLogger); 38 | Configurator.initialize(builder.build()); 39 | 40 | logger = LogManager.getRootLogger(); 41 | logger.info("\n\n\nFacet Agent Starting..."); 42 | } 43 | 44 | public Logger getLogger() { 45 | return logger; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /db/configuration/circuit_breakers/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "attribute": [ 3 | { 4 | "returnType": "void", 5 | "toggle": { 6 | "method": { 7 | "body": "if(!${toggle}) {return;}" 8 | } 9 | } 10 | }, 11 | { 12 | "returnType": "byte", 13 | "toggle": { 14 | "method": { 15 | "body": "if(!${toggle}) {return Byte.MIN_VALUE;}" 16 | } 17 | } 18 | }, 19 | { 20 | "returnType": "short", 21 | "toggle": { 22 | "method": { 23 | "body": "if(!${toggle}) {return Short.MIN_VALUE;}" 24 | } 25 | } 26 | }, 27 | { 28 | "returnType": "int", 29 | "toggle": { 30 | "method": { 31 | "body": "if(!${toggle}) {return Integer.MIN_VALUE;}" 32 | } 33 | } 34 | }, 35 | { 36 | "returnType": "long", 37 | "toggle": { 38 | "method": { 39 | "body": "if(!${toggle}) {return Long.MIN_VALUE;}" 40 | } 41 | } 42 | }, 43 | { 44 | "returnType": "float", 45 | "toggle": { 46 | "method": { 47 | "body": "if(!${toggle}) {return Float.MIN_VALUE;}" 48 | } 49 | } 50 | }, 51 | { 52 | "returnType": "double", 53 | "toggle": { 54 | "method": { 55 | "body": "if(!${toggle}) {return Double.MIN_VALUE;}" 56 | } 57 | } 58 | }, 59 | { 60 | "returnType": "boolean", 61 | "toggle": { 62 | "method": { 63 | "body": "if(!${toggle}) {return false;}" 64 | } 65 | } 66 | }, 67 | { 68 | "returnType": "char", 69 | "toggle": { 70 | "method": { 71 | "body": "if(!${toggle}) {return Character.MIN_VALUE;}" 72 | } 73 | } 74 | }, 75 | { 76 | "returnType": "default", 77 | "toggle": { 78 | "method": { 79 | "body": "if(!${toggle}) {return null;}" 80 | } 81 | } 82 | } 83 | ], 84 | "id": "JAVA~1", 85 | "property": "CIRCUIT_BREAKER~" 86 | } -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/App.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.util.StringUtils; 6 | import run.facet.agent.java.exception.InstallException; 7 | 8 | @Component 9 | public class App { 10 | private Object attribute; 11 | private String name; 12 | private String environment; 13 | private String workspaceId; 14 | private String apiKey; 15 | // need this for API AppId 16 | private String Id; 17 | 18 | @Autowired 19 | public App(Properties properties) throws InstallException { 20 | App app = (App) properties.getProperty(App.class); 21 | this.name = app.name; 22 | this.environment = app.environment; 23 | this.workspaceId = app.workspaceId; 24 | this.apiKey = app.apiKey; 25 | if(!StringUtils.hasLength(this.name) || !StringUtils.hasLength(this.environment) || !StringUtils.hasLength(this.workspaceId)|| !StringUtils.hasLength(this.apiKey)) { 26 | throw new InstallException("The following properties are required in file=[" + properties.getFacetYamlPath()+ "],properties=[name, environment, workspaceId, apiKey]"); 27 | } 28 | } 29 | 30 | public App() { 31 | } 32 | 33 | public Object getAttribute() { 34 | return attribute; 35 | } 36 | 37 | public void setAttribute(Object attribute) { 38 | this.attribute = attribute; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getEnvironment() { 50 | return environment; 51 | } 52 | 53 | public void setEnvironment(String environment) { 54 | this.environment = environment; 55 | } 56 | 57 | public String getWorkspaceId() { 58 | return workspaceId; 59 | } 60 | 61 | public void setWorkspaceId(String workspaceId) { 62 | this.workspaceId = workspaceId; 63 | } 64 | 65 | public String getApiKey() { 66 | return apiKey; 67 | } 68 | 69 | public void setApiKey(String apiKey) { 70 | this.apiKey = apiKey; 71 | } 72 | 73 | public String getId() { 74 | return Id; 75 | } 76 | 77 | public void setId(String id) { 78 | Id = id; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Frameworks.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.*; 7 | 8 | @Component 9 | public class Frameworks { 10 | private String id = "JAVA~1"; 11 | private String property = "FRAMEWORK~"; 12 | private List frameworks; 13 | private Map frameworkAnnotationMap; 14 | 15 | private WebRequest webRequest; 16 | 17 | @Autowired 18 | private Frameworks(WebRequest webRequest) { 19 | this.webRequest = webRequest; 20 | fetchFrameworks(); 21 | } 22 | 23 | private void fetchFrameworks() { 24 | Framework framework = (Framework) webRequest.fetchConfiguration(this.property, this.id,"attribute", Framework.class); 25 | List frameworks = new ArrayList<>(){{add(framework);}}; 26 | this.frameworks = frameworks; 27 | this.frameworkAnnotationMap = generateMap(frameworks); 28 | } 29 | 30 | public Map generateMap(List frameworks) { 31 | Map frameworkAnnotationMap = new HashMap<>(); 32 | for (Framework framework : frameworks) { 33 | for (Sensor sensor : framework.getSensors()) { 34 | for(Annotation annotation : sensor.getAnnotations()) 35 | frameworkAnnotationMap.put(annotation.getClassName(), framework); 36 | } 37 | } 38 | return frameworkAnnotationMap; 39 | } 40 | 41 | public boolean isFramework(Annotation annotation) { 42 | return frameworkAnnotationMap.containsKey(annotation.getClassName()); 43 | } 44 | 45 | public boolean isFramework(List annotations) { 46 | for(Annotation annotation : annotations) { 47 | if(isFramework(annotation)) { 48 | return true; 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | public Framework getFramework(Annotation annotation) { 55 | return frameworkAnnotationMap.get(annotation.getClassName()); 56 | } 57 | 58 | public Framework getFramework(List annotations) { 59 | for(Annotation annotation : annotations) { 60 | if(isFramework(annotation)) { 61 | return getFramework(annotation); 62 | } 63 | } 64 | return null; 65 | } 66 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/CircuitBreakers.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.*; 8 | import java.util.concurrent.locks.ReadWriteLock; 9 | import java.util.concurrent.locks.ReentrantReadWriteLock; 10 | 11 | @Component 12 | public class CircuitBreakers { 13 | private String id = "JAVA~1"; 14 | private String property = "CIRCUIT_BREAKER~"; 15 | private List circuitBreakers; 16 | private static Map circuitBreakerMap; 17 | 18 | private Timer timer; 19 | private int cacheRefreshInterval = 30000; 20 | 21 | private ReadWriteLock lock = new ReentrantReadWriteLock(); 22 | private WebRequest webRequest; 23 | private LogInitializer logInitializer; 24 | private Logger logger; 25 | 26 | public CircuitBreakers() { 27 | circuitBreakers = new ArrayList<>(); 28 | circuitBreakerMap = new HashMap<>(); 29 | } 30 | 31 | @Autowired 32 | private CircuitBreakers(WebRequest webRequest, LogInitializer logInitializer) { 33 | this.logInitializer = logInitializer; 34 | this.logger = logInitializer.getLogger(); 35 | this.webRequest = webRequest; 36 | fetchCircuitBreakerList(); 37 | timer = new Timer(); 38 | timer.schedule(new CircuitBreakerTimer(), cacheRefreshInterval, cacheRefreshInterval); 39 | } 40 | 41 | private void fetchCircuitBreakerList() { 42 | 43 | try { 44 | List circuitBreakers = (List) (Object) webRequest.fetchConfigurationList(this.property, this.id, "attribute", CircuitBreaker.class); 45 | Map circuitBreakerMap = createMap(circuitBreakers); 46 | lock.writeLock().lock(); 47 | this.circuitBreakers = circuitBreakers; 48 | this.circuitBreakerMap = circuitBreakerMap; 49 | lock.writeLock().unlock(); 50 | } catch (java.lang.Exception e) { 51 | logger.error(e.getMessage(), e); 52 | } 53 | } 54 | 55 | private Map createMap(List circuitBreakers) { 56 | Map circuitBreakerMap = new HashMap<>(); 57 | for (CircuitBreaker circuitBreaker : circuitBreakers) { 58 | circuitBreakerMap.put(circuitBreaker.getReturnType(), circuitBreaker); 59 | } 60 | return circuitBreakerMap; 61 | } 62 | 63 | public CircuitBreaker getBreaker(String returnType) { 64 | if (circuitBreakerMap.containsKey(returnType)) { 65 | return circuitBreakerMap.get(returnType); 66 | } else { 67 | return circuitBreakerMap.get("default"); 68 | } 69 | } 70 | 71 | 72 | private class CircuitBreakerTimer extends TimerTask { 73 | @Override 74 | public void run() { 75 | fetchCircuitBreakerList(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/BlockList.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.*; 8 | import java.util.concurrent.locks.ReadWriteLock; 9 | import java.util.concurrent.locks.ReentrantReadWriteLock; 10 | 11 | @Component 12 | public class BlockList { 13 | private String id = "JAVA_PACKAGE_PREFIX~"; 14 | private String property = "BLOCK_LIST~"; 15 | private static Map blockList = new HashMap<>() {{ 16 | put("java", "java"); 17 | put("sun", "sun"); 18 | put("jdk", "jdk"); 19 | put("org", "org"); 20 | }}; 21 | 22 | private Timer timer; 23 | private int blockListRefreshIntervalInSeconds = 30000; 24 | 25 | private ReadWriteLock lock = new ReentrantReadWriteLock(); 26 | private WebRequest webRequest; 27 | private LogInitializer logInitializer; 28 | private Logger logger; 29 | 30 | @Autowired 31 | private BlockList(WebRequest webRequest, LogInitializer logInitializer) { 32 | this.logInitializer = logInitializer; 33 | this.logger = logInitializer.getLogger(); 34 | this.webRequest = webRequest; 35 | fetchBlockList(); 36 | timer = new Timer(true); 37 | timer.schedule(new BlockListTimer(), blockListRefreshIntervalInSeconds, blockListRefreshIntervalInSeconds); 38 | } 39 | 40 | private void fetchBlockList() { 41 | try { 42 | Configuration configuration = webRequest.fetchConfiguration(this.property, this.id); 43 | Map newBlockList = convertConfigurationToBlockList(configuration); 44 | lock.writeLock().lock(); 45 | blockList = newBlockList; 46 | lock.writeLock().unlock(); 47 | } catch (java.lang.Exception e) { 48 | logger.error(e.getMessage(), e); 49 | } 50 | } 51 | 52 | 53 | public Map convertConfigurationToBlockList(Configuration configuration) { 54 | Map attribute = configuration.getAttribute(); 55 | Map blockList = new HashMap<>(); 56 | for (String item : (List) attribute.get("signature")) { 57 | blockList.put(item, item); 58 | } 59 | return blockList; 60 | } 61 | 62 | public boolean contains(String className) { 63 | String[] packageParts = className.split("/"); 64 | String qualifiedName = ""; 65 | lock.readLock().lock(); 66 | try { 67 | for (String part : packageParts) { 68 | if (qualifiedName == "") { 69 | qualifiedName = part; 70 | } else { 71 | qualifiedName += "/" + part; 72 | } 73 | if (blockList.containsKey(qualifiedName)) { 74 | return true; 75 | } 76 | } 77 | return false; 78 | } finally { 79 | lock.readLock().unlock(); 80 | } 81 | 82 | } 83 | 84 | private class BlockListTimer extends TimerTask { 85 | @Override 86 | public void run() { 87 | fetchBlockList(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Facets.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import org.apache.logging.log4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.*; 8 | import java.util.concurrent.locks.ReadWriteLock; 9 | import java.util.concurrent.locks.ReentrantReadWriteLock; 10 | 11 | @Component 12 | public class Facets { 13 | private List facets; 14 | private Map facetMap; 15 | private App app; 16 | private Timer timer; 17 | private int refreshInterval = 10000; 18 | private ReadWriteLock lock = new ReentrantReadWriteLock(); 19 | 20 | private Toggles toggles; 21 | private WebRequest webRequest; 22 | private LogInitializer logInitializer; 23 | private Logger logger; 24 | 25 | //TODO fix race condition where facets could be overwritten by the timer during parsing and vice versa. 26 | @Autowired 27 | public Facets(App app, WebRequest webRequest, Toggles toggles, LogInitializer logInitializer) { 28 | this.logInitializer = logInitializer; 29 | this.logger = logInitializer.getLogger(); 30 | this.webRequest = webRequest; 31 | this.app = app; 32 | this.facetMap = new HashMap<>(); 33 | this.toggles = toggles; 34 | fetchFacets(); 35 | timer = new Timer(true); 36 | timer.schedule(new FacetTimer(), refreshInterval, refreshInterval); 37 | } 38 | 39 | private void fetchFacets() { 40 | try { 41 | List facets = webRequest.fetchFacet(); 42 | Map facetMap = updateFacetMaps(facets); 43 | lock.writeLock().lock(); 44 | this.facets = facets; 45 | this.facetMap = facetMap; 46 | lock.writeLock().unlock(); 47 | } catch (java.lang.Exception e) { 48 | logger.error(e.getMessage(), e); 49 | } 50 | } 51 | 52 | private Map updateFacetMaps(List facets) { 53 | Map facetMap = new HashMap<>(); 54 | for (Facet facet : facets) { 55 | facetMap.put(facet.getFullyQualifiedName(), facet); 56 | updateToggles(facet); 57 | } 58 | return facetMap; 59 | } 60 | 61 | public void updateToggles(Facet facet) { 62 | for (Signature signature : facet.getSignature()) { 63 | toggles.updateToggle(facet.getFullyQualifiedName(), signature.getSignature(), signature.isEnabled()); 64 | } 65 | } 66 | 67 | public void add(Facet facet) { 68 | //TODO add support for facets changes 69 | if (!contains(facet)) { 70 | lock.writeLock().lock(); 71 | this.facets.add(facet); 72 | facetMap.put(facet.getFullyQualifiedName(), facet); 73 | updateToggles(facet); 74 | lock.writeLock().unlock(); 75 | webRequest.createFacet(facet); 76 | } 77 | 78 | } 79 | 80 | public boolean contains(Facet facet) { 81 | return facetMap.containsKey(facet.getFullyQualifiedName()); 82 | } 83 | 84 | public Facet get(String name) { 85 | return facetMap.get(name); 86 | } 87 | 88 | private class FacetTimer extends TimerTask { 89 | @Override 90 | public void run() { 91 | fetchFacets(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Signature.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Signature { 9 | private boolean enabled; 10 | private String name; 11 | private List parameter; 12 | private Map parameterClassNameMap; 13 | private Map parameterNameMap; 14 | private String returnType; 15 | private String signature; 16 | private List annotation; 17 | private Map annotationMap; 18 | 19 | public Signature() { 20 | this.parameter = new ArrayList<>(); 21 | this.parameterNameMap = new HashMap<>(); 22 | this.parameterClassNameMap = new HashMap<>(); 23 | } 24 | 25 | public boolean isEnabled() { 26 | return enabled; 27 | } 28 | 29 | public void setEnabled(boolean enabled) { 30 | this.enabled = enabled; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public List getParameter() { 42 | return parameter; 43 | } 44 | 45 | public void setParameter(List parameter) { 46 | this.parameter = parameter; 47 | } 48 | 49 | public void addParameter(Parameter parameter) { 50 | this.parameter.add(parameter); 51 | if(parameter.getClassName() != null) { 52 | this.parameterClassNameMap.put(parameter.getClassName(),parameter); 53 | } 54 | this.parameterNameMap.put(parameter.getName(),parameter); 55 | } 56 | 57 | public Parameter getParameterByName(String name) { 58 | return this.parameterNameMap.get(name); 59 | } 60 | 61 | public Parameter getParameterByReturnType(String returnType) { 62 | return this.parameterClassNameMap.get(returnType); 63 | } 64 | 65 | public String getReturnType() { 66 | return returnType; 67 | } 68 | 69 | public void setReturnType(String returnType) { 70 | this.returnType = returnType; 71 | } 72 | 73 | public void setSignature(String signature) { 74 | this.signature = signature; 75 | } 76 | 77 | public String getSignature() { 78 | if(this.signature == null) { 79 | String signature = this.returnType + ";" + this. name + "("; 80 | String params = ""; 81 | for (Parameter parameter: this.parameter) { 82 | params += parameter.getType() + ","; 83 | } 84 | 85 | if (params.length() > 1) { 86 | params = params.substring(0,params.length() - 1); 87 | } 88 | 89 | signature += params + ")"; 90 | this.signature = signature; 91 | } 92 | return signature; 93 | 94 | } 95 | 96 | public List getAnnotation() { 97 | return annotation; 98 | } 99 | 100 | public void setAnnotation(List annotation) { 101 | this.annotation = annotation; 102 | } 103 | 104 | public void updateAnnotationMap(List annotations) { 105 | Map newAnnotationMap = new HashMap<>(); 106 | for(Annotation annotation : annotations) { 107 | 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others’ private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@facet.ninja. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 44 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 45 | 46 | For answers to common questions about this code of conduct, see 47 | https://www.contributor-covenant.org/faq 48 | -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Facet.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Facet { 9 | private String appId; 10 | private String fullyQualifiedName; 11 | private List signature; 12 | private Map signatureMap; 13 | private String version; 14 | private Map attribute; 15 | private Language language; 16 | private List framework; 17 | private List interfaceSignature; 18 | private String parentSignature; 19 | private String type; 20 | private List annotation; 21 | 22 | 23 | public Facet(String appId, String fullyQualifiedName, String type, String version, Language language) { 24 | this.appId = appId; 25 | this.fullyQualifiedName = fullyQualifiedName; 26 | this.version = version; 27 | this.language = language; 28 | this.type = type; 29 | this.signature = new ArrayList<>(); 30 | this.signatureMap = new HashMap<>(); 31 | this.attribute = new HashMap<>(); 32 | this.framework = new ArrayList<>(); 33 | this.interfaceSignature = new ArrayList<>(); 34 | this.annotation = new ArrayList<>(); 35 | } 36 | 37 | public Facet() { 38 | } 39 | 40 | public String getAppId() { 41 | return appId; 42 | } 43 | 44 | public void setAppId(String appId) { 45 | this.appId = appId; 46 | } 47 | 48 | public String getFullyQualifiedName() { 49 | return fullyQualifiedName; 50 | } 51 | 52 | public void setFullyQualifiedName(String fullyQualifiedName) { 53 | this.fullyQualifiedName = fullyQualifiedName; 54 | } 55 | 56 | public List getSignature() { 57 | return signature; 58 | } 59 | 60 | public void setSignature(List signatureList) { 61 | if(signatureList == null) { 62 | signatureList = new ArrayList<>(); 63 | } 64 | this.signature = signatureList; 65 | signatureMap = new HashMap<>(); 66 | for (Signature signature : signatureList) { 67 | signatureMap.put(signature.getSignature(), signature); 68 | } 69 | } 70 | 71 | public boolean hasSignature(String signature) { 72 | return !(signatureMap == null) && signatureMap.containsKey(signature); 73 | } 74 | 75 | public Signature getSignature(String signature) { 76 | return signatureMap.get(signature); 77 | } 78 | 79 | public String getParentSignature() { 80 | return parentSignature; 81 | } 82 | 83 | public void setParentSignature(String parentSignature) { 84 | this.parentSignature = parentSignature; 85 | } 86 | 87 | public String getVersion() { 88 | return version; 89 | } 90 | 91 | public void setVersion(String version) { 92 | this.version = version; 93 | } 94 | 95 | public Map getAttribute() { 96 | return attribute; 97 | } 98 | 99 | public void setAttribute(Map attribute) { 100 | this.attribute = attribute; 101 | } 102 | 103 | public Language getLanguage() { 104 | return language; 105 | } 106 | 107 | public void setLanguage(Language language) { 108 | this.language = language; 109 | } 110 | 111 | public List getFramework() { 112 | return framework; 113 | } 114 | 115 | public void setFramework(List framework) { 116 | this.framework = framework; 117 | } 118 | 119 | public List getInterfaceSignature() { 120 | return interfaceSignature; 121 | } 122 | 123 | public void setInterfaceSignature(List interfaceSignature) { 124 | this.interfaceSignature = interfaceSignature; 125 | } 126 | 127 | public String getType() { 128 | return type; 129 | } 130 | 131 | public void setType(String type) { 132 | this.type = type; 133 | } 134 | 135 | public List getAnnotation() { 136 | return annotation; 137 | } 138 | 139 | public void setAnnotation(List annotation) { 140 | this.annotation = annotation; 141 | } 142 | } -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Method.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import javassist.CtClass; 4 | import javassist.Modifier; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class Method { 10 | private List annotations; 11 | private String body; 12 | private List exceptions; 13 | private String modifier; 14 | private String name; 15 | private List parameters; 16 | private String returnType; 17 | 18 | public Method() { 19 | this.annotations = new ArrayList<>(); 20 | this.exceptions = new ArrayList<>(); 21 | this.parameters = new ArrayList<>(); 22 | } 23 | 24 | public List getAnnotations() { 25 | return annotations; 26 | } 27 | 28 | public void setAnnotations(List annotations) { 29 | this.annotations = annotations; 30 | } 31 | 32 | public String getBody() { 33 | return body; 34 | } 35 | 36 | public void setBody(String body) { 37 | this.body = body; 38 | } 39 | 40 | public List getExceptions() { 41 | return exceptions; 42 | } 43 | 44 | public void setExceptions(List exceptions) { 45 | this.exceptions = exceptions; 46 | } 47 | 48 | public String getModifier() { 49 | return modifier; 50 | } 51 | 52 | public int getModifierInt (String modifier) { 53 | int modifierInt; 54 | switch(modifier) { 55 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.PUBLIC": 56 | modifierInt = Modifier.PUBLIC; 57 | break; 58 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.PRIVATE": 59 | modifierInt = Modifier.PRIVATE; 60 | break; 61 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.PROTECTED": 62 | modifierInt = Modifier.PROTECTED; 63 | break; 64 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.STATIC": 65 | modifierInt = Modifier.STATIC; 66 | break; 67 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.FINAL": 68 | modifierInt = Modifier.FINAL; 69 | break; 70 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.SYNCHRONIZED": 71 | modifierInt = Modifier.SYNCHRONIZED; 72 | break; 73 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.VOLATILE": 74 | modifierInt = Modifier.VOLATILE; 75 | break; 76 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.VARARGS": 77 | modifierInt = Modifier.VARARGS; 78 | break; 79 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.TRANSIENT": 80 | modifierInt = Modifier.TRANSIENT; 81 | break; 82 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.NATIVE": 83 | modifierInt = Modifier.NATIVE; 84 | break; 85 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.INTERFACE": 86 | modifierInt = Modifier.INTERFACE; 87 | break; 88 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.ABSTRACT": 89 | modifierInt = Modifier.ABSTRACT; 90 | break; 91 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.STRICT": 92 | modifierInt = Modifier.STRICT; 93 | break; 94 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.ANNOTATION": 95 | modifierInt = Modifier.ANNOTATION; 96 | break; 97 | case "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.ENUM": 98 | modifierInt = Modifier.ENUM; 99 | break; 100 | default: 101 | modifierInt = Integer.MIN_VALUE; 102 | } 103 | return modifierInt; 104 | } 105 | 106 | public void setModifier(String modifier) { 107 | this.modifier = modifier; 108 | } 109 | 110 | public String getName() { 111 | return name; 112 | } 113 | 114 | public void setName(String name) { 115 | this.name = name; 116 | } 117 | 118 | public List getParameters() { 119 | return parameters; 120 | } 121 | 122 | public void setParameters(List parameters) { 123 | this.parameters = parameters; 124 | } 125 | 126 | public String getReturnType() { 127 | return this.returnType; 128 | } 129 | 130 | public CtClass getReturnType2(String returnType) { 131 | CtClass ctClass; 132 | switch (returnType) { 133 | case "run.facet.dependencies.javassist.CtClass.booleanType": 134 | ctClass = CtClass.booleanType; 135 | break; 136 | case "run.facet.dependencies.javassist.CtClass.charType": 137 | ctClass = CtClass.charType; 138 | break; 139 | case "run.facet.dependencies.javassist.CtClass.byteType": 140 | ctClass = CtClass.byteType; 141 | break; 142 | case "run.facet.dependencies.javassist.CtClass.shortType": 143 | ctClass = CtClass.shortType; 144 | break; 145 | case "run.facet.dependencies.javassist.CtClass.intType": 146 | ctClass = CtClass.intType; 147 | break; 148 | case "run.facet.dependencies.javassist.CtClass.longType": 149 | ctClass = CtClass.longType; 150 | break; 151 | case "run.facet.dependencies.javassist.CtClass.floatType": 152 | ctClass = CtClass.floatType; 153 | break; 154 | case "run.facet.dependencies.javassist.CtClass.doubleType": 155 | ctClass = CtClass.doubleType; 156 | break; 157 | case "run.facet.dependencies.javassist.CtClass.voidType": 158 | ctClass = CtClass.voidType; 159 | break; 160 | default: 161 | ctClass = null; 162 | } 163 | return ctClass; 164 | } 165 | 166 | public void setReturnType(String returnType) { 167 | this.returnType = returnType; 168 | } 169 | } 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 |
3 | 4 | Facet logo 5 |
6 |
7 | Toggle methods and endpoints instantly 8 |
9 |
10 |
11 | 12 | [![Github](https://github.com/facet-tech/agent-java/actions/workflows/main.yml/badge.svg)](https://github.com/facet-tech/agent-java/actions/workflows/main.yml/badge.svg) 13 | [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) 14 | 15 | > The Facet Java Agent uses the Bytecode Instrumentation API to dynamically enable and disable methods at runtime within your application. 16 | > This is achieved by transferring control back to the caller of the method via injected return statements. 17 | > After integration, you can toggle methods and endpoints in realtime without modifying or restarting you application. 18 | 19 | ## Download 20 | Download the latest version of the Facet java-agent from the [maven central](https://repo1.maven.org/maven2/run/facet/agent/java/facet-agent/0.0.13/facet-agent-0.0.13.jar). 21 | 22 | ## Install 23 | 1. Move the Facet java-agent JAR in the root of the application folder, or in a subfolder in your project. 24 | 2. Configure your JVM to load the agent during your application's premain start-up by passing this command-line argument: `-javaagent:/facet-agent-VERSION.jar`. Replace `facet-ageent-VERSION.jar` with the **absolute path** of the Facet java-agent JAR. For IDEA users, this usually can be found at "Edit Configuration" -> "VM options". 25 | 26 | 3. Create a `facet.yml` file located in the **same directory** as the `facet-agent.jar` from step 1. 27 | 28 | ``` 29 | apiKey: API_KEY 30 | workspaceId: WORKSPACE_ID 31 | name: APPLICATION_NAME 32 | environment: ENVIRONMENT 33 | ``` 34 | In order to retrieve `workspaceId` and `apiKey`, you need to create an account in the [Facet Dashboard](https://app.facet.run) 35 | 36 | `apiKey` Used for Facet API authentication. 37 | `workspaceId` The ID of the workspace. 38 | `name` The name of your application. 39 | `environment` The environment of your application deployment. For instance, you may use `local` for your local environment. 40 | 41 | 4. Start your application and toggle endpoints and methods via the [Facet Dashboard](https://app.facet.run) -> *Applications*. 42 | 43 | ## Troubleshooting 44 | 1. In case you've encountered some warnings/error, you can troubleshoot by opening the `facet.log` file, which is generated in the _same directory_ with the facet.yml, once you start the application. Potential warnings and errors are logged in that file. 45 | 2. If you encounter `javax.management.InstanceAlreadyExistsException` while running locally, *disable* JMX integration in your IDE 46 | 47 | ## Usage Cases 48 | 49 | 1. Endpoint management - disable or reroute endpoints on the fly for beta features or when application and performance issues arise. 50 | 2. Chaos engineering - automated chaos engineering testing framework. 51 | 3. Logging toggles - increase and decrease logging or add and remove log statements dynamically without deployment and restarting applications. 52 | 4. Automatic documentation generation, as well as endpoint detection. 53 | 5. Low code feature flags - low code alternative to traditional feature flag toggles. 54 | 55 | We would love to hear your feedback! 56 | 57 | ## Feedback and Support 58 | 59 | Open an [issue](https://github.com/facet-tech/agent-java/issues) or send an email at `engineering@facet.run`. 60 | 61 | ## Technical Details 62 | 63 | ### Circuit Breakers 64 | 65 | Method toggles are achieved by stopping method execution and transferring control back to the caller via injected return statements called circuit breakers. Below are the default return values for method return types. 66 | 67 | ``` 68 | METHOD_RETURN_TYPE VALUE 69 | ------------------ ----------------------- 70 | byte Byte.MIN_VALUE 71 | short Short.MIN_VALUE 72 | int Integer.MIN_VALUE 73 | long Long.MIN_VALUE 74 | float Float.MIN_VALUE 75 | double Double.MIN_VALUE 76 | char Character.MIN_VALUE 77 | boolean false 78 | void void 79 | other null 80 | ``` 81 | Circuit breakers are data driven and will soon be configurable. They are stored in this [directory](db/configuration/circuit_breakers). 82 | 83 | ### Frameworks 84 | 85 | Frameworks are a combination circuit breakers which detect annotations, interfaces, and inheritance to customize the return value (response) creating support for HTTP requests and endpoints. If a framework is detected, the following return values will be used instead of the default circuit breaker mapping. 86 | 87 | Currently, we support Spring Framework version 3.0.x and higher via the following spring annotations. 88 | 89 | ``` 90 | ANNOTATION_CLASS RETURN VALUE 91 | (org.springframework.web.bind.annotation) (javax.servlet.http.HttpServletResponse) 92 | ----------------------------------------- ----------------- 93 | RequestMapping sendError(403,"Access Denied") 94 | GetMapping sendError(403,"Access Denied") 95 | PostMapping sendError(403,"Access Denied") 96 | PutMapping sendError(403,"Access Denied") 97 | DeleteMapping sendError(403,"Access Denied") 98 | PatchMapping sendError(403,"Access Denied") 99 | ``` 100 | 101 | Frameworks are data driven and will soon be configurable. 102 | 103 | Frameworks are stored in this [directory](db/configuration/frameworks). 104 | 105 | ### Block List 106 | 107 | A list of packages and classes not processed preventing method and endpoint toggle overload. 108 | 109 | See [block list.](db/configuration/block_list/default.json) 110 | 111 | Block lists are data driven and will soon be configurable. 112 | 113 | Block lists are stored in this [directory](https://github.com/facet-tech/agent-java/tree/main/db/configuration/circuit_breakers). 114 | 115 | ## Roadmap 116 | 117 | The future plans and high priority features and enhancements can be found in the [GitHub project board](https://github.com/orgs/facet-tech/projects/3). 118 | 119 | ## Contributing 120 | 121 | Thank you for contributing to the project! Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file to get started with the Facet Java-Agent project! 122 | 123 | ## Compatibility 124 | Java: JVM runtime 11 or higher is required, however, your source and target can be compiled to any java version. 125 | 126 | ## Demo 127 | 128 | View the [demo here](https://facet.run/video/java.mp4). 129 | 130 | ## License 131 | 132 | [MIT](./LICENSE) 133 | -------------------------------------------------------------------------------- /db/configuration/frameworks/spring.json: -------------------------------------------------------------------------------- 1 | { 2 | "attribute": { 3 | "circuitBreakers": [ 4 | { 5 | "precedence": 1, 6 | "toggle": { 7 | "method": { 8 | "body": "if(!${toggle}) {try { ${response}.sendError(403,\"Access Denied\"); } catch (Exception e) { } return null;}" 9 | }, 10 | "parameterMapping": { 11 | "response": "javax.servlet.http.HttpServletResponse" 12 | } 13 | } 14 | }, 15 | { 16 | "methodsToCreate": [ 17 | { 18 | "annotations": [ 19 | { 20 | "className": "org.springframework.web.bind.annotation.ExceptionHandler", 21 | "parameters": [ 22 | { 23 | "className": "run.facet.dependencies.javassist.bytecode.annotation.ArrayMemberValue", 24 | "name": "value", 25 | "type": "list", 26 | "values": [ 27 | { 28 | "className": "run.facet.dependencies.javassist.bytecode.annotation.ClassMemberValue", 29 | "type": "string", 30 | "value": "run.facet.agent.java.CircuitBreakerException" 31 | } 32 | ] 33 | } 34 | ], 35 | "visibility": "run.facet.dependencies.javassist.bytecode.AnnotationsAttribute.visibleTag" 36 | } 37 | ], 38 | "body": "try { $3.sendError(403,\"Access Denied\"); } catch (Exception e) {}", 39 | "modifier": "run.facet.dependencies.javassist.bytecode.AccessFlag.Modifier.PUBLIC", 40 | "name": "handleFacetRunCircuitBreakerException", 41 | "parameters": [ 42 | { 43 | "className": "run.facet.agent.java.CircuitBreakerException" 44 | }, 45 | { 46 | "className": "javax.servlet.http.HttpServletRequest" 47 | }, 48 | { 49 | "className": "javax.servlet.http.HttpServletResponse" 50 | } 51 | ], 52 | "returnType": "run.facet.dependencies.javassist.CtClass.voidType" 53 | } 54 | ], 55 | "precedence": 2, 56 | "toggle": { 57 | "method": { 58 | "body": "if(!${toggle}) {throw new run.facet.agent.java.CircuitBreakerException();}", 59 | "exceptions": [ 60 | { 61 | "className": "run.facet.agent.java.CircuitBreakerException" 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | ], 68 | "name": "Spring", 69 | "sensors": [ 70 | { 71 | "annotations": [ 72 | { 73 | "className": "org.springframework.web.bind.annotation.RequestMapping", 74 | "parameters": [ 75 | { 76 | "name": "consumes" 77 | }, 78 | { 79 | "name": "headers" 80 | }, 81 | { 82 | "name": "name" 83 | }, 84 | { 85 | "name": "params" 86 | }, 87 | { 88 | "name": "path" 89 | }, 90 | { 91 | "name": "produces" 92 | }, 93 | { 94 | "name": "value" 95 | } 96 | ] 97 | }, 98 | { 99 | "className": "org.springframework.web.bind.annotation.GetMapping", 100 | "parameters": [ 101 | { 102 | "name": "consumes" 103 | }, 104 | { 105 | "name": "headers" 106 | }, 107 | { 108 | "name": "name" 109 | }, 110 | { 111 | "name": "params" 112 | }, 113 | { 114 | "name": "path" 115 | }, 116 | { 117 | "name": "produces" 118 | }, 119 | { 120 | "name": "value" 121 | } 122 | ] 123 | }, 124 | { 125 | "className": "org.springframework.web.bind.annotation.PostMapping", 126 | "parameters": [ 127 | { 128 | "name": "consumes" 129 | }, 130 | { 131 | "name": "headers" 132 | }, 133 | { 134 | "name": "name" 135 | }, 136 | { 137 | "name": "params" 138 | }, 139 | { 140 | "name": "path" 141 | }, 142 | { 143 | "name": "produces" 144 | }, 145 | { 146 | "name": "value" 147 | } 148 | ] 149 | }, 150 | { 151 | "className": "org.springframework.web.bind.annotation.PutMapping", 152 | "parameters": [ 153 | { 154 | "name": "consumes" 155 | }, 156 | { 157 | "name": "headers" 158 | }, 159 | { 160 | "name": "name" 161 | }, 162 | { 163 | "name": "params" 164 | }, 165 | { 166 | "name": "path" 167 | }, 168 | { 169 | "name": "produces" 170 | }, 171 | { 172 | "name": "value" 173 | } 174 | ] 175 | }, 176 | { 177 | "className": "org.springframework.web.bind.annotation.DeleteMapping", 178 | "parameters": [ 179 | { 180 | "name": "consumes" 181 | }, 182 | { 183 | "name": "headers" 184 | }, 185 | { 186 | "name": "name" 187 | }, 188 | { 189 | "name": "params" 190 | }, 191 | { 192 | "name": "path" 193 | }, 194 | { 195 | "name": "produces" 196 | }, 197 | { 198 | "name": "value" 199 | } 200 | ] 201 | }, 202 | { 203 | "className": "org.springframework.web.bind.annotation.PatchMapping", 204 | "parameters": [ 205 | { 206 | "name": "consumes" 207 | }, 208 | { 209 | "name": "headers" 210 | }, 211 | { 212 | "name": "name" 213 | }, 214 | { 215 | "name": "params" 216 | }, 217 | { 218 | "name": "path" 219 | }, 220 | { 221 | "name": "produces" 222 | }, 223 | { 224 | "name": "value" 225 | } 226 | ] 227 | } 228 | ] 229 | } 230 | ], 231 | "version": "" 232 | }, 233 | "id": "JAVA~1", 234 | "property": "FRAMEWORK~" 235 | } -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/WebRequest.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.ObjectWriter; 8 | import com.fasterxml.jackson.databind.type.CollectionType; 9 | import org.apache.logging.log4j.Logger; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | import run.facet.agent.java.exception.InstallException; 13 | 14 | import java.net.URI; 15 | import java.net.http.HttpClient; 16 | import java.net.http.HttpRequest; 17 | import java.net.http.HttpResponse; 18 | import java.time.Duration; 19 | import java.util.List; 20 | import java.util.concurrent.*; 21 | 22 | @Component 23 | public class WebRequest { 24 | 25 | private String API_KEY = "ApiKey"; 26 | private static ExecutorService threadPool = Executors.newFixedThreadPool(20, new MyThreadFactory()); 27 | private final String BaseUrl = "https://api.facet.run/"; 28 | private App app; 29 | private static Logger logger; 30 | private LogInitializer logInitializer; 31 | private Properties properties; 32 | 33 | @Autowired 34 | public WebRequest(App app, LogInitializer logInitializer, Properties properties) throws InstallException { 35 | this.logInitializer = logInitializer; 36 | this.logger = logInitializer.getLogger(); 37 | this.app = app; 38 | this.properties = properties; 39 | createApp(); 40 | } 41 | 42 | static { 43 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 44 | logger.debug("Performing some shutdown cleanup..."); 45 | threadPool.shutdown(); 46 | while (true) { 47 | try { 48 | logger.debug("Waiting for the service to terminate..."); 49 | if (threadPool.awaitTermination(30, TimeUnit.SECONDS)) { 50 | break; 51 | } 52 | } catch (InterruptedException e) { 53 | } 54 | } 55 | logger.debug("Done cleaning"); 56 | })); 57 | } 58 | 59 | public void handleAuthenticationError(HttpResponse response) throws InstallException { 60 | String facetYamlPath = ""; 61 | try {facetYamlPath = properties.getFacetYamlPath();} catch (java.lang.Exception e){} 62 | if(response.statusCode() == 401) { 63 | throw new InstallException("Authentication error, verify your apiKey is correctly set in facet.yaml=[" + facetYamlPath + "]"); 64 | } 65 | 66 | } 67 | 68 | public List fetchFacet() { 69 | List facetList = null; 70 | try { 71 | ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); 72 | HttpClient client = HttpClient.newHttpClient(); 73 | HttpRequest request = HttpRequest.newBuilder() 74 | .uri(new URI(BaseUrl + "facet/backend?appId=" + app.getName())) 75 | .version(HttpClient.Version.HTTP_1_1) 76 | .header(API_KEY, app.getApiKey()) 77 | .GET() 78 | .timeout(Duration.ofMillis(10000)) 79 | .build(); 80 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 81 | handleAuthenticationError(response); 82 | ObjectMapper objectMapper = new ObjectMapper(); 83 | facetList = objectMapper.readValue(response.body(), new TypeReference>() {}); 84 | } catch (Throwable e) { 85 | logger.error(e.getMessage(),e); 86 | } finally { 87 | return facetList; 88 | } 89 | 90 | } 91 | 92 | public App createApp() throws InstallException { 93 | App createdApp = null; 94 | try { 95 | ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); 96 | String json = ow.writeValueAsString(app); 97 | HttpClient client = HttpClient.newHttpClient(); 98 | HttpRequest request = HttpRequest.newBuilder() 99 | .uri(new URI(BaseUrl + "app")) 100 | .header(API_KEY, app.getApiKey()) 101 | .version(HttpClient.Version.HTTP_1_1) 102 | .POST(HttpRequest.BodyPublishers.ofString(json)) 103 | .timeout(Duration.ofMillis(10000)) 104 | .build(); 105 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 106 | handleAuthenticationError(response); 107 | ObjectMapper objectMapper = new ObjectMapper(); 108 | createdApp = objectMapper.readValue(response.body(), App.class); 109 | } catch (InstallException e) { 110 | throw e; 111 | } catch (java.lang.Exception e) { 112 | throw new InstallException(e); 113 | } 114 | return createdApp; 115 | } 116 | 117 | public void createFacet(Facet facetDTO) { 118 | try { 119 | ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); 120 | String json = ow.writeValueAsString(facetDTO); 121 | HttpClient client = HttpClient.newBuilder() 122 | .executor(threadPool) 123 | .build(); 124 | HttpRequest request = HttpRequest.newBuilder() 125 | .uri(new URI(BaseUrl + "facet/backend")) 126 | .version(HttpClient.Version.HTTP_1_1) 127 | .header(API_KEY, app.getApiKey()) 128 | .POST(HttpRequest.BodyPublishers.ofString(json)) 129 | .timeout(Duration.ofMillis(10000)) 130 | .build(); 131 | CompletableFuture> response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); 132 | } catch (Throwable e) { 133 | logger.error(e.getMessage(),e); 134 | } 135 | } 136 | 137 | public Configuration fetchConfiguration(String property, String id) { 138 | Configuration configuration = null; 139 | try { 140 | HttpClient client = HttpClient.newHttpClient(); 141 | HttpRequest request = HttpRequest.newBuilder() 142 | .uri(new URI(BaseUrl + "facet/configuration?property=" + property + "&id=" + id)) 143 | .version(HttpClient.Version.HTTP_1_1) 144 | .header(API_KEY, app.getApiKey()) 145 | .GET() 146 | .timeout(Duration.ofMillis(10000)) 147 | .build(); 148 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 149 | handleAuthenticationError(response); 150 | ObjectMapper objectMapper = new ObjectMapper(); 151 | configuration = objectMapper.readValue(response.body(), Configuration.class); 152 | } catch (Throwable e) { 153 | logger.error(e.getMessage(),e); 154 | } 155 | return configuration; 156 | } 157 | 158 | public Object fetchConfiguration(String property, String id, String field, Class clazz) { 159 | Object object = null; 160 | try { 161 | HttpClient client = HttpClient.newHttpClient(); 162 | HttpRequest request = HttpRequest.newBuilder() 163 | .uri(new URI(BaseUrl + "facet/configuration?property=" + property + "&id=" + id)) 164 | .version(HttpClient.Version.HTTP_1_1) 165 | .header(API_KEY, app.getApiKey()) 166 | .GET() 167 | .timeout(Duration.ofMillis(10000)) 168 | .build(); 169 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 170 | handleAuthenticationError(response); 171 | ObjectMapper objectMapper = new ObjectMapper(); 172 | String responseBody = response.body(); 173 | JsonNode productNode = new ObjectMapper().readTree(responseBody); 174 | object = objectMapper.readValue(productNode.get(field).toString(), clazz); 175 | } catch (Throwable e) { 176 | logger.error(e.getMessage(),e); 177 | } 178 | return object; 179 | } 180 | 181 | public List fetchConfigurationList(String property, String id, String field, Class clazz) { 182 | List objectList = null; 183 | try { 184 | HttpClient client = HttpClient.newHttpClient(); 185 | HttpRequest request = HttpRequest.newBuilder() 186 | .uri(new URI(BaseUrl + "facet/configuration?property=" + property + "&id=" + id)) 187 | .version(HttpClient.Version.HTTP_1_1) 188 | .header(API_KEY, app.getApiKey()) 189 | .GET() 190 | .timeout(Duration.ofMillis(10000)) 191 | .build(); 192 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 193 | handleAuthenticationError(response); 194 | ObjectMapper objectMapper = new ObjectMapper(); 195 | String responseBody = response.body(); 196 | JsonNode rootNode = new ObjectMapper().readTree(responseBody); 197 | CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(List.class, clazz); 198 | objectList = objectMapper.readValue(rootNode.get(field).toString(), listType); 199 | } catch (Throwable e) { 200 | logger.error(e.getMessage(),e); 201 | } 202 | return objectList; 203 | } 204 | public List fetchConfigurations(String property, String id) { 205 | List configurations = null; 206 | try { 207 | HttpClient client = HttpClient.newHttpClient(); 208 | HttpRequest request = HttpRequest.newBuilder() 209 | .uri(new URI(BaseUrl + "facet/configurations?property=" + property + "&id=" + id)) 210 | .header(API_KEY, app.getApiKey()) 211 | .version(HttpClient.Version.HTTP_1_1) 212 | .GET() 213 | .timeout(Duration.ofMillis(10000)) 214 | .build(); 215 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 216 | handleAuthenticationError(response); 217 | ObjectMapper objectMapper = new ObjectMapper(); 218 | configurations = objectMapper.readValue(response.body(), new TypeReference>() {}); 219 | } catch (Throwable e) { 220 | logger.error(e.getMessage(),e); 221 | } 222 | return configurations; 223 | } 224 | 225 | 226 | private static class MyThreadFactory implements ThreadFactory { 227 | @Override 228 | public Thread newThread(Runnable r) { 229 | Thread thread = new Thread(r); 230 | thread.setDaemon(false); 231 | return thread; 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /src/main/java/run/facet/agent/java/Transformer.java: -------------------------------------------------------------------------------- 1 | package run.facet.agent.java; 2 | 3 | import javassist.*; 4 | import javassist.bytecode.AnnotationsAttribute; 5 | import javassist.bytecode.ConstPool; 6 | import javassist.bytecode.annotation.AnnotationImpl; 7 | import javassist.bytecode.annotation.ArrayMemberValue; 8 | import javassist.bytecode.annotation.ClassMemberValue; 9 | import javassist.bytecode.annotation.MemberValue; 10 | import org.apache.logging.log4j.Logger; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | import run.facet.agent.java.exception.InstallException; 14 | 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.lang.instrument.ClassFileTransformer; 19 | import java.lang.instrument.IllegalClassFormatException; 20 | import java.lang.reflect.InvocationHandler; 21 | import java.lang.reflect.Proxy; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.security.ProtectionDomain; 25 | import java.util.*; 26 | 27 | @Component 28 | public class Transformer implements ClassFileTransformer { 29 | private Properties properties; 30 | private App app; 31 | private CircuitBreakers circuitBreakers; 32 | private BlockList blockList; 33 | private Toggles toggles; 34 | private Facets facets; 35 | private Frameworks frameworks; 36 | private WebRequest webRequest; 37 | private LogInitializer logInitializer; 38 | private Logger logger; 39 | 40 | @Autowired 41 | public Transformer(App app, Properties properties, BlockList blockList, CircuitBreakers circuitBreakers, Toggles toggles, Facets facets, Frameworks frameworks, WebRequest webRequest, LogInitializer logInitializer) throws java.lang.Exception { 42 | this.properties = properties; 43 | this.logInitializer = logInitializer; 44 | this.logger = logInitializer.getLogger(); 45 | this.app = app; 46 | this.webRequest = webRequest; 47 | this.circuitBreakers = circuitBreakers; 48 | this.blockList = blockList; 49 | this.toggles = toggles; 50 | this.facets = facets; 51 | this.frameworks = frameworks; 52 | } 53 | 54 | @Override 55 | public byte[] transform(ClassLoader loader, 56 | String className, 57 | Class classBeingRedefined, 58 | ProtectionDomain protectionDomain, 59 | byte[] classfileBuffer) throws IllegalClassFormatException { 60 | try { 61 | if (!blockList.contains(className)) { 62 | logger.info(className); 63 | ClassPool classPool = ClassPool.getDefault(); 64 | classPool.appendSystemPath(); 65 | try { 66 | CtClass cf = classPool.get(className.replace("/", ".")); 67 | facets.add(createFacet(cf, classPool)); 68 | classfileBuffer = cf.toBytecode(); 69 | writeModifiedClassToFile(className, classfileBuffer); 70 | cf.detach(); 71 | } catch (NotFoundException e) { 72 | logger.warn("Unable to find class:" + className); 73 | } 74 | } 75 | } catch (Throwable e) { 76 | logger.error(e.getMessage(),e); 77 | } finally { 78 | return classfileBuffer; 79 | } 80 | } 81 | 82 | public void writeModifiedClassToFile(String className, byte[] classToWrite) { 83 | FileOutputStream fos = null; 84 | try { 85 | String baseDir = properties.getJarPath() + "/facetRunModifiedClasses/"; 86 | Files.createDirectories(Path.of(baseDir)); 87 | File test = new File(baseDir + className.replace("/", ".") + ".class"); 88 | fos = new FileOutputStream(test); 89 | fos.write(classToWrite); 90 | } catch (IOException e) { 91 | logger.error(e.getStackTrace()); 92 | } catch (InstallException e) { 93 | logger.error(e.getStackTrace()); 94 | } finally { 95 | try { 96 | fos.close(); 97 | } catch (IOException e) { 98 | e.printStackTrace(); 99 | } 100 | } 101 | } 102 | 103 | 104 | public Facet createFacet(CtClass cf, ClassPool classPool) throws NotFoundException, ClassNotFoundException, CannotCompileException { 105 | Facet facet = null; 106 | facet = new Facet(app.getName(), cf.getName(), cf.isInterface() ? "interface" : "class", "0.0.1", new Language("java", System.getProperty("java.version"))); 107 | facet.setParentSignature(cf.getSuperclass().getName()); 108 | facet.setAnnotation(parseAnnotations(cf.getAnnotations())); 109 | facet.setSignature(parseMethods(cf, cf.getDeclaredMethods(), facet, classPool)); 110 | facet.setInterfaceSignature(parseInterfaces(cf.getInterfaces())); 111 | return facet; 112 | 113 | } 114 | 115 | public List parseMethods(CtClass ctClass, CtMethod[] methods, Facet facet, ClassPool classPool) throws CannotCompileException, NotFoundException, ClassNotFoundException { 116 | List signatureList = new ArrayList<>(); 117 | HashMap createdMethods = new HashMap<>(); 118 | for (CtMethod method : methods) { 119 | if (!Modifier.isAbstract(method.getModifiers())) { 120 | Signature signature = new Signature(); 121 | signature.setName(method.getName()); 122 | signature.setReturnType(method.getReturnType().getName()); 123 | addSignatureParameters(method, signature); 124 | signature.setEnabled(calculateEnabled(facet, signature)); 125 | signature.setAnnotation(parseAnnotations(method.getAnnotations())); 126 | signatureList.add(signature); 127 | toggles.updateToggle(facet.getFullyQualifiedName(), signature.getSignature(), signature.isEnabled()); 128 | insertToggleLogic(ctClass, classPool, facet, method, signature, createdMethods); 129 | } 130 | } 131 | return signatureList; 132 | } 133 | 134 | public void addSignatureParameters(CtMethod method, Signature signature) throws NotFoundException { 135 | int position = 1; 136 | for (CtClass param : method.getParameterTypes()) { 137 | Parameter parameter = new Parameter(); 138 | parameter.setClassName(param.getName()); 139 | parameter.setPosition(position); 140 | parameter.setType(Parameter.Type.string); 141 | signature.addParameter(parameter); 142 | position++; 143 | } 144 | } 145 | 146 | public boolean calculateEnabled(Facet facet, Signature signature) { 147 | //TODO fix race condition where facets could be overwritten by the timer during parsing and vice versa. 148 | boolean isEnabled = true; 149 | if (facets.contains(facet) && facets.get(facet.getFullyQualifiedName()).hasSignature(signature.getSignature())) { 150 | isEnabled = facets.get(facet.getFullyQualifiedName()).getSignature(signature.getSignature()).isEnabled(); 151 | } 152 | return isEnabled; 153 | } 154 | 155 | public void insertToggleLogic(CtClass ctclass, ClassPool classPool, Facet facet, CtMethod method, Signature signature, Map createdMethods) throws CannotCompileException, NotFoundException { 156 | CircuitBreaker circuitBreaker = getBreaker(signature); 157 | List methodsToCreate = createMethods(circuitBreaker, ctclass, classPool, method, signature, facet, createdMethods); 158 | 159 | for (CtMethod ctMethod : methodsToCreate) { 160 | ctclass.addMethod(ctMethod); 161 | } 162 | if (circuitBreaker.getToggle().getMethod().getExceptions().size() > 0) { 163 | method.setExceptionTypes(createExceptions(circuitBreaker.getToggle().getMethod().getExceptions(), method.getExceptionTypes(), classPool)); 164 | } 165 | method.insertBefore(createToggleLogic(signature, circuitBreaker.getToggle(), facet)); 166 | } 167 | 168 | public List createMethods(CircuitBreaker circuitBreaker, CtClass ctclass, ClassPool classPool, CtMethod method, Signature signature, Facet facet, Map createdMethods) throws CannotCompileException, NotFoundException { 169 | List methods = new ArrayList<>(); 170 | for (Method methodToCreate : circuitBreaker.getMethodsToCreate()) { 171 | if (!createdMethods.containsKey(methodToCreate.getName())) { 172 | createdMethods.put(methodToCreate.getName(), methodToCreate.getName()); 173 | ConstPool constantPool = method.getMethodInfo().getConstPool(); 174 | 175 | CtMethod ctMethod = CtNewMethod.make( 176 | methodToCreate.getModifierInt(methodToCreate.getModifier()), 177 | methodToCreate.getReturnType2(methodToCreate.getReturnType()), 178 | methodToCreate.getName(), 179 | getParameters(methodToCreate, classPool), 180 | getExceptions(methodToCreate, classPool), 181 | createToggleLogic(signature, new Toggle(methodToCreate), facet) /* replace body */, 182 | ctclass 183 | ); 184 | 185 | for (Annotation annotation : methodToCreate.getAnnotations()) { 186 | AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, annotation.getVisibilityString(annotation.getVisibility())); 187 | javassist.bytecode.annotation.Annotation ann = new javassist.bytecode.annotation.Annotation(annotation.getClassName(), constantPool); 188 | for (Parameter parameter : annotation.getParameters()) { 189 | if ("run.facet.dependencies.javassist.bytecode.annotation.ArrayMemberValue".equals(parameter.getClassName())) { 190 | ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constantPool); 191 | MemberValue[] memberValueArray = new MemberValue[parameter.getValues().size()]; 192 | int index = 0; 193 | for (Parameter subParam : parameter.getValues()) { 194 | if ("run.facet.dependencies.javassist.bytecode.annotation.ClassMemberValue".equals(subParam.getClassName())) { 195 | ClassMemberValue classMemberValue = new ClassMemberValue(subParam.getValue(), constantPool); 196 | memberValueArray[index] = classMemberValue; 197 | index++; 198 | } 199 | } 200 | arrayMemberValue.setValue(memberValueArray); 201 | ann.addMemberValue(parameter.getName(), arrayMemberValue); 202 | attr.addAnnotation(ann); 203 | ctMethod.getMethodInfo().addAttribute(attr); 204 | } 205 | } 206 | } 207 | methods.add(ctMethod); 208 | } 209 | } 210 | return methods; 211 | } 212 | 213 | public String createToggleLogic(Signature signature, Toggle toggle, Facet facet) { 214 | String toggleLogic = toggle.getMethod().getBody().replace("${toggle}", "run.facet.agent.java.Toggles.isEnabled(\"" + toggles.getToggleName(facet.getFullyQualifiedName(), signature.getSignature()) + "\")"); 215 | for (Map.Entry entry : toggle.getParameterMapping().entrySet()) { 216 | toggleLogic = toggleLogic.replace("${" + entry.getKey() + "}", "$" + signature.getParameterByReturnType(entry.getValue()).getPosition()); 217 | } 218 | return toggleLogic; 219 | } 220 | 221 | public CtClass[] createExceptions(List exceptions, CtClass[] existingExceptions, ClassPool classPool) throws NotFoundException { 222 | CtClass[] exceptionList = new CtClass[exceptions.size() + existingExceptions.length]; 223 | int index = 0; 224 | for (CtClass exception : existingExceptions) { 225 | exceptionList[index] = exception; 226 | index++; 227 | } 228 | for (Exception exception : exceptions) { 229 | exceptionList[index] = classPool.get(exception.getClassName()); 230 | } 231 | return exceptionList; 232 | } 233 | 234 | public CtClass[] getParameters(Method method, ClassPool classPool) throws NotFoundException { 235 | CtClass[] parameters = new CtClass[method.getParameters().size()]; 236 | int index = 0; 237 | for (Parameter parameter : method.getParameters()) { 238 | parameters[index] = classPool.get(parameter.getClassName()); 239 | index++; 240 | } 241 | return parameters; 242 | } 243 | 244 | public CtClass[] getExceptions(Method method, ClassPool classPool) throws NotFoundException { 245 | CtClass[] exceptions = new CtClass[method.getExceptions().size()]; 246 | int index = 0; 247 | for (Exception exception : method.getExceptions()) { 248 | exceptions[index] = classPool.get(exception.getClassName()); 249 | index++; 250 | } 251 | return exceptions; 252 | } 253 | 254 | 255 | public CircuitBreaker getBreaker(Signature signature) { 256 | CircuitBreaker circuitBreaker = null; 257 | if (frameworks.isFramework(signature.getAnnotation())) { 258 | List cbs = frameworks.getFramework(signature.getAnnotation()).getCircuitBreakers(); 259 | outerLoop: 260 | for (CircuitBreaker cb : cbs) { 261 | for (Map.Entry entry : cb.getToggle().getParameterMapping().entrySet()) { 262 | if (signature.getParameterByReturnType(entry.getValue()) == null) { 263 | continue outerLoop; 264 | } 265 | } 266 | circuitBreaker = cb; 267 | break; 268 | } 269 | } 270 | if (circuitBreaker == null) { 271 | circuitBreaker = circuitBreakers.getBreaker(signature.getReturnType()); 272 | } 273 | return circuitBreaker; 274 | } 275 | 276 | public List parseAnnotations(Object[] objectList) { 277 | List annotations = new ArrayList<>(); 278 | for (Object object : objectList) { 279 | if (object instanceof Proxy) { 280 | InvocationHandler invocationHandler = Proxy.getInvocationHandler((Proxy) object); 281 | if (invocationHandler instanceof AnnotationImpl) { 282 | javassist.bytecode.annotation.Annotation annotation = ((AnnotationImpl) invocationHandler).getAnnotation(); 283 | run.facet.agent.java.Annotation facetAnnotation = new run.facet.agent.java.Annotation(); 284 | facetAnnotation.setClassName(annotation.getTypeName()); 285 | Set parameters = annotation.getMemberNames(); 286 | if (parameters != null) { 287 | for (String parameter : parameters) { 288 | Parameter param = new Parameter(); 289 | param.setType(Parameter.Type.string); 290 | param.setName(parameter); 291 | param.setValue(annotation.getMemberValue(parameter).toString()); 292 | facetAnnotation.addParameter(param); 293 | } 294 | } 295 | annotations.add(facetAnnotation); 296 | } 297 | } 298 | } 299 | return annotations; 300 | } 301 | 302 | public List parseInterfaces(CtClass[] interfaces) { 303 | List interfaceList = new ArrayList<>(); 304 | for (CtClass inter : interfaces) { 305 | interfaceList.add(inter.getName()); 306 | } 307 | return interfaceList; 308 | } 309 | } --------------------------------------------------------------------------------