diff --git a/README.md b/README.md index 95e2f40..1994209 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,7 @@ [Spring Dependency Injection](docs%2FDI.md) [Spring Security](docs%2FSecurity.md) + +[Spring Data Jpa.md](docs%2FJpa.md) + +[Spring Cloud OpenFeign.md](docs%2FSprinf-Cloud-OpenFeign.md) \ No newline at end of file diff --git a/docs/1.png b/docs/1.png new file mode 100644 index 0000000..26000d0 Binary files /dev/null and b/docs/1.png differ diff --git a/docs/2.png b/docs/2.png new file mode 100644 index 0000000..83bba3b Binary files /dev/null and b/docs/2.png differ diff --git a/docs/3.png b/docs/3.png new file mode 100644 index 0000000..8e83ee9 Binary files /dev/null and b/docs/3.png differ diff --git a/docs/4.png b/docs/4.png new file mode 100644 index 0000000..c6e4b90 Binary files /dev/null and b/docs/4.png differ diff --git a/docs/5.png b/docs/5.png new file mode 100644 index 0000000..ec72dac Binary files /dev/null and b/docs/5.png differ diff --git a/docs/Jpa.md b/docs/Jpa.md new file mode 100644 index 0000000..1ccaaaa --- /dev/null +++ b/docs/Jpa.md @@ -0,0 +1,356 @@ +## Java Persistence API (JPA) + +### Entity + +The Java Persistence API (JPA) provides a standard way for Java developers to interact with databases. Database operations often consist of repetitive, low-level SQL codes, and maintaining and managing these codes can be time-consuming. JPA allows developers to access the database via object-relational mapping (ORM), without having to rewrite these repetitive tasks. + +An Entity in JPA is a class that represents a table in a database. An Entity class usually contains a table and fields that represent the columns of the table. Each instance of an entity represents a row in the table. + +``` +@Entity +@Table(name = "students") +public class Student { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name",unique = true, nullable = false, length = 50) + private String name; + + @Column(name = "age") + private int age; + + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable( + name = "student_course", + joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "course_id") + ) + private Set courses = new HashSet<>(); +``` + +@Entity: This annotation indicates that a class is an entity representing a database table. Through this annotation, JPA knows that this class needs to be associated with a table. + +@Table: This annotation indicates which database table an entity is associated with. The 'name' parameter specifies the name of the table in the database. + +@Id: This annotation indicates that a field is the primary key of this entity. A primary key ensures each row in a table is uniquely identifiable. + +@GeneratedValue: This annotation indicates how the primary key will be generated. The 'strategy' parameter specifies the strategy to be used. The GenerationType.IDENTITY strategy typically represents an auto-incrementing value. + +@Column: This annotation indicates that a field corresponds to a column in a database table. The 'name' parameter specifies the name of the column. The 'unique' parameter indicates that the column should contain a unique value. The 'nullable' parameter indicates whether the column can be empty or not. The 'length' parameter specifies the maximum number of characters in the column. + +@ManyToMany: This annotation indicates a many-to-many relationship. In such a relationship, an item can be associated with multiple items, and each item can also be associated with multiple items. + +@JoinTable: This annotation specifies how the join table will be created in a many-to-many relationship. The 'name' parameter specifies the name of the join table. The 'joinColumns' and 'inverseJoinColumns' parameters specify the columns in the join table. + +@JoinColumn: This annotation specifies a join column. The 'name' parameter specifies the name of the join column. + +CascadeType.ALL: This allows changes in an entity to automatically propagate to associated entities. This is used, for example, when an item is deleted, the associated items are also deleted. + +orphanRemoval: This attribute specifies that the removal of an entity from a collection results in the removal of that entity from the database. For example, if a Course entity is removed from a Student's collection and orphanRemoval is set to true, this Course entity will also be deleted from the database. + +### Repository and Queries + +In Spring Data JPA, a repository is an interface that provides CRUD operations for a specific type of entity. These CRUD operations are usually standard operations like findAll(), findById(), save(), delete(). Users can add custom methods to the repository interface to define complex queries. + +Custom methods work with queries derived from method names. For example, the method findByName(String name) looks for an entity with the given name. Such methods allow query generation through method name resolution. + +``` +public interface UserRepository extends JpaRepository { + + List findByLastname(String lastname); + + User findByEmailAddress(String emailAddress); +} + +``` +You can limit the results of query methods by using the first or top keywords, which you can use interchangeably. You can append an optional numeric value to top or first to specify the maximum result size to be returned. If the number is left out, a result size of 1 is assumed. The following example shows how to limit the query size: + +``` +User findFirstByOrderByLastnameAsc(); + +User findTopByOrderByAgeDesc(); + +Page queryFirst10ByLastname(String lastname, Pageable pageable); + +Slice findTop3ByLastname(String lastname, Pageable pageable); + +List findFirst10ByLastname(String lastname, Sort sort); + +List findTop10ByLastname(String lastname, Pageable pageable); +``` + +For complex database interactions, method name resolution might not be sufficient or practical. That's where the use of Native Query, Criteria Query, QueryDSL, and Specification comes into play, offering more flexible and powerful ways to interact with the database. + +Native Query: In cases where you need to write SQL queries directly due to the complexity of the query or the need for a specific database feature, native queries are used. These are actual SQL queries written as strings in your application. While this method is very powerful and flexible, it lacks type safety and is susceptible to errors like syntax mistakes or incorrect column names that are only caught at runtime. +Native queries can be used with entityManager.createquery or @Query annotation on repository +In NativeQueriesTest class: + +``` +@Test + public void native_queries_basic() { + Query query = em.createNativeQuery("SELECT * FROM COURSE", Course.class); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE -> {}", resultList); + } + + @Test + public void native_queries_with_parameter() { + Query query = em.createNativeQuery("SELECT * FROM COURSE where id = ?", Course.class); + query.setParameter(1, 10010L); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE where id = ? -> {}", resultList); + //[Course[Math]] + } + + @Test + public void native_queries_with_named_parameter() { + Query query = em.createNativeQuery("SELECT * FROM COURSE where id = :id", Course.class); + query.setParameter("id", 10001L); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE where id = :id -> {}", resultList); + } + + @Test + @Transactional + public void native_queries_to_update() { + Query query = em.createNativeQuery("Update COURSE set last_updated_date=CURRENT_DATE()"); + int noOfRowsUpdated = query.executeUpdate(); + logger.info("noOfRowsUpdated -> {}", noOfRowsUpdated); + } + +``` + +In CourseSpringDataRepository class: + +``` + @Query(value = "Select * From Course c where c.name like '%Math'", nativeQuery = true) + List courseWithMathInNameUsingNativeQuery(); + +``` + +JPQL : JPQL, or Java Persistence Query Language, is a platform-independent object-oriented query language defined as part of the Java Persistence API (JPA) specification. JPQL is used to make queries against entities stored in a relational database. It is an abstraction over SQL and provides a way to perform database operations based on the entity model rather than on the database tables.JPQL is similar to SQL, but it operates on objects, attributes, and relationships instead of tables and columns. +JPQL queries can be used with entityManager.createquery or @Query annotation on repository. + +In JPQLTest class: + +``` +@Test + public void jpql_basic() { + Query query = em.createQuery("Select c From Course c"); + List resultList = query.getResultList(); + logger.info("Select c From Course c -> {}",resultList); + } + + + @Test + public void jpql_typed() { + TypedQuery query = + em.createQuery("Select c From Course c", Course.class); + + List resultList = query.getResultList(); + + logger.info("Select c From Course c -> {}",resultList); + } + + @Test + public void jpql_where() { + TypedQuery query = + em.createQuery("Select c From Course c where name like '%Math'", Course.class); + + List resultList = query.getResultList(); + + logger.info("Select c From Course c where name like '%Math'-> {}",resultList); + } + +``` +In CourseSpringDataRepository class: + +``` + @Query("Select c From Course c where c.name like '%Math'") + List courseWithMathInName(); + + +``` + + + +Criteria Query: The Criteria API is a predefined API that is used to define queries for entities stored in a relational database. It is type-safe, meaning the Java compiler verifies the query construction and alerts you to any errors at compile time. The main advantage of the Criteria API is its ability to create complex queries programmatically, which can be very useful in scenarios where the exact structure of the query is not known at compile time and is built based on user input or application state. +example of a Criteria Query that fetches a Student entity based on a given name: + +``` +// Assume that we have an EntityManager instance 'em' +CriteriaBuilder cb = em.getCriteriaBuilder(); +CriteriaQuery cq = cb.createQuery(Student.class); + +Root student = cq.from(Student.class); +cq.select(student).where(cb.equal(student.get("name"), "givenName")); + +TypedQuery query = em.createQuery(cq); +List result = query.getResultList(); + +``` + +QueryDSL: Similar to Criteria Query, QueryDSL is a framework that enables the construction of type-safe SQL-like queries. It can be seen as an alternative to both JPQL and Criteria Queries. QueryDSL excels in situations where you need to create a query dynamically and in a readable and secure manner. + +``` + +QStudent student = QStudent.student; + +Predicate predicate = student.name.equalsIgnoreCase("dave"); + +courseSpringDataRepository.findAll(predicate); + + +``` + +Specification: Specification is a part of Spring Data JPA that is used to encapsulate the logic needed to execute a particular database operation. It's based on the Criteria API and allows for the creation of dynamic, complex queries with reusable components. It is often used for implementing filter-like functionality where the query is constructed based on various criteria. + +``` +Specification hasName = (student, cq, cb) -> cb.equal(student.get("name"), "givenName"); +List students = studentRepository.findAll(hasName); + +``` + +Query By Example: Query by Example (QBE) is a technique in Spring Data used for automatic query generation, based on an example of the object the user is searching for. It allows you to execute queries in the underlying datastore by providing a probe that holds the attributes the resulting objects should have. +``` + +Student studentExample = new Student(); +studentExample.setName("givenName"); +Example example = Example.of(studentExample); +List students = studentRepository.findAll(example); + +``` + + + +### Projection + +Projection allows for the queried data to be viewed in the desired format. Instead of returning the whole entity as a query result, specific fields may be requested. + +``` +class Person { + + @Id UUID id; + String firstname, lastname; + Address address; + + static class Address { + String zipCode, city, street; + } +} + +interface PersonRepository extends Repository { + + Collection findByLastname(String lastname); +} + +interface NameOnly { + + String getFirstname(); + } + +interface PersonRepository extends Repository { + + Collection findByLastname(String lastname); + + + //In the select query, only firstname will be requested +} + +``` + +## LazyInitilizationException: + +This exception occurs when an entity has a collection that is lazily initialized, and this collection is accessed outside of the transaction in which the entity was fetched. + +In Hibernate and other JPA implementations, fetching strategies can be defined - these are either EAGER or LAZY. When a fetch type of EAGER is used, the data is fetched immediately when the entity is retrieved. When LAZY is used, the data isn't retrieved until it's accessed for the first time. This is a way of improving performance by not fetching data that isn't needed. + +However, if the data is accessed after the transaction has ended (for instance, in the view layer), a LazyInitializationException is thrown because the Hibernate Session, which is needed to fetch the data, has already been closed. + +To avoid this there are multiple ways: + +* Make the access in transaction by using @Transacational annotation. +* EntityGraph is a feature in JPA that allows you to define what associations in the entity graph should be eagerly fetched. This can be very useful to avoid LazyInitializationException as it lets you specify fetch plans at the method level, depending on use case. + +By using an EntityGraph, you can specify that a certain association should be eagerly fetched for a certain query even though it might be marked as LAZY in the entity mapping. This way, when you fetch an entity, the associated entities defined in the EntityGraph are also fetched in the same transaction, thus avoiding the LazyInitializationException. + +Here is an example: +``` +@EntityGraph(attributePaths = {"courses"}) +List findAll(); + +``` + +* Fetch Join is another way to solve the LazyInitializationException. It is a feature provided by JPQL (Java Persistence Query Language) which allows you to load related entities as part of your initial query, thereby avoiding the LazyInitializationException. + +A fetch join does not usually return a new set of data or change the way results are returned. Instead, it affects the way data is retrieved. It tells the JPA provider to not only fetch the primary entities but also to fetch the related entities. + +Here's an example of how to use a fetch join in a JPQL query: +``` +TypedQuery query = em.createQuery( + "SELECT s FROM Student s JOIN FETCH s.courses WHERE s.name = :name", + Student.class); + +query.setParameter("name", "John Doe"); +List students = query.getResultList(); + + +``` +Also we can use fetch join at all repository methods by using @Fetch(FetchMode.JOIN) . Example: + +``` + @Fetch(FetchMode.JOIN) + @ManyToMany(mappedBy="courses") + @JsonIgnore + private List students = new ArrayList<>(); +``` +After that, all the find methods in repository (like findAll, findById) , there will be a fetch join on students. + + +### N+1 Problem: + +The N+1 problem is a common issue in ORM libraries like Hibernate where an application ends up issuing many more queries than necessary, often in a loop. + +This typically happens when you have two entities with a relationship (like OneToOne, OneToMany, etc.), and you fetch one entity first, and then fetch the related entities individually. For example, you might fetch a list of Post entities, and then for each Post, fetch its related Comment entities separately. This results in 1 query to fetch the Posts, plus N additional queries to fetch the Comments for each Post, hence the name "N+1 problem". + +This can be a significant performance problem because it results in a large number of separate database round-trips. + +To avoid this problem, you can use eager fetching (though this might lead to other performance problems if not all the data is needed), or better, use a JPQL JOIN FETCH clause or EntityGraph to fetch all the data you need in a single database round-trip. + +Also @Fetch(FetchMode.JOIN) solves this problem too. + +There are som tests about n+1 problet at PerformanceTuningTest class. + +### Repository creation: +During bean creation, after a series of processes, the getRepository method in the RepositoryFactorySupport class found in the spring-data-commons package is invoked. This method analyzes the repository interface. + + + +Subsequently, based on the analysis results, the getTargetRepository method is called. + +![5.png](5.png) + +This method is overridden by the JpaRepositoryFactory class which extends RepositoryFactorySupport. The result of the operations here creates the SimpleJpaRepository according to the provided information. + +![1.png](1.png) + +After this process, the getRepository method in the RepositoryFactorySupport class will continue its operations. A proxy is created: + +![2.png](2.png) + +Then, according to the information obtained from the analysis again (information object), QueryExecutorMethodInterceptor and ImplementationMethodExecutionInterceptor objects are created and added to the proxy. The QueryExecutorMethodInterceptor object creates the queries of custom query methods (such as deleteByAge) of the repo with a query derivation mechanism (i.e., method name analysis). Later, when these methods are called, the QueryExecutorMethodInterceptor in the proxy will intervene. The process of creating queries from method names occurs thanks to the mapMethodsToQuery method in the PartTreeJpaQuery class. + +![3.png](3.png) + +Explanation about Interceptors: + +ImplementationMethodExecutionInterceptor: This interceptor executes the default implementations of repository methods (such as CRUD operations) and custom implementations provided by the user (i.e., special methods defined in a class like PersonRepositoryImpl). + +QueryExecutorMethodInterceptor: This interceptor executes custom query methods defined in the repository interface and created with a query derivation mechanism (i.e., method name analysis). For example, a method like deleteByAge(int age) falls into this category. Since such a method is not a default CRUD operation and does not have a custom implementation provided by the user, Spring Data takes over the responsibility to create and execute queries for such methods. + +In general, methods like findAll() are executed by the ImplementationMethodExecutionInterceptor, while methods like deleteByAge(int age) are run by the QueryExecutorMethodInterceptor. However, this means if a custom implementation is provided for methods like findAll(), this custom implementation will also be called by the ImplementationMethodExecutionInterceptor. + +For example, when we call the deleteByAge method in the application, the invoke function in the QueryExecutorMethodInterceptor class will be called. + +![4.png](4.png) \ No newline at end of file diff --git a/docs/Sprinf-Cloud-OpenFeign.md b/docs/Sprinf-Cloud-OpenFeign.md new file mode 100644 index 0000000..6b9e6d1 --- /dev/null +++ b/docs/Sprinf-Cloud-OpenFeign.md @@ -0,0 +1,179 @@ +## Spring Cloud Openfeign + +In the world of microservices architecture, Spring Cloud OpenFeign plays a crucial role in simplifying HTTP API clients. Microservices often need to communicate with each other, and this inter-service communication is usually done over HTTP. Managing this communication manually can become complex and error-prone, especially when you have a large number of microservices. This is where Spring Cloud OpenFeign comes in. + +To enable Feign Clients in your Spring Boot application, you need to annotate your main class (or a configuration class) with @EnableFeignClients. + +``` +@SpringBootApplication +@EnableFeignClients +public class SpringCloudOpenFeignApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringCloudOpenFeignApplication.class, args); + } + +} + +``` + +### Structure of a feign client: + +``` +@FeignClient(value = "securityClient", + url = "${security.api.url}", + configuration = SecurityServiceConfig.class, + fallback = SecurityServiceFallback.class) +public interface SecurityService { + + @PostMapping("/auth/register") + AuthenticationResponse register(@RequestBody RegisterRequest registerRequest); + + @PostMapping("/auth/authenticate") + AuthenticationResponse authenticate(@RequestBody AuthenticationRequest authenticationRequest); + + + @GetMapping("/demo") + public ResponseEntity demo(); + +} + +``` + +* value or name: The name of the service with which to create a ribbon load balancer. This is an arbitrary name which can be used to create a URL. +* url: A URL or a complete URL to the desired service. +* configuration: A custom configuration class for the feign client. You can define beans that you want to use with this particular Feign Client in this class. +* fallback: This defines a class that is a fallback when a failure occurs on the method call. This class should implement the interface annotated by @FeignClient and provide a plausible fallback response for its methods. +* The @FeignClient annotation tells Spring to generate a proxy for this interface so that it can intercept calls to the interface's methods. +* The methods in the SecurityService interface correspond to HTTP endpoints in the target service (in this case, the "securityClient" service). They are annotated with standard Spring MVC annotations like @GetMapping, @PostMapping, etc. +* The @RequestBody and @RequestHeader annotations work just like they do in Spring MVC. They indicate that a method parameter should be bound to the body of the web request, or a request header. + +Spring Cloud OpenFeign does not provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client: +``` +Logger.Level +Retryer +ErrorDecoder +Request.Options +Collection +SetterFactory +QueryMapEncoder +Capability + +``` + + + + +### Structure of the SecurityServiceConfig: + +``` +public class SecurityServiceConfig { + + + @Bean + Logger.Level feignLoggerLevel(){ + return Logger.Level.FULL; + } + + + @Bean + public RequestInterceptor requestInterceptor() { + return new RequestInterceptor() { + @Override + public void apply(RequestTemplate template) { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + template.header(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION)); + } + } + }; + } + + @Bean + public ErrorDecoder errorDecoder(){ + return new FeignErrorDecoder(); + + } +} + +``` + + + +* Logger.Level feignLoggerLevel(): Feign includes a logger which you can use to output requests and responses to the console. This feignLoggerLevel bean sets the logging level for Feign's logger. The Logger.Level.FULL value means that Feign will log the full contents of HTTP requests and responses including headers and body. Note that this logging may include sensitive data and should not be used in production. + +* RequestInterceptor requestInterceptor(): The purpose of a RequestInterceptor is to perform operations on the request before it is sent. In this specific case, the interceptor is adding an "Authorization" header to every request, copying it from the incoming HTTP request. This is useful in microservices architectures where you might want to propagate authentication information from one service to another. + +* ErrorDecoder errorDecoder(): An ErrorDecoder is used to handle errors returned by the Feign client. The default ErrorDecoder provided by Feign does nothing special: it simply wraps the HTTP status code and error message into an exception. However, you can define your own ErrorDecoder if you need custom error handling. In this case, a custom FeignErrorDecoder is used. This ErrorDecoder could, for example, map certain HTTP status codes to custom exceptions. + + +### Structure of the FeignErrorDecoder: + +``` + +public class FeignErrorDecoder implements ErrorDecoder { + + + @Override + public Exception decode(String s, Response response) { + switch (response.status()){ + case 400: + return new ApiRequestException("Bad request"); + case 403: + return new AccessDeniedException("Access denied"); + default: + return new Exception("Library generic exception"); + } + } +} + +``` +This class is implementing the ErrorDecoder interface provided by Feign. The purpose of an ErrorDecoder is to handle errors returned by the Feign client. When the Feign client receives a non-2xx HTTP response, it invokes the ErrorDecoder with the response. + +Without the ErrorDecoder, Feign client would throw a FeignException for all non-2xx HTTP responses. This would result in a 500 Internal Server Error being returned for all types of errors, making it difficult to distinguish between different types of errors and to handle them appropriately. + +### Structure of the SecurityServiceFallback: + +``` +@Component +public class SecurityServiceFallback implements SecurityService { + @Override + public AuthenticationResponse register(RegisterRequest registerRequest) { + return new AuthenticationResponse("response for fallback"); + } + + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) { + + return new AuthenticationResponse("response for fallback"); + + } + + @Override + public ResponseEntity demo() { + + return ResponseEntity.ok(new AuthenticationResponse("response for fallback")); + } +} + +``` + + +@FeignClient annotation provides a fallback attribute which can be used to specify a fallback class. This class should implement the same interface as the Feign client and is used as a backup in case the main Feign client fails to call the remote service. The methods in the fallback class are called when the methods of the main Feign client fail. + +To enable fallbacks in Feign, you need to set the spring.cloud.openfeign.circuitbreaker.enabled=true in application.properties. + + + +If we want to create multiple feign clients with the same name or url so that they would point to the same server but each with a different custom configuration then we have to use contextId attribute of the @FeignClient in order to avoid name collision of these configuration beans. + +@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) +public interface FooClient { +//.. +} + +@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) +public interface BarClient { +//.. +} \ No newline at end of file diff --git a/jpa-example/README.md b/jpa-example/README.md index c348cb4..1ccaaaa 100644 --- a/jpa-example/README.md +++ b/jpa-example/README.md @@ -64,7 +64,7 @@ public interface UserRepository extends JpaRepository { } ``` -ou can limit the results of query methods by using the first or top keywords, which you can use interchangeably. You can append an optional numeric value to top or first to specify the maximum result size to be returned. If the number is left out, a result size of 1 is assumed. The following example shows how to limit the query size: +You can limit the results of query methods by using the first or top keywords, which you can use interchangeably. You can append an optional numeric value to top or first to specify the maximum result size to be returned. If the number is left out, a result size of 1 is assumed. The following example shows how to limit the query size: ``` User findFirstByOrderByLastnameAsc(); @@ -83,25 +83,244 @@ List findTop10ByLastname(String lastname, Pageable pageable); For complex database interactions, method name resolution might not be sufficient or practical. That's where the use of Native Query, Criteria Query, QueryDSL, and Specification comes into play, offering more flexible and powerful ways to interact with the database. Native Query: In cases where you need to write SQL queries directly due to the complexity of the query or the need for a specific database feature, native queries are used. These are actual SQL queries written as strings in your application. While this method is very powerful and flexible, it lacks type safety and is susceptible to errors like syntax mistakes or incorrect column names that are only caught at runtime. +Native queries can be used with entityManager.createquery or @Query annotation on repository +In NativeQueriesTest class: + +``` +@Test + public void native_queries_basic() { + Query query = em.createNativeQuery("SELECT * FROM COURSE", Course.class); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE -> {}", resultList); + } + + @Test + public void native_queries_with_parameter() { + Query query = em.createNativeQuery("SELECT * FROM COURSE where id = ?", Course.class); + query.setParameter(1, 10010L); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE where id = ? -> {}", resultList); + //[Course[Math]] + } + + @Test + public void native_queries_with_named_parameter() { + Query query = em.createNativeQuery("SELECT * FROM COURSE where id = :id", Course.class); + query.setParameter("id", 10001L); + List resultList = query.getResultList(); + logger.info("SELECT * FROM COURSE where id = :id -> {}", resultList); + } + + @Test + @Transactional + public void native_queries_to_update() { + Query query = em.createNativeQuery("Update COURSE set last_updated_date=CURRENT_DATE()"); + int noOfRowsUpdated = query.executeUpdate(); + logger.info("noOfRowsUpdated -> {}", noOfRowsUpdated); + } + +``` + +In CourseSpringDataRepository class: + +``` + @Query(value = "Select * From Course c where c.name like '%Math'", nativeQuery = true) + List courseWithMathInNameUsingNativeQuery(); + +``` + +JPQL : JPQL, or Java Persistence Query Language, is a platform-independent object-oriented query language defined as part of the Java Persistence API (JPA) specification. JPQL is used to make queries against entities stored in a relational database. It is an abstraction over SQL and provides a way to perform database operations based on the entity model rather than on the database tables.JPQL is similar to SQL, but it operates on objects, attributes, and relationships instead of tables and columns. +JPQL queries can be used with entityManager.createquery or @Query annotation on repository. + +In JPQLTest class: + +``` +@Test + public void jpql_basic() { + Query query = em.createQuery("Select c From Course c"); + List resultList = query.getResultList(); + logger.info("Select c From Course c -> {}",resultList); + } + + + @Test + public void jpql_typed() { + TypedQuery query = + em.createQuery("Select c From Course c", Course.class); + + List resultList = query.getResultList(); + + logger.info("Select c From Course c -> {}",resultList); + } + + @Test + public void jpql_where() { + TypedQuery query = + em.createQuery("Select c From Course c where name like '%Math'", Course.class); + + List resultList = query.getResultList(); + + logger.info("Select c From Course c where name like '%Math'-> {}",resultList); + } + +``` +In CourseSpringDataRepository class: + +``` + @Query("Select c From Course c where c.name like '%Math'") + List courseWithMathInName(); + + +``` + + Criteria Query: The Criteria API is a predefined API that is used to define queries for entities stored in a relational database. It is type-safe, meaning the Java compiler verifies the query construction and alerts you to any errors at compile time. The main advantage of the Criteria API is its ability to create complex queries programmatically, which can be very useful in scenarios where the exact structure of the query is not known at compile time and is built based on user input or application state. +example of a Criteria Query that fetches a Student entity based on a given name: + +``` +// Assume that we have an EntityManager instance 'em' +CriteriaBuilder cb = em.getCriteriaBuilder(); +CriteriaQuery cq = cb.createQuery(Student.class); + +Root student = cq.from(Student.class); +cq.select(student).where(cb.equal(student.get("name"), "givenName")); + +TypedQuery query = em.createQuery(cq); +List result = query.getResultList(); + +``` QueryDSL: Similar to Criteria Query, QueryDSL is a framework that enables the construction of type-safe SQL-like queries. It can be seen as an alternative to both JPQL and Criteria Queries. QueryDSL excels in situations where you need to create a query dynamically and in a readable and secure manner. +``` + +QStudent student = QStudent.student; + +Predicate predicate = student.name.equalsIgnoreCase("dave"); + +courseSpringDataRepository.findAll(predicate); + + +``` + Specification: Specification is a part of Spring Data JPA that is used to encapsulate the logic needed to execute a particular database operation. It's based on the Criteria API and allows for the creation of dynamic, complex queries with reusable components. It is often used for implementing filter-like functionality where the query is constructed based on various criteria. +``` +Specification hasName = (student, cq, cb) -> cb.equal(student.get("name"), "givenName"); +List students = studentRepository.findAll(hasName); + +``` + +Query By Example: Query by Example (QBE) is a technique in Spring Data used for automatic query generation, based on an example of the object the user is searching for. It allows you to execute queries in the underlying datastore by providing a probe that holds the attributes the resulting objects should have. +``` + +Student studentExample = new Student(); +studentExample.setName("givenName"); +Example example = Example.of(studentExample); +List students = studentRepository.findAll(example); + +``` + + ### Projection Projection allows for the queried data to be viewed in the desired format. Instead of returning the whole entity as a query result, specific fields may be requested. ``` +class Person { + + @Id UUID id; + String firstname, lastname; + Address address; + static class Address { + String zipCode, city, street; + } +} + +interface PersonRepository extends Repository { + Collection findByLastname(String lastname); +} + +interface NameOnly { + + String getFirstname(); + } + +interface PersonRepository extends Repository { + + Collection findByLastname(String lastname); + + + //In the select query, only firstname will be requested +} ``` +## LazyInitilizationException: + +This exception occurs when an entity has a collection that is lazily initialized, and this collection is accessed outside of the transaction in which the entity was fetched. + +In Hibernate and other JPA implementations, fetching strategies can be defined - these are either EAGER or LAZY. When a fetch type of EAGER is used, the data is fetched immediately when the entity is retrieved. When LAZY is used, the data isn't retrieved until it's accessed for the first time. This is a way of improving performance by not fetching data that isn't needed. + +However, if the data is accessed after the transaction has ended (for instance, in the view layer), a LazyInitializationException is thrown because the Hibernate Session, which is needed to fetch the data, has already been closed. + +To avoid this there are multiple ways: + +* Make the access in transaction by using @Transacational annotation. +* EntityGraph is a feature in JPA that allows you to define what associations in the entity graph should be eagerly fetched. This can be very useful to avoid LazyInitializationException as it lets you specify fetch plans at the method level, depending on use case. + +By using an EntityGraph, you can specify that a certain association should be eagerly fetched for a certain query even though it might be marked as LAZY in the entity mapping. This way, when you fetch an entity, the associated entities defined in the EntityGraph are also fetched in the same transaction, thus avoiding the LazyInitializationException. + +Here is an example: +``` +@EntityGraph(attributePaths = {"courses"}) +List findAll(); + +``` + +* Fetch Join is another way to solve the LazyInitializationException. It is a feature provided by JPQL (Java Persistence Query Language) which allows you to load related entities as part of your initial query, thereby avoiding the LazyInitializationException. + +A fetch join does not usually return a new set of data or change the way results are returned. Instead, it affects the way data is retrieved. It tells the JPA provider to not only fetch the primary entities but also to fetch the related entities. + +Here's an example of how to use a fetch join in a JPQL query: +``` +TypedQuery query = em.createQuery( + "SELECT s FROM Student s JOIN FETCH s.courses WHERE s.name = :name", + Student.class); + +query.setParameter("name", "John Doe"); +List students = query.getResultList(); + + +``` +Also we can use fetch join at all repository methods by using @Fetch(FetchMode.JOIN) . Example: + +``` + @Fetch(FetchMode.JOIN) + @ManyToMany(mappedBy="courses") + @JsonIgnore + private List students = new ArrayList<>(); +``` +After that, all the find methods in repository (like findAll, findById) , there will be a fetch join on students. + + +### N+1 Problem: + +The N+1 problem is a common issue in ORM libraries like Hibernate where an application ends up issuing many more queries than necessary, often in a loop. + +This typically happens when you have two entities with a relationship (like OneToOne, OneToMany, etc.), and you fetch one entity first, and then fetch the related entities individually. For example, you might fetch a list of Post entities, and then for each Post, fetch its related Comment entities separately. This results in 1 query to fetch the Posts, plus N additional queries to fetch the Comments for each Post, hence the name "N+1 problem". + +This can be a significant performance problem because it results in a large number of separate database round-trips. + +To avoid this problem, you can use eager fetching (though this might lead to other performance problems if not all the data is needed), or better, use a JPQL JOIN FETCH clause or EntityGraph to fetch all the data you need in a single database round-trip. + +Also @Fetch(FetchMode.JOIN) solves this problem too. +There are som tests about n+1 problet at PerformanceTuningTest class. ### Repository creation: During bean creation, after a series of processes, the getRepository method in the RepositoryFactorySupport class found in the spring-data-commons package is invoked. This method analyzes the repository interface. diff --git a/jpa-example/presentation.pptm b/jpa-example/presentation.pptm new file mode 100644 index 0000000..b126cdb Binary files /dev/null and b/jpa-example/presentation.pptm differ diff --git a/security/pom.xml b/security/pom.xml index bde80ca..4b524ea 100644 --- a/security/pom.xml +++ b/security/pom.xml @@ -65,6 +65,11 @@ spring-security-test test + + + org.springframework.boot + spring-boot-starter-validation + diff --git a/security/src/main/java/com/springboot/security/auth/AuthenticationRequest.java b/security/src/main/java/com/springboot/security/auth/AuthenticationRequest.java index 43adb9e..c528e6f 100644 --- a/security/src/main/java/com/springboot/security/auth/AuthenticationRequest.java +++ b/security/src/main/java/com/springboot/security/auth/AuthenticationRequest.java @@ -1,9 +1,6 @@ package com.springboot.security.auth; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; @Data @Builder @@ -11,6 +8,9 @@ @AllArgsConstructor public class AuthenticationRequest { + @NonNull private String email; + + @NonNull private String password; } diff --git a/security/src/main/java/com/springboot/security/auth/RegisterRequest.java b/security/src/main/java/com/springboot/security/auth/RegisterRequest.java index b75a9c5..5538029 100644 --- a/security/src/main/java/com/springboot/security/auth/RegisterRequest.java +++ b/security/src/main/java/com/springboot/security/auth/RegisterRequest.java @@ -1,19 +1,22 @@ package com.springboot.security.auth; import com.springboot.security.user.Role; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class RegisterRequest { + private String firstname; + private String lastname; + + @NonNull private String email; + + @NonNull private String password; diff --git a/security/src/main/java/com/springboot/security/config/SecurityConfig.java b/security/src/main/java/com/springboot/security/config/SecurityConfig.java index ebbddc2..f7ef75d 100644 --- a/security/src/main/java/com/springboot/security/config/SecurityConfig.java +++ b/security/src/main/java/com/springboot/security/config/SecurityConfig.java @@ -52,6 +52,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity)throws @Bean public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**","/h2-console").anyRequest(); + return (web) -> web.ignoring().requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**","/h2-console"); } } diff --git a/security/src/main/java/com/springboot/security/demo/DemoController.java b/security/src/main/java/com/springboot/security/demo/DemoController.java index 435a062..81e3424 100644 --- a/security/src/main/java/com/springboot/security/demo/DemoController.java +++ b/security/src/main/java/com/springboot/security/demo/DemoController.java @@ -1,6 +1,7 @@ package com.springboot.security.demo; +import com.springboot.security.user.UserResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; @@ -18,7 +19,7 @@ public class DemoController { public ResponseEntity demo(){ UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return ResponseEntity.ok(userDetails.getUsername()); + return ResponseEntity.ok(UserResponse.builder().email(userDetails.getUsername()).build()); } @GetMapping("/admin") diff --git a/security/src/main/java/com/springboot/security/user/UserResponse.java b/security/src/main/java/com/springboot/security/user/UserResponse.java new file mode 100644 index 0000000..8f9b9d8 --- /dev/null +++ b/security/src/main/java/com/springboot/security/user/UserResponse.java @@ -0,0 +1,15 @@ +package com.springboot.security.user; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserResponse { + + String email; +} diff --git a/security/src/main/resources/application.properties b/security/src/main/resources/application.properties index 0000b09..657613b 100644 --- a/security/src/main/resources/application.properties +++ b/security/src/main/resources/application.properties @@ -3,6 +3,7 @@ spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +server.port=8081 spring.jpa.hibernate.ddl-auto= create-drop spring.jpa.show-sql= true diff --git a/spring-cloud-open-feign/.gitignore b/spring-cloud-open-feign/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/spring-cloud-open-feign/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..bf82ff0 Binary files /dev/null and b/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ca5ab4b --- /dev/null +++ b/spring-cloud-open-feign/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/spring-cloud-open-feign/README.md b/spring-cloud-open-feign/README.md new file mode 100644 index 0000000..6b9e6d1 --- /dev/null +++ b/spring-cloud-open-feign/README.md @@ -0,0 +1,179 @@ +## Spring Cloud Openfeign + +In the world of microservices architecture, Spring Cloud OpenFeign plays a crucial role in simplifying HTTP API clients. Microservices often need to communicate with each other, and this inter-service communication is usually done over HTTP. Managing this communication manually can become complex and error-prone, especially when you have a large number of microservices. This is where Spring Cloud OpenFeign comes in. + +To enable Feign Clients in your Spring Boot application, you need to annotate your main class (or a configuration class) with @EnableFeignClients. + +``` +@SpringBootApplication +@EnableFeignClients +public class SpringCloudOpenFeignApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringCloudOpenFeignApplication.class, args); + } + +} + +``` + +### Structure of a feign client: + +``` +@FeignClient(value = "securityClient", + url = "${security.api.url}", + configuration = SecurityServiceConfig.class, + fallback = SecurityServiceFallback.class) +public interface SecurityService { + + @PostMapping("/auth/register") + AuthenticationResponse register(@RequestBody RegisterRequest registerRequest); + + @PostMapping("/auth/authenticate") + AuthenticationResponse authenticate(@RequestBody AuthenticationRequest authenticationRequest); + + + @GetMapping("/demo") + public ResponseEntity demo(); + +} + +``` + +* value or name: The name of the service with which to create a ribbon load balancer. This is an arbitrary name which can be used to create a URL. +* url: A URL or a complete URL to the desired service. +* configuration: A custom configuration class for the feign client. You can define beans that you want to use with this particular Feign Client in this class. +* fallback: This defines a class that is a fallback when a failure occurs on the method call. This class should implement the interface annotated by @FeignClient and provide a plausible fallback response for its methods. +* The @FeignClient annotation tells Spring to generate a proxy for this interface so that it can intercept calls to the interface's methods. +* The methods in the SecurityService interface correspond to HTTP endpoints in the target service (in this case, the "securityClient" service). They are annotated with standard Spring MVC annotations like @GetMapping, @PostMapping, etc. +* The @RequestBody and @RequestHeader annotations work just like they do in Spring MVC. They indicate that a method parameter should be bound to the body of the web request, or a request header. + +Spring Cloud OpenFeign does not provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client: +``` +Logger.Level +Retryer +ErrorDecoder +Request.Options +Collection +SetterFactory +QueryMapEncoder +Capability + +``` + + + + +### Structure of the SecurityServiceConfig: + +``` +public class SecurityServiceConfig { + + + @Bean + Logger.Level feignLoggerLevel(){ + return Logger.Level.FULL; + } + + + @Bean + public RequestInterceptor requestInterceptor() { + return new RequestInterceptor() { + @Override + public void apply(RequestTemplate template) { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + template.header(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION)); + } + } + }; + } + + @Bean + public ErrorDecoder errorDecoder(){ + return new FeignErrorDecoder(); + + } +} + +``` + + + +* Logger.Level feignLoggerLevel(): Feign includes a logger which you can use to output requests and responses to the console. This feignLoggerLevel bean sets the logging level for Feign's logger. The Logger.Level.FULL value means that Feign will log the full contents of HTTP requests and responses including headers and body. Note that this logging may include sensitive data and should not be used in production. + +* RequestInterceptor requestInterceptor(): The purpose of a RequestInterceptor is to perform operations on the request before it is sent. In this specific case, the interceptor is adding an "Authorization" header to every request, copying it from the incoming HTTP request. This is useful in microservices architectures where you might want to propagate authentication information from one service to another. + +* ErrorDecoder errorDecoder(): An ErrorDecoder is used to handle errors returned by the Feign client. The default ErrorDecoder provided by Feign does nothing special: it simply wraps the HTTP status code and error message into an exception. However, you can define your own ErrorDecoder if you need custom error handling. In this case, a custom FeignErrorDecoder is used. This ErrorDecoder could, for example, map certain HTTP status codes to custom exceptions. + + +### Structure of the FeignErrorDecoder: + +``` + +public class FeignErrorDecoder implements ErrorDecoder { + + + @Override + public Exception decode(String s, Response response) { + switch (response.status()){ + case 400: + return new ApiRequestException("Bad request"); + case 403: + return new AccessDeniedException("Access denied"); + default: + return new Exception("Library generic exception"); + } + } +} + +``` +This class is implementing the ErrorDecoder interface provided by Feign. The purpose of an ErrorDecoder is to handle errors returned by the Feign client. When the Feign client receives a non-2xx HTTP response, it invokes the ErrorDecoder with the response. + +Without the ErrorDecoder, Feign client would throw a FeignException for all non-2xx HTTP responses. This would result in a 500 Internal Server Error being returned for all types of errors, making it difficult to distinguish between different types of errors and to handle them appropriately. + +### Structure of the SecurityServiceFallback: + +``` +@Component +public class SecurityServiceFallback implements SecurityService { + @Override + public AuthenticationResponse register(RegisterRequest registerRequest) { + return new AuthenticationResponse("response for fallback"); + } + + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) { + + return new AuthenticationResponse("response for fallback"); + + } + + @Override + public ResponseEntity demo() { + + return ResponseEntity.ok(new AuthenticationResponse("response for fallback")); + } +} + +``` + + +@FeignClient annotation provides a fallback attribute which can be used to specify a fallback class. This class should implement the same interface as the Feign client and is used as a backup in case the main Feign client fails to call the remote service. The methods in the fallback class are called when the methods of the main Feign client fail. + +To enable fallbacks in Feign, you need to set the spring.cloud.openfeign.circuitbreaker.enabled=true in application.properties. + + + +If we want to create multiple feign clients with the same name or url so that they would point to the same server but each with a different custom configuration then we have to use contextId attribute of the @FeignClient in order to avoid name collision of these configuration beans. + +@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) +public interface FooClient { +//.. +} + +@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) +public interface BarClient { +//.. +} \ No newline at end of file diff --git a/spring-cloud-open-feign/mvnw b/spring-cloud-open-feign/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/spring-cloud-open-feign/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-cloud-open-feign/mvnw.cmd b/spring-cloud-open-feign/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/spring-cloud-open-feign/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-cloud-open-feign/pom.xml b/spring-cloud-open-feign/pom.xml new file mode 100644 index 0000000..347404d --- /dev/null +++ b/spring-cloud-open-feign/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.6 + + + com.example + spring-cloud-open-feign + 0.0.1-SNAPSHOT + spring-cloud-open-feign + Demo project for Spring Cloud Open Feign + + 17 + 2022.0.2 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplication.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplication.java new file mode 100644 index 0000000..66f2ee7 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplication.java @@ -0,0 +1,15 @@ +package com.example.springcloudopenfeign; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +public class SpringCloudOpenFeignApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringCloudOpenFeignApplication.class, args); + } + +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/config/SecurityServiceConfig.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/config/SecurityServiceConfig.java new file mode 100644 index 0000000..46d38a7 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/config/SecurityServiceConfig.java @@ -0,0 +1,43 @@ +package com.example.springcloudopenfeign.config; + +import com.example.springcloudopenfeign.decoder.FeignErrorDecoder; +import feign.Logger; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.codec.ErrorDecoder; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +public class SecurityServiceConfig { + + + @Bean + Logger.Level feignLoggerLevel(){ + return Logger.Level.FULL; + } + + + @Bean + public RequestInterceptor requestInterceptor() { + return new RequestInterceptor() { + @Override + public void apply(RequestTemplate template) { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + template.header(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION)); + } + } + }; + } + + @Bean + public ErrorDecoder errorDecoder(){ + return new FeignErrorDecoder(); + + } +} \ No newline at end of file diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/controller/SecurityController.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/controller/SecurityController.java new file mode 100644 index 0000000..5a0f721 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/controller/SecurityController.java @@ -0,0 +1,37 @@ +package com.example.springcloudopenfeign.controller; + + +import com.example.springcloudopenfeign.dto.request.AuthenticationRequest; +import com.example.springcloudopenfeign.dto.request.RegisterRequest; +import com.example.springcloudopenfeign.dto.response.AuthenticationResponse; +import com.example.springcloudopenfeign.service.SecurityService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/security") +public class SecurityController { + + private final SecurityService securityService; + + @PostMapping("/register") + private AuthenticationResponse register(@RequestBody RegisterRequest registerRequest){ + return securityService.register(registerRequest); + } + + @PostMapping("/authenticate") + private AuthenticationResponse authenticate(@RequestBody AuthenticationRequest authenticationRequest){ + return securityService.authenticate(authenticationRequest); + } + + @GetMapping("/demo") + public ResponseEntity demo(){ + return securityService.demo(); + } + + + + +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/decoder/FeignErrorDecoder.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/decoder/FeignErrorDecoder.java new file mode 100644 index 0000000..a6a6a79 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/decoder/FeignErrorDecoder.java @@ -0,0 +1,22 @@ +package com.example.springcloudopenfeign.decoder; + +import com.example.springcloudopenfeign.exceptions.AccessDeniedException; +import com.example.springcloudopenfeign.exceptions.ApiRequestException; +import feign.Response; +import feign.codec.ErrorDecoder; + +public class FeignErrorDecoder implements ErrorDecoder { + + + @Override + public Exception decode(String s, Response response) { + switch (response.status()){ + case 400: + return new ApiRequestException("Bad request"); + case 403: + return new AccessDeniedException("Access denied"); + default: + return new Exception("Library generic exception"); + } + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/AuthenticationRequest.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/AuthenticationRequest.java new file mode 100644 index 0000000..ffccbcb --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/AuthenticationRequest.java @@ -0,0 +1,16 @@ +package com.example.springcloudopenfeign.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AuthenticationRequest { + + private String email; + private String password; +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/RegisterRequest.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/RegisterRequest.java new file mode 100644 index 0000000..360323b --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/request/RegisterRequest.java @@ -0,0 +1,20 @@ +package com.example.springcloudopenfeign.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RegisterRequest { + private String firstname; + private String lastname; + private String email; + private String password; + + + +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/response/AuthenticationResponse.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/response/AuthenticationResponse.java new file mode 100644 index 0000000..ad86e60 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/dto/response/AuthenticationResponse.java @@ -0,0 +1,14 @@ +package com.example.springcloudopenfeign.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AuthenticationResponse { + String token; +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/AccessDeniedException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/AccessDeniedException.java new file mode 100644 index 0000000..9174022 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/AccessDeniedException.java @@ -0,0 +1,7 @@ +package com.example.springcloudopenfeign.exceptions; + +public class AccessDeniedException extends RuntimeException { + public AccessDeniedException(String message) { + super(message); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiException.java new file mode 100644 index 0000000..60103be --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiException.java @@ -0,0 +1,21 @@ +package com.example.springcloudopenfeign.exceptions; + +import lombok.Data; +import org.springframework.http.HttpStatus; + +import java.time.ZonedDateTime; + +@Data +public class ApiException { + private String msg; + + private HttpStatus httpStatus; + + private ZonedDateTime timestamp; + + public ApiException(String msg, HttpStatus httpStatus, ZonedDateTime timestamp) { + this.msg = msg; + this.httpStatus = httpStatus; + this.timestamp = timestamp; + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiExceptionHandler.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiExceptionHandler.java new file mode 100644 index 0000000..bd2ad74 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiExceptionHandler.java @@ -0,0 +1,67 @@ +package com.example.springcloudopenfeign.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +@RestControllerAdvice +public class ApiExceptionHandler { + @ExceptionHandler(value = {ApiRequestException.class}) + public ResponseEntity handleApiRequestException(ApiRequestException e) { + HttpStatus httpStatus = HttpStatus.BAD_REQUEST; + ApiException apiException = new ApiException( + e.getMessage(), + httpStatus, + ZonedDateTime.now(ZoneId.of("Z")) + ); + return new ResponseEntity<>(apiException, httpStatus); + } + + @ExceptionHandler(value = {ResultNotFoundException.class}) + public ResponseEntity handleResultNotFoundException(ResultNotFoundException e) { + HttpStatus httpStatus = HttpStatus.NOT_FOUND; + ApiException apiException = new ApiException( + e.getMessage(), + httpStatus, + ZonedDateTime.now(ZoneId.of("Z")) + ); + return new ResponseEntity<>(apiException, httpStatus); + } + + @ExceptionHandler(value = {ConflictException.class}) + public ResponseEntity handleConflictException(ConflictException e) { + HttpStatus httpStatus = HttpStatus.CONFLICT; + ApiException apiException = new ApiException( + e.getMessage(), + httpStatus, + ZonedDateTime.now(ZoneId.of("Z")) + ); + return new ResponseEntity<>(apiException, httpStatus); + } + + @ExceptionHandler(value = {AccessDeniedException.class}) + public ResponseEntity handleAccessDeniedException(AccessDeniedException e) { + HttpStatus httpStatus = HttpStatus.FORBIDDEN; + ApiException apiException = new ApiException( + e.getMessage(), + httpStatus, + ZonedDateTime.now(ZoneId.of("Z")) + ); + return new ResponseEntity<>(apiException, httpStatus); + } + + @ExceptionHandler(value = {ServerException.class}) + public ResponseEntity handleServerException(ServerException e) { + HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + ApiException apiException = new ApiException( + e.getMessage(), + httpStatus, + ZonedDateTime.now(ZoneId.of("Z")) + ); + return new ResponseEntity<>(apiException, httpStatus); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiRequestException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiRequestException.java new file mode 100644 index 0000000..11b9f35 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ApiRequestException.java @@ -0,0 +1,8 @@ +package com.example.springcloudopenfeign.exceptions; + +public class ApiRequestException extends RuntimeException { + + public ApiRequestException(String message) { + super(message); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ConflictException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ConflictException.java new file mode 100644 index 0000000..322e67b --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ConflictException.java @@ -0,0 +1,8 @@ +package com.example.springcloudopenfeign.exceptions; + +public class ConflictException extends RuntimeException{ + + public ConflictException(String message) { + super(message); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ResultNotFoundException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ResultNotFoundException.java new file mode 100644 index 0000000..5fe8322 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ResultNotFoundException.java @@ -0,0 +1,6 @@ +package com.example.springcloudopenfeign.exceptions; +public class ResultNotFoundException extends RuntimeException{ + public ResultNotFoundException(String message) { + super(message); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ServerException.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ServerException.java new file mode 100644 index 0000000..fa00e92 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/exceptions/ServerException.java @@ -0,0 +1,7 @@ +package com.example.springcloudopenfeign.exceptions; + +public class ServerException extends RuntimeException { + public ServerException(String message) { + super(message); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/fallback/SecurityServiceFallback.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/fallback/SecurityServiceFallback.java new file mode 100644 index 0000000..6675e8a --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/fallback/SecurityServiceFallback.java @@ -0,0 +1,30 @@ +package com.example.springcloudopenfeign.fallback; + + +import com.example.springcloudopenfeign.dto.request.AuthenticationRequest; +import com.example.springcloudopenfeign.dto.request.RegisterRequest; +import com.example.springcloudopenfeign.dto.response.AuthenticationResponse; +import com.example.springcloudopenfeign.service.SecurityService; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class SecurityServiceFallback implements SecurityService { + @Override + public AuthenticationResponse register(RegisterRequest registerRequest) { + return new AuthenticationResponse("response for fallback"); + } + + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) { + + return new AuthenticationResponse("response for fallback"); + + } + + @Override + public ResponseEntity demo() { + + return ResponseEntity.ok(new AuthenticationResponse("response for fallback")); + } +} diff --git a/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/service/SecurityService.java b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/service/SecurityService.java new file mode 100644 index 0000000..b8369f9 --- /dev/null +++ b/spring-cloud-open-feign/src/main/java/com/example/springcloudopenfeign/service/SecurityService.java @@ -0,0 +1,29 @@ +package com.example.springcloudopenfeign.service; + + +import com.example.springcloudopenfeign.config.SecurityServiceConfig; +import com.example.springcloudopenfeign.dto.request.AuthenticationRequest; +import com.example.springcloudopenfeign.dto.request.RegisterRequest; +import com.example.springcloudopenfeign.dto.response.AuthenticationResponse; +import com.example.springcloudopenfeign.fallback.SecurityServiceFallback; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@FeignClient(value = "securityClient", + url = "${security.api.url}", + configuration = SecurityServiceConfig.class, + fallback = SecurityServiceFallback.class) +public interface SecurityService { + + @PostMapping("/auth/register") + AuthenticationResponse register(@RequestBody RegisterRequest registerRequest); + + @PostMapping("/auth/authenticate") + AuthenticationResponse authenticate(@RequestBody AuthenticationRequest authenticationRequest); + + + @GetMapping("/demo") + public ResponseEntity demo(); + +} diff --git a/spring-cloud-open-feign/src/main/resources/application.properties b/spring-cloud-open-feign/src/main/resources/application.properties new file mode 100644 index 0000000..56073b7 --- /dev/null +++ b/spring-cloud-open-feign/src/main/resources/application.properties @@ -0,0 +1,6 @@ +security.api.url=http://localhost:8081 +server.port=8082 +spring.cloud.openfeign.circuitbreaker.enabled=false + + + diff --git a/spring-cloud-open-feign/src/test/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplicationTests.java b/spring-cloud-open-feign/src/test/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplicationTests.java new file mode 100644 index 0000000..71bb3ad --- /dev/null +++ b/spring-cloud-open-feign/src/test/java/com/example/springcloudopenfeign/SpringCloudOpenFeignApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.springcloudopenfeign; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringCloudOpenFeignApplicationTests { + + @Test + void contextLoads() { + } + +}