Commit 1a9779ee authored by Lukas Koschine's avatar Lukas Koschine

implemented synchronous exchange between core and gateway instances with REST...

implemented synchronous exchange between core and gateway instances with REST APIs, health check, refactoring, error pages corrected
parent 864462b9
......@@ -23,7 +23,7 @@ to be installed.
```
git clone git@sources.florianstendel.de:agawa/agawa-project.git
```
You can run the "build.bat" under /build or
For each project in the following order:
```
......
......@@ -4,10 +4,10 @@ call %THIS_PATH%\01parent.bat
call %THIS_PATH%\02springbootparent.bat
call %THIS_PATH%\03model.bat
call %THIS_PATH%\04provider.bat
call %THIS_PATH%\05core.bat
call %THIS_PATH%\06templates.bat
call %THIS_PATH%\07gateway.bat
rem call %THIS_PATH%\08keycloak-authentication-provider.bat
call %THIS_PATH%\05keycloak-authentication-provider.bat
call %THIS_PATH%\06core.bat
call %THIS_PATH%\07templates.bat
call %THIS_PATH%\08gateway.bat
mkdir %THIS_PATH%\dist\
mkdir %THIS_PATH%\dist\agawa\
mkdir %THIS_PATH%\dist\agawa\templates\
......
......@@ -34,6 +34,16 @@
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${org.springframework.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${org.springframework.boot.version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
......
......@@ -7,12 +7,14 @@ import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
@EnableCaching
@EntityScan(basePackages = "de.bytepri.agawa.model")
@EnableScheduling
@EntityScan
public class Application {
public static void main(String[] args) {
......@@ -22,7 +24,7 @@ public class Application {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages", "classpath:application");
messageSource.setBasenames("classpath:application");
return messageSource;
}
......
package de.bytepri.agawa.core.configuration;
public class Initialization {
//availability retries
private int retries = 2;
//inititalization tries
private int tries = 3;
private boolean initialized = false;
private String revision;
public int getTries() {
return tries;
}
public void setTries(int tries) {
this.tries = tries;
}
public boolean isInitialized() {
return initialized;
}
public void setInitialized(boolean initialized) {
this.initialized = initialized;
}
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
}
public int getRetries() {
return retries;
}
public void setRetries(int retries) {
this.retries = retries;
}
}
package de.bytepri.agawa.core.configuration;
import de.bytepri.agawa.core.entity.Privilege;
import de.bytepri.agawa.core.entity.Role;
import de.bytepri.agawa.core.entity.User;
import de.bytepri.agawa.core.repository.PrivilegeRepository;
import de.bytepri.agawa.core.repository.RoleRepository;
import de.bytepri.agawa.core.repository.UserRepository;
import de.bytepri.agawa.model.entity.Privilege;
import de.bytepri.agawa.model.entity.Role;
import de.bytepri.agawa.model.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
......
......@@ -23,14 +23,17 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.authorizeRequests()
.antMatchers("/agawa/ui/admin/**").hasRole("ADMIN")
.antMatchers("/agawa/ui/api/**").hasRole("USER")
.antMatchers("/agawa/ui/index", "/agawa/ui/error", "/agawa/ui", "/agawa/ui/media/**").permitAll()
.antMatchers("/agawa/ui/index", "/error", "/agawa/ui", "/agawa/admin/register", "/agawa/ui/media/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/agawa/ui/login")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/agawa/ui/logout")).logoutSuccessUrl("/agawa/ui/index");
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/agawa/ui/logout")).logoutSuccessUrl("/agawa/ui/index")
.and().
csrf().disable();
}
@Autowired
......
......@@ -2,6 +2,7 @@ package de.bytepri.agawa.core.controller;
import de.bytepri.agawa.core.dao.TenantDao;
import de.bytepri.agawa.core.dao.UserDao;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.core.model.*;
import de.bytepri.agawa.core.validation.TenantValidator;
import de.bytepri.agawa.core.validation.UserFormValidator;
......@@ -25,7 +26,6 @@ import static de.bytepri.agawa.core.controller.BaseController.BASE_PATH_VALUE;
@RequestMapping(BASE_PATH_VALUE + "/admin")
public class AdminController extends BaseController {
private Logger logger;
private TenantDao tenantDao;
private UserFormValidator userFormValidator;
private TenantValidator tenantValidator;
......@@ -33,9 +33,9 @@ public class AdminController extends BaseController {
public AdminController(@Autowired Logger logger, @Autowired TenantDao tenantDao,
@Autowired UserFormValidator userFormValidator, @Autowired TenantValidator tenantValidator,
@Autowired PasswordEncoder passwordEncoder, @Autowired UserDao userDao) {
super(userDao);
this.logger = logger;
@Autowired PasswordEncoder passwordEncoder, @Autowired UserDao userDao,
@Autowired GatewayPublisher gatewayPublisher) {
super(logger, userDao, gatewayPublisher);
this.tenantDao = tenantDao;
this.userFormValidator = userFormValidator;
this.tenantValidator = tenantValidator;
......@@ -77,17 +77,26 @@ public class AdminController extends BaseController {
public String postEditTenant(ModelMap model, @Valid EditTenantModel tenantModel, BindingResult bindingResult) {
logger.debug("editTenant");
tenantValidator.validate(tenantModel, bindingResult);
try {
if (bindingResult.hasErrors()) {
tenantValidator.validate(tenantModel, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
model.addAttribute("tenants", tenantDao.findAll());
model.addAttribute("editTenant", tenantModel);
return get("tenants", model);
}
tenantDao.update(tenantDao.findByName(tenantModel.getOldName()), tenantModel);
} catch (RuntimeException e) {
bindingResult.rejectValue("name", null, "could not be edited");
model.addAttribute("errors", bindingResult.getAllErrors());
model.addAttribute("tenants", tenantDao.findAll());
model.addAttribute("editTenant", tenantModel);
return get("tenants", model);
}
tenantDao.update(tenantDao.findByName(tenantModel.getOldName()), tenantModel);
return getRedirect("tenants");
}
......@@ -95,29 +104,36 @@ public class AdminController extends BaseController {
public String postDeleteTenant(ModelMap model, @Valid TenantName tenantName, BindingResult bindingResult) {
logger.debug("deleteTenant");
TenantModel tenantModel = null;
if (tenantName == null || StringUtils.isEmpty(tenantName.getName())) {
bindingResult.rejectValue("name", null, "not set");
} else {
tenantModel = tenantDao.findByName(tenantName.getName());
if (tenantModel == null) {
bindingResult.rejectValue("name", null, "not found");
try {
TenantModel tenantModel = null;
if (tenantName == null || StringUtils.isEmpty(tenantName.getName())) {
bindingResult.rejectValue("name", null, "not set");
} else {
List<UserModel> userModels = userDao.findByTenant_Name(tenantName.getName());
if (userModels != null) {
for (UserModel userModel : userModels) {
userDao.delete(userModel);
tenantModel = tenantDao.findByName(tenantName.getName());
if (tenantModel == null) {
bindingResult.rejectValue("name", null, "not found");
} else {
List<UserModel> userModels = userDao.findByTenant_Name(tenantName.getName());
if (userModels != null) {
for (UserModel userModel : userModels) {
userDao.delete(userModel);
}
}
}
}
}
if (bindingResult.hasErrors()) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
model.addAttribute("tenants", tenantDao.findAll());
return get("tenants", model);
}
tenantDao.delete(tenantModel);
} catch (RuntimeException e) {
bindingResult.rejectValue("name", null, "could not be deleted");
model.addAttribute("errors", bindingResult.getAllErrors());
model.addAttribute("tenants", tenantDao.findAll());
return get("tenants", model);
}
tenantDao.delete(tenantModel);
return getRedirect("tenants");
}
......@@ -195,7 +211,6 @@ public class AdminController extends BaseController {
userDao.delete(userModel);
return getRedirect("users");
}
......
......@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import de.bytepri.agawa.core.dao.ApiDao;
import de.bytepri.agawa.core.dao.UserDao;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.core.model.*;
import de.bytepri.agawa.core.validation.SyntaxValidator;
import org.apache.commons.io.IOUtils;
......@@ -33,12 +34,11 @@ import static de.bytepri.agawa.core.controller.BaseController.BASE_PATH_VALUE;
@RequestMapping(BASE_PATH_VALUE + "/api")
public class ApiManagementController extends BaseController {
private Logger logger;
private ApiDao apiDao;
public ApiManagementController(@Autowired Logger logger, @Autowired ApiDao apiDao, @Autowired UserDao userDao) {
super(userDao);
this.logger = logger;
public ApiManagementController(@Autowired Logger logger, @Autowired ApiDao apiDao, @Autowired UserDao userDao,
@Autowired GatewayPublisher gatewayPublisher) {
super(logger, userDao, gatewayPublisher);
this.apiDao = apiDao;
}
......
package de.bytepri.agawa.core.controller;
import de.bytepri.agawa.core.dao.UserDao;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.core.model.UserModel;
import org.slf4j.Logger;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
......@@ -13,11 +15,16 @@ public abstract class BaseController {
protected static final String HEADLINE = "headLine";
protected static final String CONTEXT = "context";
protected static final String USER = "user";
private static final String CLIENT_BY_STATUS = "clientByStatus";
protected UserDao userDao;
protected GatewayPublisher gatewayPublisher;
protected Logger logger;
public BaseController(UserDao userDao) {
public BaseController(Logger logger, UserDao userDao, GatewayPublisher gatewayPublisher) {
this.logger = logger;
this.userDao = userDao;
this.gatewayPublisher = gatewayPublisher;
}
protected abstract String getContext();
......@@ -37,6 +44,7 @@ public abstract class BaseController {
model.addAttribute(CONTEXT, getContext());
model.addAttribute(USER, getLoggedInUser());
model.addAttribute(BASE_PATH, BASE_PATH_VALUE);
model.addAttribute(CLIENT_BY_STATUS, gatewayPublisher.getClientsCopy());
//doesn't belong here, does it?
model.addAttribute(HEADLINE, getHeadLine());
......
package de.bytepri.agawa.core.controller;
import de.bytepri.agawa.core.dao.UserDao;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DefaultErrorController extends BaseController implements ErrorController {
private static final String ERROR_PATH = "/error";
public DefaultErrorController(@Autowired UserDao userDao, @Autowired Logger logger,
@Autowired GatewayPublisher gatewayPublisher) {
super(logger, userDao, gatewayPublisher);
}
@GetMapping(value = ERROR_PATH)
public String error() {
return "/agawa/ui" + ERROR_PATH;
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
@Override
protected String getContext() {
return "";
}
@Override
protected String getHeadLine() {
return "Error";
}
}
package de.bytepri.agawa.core.controller;
import de.bytepri.agawa.core.dao.UserDao;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorController;
......@@ -13,26 +14,17 @@ import static de.bytepri.agawa.core.controller.BaseController.BASE_PATH_VALUE;
@Controller
@RequestMapping(BASE_PATH_VALUE)
public class LandingController extends BaseController implements ErrorController {
public class LandingController extends BaseController {
private static final String ERROR_PATH = "/error";
private Logger logger;
public LandingController(@Autowired UserDao userDao, @Autowired Logger logger) {
super(userDao);
public LandingController(@Autowired UserDao userDao, @Autowired Logger logger,
@Autowired GatewayPublisher gatewayPublisher) {
super(logger, userDao, gatewayPublisher);
this.logger = logger;
}
@GetMapping(value = ERROR_PATH)
public String error() {
return get(ERROR_PATH);
}
@Override
public String getErrorPath() {
return BASE_PATH_VALUE + ERROR_PATH;
}
@GetMapping("/")
public String root(ModelMap model) {
logger.info("root");
......
package de.bytepri.agawa.core.controller;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.model.api.RegistrationInput;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/agawa/admin")
public class RegistrationEndpoint {
private Logger logger;
private GatewayPublisher gatewayPublisher;
public RegistrationEndpoint(@Autowired Logger logger, @Autowired GatewayPublisher gatewayPublisher) {
this.logger = logger;
this.gatewayPublisher = gatewayPublisher;
}
@PostMapping(path = "/register")
public ResponseEntity<?> register(@RequestBody RegistrationInput registrationInput, HttpServletRequest httpServletRequest) {
String client = registrationInput.getLocation();
if (gatewayPublisher.clientExists(client)) {
logger.warn("duplicate client at '{}'. Resetting initialization flag.", client);
gatewayPublisher.reset(client);
return new ResponseEntity<>("", HttpStatus.OK);
} else {
gatewayPublisher.set(client);
logger.info("Added gateway client at {}", client);
return new ResponseEntity<>("", HttpStatus.CREATED);
}
}
}
package de.bytepri.agawa.core.dao;
import de.bytepri.agawa.core.entity.Api;
import de.bytepri.agawa.core.entity.ApiVersion;
import de.bytepri.agawa.core.entity.Tenant;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.core.model.ApiModel;
import de.bytepri.agawa.core.model.ApiVersionModel;
import de.bytepri.agawa.core.repository.ApiRepository;
import de.bytepri.agawa.core.repository.ApiVersionRepository;
import de.bytepri.agawa.core.repository.TenantRepository;
import de.bytepri.agawa.model.entity.Api;
import de.bytepri.agawa.model.entity.ApiVersion;
import de.bytepri.agawa.model.entity.Tenant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
......@@ -20,11 +21,14 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
private ApiRepository apiRepository;
private ApiVersionRepository apiVersionRepository;
private GatewayPublisher gatewayPublisher;
public ApiDao(@Autowired ApiRepository apiRepository, @Autowired ApiVersionRepository apiVersionRepository, @Autowired TenantRepository tenantRepository) {
public ApiDao(@Autowired ApiRepository apiRepository, @Autowired ApiVersionRepository apiVersionRepository,
@Autowired TenantRepository tenantRepository, @Autowired GatewayPublisher gatewayPublisher) {
super(tenantRepository);
this.apiRepository = apiRepository;
this.apiVersionRepository = apiVersionRepository;
this.gatewayPublisher = gatewayPublisher;
}
@Override
......@@ -34,6 +38,8 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
if (tenant.getApis().isEmpty() || apiModel.getVersions().isEmpty()) {
newApi = modelMapper.map(apiModel, Api.class);
newApi.setTenant(tenant);
tenant.getApis().clear();
tenant.getApis().add(newApi);
} else {
for (Api api : tenant.getApis()) {
if (api.getName().equals(apiModel.getName())) {
......@@ -56,6 +62,7 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
}
}
if (newApi != null) {
gatewayPublisher.updateTenant(newApi.getTenant());
apiRepository.save(newApi);
}
}
......@@ -67,11 +74,13 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
if (any.isPresent()) {
Api api = any.get();
modelMapper.map(newApi, api);
gatewayPublisher.updateTenant(api.getTenant());
apiRepository.save(api);
}
}
public void updateVersion(ApiVersionModel apiVersionModel, String tenantName, String apiName, String oldVersion) {
Api version = null;
for (Api api : tenantRepository.findByName(tenantName).getApis()) {
if (api.getName().equals(apiName)) {
for (ApiVersion apiVersion : api.getApiVersions()) {
......@@ -80,12 +89,16 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
apiVersion.setBackendEndpoint(apiVersionModel.getBackendEndpoint());
apiVersion.setEndUserHeaderName(apiVersionModel.getEndUserHeaderName());
apiVersion.setSwaggerDefinition(apiVersionModel.getSwaggerDefinition());
apiRepository.save(api);
version = api;
break;
}
}
}
}
if (version != null) {
gatewayPublisher.updateTenant(version.getTenant());
apiRepository.save(version);
}
}
@Override
......@@ -122,6 +135,7 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
if (any.isPresent()) {
Api api = any.get();
tenant.getApis().remove(api);
gatewayPublisher.updateTenant(tenant);
tenantRepository.save(tenant);
apiRepository.delete(api);
}
......@@ -135,11 +149,16 @@ public class ApiDao extends EntityDao<Api, ApiModel> {
Api api = anyApi.get();
Optional<ApiVersion> anyVersion = api.getApiVersions().parallelStream().filter(apiVersion -> apiVersionToDelete.equals(apiVersion.getVersion())).findAny();
List<ApiVersion> versionsToDelete = new LinkedList<>();
if (anyVersion.isPresent()) {
ApiVersion version = anyVersion.get();
apiVersionRepository.delete(version);
versionsToDelete.add(version);
api.getApiVersions().remove(version);
}
gatewayPublisher.updateTenant(api.getTenant());
for (ApiVersion apiVersion : versionsToDelete) {
apiVersionRepository.delete(apiVersion);
}
apiRepository.save(api);
}
}
......
package de.bytepri.agawa.core.dao;
import de.bytepri.agawa.core.entity.Tenant;
import de.bytepri.agawa.core.exchange.GatewayPublisher;
import de.bytepri.agawa.core.model.TenantModel;
import de.bytepri.agawa.core.repository.TenantRepository;
import de.bytepri.agawa.model.entity.Tenant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
......@@ -12,19 +13,27 @@ import java.util.List;
@Component
public class TenantDao extends EntityDao<Tenant, TenantModel> {
public TenantDao(@Autowired TenantRepository tenantRepository) {
private GatewayPublisher gatewayPublisher;
public TenantDao(@Autowired TenantRepository tenantRepository, @Autowired GatewayPublisher gatewayPublisher) {
super(tenantRepository);
this.gatewayPublisher = gatewayPublisher;
}
@Override
public void save(TenantModel tenantModel) {
tenantRepository.save(modelMapper.map(tenantModel, Tenant.class));
Tenant tenant = modelMapper.map(tenantModel, Tenant.class);
gatewayPublisher.createTenant(tenant);
tenantRepository.save(tenant);
}
@Override
public void update(TenantModel oldTenant, TenantModel newTenant) {
String oldTenantName = new String(oldTenant.getName());
String theOtherWay = oldTenant.getName();
Tenant tenant = tenantRepository.findByName(oldTenant.getName());
modelMapper.map(newTenant, tenant);
gatewayPublisher.updateTenant(tenant, oldTenantName);
tenantRepository.save(tenant);
}
......@@ -47,6 +56,7 @@ public class TenantDao extends EntityDao<Tenant, TenantModel> {
@Override
public void delete(TenantModel tenantModel) {
gatewayPublisher.deleteTenant(tenantModel.getName());
tenantRepository.delete(tenantRepository.findByName(tenantModel.getName()));
}
}
package de.bytepri.agawa.core.dao;
import de.bytepri.agawa.core.entity.Role;
import de.bytepri.agawa.core.entity.User;
import de.bytepri.agawa.core.model.EditUserModel;
import de.bytepri.agawa.core.model.UserModel;
import de.bytepri.agawa.core.repository.RoleRepository;
import de.bytepri.agawa.core.repository.TenantRepository;
import de.bytepri.agawa.core.repository.UserRepository;
import de.bytepri.agawa.model.entity.Role;
import de.bytepri.agawa.model.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
......
package de.bytepri.agawa.model.entity;
package de.bytepri.agawa.core.entity;
import javax.persistence.*;
import java.util.LinkedList;
......
package de.bytepri.agawa.model.entity;
package de.bytepri.agawa.core.entity;
import javax.persistence.*;
......
package de.bytepri.agawa.model.entity;
package de.bytepri.agawa.core.entity;
import javax.persistence.*;
import java.util.Collection;
......
package de.bytepri.agawa.model.entity;
package de.bytepri.agawa.core.entity;
import javax.persistence.*;
import java.util.Collection;
......
package de.bytepri.agawa.model.entity;
package de.bytepri.agawa.core.entity;
import javax.persistence.*;
import java.util.LinkedList;
......