Commit 5f134082 authored by Jason Song's avatar Jason Song Committed by GitHub

Merge pull request #310 from lepdou/namespace-as-file-format-2

namespace as file
parents 060126fd 1665f492
...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.biz.utils.ApolloSwitcher; ...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.biz.utils.ApolloSwitcher;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets; import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.BadRequestException; import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.exception.ServiceException;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
...@@ -42,13 +43,14 @@ public class NamespaceLockAspect { ...@@ -42,13 +43,14 @@ public class NamespaceLockAspect {
@Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)") @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)")
public void requireLockAdvice(String appId, String clusterName, String namespaceName, ItemDTO item) { public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemDTO item) {
acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy()); acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
} }
@Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)") @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)")
public void requireLockAdvice(String appId, String clusterName, String namespaceName, public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemChangeSets changeSet) { ItemChangeSets changeSet) {
acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy()); acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy());
} }
...@@ -59,7 +61,8 @@ public class NamespaceLockAspect { ...@@ -59,7 +61,8 @@ public class NamespaceLockAspect {
acquireLock(item.getNamespaceId(), operator); acquireLock(item.getNamespaceId(), operator);
} }
private void acquireLock(String appId, String clusterName, String namespaceName, String currentUser) { private void acquireLock(String appId, String clusterName, String namespaceName,
String currentUser) {
if (apolloSwitcher.isNamespaceLockSwitchOff()) { if (apolloSwitcher.isNamespaceLockSwitchOff()) {
return; return;
} }
...@@ -90,17 +93,15 @@ public class NamespaceLockAspect { ...@@ -90,17 +93,15 @@ public class NamespaceLockAspect {
//lock success //lock success
} catch (DataIntegrityViolationException e) { } catch (DataIntegrityViolationException e) {
//lock fail //lock fail
acquireLockFail(namespace, currentUser); namespaceLock = namespaceLockService.findLock(namespaceId);
} catch (Exception e){ checkLock(namespace, namespaceLock, currentUser);
} catch (Exception e) {
logger.error("try lock error", e); logger.error("try lock error", e);
throw e; throw e;
} }
} else { } else {
//check lock owner is current user //check lock owner is current user
String lockOwner = namespaceLock.getDataChangeCreatedBy(); checkLock(namespace, namespaceLock, currentUser);
if (!lockOwner.equals(currentUser)) {
acquireLockFail(namespace, currentUser);
}
} }
} }
...@@ -112,15 +113,19 @@ public class NamespaceLockAspect { ...@@ -112,15 +113,19 @@ public class NamespaceLockAspect {
namespaceLockService.tryLock(lock); namespaceLockService.tryLock(lock);
} }
private void acquireLockFail(Namespace namespace, String currentUser){ private void checkLock(Namespace namespace, NamespaceLock namespaceLock,
NamespaceLock namespaceLock = namespaceLockService.findLock(namespace.getId()); String currentUser) {
if (namespaceLock == null){ if (namespaceLock == null) {
acquireLock(namespace, currentUser); throw new ServiceException(
String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
} }
String lockOwner = namespaceLock.getDataChangeCreatedBy(); String lockOwner = namespaceLock.getDataChangeCreatedBy();
throw new BadRequestException("namespace:" + namespace.getNamespaceName() + " is modifying by " + lockOwner); if (!lockOwner.equals(currentUser)) {
throw new BadRequestException(
"namespace:" + namespace.getNamespaceName() + " is modified by " + lockOwner);
}
} }
} }
...@@ -31,19 +31,18 @@ public class AppController { ...@@ -31,19 +31,18 @@ public class AppController {
private AdminService adminService; private AdminService adminService;
@RequestMapping(path = "/apps", method = RequestMethod.POST) @RequestMapping(path = "/apps", method = RequestMethod.POST)
public AppDTO createOrUpdate(@RequestBody AppDTO dto) { public AppDTO create(@RequestBody AppDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getAppId())) { if (!InputValidator.isValidClusterNamespace(dto.getAppId())) {
throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE)); throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
} }
App entity = BeanUtils.transfrom(App.class, dto); App entity = BeanUtils.transfrom(App.class, dto);
App managedEntity = appService.findOne(entity.getAppId()); App managedEntity = appService.findOne(entity.getAppId());
if (managedEntity != null) { if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity); throw new BadRequestException("app already exist.");
entity = appService.update(managedEntity);
} else {
entity = adminService.createNewApp(entity);
} }
entity = adminService.createNewApp(entity);
dto = BeanUtils.transfrom(AppDTO.class, entity); dto = BeanUtils.transfrom(AppDTO.class, entity);
return dto; return dto;
} }
...@@ -51,13 +50,15 @@ public class AppController { ...@@ -51,13 +50,15 @@ public class AppController {
@RequestMapping(path = "/apps/{appId}", method = RequestMethod.DELETE) @RequestMapping(path = "/apps/{appId}", method = RequestMethod.DELETE)
public void delete(@PathVariable("appId") String appId, @RequestParam String operator) { public void delete(@PathVariable("appId") String appId, @RequestParam String operator) {
App entity = appService.findOne(appId); App entity = appService.findOne(appId);
if (entity == null) throw new NotFoundException("app not found for appId " + appId); if (entity == null) {
throw new NotFoundException("app not found for appId " + appId);
}
appService.delete(entity.getId(), operator); appService.delete(entity.getId(), operator);
} }
@RequestMapping("/apps") @RequestMapping("/apps")
public List<AppDTO> find(@RequestParam(value = "name", required = false) String name, public List<AppDTO> find(@RequestParam(value = "name", required = false) String name,
Pageable pageable) { Pageable pageable) {
List<App> app = null; List<App> app = null;
if (StringUtils.isBlank(name)) { if (StringUtils.isBlank(name)) {
app = appService.findAll(pageable); app = appService.findAll(pageable);
...@@ -70,7 +71,9 @@ public class AppController { ...@@ -70,7 +71,9 @@ public class AppController {
@RequestMapping("/apps/{appId}") @RequestMapping("/apps/{appId}")
public AppDTO get(@PathVariable("appId") String appId) { public AppDTO get(@PathVariable("appId") String appId) {
App app = appService.findOne(appId); App app = appService.findOne(appId);
if (app == null) throw new NotFoundException("app not found for appId " + appId); if (app == null) {
throw new NotFoundException("app not found for appId " + appId);
}
return BeanUtils.transfrom(AppDTO.class, app); return BeanUtils.transfrom(AppDTO.class, app);
} }
......
...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.common.entity.AppNamespace; ...@@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO; import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import java.util.List; import java.util.List;
...@@ -20,31 +21,18 @@ public class AppNamespaceController { ...@@ -20,31 +21,18 @@ public class AppNamespaceController {
@Autowired @Autowired
private AppNamespaceService appNamespaceService; private AppNamespaceService appNamespaceService;
@RequestMapping("/apps/{appId}/appnamespace/{appnamespace}/unique")
public boolean isAppNamespaceUnique(@PathVariable("appId") String appId,
@PathVariable("appnamespace") String appnamespace) {
return appNamespaceService.isAppNamespaceNameUnique(appId, appnamespace);
}
@RequestMapping("/appnamespaces/public")
public List<AppNamespaceDTO> findPublicAppNamespaces(){
List<AppNamespace> appNamespaces = appNamespaceService.findPublicAppNamespaces();
return BeanUtils.batchTransform(AppNamespaceDTO.class, appNamespaces);
}
@RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
public AppNamespaceDTO createOrUpdate( @RequestBody AppNamespaceDTO appNamespace){ public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace) {
AppNamespace entity = BeanUtils.transfrom(AppNamespace.class, appNamespace); AppNamespace entity = BeanUtils.transfrom(AppNamespace.class, appNamespace);
AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName()); AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
if (managedEntity != null){ if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity); throw new BadRequestException("app namespaces already exist.");
entity = appNamespaceService.update(managedEntity);
}else {
entity = appNamespaceService.createAppNamespace(entity, entity.getDataChangeCreatedBy());
} }
entity = appNamespaceService.createAppNamespace(entity, entity.getDataChangeCreatedBy());
return BeanUtils.transfrom(AppNamespaceDTO.class, entity); return BeanUtils.transfrom(AppNamespaceDTO.class, entity);
} }
......
...@@ -25,18 +25,16 @@ public class ClusterController { ...@@ -25,18 +25,16 @@ public class ClusterController {
private ClusterService clusterService; private ClusterService clusterService;
@RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST)
public ClusterDTO createOrUpdate(@PathVariable("appId") String appId, @RequestBody ClusterDTO dto) { public ClusterDTO create(@PathVariable("appId") String appId, @RequestBody ClusterDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getName())) { if (!InputValidator.isValidClusterNamespace(dto.getName())) {
throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE)); throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
} }
Cluster entity = BeanUtils.transfrom(Cluster.class, dto); Cluster entity = BeanUtils.transfrom(Cluster.class, dto);
Cluster managedEntity = clusterService.findOne(appId, entity.getName()); Cluster managedEntity = clusterService.findOne(appId, entity.getName());
if (managedEntity != null) { if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity); throw new BadRequestException("cluster already exist.");
entity = clusterService.update(managedEntity);
} else {
entity = clusterService.save(entity);
} }
entity = clusterService.save(entity);
dto = BeanUtils.transfrom(ClusterDTO.class, entity); dto = BeanUtils.transfrom(ClusterDTO.class, entity);
return dto; return dto;
...@@ -44,10 +42,11 @@ public class ClusterController { ...@@ -44,10 +42,11 @@ public class ClusterController {
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName:.+}", method = RequestMethod.DELETE) @RequestMapping(path = "/apps/{appId}/clusters/{clusterName:.+}", method = RequestMethod.DELETE)
public void delete(@PathVariable("appId") String appId, public void delete(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @RequestParam String operator) { @PathVariable("clusterName") String clusterName, @RequestParam String operator) {
Cluster entity = clusterService.findOne(appId, clusterName); Cluster entity = clusterService.findOne(appId, clusterName);
if (entity == null) if (entity == null) {
throw new NotFoundException("cluster not found for clusterName " + clusterName); throw new NotFoundException("cluster not found for clusterName " + clusterName);
}
clusterService.delete(entity.getId(), operator); clusterService.delete(entity.getId(), operator);
} }
...@@ -59,15 +58,17 @@ public class ClusterController { ...@@ -59,15 +58,17 @@ public class ClusterController {
@RequestMapping("/apps/{appId}/clusters/{clusterName:.+}") @RequestMapping("/apps/{appId}/clusters/{clusterName:.+}")
public ClusterDTO get(@PathVariable("appId") String appId, public ClusterDTO get(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName) { @PathVariable("clusterName") String clusterName) {
Cluster cluster = clusterService.findOne(appId, clusterName); Cluster cluster = clusterService.findOne(appId, clusterName);
if (cluster == null) throw new NotFoundException("cluster not found for name " + clusterName); if (cluster == null) {
throw new NotFoundException("cluster not found for name " + clusterName);
}
return BeanUtils.transfrom(ClusterDTO.class, cluster); return BeanUtils.transfrom(ClusterDTO.class, cluster);
} }
@RequestMapping("/apps/{appId}/cluster/{clusterName}/unique") @RequestMapping("/apps/{appId}/cluster/{clusterName}/unique")
public boolean isAppIdUnique(@PathVariable("appId") String appId, public boolean isAppIdUnique(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName) { @PathVariable("clusterName") String clusterName) {
return clusterService.isClusterNameUnique(appId, clusterName); return clusterService.isClusterNameUnique(appId, clusterName);
} }
} }
...@@ -21,6 +21,7 @@ import com.ctrip.framework.apollo.biz.service.NamespaceService; ...@@ -21,6 +21,7 @@ import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder; import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.exception.NotFoundException; import com.ctrip.framework.apollo.core.exception.NotFoundException;
@RestController @RestController
...@@ -72,6 +73,40 @@ public class ItemController { ...@@ -72,6 +73,40 @@ public class ItemController {
return dto; return dto;
} }
@PreAcquireNamespaceLock
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT)
public ItemDTO update(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO itemDTO) {
Item entity = BeanUtils.transfrom(Item.class, itemDTO);
ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
if (managedEntity == null) {
throw new BadRequestException("item not exist");
}
Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedEntity);
BeanUtils.copyEntityProperties(entity, managedEntity);
entity = itemService.update(managedEntity);
builder.updateItem(beforeUpdateItem, entity);
itemDTO = BeanUtils.transfrom(ItemDTO.class, entity);
Commit commit = new Commit();
commit.setAppId(appId);
commit.setClusterName(clusterName);
commit.setNamespaceName(namespaceName);
commit.setChangeSets(builder.build());
commit.setDataChangeCreatedBy(itemDTO.getDataChangeLastModifiedBy());
commit.setDataChangeLastModifiedBy(itemDTO.getDataChangeLastModifiedBy());
commitService.save(commit);
return itemDTO;
}
@PreAcquireNamespaceLock @PreAcquireNamespaceLock
@RequestMapping(path = "/items/{itemId}", method = RequestMethod.DELETE) @RequestMapping(path = "/items/{itemId}", method = RequestMethod.DELETE)
public void delete(@PathVariable("itemId") long itemId, @RequestParam String operator) { public void delete(@PathVariable("itemId") long itemId, @RequestParam String operator) {
......
...@@ -25,7 +25,7 @@ public class NamespaceController { ...@@ -25,7 +25,7 @@ public class NamespaceController {
private NamespaceService namespaceService; private NamespaceService namespaceService;
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces", method = RequestMethod.POST)
public NamespaceDTO createOrUpdate(@PathVariable("appId") String appId, public NamespaceDTO create(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @RequestBody NamespaceDTO dto) { @PathVariable("clusterName") String clusterName, @RequestBody NamespaceDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getNamespaceName())) { if (!InputValidator.isValidClusterNamespace(dto.getNamespaceName())) {
throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE)); throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
...@@ -33,12 +33,11 @@ public class NamespaceController { ...@@ -33,12 +33,11 @@ public class NamespaceController {
Namespace entity = BeanUtils.transfrom(Namespace.class, dto); Namespace entity = BeanUtils.transfrom(Namespace.class, dto);
Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName()); Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName());
if (managedEntity != null) { if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity); throw new BadRequestException("namespace already exist.");
entity = namespaceService.update(managedEntity);
} else {
entity = namespaceService.save(entity);
} }
entity = namespaceService.save(entity);
dto = BeanUtils.transfrom(NamespaceDTO.class, entity); dto = BeanUtils.transfrom(NamespaceDTO.class, entity);
return dto; return dto;
} }
......
...@@ -35,13 +35,13 @@ public class NamespaceLockController { ...@@ -35,13 +35,13 @@ public class NamespaceLockController {
} }
if (apolloSwitcher.isNamespaceLockSwitchOff()) { if (apolloSwitcher.isNamespaceLockSwitchOff()) {
throw new NotFoundException(namespaceName + " is not locked"); return null;
} }
NamespaceLock lock = namespaceLockService.findLock(namespace.getId()); NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock == null) { if (lock == null) {
throw new NotFoundException(namespaceName + " is not locked"); return null;
} }
return BeanUtils.transfrom(NamespaceLockDTO.class, lock); return BeanUtils.transfrom(NamespaceLockDTO.class, lock);
......
...@@ -24,6 +24,7 @@ public class AppControllerTest extends AbstractControllerTest { ...@@ -24,6 +24,7 @@ public class AppControllerTest extends AbstractControllerTest {
} }
@Test @Test
@Sql(scripts = "/controller/cleanup.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public void testCheckIfAppIdUnique() { public void testCheckIfAppIdUnique() {
AppDTO dto = generateSampleDTOData(); AppDTO dto = generateSampleDTOData();
ResponseEntity<AppDTO> response = ResponseEntity<AppDTO> response =
...@@ -72,16 +73,12 @@ public class AppControllerTest extends AbstractControllerTest { ...@@ -72,16 +73,12 @@ public class AppControllerTest extends AbstractControllerTest {
Assert.assertEquals(dto.getAppId(), savedApp.getAppId()); Assert.assertEquals(dto.getAppId(), savedApp.getAppId());
Assert.assertNotNull(savedApp.getDataChangeCreatedTime()); Assert.assertNotNull(savedApp.getDataChangeCreatedTime());
response = restTemplate.postForEntity(getBaseAppUrl(), dto, AppDTO.class); try {
AppDTO second = response.getBody(); restTemplate.postForEntity(getBaseAppUrl(), dto, AppDTO.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); }catch (HttpClientErrorException e){
Assert.assertEquals(dto.getAppId(), second.getAppId()); Assert.assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode());
Assert.assertEquals(first.getId(), second.getId()); }
savedApp = appRepository.findOne(second.getId());
Assert.assertEquals(dto.getAppId(), savedApp.getAppId());
Assert.assertNotNull(savedApp.getDataChangeCreatedTime());
Assert.assertNotNull(savedApp.getDataChangeLastModifiedTime());
} }
@Test @Test
...@@ -115,27 +112,14 @@ public class AppControllerTest extends AbstractControllerTest { ...@@ -115,27 +112,14 @@ public class AppControllerTest extends AbstractControllerTest {
Assert.assertNull(deletedApp); Assert.assertNull(deletedApp);
} }
@Test
@Sql(scripts = "/controller/cleanup.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public void testUpdate() {
AppDTO dto = generateSampleDTOData();
App app = BeanUtils.transfrom(App.class, dto);
app = appRepository.save(app);
dto.setName("newName");
restTemplate.postForObject(getBaseAppUrl(), dto, AppDTO.class);
App updatedApp = appRepository.findOne(app.getId());
Assert.assertEquals(dto.getName(), updatedApp.getName());
Assert.assertNotNull(updatedApp.getDataChangeLastModifiedTime());
}
private AppDTO generateSampleDTOData() { private AppDTO generateSampleDTOData() {
AppDTO dto = new AppDTO(); AppDTO dto = new AppDTO();
dto.setAppId("someAppId"); dto.setAppId("someAppId");
dto.setName("someName"); dto.setName("someName");
dto.setOwnerName("someOwner"); dto.setOwnerName("someOwner");
dto.setOwnerEmail("someOwner@ctrip.com"); dto.setOwnerEmail("someOwner@ctrip.com");
dto.setDataChangeCreatedBy("apollo");
dto.setDataChangeLastModifiedBy("apollo");
return dto; return dto;
} }
} }
...@@ -24,6 +24,7 @@ public class AppNamespaceControllerTest extends AbstractControllerTest{ ...@@ -24,6 +24,7 @@ public class AppNamespaceControllerTest extends AbstractControllerTest{
dto.setAppId(appId); dto.setAppId(appId);
dto.setName(name); dto.setName(name);
dto.setComment(comment); dto.setComment(comment);
dto.setDataChangeCreatedBy("apollo");
AppNamespaceDTO resultDto = restTemplate.postForEntity( AppNamespaceDTO resultDto = restTemplate.postForEntity(
String.format("http://localhost:%d/apps/%s/appnamespaces", port, appId),dto, AppNamespaceDTO.class).getBody(); String.format("http://localhost:%d/apps/%s/appnamespaces", port, appId),dto, AppNamespaceDTO.class).getBody();
......
...@@ -82,7 +82,7 @@ public class ControllerExceptionTest { ...@@ -82,7 +82,7 @@ public class ControllerExceptionTest {
when(adminService.createNewApp(any(App.class))) when(adminService.createNewApp(any(App.class)))
.thenThrow(new ServiceException("create app failed")); .thenThrow(new ServiceException("create app failed"));
appController.createOrUpdate(dto); appController.create(dto);
} }
private AppDTO generateSampleDTOData() { private AppDTO generateSampleDTOData() {
......
...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.common.entity.BaseEntity; ...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.common.entity.BaseEntity;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table; import javax.persistence.Table;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
...@@ -22,6 +23,7 @@ public class Item extends BaseEntity { ...@@ -22,6 +23,7 @@ public class Item extends BaseEntity {
private String key; private String key;
@Column(name = "value") @Column(name = "value")
@Lob
private String value; private String value;
@Column(name = "comment") @Column(name = "comment")
......
...@@ -10,8 +10,8 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa ...@@ -10,8 +10,8 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
AppNamespace findByAppIdAndName(String appId, String namespaceName); AppNamespace findByAppIdAndName(String appId, String namespaceName);
AppNamespace findByName(String namespaceName); AppNamespace findByNameAndIsPublicTrue(String namespaceName);
List<AppNamespace> findByNameNot(String namespaceName); List<AppNamespace> findByAppIdAndIsPublic(String appId, boolean isPublic);
} }
...@@ -33,7 +33,7 @@ public class AdminService { ...@@ -33,7 +33,7 @@ public class AdminService {
clusterService.createDefaultCluster(appId, createBy); clusterService.createDefaultCluster(appId, createBy);
namespaceService.createDefaultNamespace(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy); namespaceService.createPrivateNamespace(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
return app; return app;
} }
......
...@@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository; import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository;
...@@ -22,7 +24,10 @@ public class AppNamespaceService { ...@@ -22,7 +24,10 @@ public class AppNamespaceService {
@Autowired @Autowired
private AppNamespaceRepository appNamespaceRepository; private AppNamespaceRepository appNamespaceRepository;
@Autowired
private NamespaceService namespaceService;
@Autowired
private ClusterService clusterService;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
...@@ -32,9 +37,13 @@ public class AppNamespaceService { ...@@ -32,9 +37,13 @@ public class AppNamespaceService {
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName)); return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
} }
public AppNamespace findByNamespaceName(String namespaceName) { public AppNamespace findPublicNamespaceByName(String namespaceName) {
Preconditions.checkArgument(namespaceName != null, "Namespace must not be null"); Preconditions.checkArgument(namespaceName != null, "Namespace must not be null");
return appNamespaceRepository.findByName(namespaceName); return appNamespaceRepository.findByNameAndIsPublicTrue(namespaceName);
}
public List<AppNamespace> findPrivateAppNamespace(String appId){
return appNamespaceRepository.findByAppIdAndIsPublic(appId, false);
} }
public AppNamespace findOne(String appId, String namespaceName){ public AppNamespace findOne(String appId, String namespaceName){
...@@ -68,16 +77,28 @@ public class AppNamespaceService { ...@@ -68,16 +77,28 @@ public class AppNamespaceService {
appNamespace.setDataChangeCreatedBy(createBy); appNamespace.setDataChangeCreatedBy(createBy);
appNamespace.setDataChangeLastModifiedBy(createBy); appNamespace.setDataChangeLastModifiedBy(createBy);
appNamespace = appNamespaceRepository.save(appNamespace); appNamespace = appNamespaceRepository.save(appNamespace);
//所有的cluster下面link新建的appnamespace
if (!appNamespace.isPublic()){
String appId = appNamespace.getAppId();
String namespaceName = appNamespace.getName();
List<Cluster> clusters = clusterService.findClusters(appId);
for (Cluster cluster: clusters){
Namespace namespace = new Namespace();
namespace.setClusterName(cluster.getName());
namespace.setAppId(appId);
namespace.setNamespaceName(namespaceName);
namespace.setDataChangeCreatedBy(createBy);
namespace.setDataChangeLastModifiedBy(createBy);
namespaceService.save(namespace);
}
}
auditService.audit(AppNamespace.class.getSimpleName(), appNamespace.getId(), Audit.OP.INSERT, auditService.audit(AppNamespace.class.getSimpleName(), appNamespace.getId(), Audit.OP.INSERT,
createBy); createBy);
return appNamespace; return appNamespace;
} }
public List<AppNamespace> findPublicAppNamespaces(){
return appNamespaceRepository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION);
}
public AppNamespace update(AppNamespace appNamespace){ public AppNamespace update(AppNamespace appNamespace){
AppNamespace managedNs = appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName()); AppNamespace managedNs = appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName());
BeanUtils.copyEntityProperties(appNamespace, managedNs); BeanUtils.copyEntityProperties(appNamespace, managedNs);
......
...@@ -22,12 +22,13 @@ public class ClusterService { ...@@ -22,12 +22,13 @@ public class ClusterService {
@Autowired @Autowired
private ClusterRepository clusterRepository; private ClusterRepository clusterRepository;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired @Autowired
private NamespaceService namespaceService; private NamespaceService namespaceService;
@Autowired
private AppNamespaceService appNamespaceService;
public boolean isClusterNameUnique(String appId, String clusterName) { public boolean isClusterNameUnique(String appId, String clusterName) {
Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(appId, "AppId must not be null");
...@@ -59,7 +60,7 @@ public class ClusterService { ...@@ -59,7 +60,7 @@ public class ClusterService {
entity.setId(0);//protection entity.setId(0);//protection
Cluster cluster = clusterRepository.save(entity); Cluster cluster = clusterRepository.save(entity);
namespaceService.createDefaultNamespace(cluster.getAppId(), cluster.getName(), cluster.getDataChangeCreatedBy()); namespaceService.createPrivateNamespace(cluster.getAppId(), cluster.getName(), cluster.getDataChangeCreatedBy());
auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT, auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT,
cluster.getDataChangeCreatedBy()); cluster.getDataChangeCreatedBy());
......
...@@ -7,6 +7,7 @@ import com.ctrip.framework.apollo.biz.repository.ItemRepository; ...@@ -7,6 +7,7 @@ import com.ctrip.framework.apollo.biz.repository.ItemRepository;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository; import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.exception.NotFoundException; import com.ctrip.framework.apollo.core.exception.NotFoundException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -27,18 +28,22 @@ public class ItemService { ...@@ -27,18 +28,22 @@ public class ItemService {
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired
private ServerConfigService serverConfigService;
@Transactional @Transactional
public void delete(long id, String operator) { public Item delete(long id, String operator) {
Item item = itemRepository.findOne(id); Item item = itemRepository.findOne(id);
if (item == null) { if (item == null) {
return; throw new IllegalArgumentException("item not exist. ID:" + id);
} }
item.setDeleted(true); item.setDeleted(true);
item.setDataChangeLastModifiedBy(operator); item.setDataChangeLastModifiedBy(operator);
itemRepository.save(item); Item deletedItem = itemRepository.save(item);
auditService.audit(Item.class.getSimpleName(), id, Audit.OP.DELETE, operator); auditService.audit(Item.class.getSimpleName(), id, Audit.OP.DELETE, operator);
return deletedItem;
} }
public Item findOne(String appId, String clusterName, String namespaceName, String key) { public Item findOne(String appId, String clusterName, String namespaceName, String key) {
...@@ -88,6 +93,8 @@ public class ItemService { ...@@ -88,6 +93,8 @@ public class ItemService {
@Transactional @Transactional
public Item save(Item entity) { public Item save(Item entity) {
checkItemValueLength(entity.getValue());
entity.setId(0);//protection entity.setId(0);//protection
Item item = itemRepository.save(entity); Item item = itemRepository.save(entity);
...@@ -99,6 +106,7 @@ public class ItemService { ...@@ -99,6 +106,7 @@ public class ItemService {
@Transactional @Transactional
public Item update(Item item) { public Item update(Item item) {
checkItemValueLength(item.getValue());
Item managedItem = itemRepository.findOne(item.getId()); Item managedItem = itemRepository.findOne(item.getId());
BeanUtils.copyEntityProperties(item, managedItem); BeanUtils.copyEntityProperties(item, managedItem);
managedItem = itemRepository.save(managedItem); managedItem = itemRepository.save(managedItem);
...@@ -109,4 +117,12 @@ public class ItemService { ...@@ -109,4 +117,12 @@ public class ItemService {
return managedItem; return managedItem;
} }
private boolean checkItemValueLength(String value){
int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.value.length.limit", "20000"));
if (!StringUtils.isEmpty(value) && value.length() > lengthLimit){
throw new IllegalArgumentException("value too long. length limit:" + lengthLimit);
}
return true;
}
} }
...@@ -8,7 +8,6 @@ import org.springframework.util.CollectionUtils; ...@@ -8,7 +8,6 @@ import org.springframework.util.CollectionUtils;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Commit; import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.repository.ItemRepository;
import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder; import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets; import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
...@@ -19,15 +18,15 @@ import com.ctrip.framework.apollo.core.utils.StringUtils; ...@@ -19,15 +18,15 @@ import com.ctrip.framework.apollo.core.utils.StringUtils;
@Service @Service
public class ItemSetService { public class ItemSetService {
@Autowired
private ItemRepository itemRepository;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired @Autowired
private CommitService commitService; private CommitService commitService;
@Autowired
private ItemService itemService;
@Transactional @Transactional
public ItemChangeSets updateSet(String appId, String clusterName, public ItemChangeSets updateSet(String appId, String clusterName,
...@@ -38,10 +37,9 @@ public class ItemSetService { ...@@ -38,10 +37,9 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) { if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) {
for (ItemDTO item : changeSet.getCreateItems()) { for (ItemDTO item : changeSet.getCreateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
entity.setId(0);//protection
entity.setDataChangeCreatedBy(operator); entity.setDataChangeCreatedBy(operator);
entity.setDataChangeLastModifiedBy(operator); entity.setDataChangeLastModifiedBy(operator);
Item createdItem = itemRepository.save(entity); Item createdItem = itemService.save(entity);
configChangeContentBuilder.createItem(createdItem); configChangeContentBuilder.createItem(createdItem);
} }
auditService.audit("ItemSet", null, Audit.OP.INSERT, operator); auditService.audit("ItemSet", null, Audit.OP.INSERT, operator);
...@@ -50,11 +48,11 @@ public class ItemSetService { ...@@ -50,11 +48,11 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getUpdateItems())) { if (!CollectionUtils.isEmpty(changeSet.getUpdateItems())) {
for (ItemDTO item : changeSet.getUpdateItems()) { for (ItemDTO item : changeSet.getUpdateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
Item managedItem = itemRepository.findOne(entity.getId());
Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedItem); Item beforeUpdateItem = itemService.findOne(entity.getId());
BeanUtils.copyEntityProperties(entity, managedItem);
managedItem.setDataChangeLastModifiedBy(operator); entity.setDataChangeLastModifiedBy(operator);
Item updatedItem = itemRepository.save(managedItem); Item updatedItem = itemService.update(entity);
configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem); configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem);
} }
...@@ -63,25 +61,24 @@ public class ItemSetService { ...@@ -63,25 +61,24 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getDeleteItems())) { if (!CollectionUtils.isEmpty(changeSet.getDeleteItems())) {
for (ItemDTO item : changeSet.getDeleteItems()) { for (ItemDTO item : changeSet.getDeleteItems()) {
Item entity = BeanUtils.transfrom(Item.class, item); Item deletedItem = itemService.delete(item.getId(), operator);
entity.setDeleted(true);
entity.setDataChangeLastModifiedBy(operator);
Item deletedItem = itemRepository.save(entity);
configChangeContentBuilder.deleteItem(deletedItem); configChangeContentBuilder.deleteItem(deletedItem);
} }
auditService.audit("ItemSet", null, Audit.OP.DELETE, operator); auditService.audit("ItemSet", null, Audit.OP.DELETE, operator);
} }
String configChangeContent = configChangeContentBuilder.build(); String configChangeContent = configChangeContentBuilder.build();
if (!StringUtils.isEmpty(configChangeContent)){ if (!StringUtils.isEmpty(configChangeContent)) {
createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(), changeSet.getDataChangeLastModifiedBy()); createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(),
changeSet.getDataChangeLastModifiedBy());
} }
return changeSet; return changeSet;
} }
private void createCommit(String appId, String clusterName, String namespaceName, String configChangeContent, String operator){ private void createCommit(String appId, String clusterName, String namespaceName, String configChangeContent,
String operator) {
Commit commit = new Commit(); Commit commit = new Commit();
commit.setAppId(appId); commit.setAppId(appId);
......
...@@ -5,6 +5,7 @@ import com.ctrip.framework.apollo.biz.repository.NamespaceLockRepository; ...@@ -5,6 +5,7 @@ import com.ctrip.framework.apollo.biz.repository.NamespaceLockRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
public class NamespaceLockService { public class NamespaceLockService {
...@@ -17,10 +18,12 @@ public class NamespaceLockService { ...@@ -17,10 +18,12 @@ public class NamespaceLockService {
return namespaceLockRepository.findByNamespaceId(namespaceId); return namespaceLockRepository.findByNamespaceId(namespaceId);
} }
@Transactional
public NamespaceLock tryLock(NamespaceLock lock){ public NamespaceLock tryLock(NamespaceLock lock){
return namespaceLockRepository.save(lock); return namespaceLockRepository.save(lock);
} }
@Transactional
public void unlock(Long namespaceId){ public void unlock(Long namespaceId){
namespaceLockRepository.deleteByNamespaceId(namespaceId); namespaceLockRepository.deleteByNamespaceId(namespaceId);
} }
......
...@@ -11,8 +11,8 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -11,8 +11,8 @@ import org.springframework.transaction.annotation.Transactional;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository; import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.exception.ServiceException; import com.ctrip.framework.apollo.core.exception.ServiceException;
@Service @Service
...@@ -20,9 +20,10 @@ public class NamespaceService { ...@@ -20,9 +20,10 @@ public class NamespaceService {
@Autowired @Autowired
private NamespaceRepository namespaceRepository; private NamespaceRepository namespaceRepository;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired
private AppNamespaceService appNamespaceService;
public boolean isNamespaceUnique(String appId, String cluster, String namespace) { public boolean isNamespaceUnique(String appId, String cluster, String namespace) {
Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(appId, "AppId must not be null");
...@@ -91,19 +92,21 @@ public class NamespaceService { ...@@ -91,19 +92,21 @@ public class NamespaceService {
} }
@Transactional @Transactional
public void createDefaultNamespace(String appId, String clusterName, String createBy) { public void createPrivateNamespace(String appId, String clusterName, String createBy) {
if (!isNamespaceUnique(appId, clusterName, appId)) {
throw new ServiceException("namespace not unique"); //load all private app namespace
List<AppNamespace> privateAppNamespaces = appNamespaceService.findPrivateAppNamespace(appId);
//create all private namespace
for (AppNamespace appNamespace: privateAppNamespaces){
Namespace ns = new Namespace();
ns.setAppId(appId);
ns.setClusterName(clusterName);
ns.setNamespaceName(appNamespace.getName());
ns.setDataChangeCreatedBy(createBy);
ns.setDataChangeLastModifiedBy(createBy);
namespaceRepository.save(ns);
auditService.audit(Namespace.class.getSimpleName(), ns.getId(), Audit.OP.INSERT, createBy);
} }
Namespace ns = new Namespace();
ns.setAppId(appId);
ns.setClusterName(clusterName);
ns.setNamespaceName(ConfigConsts.NAMESPACE_APPLICATION);
ns.setDataChangeCreatedBy(createBy);
ns.setDataChangeLastModifiedBy(createBy);
namespaceRepository.save(ns);
auditService.audit(Namespace.class.getSimpleName(), ns.getId(), Audit.OP.INSERT, createBy);
} }
} }
...@@ -2,9 +2,7 @@ package com.ctrip.framework.apollo.biz.repository; ...@@ -2,9 +2,7 @@ package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.BizTestConfiguration; import com.ctrip.framework.apollo.biz.BizTestConfiguration;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -13,7 +11,8 @@ import org.springframework.test.annotation.Rollback; ...@@ -13,7 +11,8 @@ import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BizTestConfiguration.class) @SpringApplicationConfiguration(classes = BizTestConfiguration.class)
...@@ -25,9 +24,16 @@ public class AppNamespaceRepositoryTest { ...@@ -25,9 +24,16 @@ public class AppNamespaceRepositoryTest {
private AppNamespaceRepository repository; private AppNamespaceRepository repository;
@Test @Test
public void testFindAllPublicAppNamespaces(){ public void testFindByNameAndIsPublicTrue() throws Exception {
List<AppNamespace> appNamespaceList = repository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION); AppNamespace appNamespace = repository.findByNameAndIsPublicTrue("fx.apollo.config");
Assert.assertEquals(4, appNamespaceList.size());
assertEquals("100003171", appNamespace.getAppId());
} }
@Test
public void testFindByNameAndNoPublicNamespace() throws Exception {
AppNamespace appNamespace = repository.findByNameAndIsPublicTrue("application");
assertNull(appNamespace);
}
} }
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003171', 'application'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'application', false);
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003171', 'fx.apollo.config'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'fx.apollo.config', true);
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'application'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'application', false);
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'fx.apollo.admin'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'fx.apollo.admin', true);
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'application'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003173', 'application', false);
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'fx.apollo.portal'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003173', 'fx.apollo.portal', true);
INSERT INTO AppNamespace (AppID, Name) VALUES ('fxhermesproducer', 'fx.hermes.producer'); INSERT INTO AppNamespace (AppID, Name, IsPublic) VALUES ('fxhermesproducer', 'fx.hermes.producer', true);
package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ConfigFile {
/**
* Get file content of the namespace
* @return file content, {@code null} if there is no content
*/
String getContent();
/**
* Whether the config file has any content
* @return true if it has content, false otherwise.
*/
boolean hasContent();
/**
* Get the namespace of this config file instance
* @return the namespace
*/
String getNamespace();
/**
* Get the file format of this config file instance
* @return the config file format enum
*/
ConfigFileFormat getConfigFileFormat();
}
package com.ctrip.framework.apollo; package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.internals.ConfigManager; import com.ctrip.framework.apollo.internals.ConfigManager;
import com.ctrip.framework.apollo.spi.ConfigFactory; import com.ctrip.framework.apollo.spi.ConfigFactory;
...@@ -38,10 +39,13 @@ public class ConfigService { ...@@ -38,10 +39,13 @@ public class ConfigService {
* @return config instance * @return config instance
*/ */
public static Config getConfig(String namespace) { public static Config getConfig(String namespace) {
return getManager().getConfig(namespace); return getManager().getConfig(namespace);
} }
public static ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return getManager().getConfigFile(namespace, configFileFormat);
}
private static ConfigManager getManager() { private static ConfigManager getManager() {
try { try {
return s_instance.m_container.lookup(ConfigManager.class); return s_instance.m_container.lookup(ConfigManager.class);
...@@ -77,6 +81,12 @@ public class ConfigService { ...@@ -77,6 +81,12 @@ public class ConfigService {
public Config create(String namespace) { public Config create(String namespace) {
return config; return config;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}); });
} }
......
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.dianping.cat.Cat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public abstract class AbstractConfigFile implements ConfigFile, RepositoryChangeListener {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigFile.class);
protected ConfigRepository m_configRepository;
protected String m_namespace;
protected AtomicReference<Properties> m_configProperties;
public AbstractConfigFile(String namespace, ConfigRepository configRepository) {
m_configRepository = configRepository;
m_namespace = namespace;
m_configProperties = new AtomicReference<>();
initialize();
}
private void initialize() {
try {
m_configProperties.set(m_configRepository.getConfig());
} catch (Throwable ex) {
Cat.logError(ex);
logger.warn("Init Apollo Config File failed - namespace: {}, reason: {}.",
m_namespace, ExceptionUtil.getDetailMessage(ex));
} finally {
//register the change listener no matter config repository is working or not
//so that whenever config repository is recovered, config could get changed
m_configRepository.addChangeListener(this);
}
}
@Override
public String getNamespace() {
return m_namespace;
}
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties.get())) {
return;
}
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
m_configProperties.set(newConfigProperties);
Cat.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}
}
package com.ctrip.framework.apollo.internals; package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
...@@ -12,4 +14,12 @@ public interface ConfigManager { ...@@ -12,4 +14,12 @@ public interface ConfigManager {
* @return the config instance for the namespace * @return the config instance for the namespace
*/ */
public Config getConfig(String namespace); public Config getConfig(String namespace);
/**
* Get the config file instance for the namespace specified.
* @param namespace the namespace
* @param configFileFormat the config file format
* @return the config file instance for the namespace
*/
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat);
} }
...@@ -3,6 +3,8 @@ package com.ctrip.framework.apollo.internals; ...@@ -3,6 +3,8 @@ package com.ctrip.framework.apollo.internals;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.spi.ConfigFactory; import com.ctrip.framework.apollo.spi.ConfigFactory;
import com.ctrip.framework.apollo.spi.ConfigFactoryManager; import com.ctrip.framework.apollo.spi.ConfigFactoryManager;
...@@ -20,6 +22,7 @@ public class DefaultConfigManager implements ConfigManager { ...@@ -20,6 +22,7 @@ public class DefaultConfigManager implements ConfigManager {
private ConfigFactoryManager m_factoryManager; private ConfigFactoryManager m_factoryManager;
private Map<String, Config> m_configs = Maps.newConcurrentMap(); private Map<String, Config> m_configs = Maps.newConcurrentMap();
private Map<String, ConfigFile> m_configFiles = Maps.newConcurrentMap();
@Override @Override
public Config getConfig(String namespace) { public Config getConfig(String namespace) {
...@@ -40,4 +43,25 @@ public class DefaultConfigManager implements ConfigManager { ...@@ -40,4 +43,25 @@ public class DefaultConfigManager implements ConfigManager {
return config; return config;
} }
@Override
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
String namespaceFileName = String.format("%s.%s", namespace, configFileFormat.getValue());
ConfigFile configFile = m_configFiles.get(namespaceFileName);
if (configFile == null) {
synchronized (this) {
configFile = m_configFiles.get(namespaceFileName);
if (configFile == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespaceFileName);
configFile = factory.createConfigFile(namespaceFileName, configFileFormat);
m_configFiles.put(namespaceFileName, configFile);
}
}
}
return configFile;
}
} }
...@@ -70,6 +70,9 @@ public class LocalFileConfigRepository extends AbstractConfigRepository ...@@ -70,6 +70,9 @@ public class LocalFileConfigRepository extends AbstractConfigRepository
try { try {
String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir(); String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir();
Path path = Paths.get(defaultCacheDir); Path path = Paths.get(defaultCacheDir);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
if (Files.exists(path) && Files.isWritable(path)) { if (Files.exists(path) && Files.isWritable(path)) {
return new File(defaultCacheDir, CONFIG_DIR); return new File(defaultCacheDir, CONFIG_DIR);
} }
......
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.dianping.cat.Cat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertiesConfigFile extends AbstractConfigFile {
private static final Logger logger = LoggerFactory.getLogger(PropertiesConfigFile.class);
protected AtomicReference<String> m_contentCache;
public PropertiesConfigFile(String namespace,
ConfigRepository configRepository) {
super(namespace, configRepository);
m_contentCache = new AtomicReference<>();
}
@Override
public String getContent() {
if (m_contentCache.get() == null) {
m_contentCache.set(doGetContent());
}
return m_contentCache.get();
}
String doGetContent() {
if (m_configProperties.get() == null) {
return null;
}
StringWriter writer = new StringWriter();
try {
m_configProperties.get().store(writer, null);
return writer.getBuffer().toString();
} catch (IOException ex) {
ApolloConfigException exception =
new ApolloConfigException(String
.format("Parse properties file content failed for namespace: %s, cause: %s", m_namespace,
ExceptionUtil.getDetailMessage(ex)));
Cat.logError(exception);
throw exception;
}
}
@Override
public boolean hasContent() {
return m_configProperties.get() != null && !m_configProperties.get().isEmpty();
}
@Override
public ConfigFileFormat getConfigFileFormat() {
return ConfigFileFormat.Properties;
}
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
super.onRepositoryChange(namespace, newProperties);
m_contentCache.set(null);
}
}
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class XmlConfigFile extends AbstractConfigFile {
public XmlConfigFile(String namespace,
ConfigRepository configRepository) {
super(namespace, configRepository);
}
@Override
public String getContent() {
if (m_configProperties.get() == null) {
return null;
}
return m_configProperties.get().getProperty(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
}
@Override
public boolean hasContent() {
if (m_configProperties.get() == null) {
return false;
}
return m_configProperties.get().containsKey(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
}
@Override
public ConfigFileFormat getConfigFileFormat() {
return ConfigFileFormat.XML;
}
}
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
...@@ -13,4 +15,11 @@ public interface ConfigFactory { ...@@ -13,4 +15,11 @@ public interface ConfigFactory {
* @return the newly created config instance * @return the newly created config instance
*/ */
public Config create(String namespace); public Config create(String namespace);
/**
* Create the config file instance for the namespace
* @param namespace the namespace
* @return the newly created config file instance
*/
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat);
} }
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.internals.DefaultConfig; import com.ctrip.framework.apollo.internals.DefaultConfig;
import com.ctrip.framework.apollo.internals.LocalFileConfigRepository; import com.ctrip.framework.apollo.internals.LocalFileConfigRepository;
import com.ctrip.framework.apollo.internals.PropertiesConfigFile;
import com.ctrip.framework.apollo.internals.RemoteConfigRepository; import com.ctrip.framework.apollo.internals.RemoteConfigRepository;
import com.ctrip.framework.apollo.internals.XmlConfigFile;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -23,6 +28,19 @@ public class DefaultConfigFactory implements ConfigFactory { ...@@ -23,6 +28,19 @@ public class DefaultConfigFactory implements ConfigFactory {
return defaultConfig; return defaultConfig;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
ConfigRepository configRepository = createLocalConfigRepository(namespace);
switch (configFileFormat) {
case Properties:
return new PropertiesConfigFile(namespace, configRepository);
case XML:
return new XmlConfigFile(namespace, configRepository);
}
return null;
}
LocalFileConfigRepository createLocalConfigRepository(String namespace) { LocalFileConfigRepository createLocalConfigRepository(String namespace) {
LocalFileConfigRepository localFileConfigRepository = LocalFileConfigRepository localFileConfigRepository =
new LocalFileConfigRepository(namespace); new LocalFileConfigRepository(namespace);
......
...@@ -5,8 +5,10 @@ import com.ctrip.framework.apollo.integration.ConfigIntegrationTest; ...@@ -5,8 +5,10 @@ import com.ctrip.framework.apollo.integration.ConfigIntegrationTest;
import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest; import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest;
import com.ctrip.framework.apollo.internals.DefaultConfigTest; import com.ctrip.framework.apollo.internals.DefaultConfigTest;
import com.ctrip.framework.apollo.internals.LocalFileConfigRepositoryTest; import com.ctrip.framework.apollo.internals.LocalFileConfigRepositoryTest;
import com.ctrip.framework.apollo.internals.PropertiesConfigFileTest;
import com.ctrip.framework.apollo.internals.RemoteConfigRepositoryTest; import com.ctrip.framework.apollo.internals.RemoteConfigRepositoryTest;
import com.ctrip.framework.apollo.internals.SimpleConfigTest; import com.ctrip.framework.apollo.internals.SimpleConfigTest;
import com.ctrip.framework.apollo.internals.XmlConfigFileTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest; import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest;
...@@ -21,7 +23,7 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -21,7 +23,7 @@ import org.junit.runners.Suite.SuiteClasses;
ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class, ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class,
DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class, DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class,
RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class, RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class,
ConfigIntegrationTest.class, ExceptionUtilTest.class ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class
}) })
public class AllTests { public class AllTests {
......
...@@ -176,6 +176,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase { ...@@ -176,6 +176,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
public int getLongPollQPS() { public int getLongPollQPS() {
return 200; return 200;
} }
@Override
public String getDefaultLocalCacheDir() {
return ClassLoaderUtil.getClassPath();
}
} }
/** /**
......
package com.ctrip.framework.apollo; package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.internals.AbstractConfig; import com.ctrip.framework.apollo.internals.AbstractConfig;
import com.ctrip.framework.apollo.spi.ConfigFactory; import com.ctrip.framework.apollo.spi.ConfigFactory;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
...@@ -62,6 +63,20 @@ public class ConfigServiceTest extends ComponentTestCase { ...@@ -62,6 +63,20 @@ public class ConfigServiceTest extends ComponentTestCase {
assertEquals(null, config.getProperty("unknown", null)); assertEquals(null, config.getProperty("unknown", null));
} }
@Test
public void testMockConfigFactoryForConfigFile() throws Exception {
String someNamespace = "mock";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
String someNamespaceFileName =
String.format("%s.%s", someNamespace, someConfigFileFormat.getValue());
defineComponent(ConfigFactory.class, someNamespaceFileName, MockConfigFactory.class);
ConfigFile configFile = ConfigService.getConfigFile(someNamespace, someConfigFileFormat);
assertEquals(someNamespaceFileName, configFile.getNamespace());
assertEquals(someNamespaceFileName + ":" + someConfigFileFormat.getValue(), configFile.getContent());
}
private static class MockConfig extends AbstractConfig { private static class MockConfig extends AbstractConfig {
private final String m_namespace; private final String m_namespace;
...@@ -79,11 +94,47 @@ public class ConfigServiceTest extends ComponentTestCase { ...@@ -79,11 +94,47 @@ public class ConfigServiceTest extends ComponentTestCase {
} }
} }
private static class MockConfigFile implements ConfigFile {
private ConfigFileFormat m_configFileFormat;
private String m_namespace;
public MockConfigFile(String namespace,
ConfigFileFormat configFileFormat) {
m_namespace = namespace;
m_configFileFormat = configFileFormat;
}
@Override
public String getContent() {
return m_namespace + ":" + m_configFileFormat.getValue();
}
@Override
public boolean hasContent() {
return true;
}
@Override
public String getNamespace() {
return m_namespace;
}
@Override
public ConfigFileFormat getConfigFileFormat() {
return m_configFileFormat;
}
}
public static class MockConfigFactory implements ConfigFactory { public static class MockConfigFactory implements ConfigFactory {
@Override @Override
public Config create(String namespace) { public Config create(String namespace) {
return new MockConfig(namespace); return new MockConfig(namespace);
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return new MockConfigFile(namespace, configFileFormat);
}
} }
public static class MockConfigUtil extends ConfigUtil { public static class MockConfigUtil extends ConfigUtil {
......
...@@ -57,6 +57,9 @@ public class ConfigIntegrationTest extends BaseIntegrationTest { ...@@ -57,6 +57,9 @@ public class ConfigIntegrationTest extends BaseIntegrationTest {
defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION; defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION;
someReleaseKey = "1"; someReleaseKey = "1";
configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache"); configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache");
if (configDir.exists()) {
configDir.delete();
}
configDir.mkdirs(); configDir.mkdirs();
} }
......
package com.ctrip.framework.apollo.internals; package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.spi.ConfigFactory; import com.ctrip.framework.apollo.spi.ConfigFactory;
import com.ctrip.framework.apollo.spi.ConfigFactoryManager; import com.ctrip.framework.apollo.spi.ConfigFactoryManager;
import com.ctrip.framework.apollo.spi.ConfigRegistry;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -11,18 +14,21 @@ import org.unidal.lookup.ComponentTestCase; ...@@ -11,18 +14,21 @@ import org.unidal.lookup.ComponentTestCase;
import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class DefaultConfigManagerTest extends ComponentTestCase { public class DefaultConfigManagerTest extends ComponentTestCase {
private DefaultConfigManager defaultConfigManager; private DefaultConfigManager defaultConfigManager;
private static String someConfigContent;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
defineComponent(ConfigFactoryManager.class, MockConfigManager.class); defineComponent(ConfigFactoryManager.class, MockConfigManager.class);
defaultConfigManager = (DefaultConfigManager) lookup(ConfigManager.class); defaultConfigManager = (DefaultConfigManager) lookup(ConfigManager.class);
someConfigContent = "someContent";
} }
@Test @Test
...@@ -48,6 +54,34 @@ public class DefaultConfigManagerTest extends ComponentTestCase { ...@@ -48,6 +54,34 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
config, equalTo(anotherConfig)); config, equalTo(anotherConfig));
} }
@Test
public void testGetConfigFile() throws Exception {
String someNamespace = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
ConfigFile configFile =
defaultConfigManager.getConfigFile(someNamespace, someConfigFileFormat);
assertEquals(someConfigFileFormat, configFile.getConfigFileFormat());
assertEquals(someConfigContent, configFile.getContent());
}
@Test
public void testGetConfigFileMultipleTimesWithSameNamespace() throws Exception {
String someNamespace = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
ConfigFile someConfigFile =
defaultConfigManager.getConfigFile(someNamespace, someConfigFileFormat);
ConfigFile anotherConfigFile =
defaultConfigManager.getConfigFile(someNamespace, someConfigFileFormat);
assertThat(
"Get config file multiple times with the same namespace should return the same config file instance",
someConfigFile, equalTo(anotherConfigFile));
}
public static class MockConfigManager implements ConfigFactoryManager { public static class MockConfigManager implements ConfigFactoryManager {
@Override @Override
...@@ -62,6 +96,28 @@ public class DefaultConfigManagerTest extends ComponentTestCase { ...@@ -62,6 +96,28 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
} }
}; };
} }
@Override
public ConfigFile createConfigFile(String namespace, final ConfigFileFormat configFileFormat) {
ConfigRepository someConfigRepository = mock(ConfigRepository.class);
return new AbstractConfigFile(namespace, someConfigRepository) {
@Override
public String getContent() {
return someConfigContent;
}
@Override
public boolean hasContent() {
return true;
}
@Override
public ConfigFileFormat getConfigFileFormat() {
return configFileFormat;
}
};
}
}; };
} }
} }
......
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class PropertiesConfigFileTest {
private String someNamespace;
@Mock
private ConfigRepository configRepository;
@Before
public void setUp() throws Exception {
someNamespace = "someName";
}
@Test
public void testWhenHasContent() throws Exception {
Properties someProperties = new Properties();
String someKey = "someKey";
String someValue = "someValue";
someProperties.setProperty(someKey, someValue);
when(configRepository.getConfig()).thenReturn(someProperties);
PropertiesConfigFile configFile = new PropertiesConfigFile(someNamespace, configRepository);
assertEquals(ConfigFileFormat.Properties, configFile.getConfigFileFormat());
assertEquals(someNamespace, configFile.getNamespace());
assertTrue(configFile.hasContent());
assertTrue(configFile.getContent().contains(String.format("%s=%s", someKey, someValue)));
}
@Test
public void testWhenHasNoContent() throws Exception {
when(configRepository.getConfig()).thenReturn(null);
PropertiesConfigFile configFile = new PropertiesConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
}
@Test
public void testWhenConfigRepositoryHasError() throws Exception {
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
PropertiesConfigFile configFile = new PropertiesConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
}
@Test
public void testOnRepositoryChange() throws Exception {
Properties someProperties = new Properties();
String someKey = "someKey";
String someValue = "someValue";
String anotherValue = "anotherValue";
someProperties.setProperty(someKey, someValue);
when(configRepository.getConfig()).thenReturn(someProperties);
PropertiesConfigFile configFile = new PropertiesConfigFile(someNamespace, configRepository);
assertTrue(configFile.getContent().contains(String.format("%s=%s", someKey, someValue)));
Properties anotherProperties = new Properties();
anotherProperties.setProperty(someKey, anotherValue);
configFile.onRepositoryChange(someNamespace, anotherProperties);
assertFalse(configFile.getContent().contains(String.format("%s=%s", someKey, someValue)));
assertTrue(configFile.getContent().contains(String.format("%s=%s", someKey, anotherValue)));
}
@Test
public void testWhenConfigRepositoryHasErrorAndThenRecovered() throws Exception {
Properties someProperties = new Properties();
String someKey = "someKey";
String someValue = "someValue";
someProperties.setProperty(someKey, someValue);
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
PropertiesConfigFile configFile = new PropertiesConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
configFile.onRepositoryChange(someNamespace, someProperties);
assertTrue(configFile.hasContent());
assertTrue(configFile.getContent().contains(String.format("%s=%s", someKey, someValue)));
}
}
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Properties;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class XmlConfigFileTest {
private String someNamespace;
@Mock
private ConfigRepository configRepository;
@Before
public void setUp() throws Exception {
someNamespace = "someName";
}
@Test
public void testWhenHasContent() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someValue";
someProperties.setProperty(key, someValue);
when(configRepository.getConfig()).thenReturn(someProperties);
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertEquals(ConfigFileFormat.XML, configFile.getConfigFileFormat());
assertEquals(someNamespace, configFile.getNamespace());
assertTrue(configFile.hasContent());
assertEquals(someValue, configFile.getContent());
}
@Test
public void testWhenHasNoContent() throws Exception {
when(configRepository.getConfig()).thenReturn(null);
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
}
@Test
public void testWhenConfigRepositoryHasError() throws Exception {
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
}
@Test
public void testOnRepositoryChange() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someValue";
String anotherValue = "anotherValue";
someProperties.setProperty(key, someValue);
when(configRepository.getConfig()).thenReturn(someProperties);
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertEquals(someValue, configFile.getContent());
Properties anotherProperties = new Properties();
anotherProperties.setProperty(key, anotherValue);
configFile.onRepositoryChange(someNamespace, anotherProperties);
assertEquals(anotherValue, configFile.getContent());
}
@Test
public void testWhenConfigRepositoryHasErrorAndThenRecovered() throws Exception {
Properties someProperties = new Properties();
String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY;
String someValue = "someValue";
someProperties.setProperty(key, someValue);
when(configRepository.getConfig()).thenThrow(new RuntimeException("someError"));
XmlConfigFile configFile = new XmlConfigFile(someNamespace, configRepository);
assertFalse(configFile.hasContent());
assertNull(configFile.getContent());
configFile.onRepositoryChange(someNamespace, someProperties);
assertTrue(configFile.hasContent());
assertEquals(someValue, configFile.getContent());
}
}
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -75,6 +77,12 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase { ...@@ -75,6 +77,12 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) { public Config create(String namespace) {
return null; return null;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}; };
@Override @Override
...@@ -96,6 +104,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase { ...@@ -96,6 +104,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) { public Config create(String namespace) {
return null; return null;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
} }
public static class AnotherConfigFactory implements ConfigFactory { public static class AnotherConfigFactory implements ConfigFactory {
...@@ -103,6 +116,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase { ...@@ -103,6 +116,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) { public Config create(String namespace) {
return null; return null;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
} }
} }
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.internals.DefaultConfig; import com.ctrip.framework.apollo.internals.DefaultConfig;
import com.ctrip.framework.apollo.internals.LocalFileConfigRepository; import com.ctrip.framework.apollo.internals.LocalFileConfigRepository;
import com.ctrip.framework.apollo.internals.PropertiesConfigFile;
import com.ctrip.framework.apollo.internals.XmlConfigFile;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -14,6 +18,7 @@ import static org.hamcrest.core.Is.is; ...@@ -14,6 +18,7 @@ import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
...@@ -51,4 +56,30 @@ public class DefaultConfigFactoryTest extends ComponentTestCase { ...@@ -51,4 +56,30 @@ public class DefaultConfigFactoryTest extends ComponentTestCase {
assertEquals(someValue, result.getProperty(someKey, null)); assertEquals(someValue, result.getProperty(someKey, null));
} }
@Test
public void testCreateConfigFile() throws Exception {
String someNamespace = "someName";
String anotherNamespace = "anotherName";
Properties someProperties = new Properties();
LocalFileConfigRepository someLocalConfigRepo = mock(LocalFileConfigRepository.class);
when(someLocalConfigRepo.getConfig()).thenReturn(someProperties);
doReturn(someLocalConfigRepo).when(defaultConfigFactory).createLocalConfigRepository(someNamespace);
doReturn(someLocalConfigRepo).when(defaultConfigFactory).createLocalConfigRepository(anotherNamespace);
ConfigFile propertyConfigFile =
defaultConfigFactory.createConfigFile(someNamespace, ConfigFileFormat.Properties);
ConfigFile xmlConfigFile =
defaultConfigFactory.createConfigFile(anotherNamespace, ConfigFileFormat.XML);
assertThat("Should create PropertiesConfigFile for properties format", propertyConfigFile, is(instanceOf(
PropertiesConfigFile.class)));
assertEquals(someNamespace, propertyConfigFile.getNamespace());
assertThat("Should create XmlConfigFile for xml format", xmlConfigFile, is(instanceOf(
XmlConfigFile.class)));
assertEquals(anotherNamespace, xmlConfigFile.getNamespace());
}
} }
package com.ctrip.framework.apollo.spi; package com.ctrip.framework.apollo.spi;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -46,5 +48,10 @@ public class DefaultConfigRegistryTest extends ComponentTestCase { ...@@ -46,5 +48,10 @@ public class DefaultConfigRegistryTest extends ComponentTestCase {
public Config create(String namespace) { public Config create(String namespace) {
return null; return null;
} }
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
} }
} }
package com.ctrip.framework.apollo.common.entity; package com.ctrip.framework.apollo.common.entity;
import javax.persistence.Column; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity @Entity
@Table(name = "AppNamespace") @Table(name = "AppNamespace")
@SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?") @SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?")
...@@ -20,6 +22,12 @@ public class AppNamespace extends BaseEntity { ...@@ -20,6 +22,12 @@ public class AppNamespace extends BaseEntity {
@Column(name = "AppId", nullable = false) @Column(name = "AppId", nullable = false)
private String appId; private String appId;
@Column(name = "Format", nullable = false)
private String format;
@Column(name = "IsPublic", columnDefinition = "Bit default '0'")
private boolean isPublic = false;
@Column(name = "Comment") @Column(name = "Comment")
private String comment; private String comment;
...@@ -47,8 +55,28 @@ public class AppNamespace extends BaseEntity { ...@@ -47,8 +55,28 @@ public class AppNamespace extends BaseEntity {
this.name = name; this.name = name;
} }
public boolean isPublic() {
return isPublic;
}
public void setPublic(boolean aPublic) {
isPublic = aPublic;
}
public ConfigFileFormat formatAsEnum() {
return ConfigFileFormat.fromString(this.format);
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String toString() { public String toString() {
return toStringHelper().add("name", name).add("appId", appId).add("comment", comment) return toStringHelper().add("name", name).add("appId", appId).add("comment", comment)
.toString(); .add("format", format).add("isPublic", isPublic).toString();
} }
} }
package com.ctrip.framework.apollo.common.utils; package com.ctrip.framework.apollo.common.utils;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -8,12 +10,24 @@ import java.util.regex.Pattern; ...@@ -8,12 +10,24 @@ import java.util.regex.Pattern;
*/ */
public class InputValidator { public class InputValidator {
public static final String INVALID_CLUSTER_NAMESPACE_MESSAGE = "只允许输入数字,字母和符号 - _ ."; public static final String INVALID_CLUSTER_NAMESPACE_MESSAGE = "只允许输入数字,字母和符号 - _ .";
public static final String INVALID_NAMESPACE_NAMESPACE_MESSAGE = "不允许以.json, .yml, .yaml, .xml, .properties结尾";
public static final String CLUSTER_NAMESPACE_VALIDATOR = "[0-9a-zA-z_.-]+"; public static final String CLUSTER_NAMESPACE_VALIDATOR = "[0-9a-zA-z_.-]+";
public static final String APP_NAMESPACE_VALIDATOR = "[a-zA-z0-9._-]+(?<!\\.(json|yml|yaml|xml|properties))$";
private static final Pattern CLUSTER_NAMESPACE_PATTERN = private static final Pattern CLUSTER_NAMESPACE_PATTERN =
Pattern.compile(CLUSTER_NAMESPACE_VALIDATOR); Pattern.compile(CLUSTER_NAMESPACE_VALIDATOR);
private static final Pattern APP_NAMESPACE_PATTERN =
Pattern.compile(APP_NAMESPACE_VALIDATOR);
public static boolean isValidClusterNamespace(String input) { public static boolean isValidClusterNamespace(String input) {
Matcher matcher = CLUSTER_NAMESPACE_PATTERN.matcher(input); Matcher matcher = CLUSTER_NAMESPACE_PATTERN.matcher(input);
return matcher.matches(); return matcher.matches();
} }
public static boolean isValidAppNamespace(String name){
if (StringUtils.isEmpty(name)){
return false;
}
return CLUSTER_NAMESPACE_PATTERN.matcher(name.toLowerCase()).matches() && APP_NAMESPACE_PATTERN.matcher(name).matches();
}
} }
...@@ -4,6 +4,7 @@ package com.ctrip.framework.apollo.common.utils; ...@@ -4,6 +4,7 @@ package com.ctrip.framework.apollo.common.utils;
import com.ctrip.framework.apollo.core.exception.BadRequestException; import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
public class RequestPrecondition { public class RequestPrecondition {
private static String CONTAIN_EMPTY_ARGUMENT = "request payload should not be contain empty."; private static String CONTAIN_EMPTY_ARGUMENT = "request payload should not be contain empty.";
...@@ -12,6 +13,7 @@ public class RequestPrecondition { ...@@ -12,6 +13,7 @@ public class RequestPrecondition {
private static String ILLEGAL_NUMBER = "number should be positive"; private static String ILLEGAL_NUMBER = "number should be positive";
public static void checkArgument(String... args) { public static void checkArgument(String... args) {
checkArgument(!StringUtils.isContainEmpty(args), CONTAIN_EMPTY_ARGUMENT); checkArgument(!StringUtils.isContainEmpty(args), CONTAIN_EMPTY_ARGUMENT);
} }
......
...@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.common.entity.AppNamespace; ...@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ConfigService; import com.ctrip.framework.apollo.biz.service.ConfigService;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig; import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.dianping.cat.Cat; import com.dianping.cat.Cat;
...@@ -41,6 +42,8 @@ public class ConfigController { ...@@ -41,6 +42,8 @@ public class ConfigController {
private ConfigService configService; private ConfigService configService;
@Autowired @Autowired
private AppNamespaceService appNamespaceService; private AppNamespaceService appNamespaceService;
@Autowired
private NamespaceUtil namespaceUtil;
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private static final Type configurationTypeReference = private static final Type configurationTypeReference =
...@@ -55,6 +58,11 @@ public class ConfigController { ...@@ -55,6 +58,11 @@ public class ConfigController {
@RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey, @RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey,
@RequestParam(value = "ip", required = false) String clientIp, @RequestParam(value = "ip", required = false) String clientIp,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
String originalNamespace = namespace;
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
List<Release> releases = Lists.newLinkedList(); List<Release> releases = Lists.newLinkedList();
Release currentAppRelease = loadConfig(appId, clusterName, namespace, dataCenter); Release currentAppRelease = loadConfig(appId, clusterName, namespace, dataCenter);
...@@ -66,8 +74,8 @@ public class ConfigController { ...@@ -66,8 +74,8 @@ public class ConfigController {
appClusterNameLoaded = currentAppRelease.getClusterName(); appClusterNameLoaded = currentAppRelease.getClusterName();
} }
//if namespace is not 'application', should check if it's a public configuration //if namespace does not belong to this appId, should check if there is a public configuration
if (!Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespace)) { if (!namespaceBelongsToAppId(appId, namespace)) {
Release publicRelease = this.findPublicConfig(appId, clusterName, namespace, dataCenter); Release publicRelease = this.findPublicConfig(appId, clusterName, namespace, dataCenter);
if (!Objects.isNull(publicRelease)) { if (!Objects.isNull(publicRelease)) {
releases.add(publicRelease); releases.add(publicRelease);
...@@ -78,9 +86,9 @@ public class ConfigController { ...@@ -78,9 +86,9 @@ public class ConfigController {
response.sendError(HttpServletResponse.SC_NOT_FOUND, response.sendError(HttpServletResponse.SC_NOT_FOUND,
String.format( String.format(
"Could not load configurations with appId: %s, clusterName: %s, namespace: %s", "Could not load configurations with appId: %s, clusterName: %s, namespace: %s",
appId, clusterName, namespace)); appId, clusterName, originalNamespace));
Cat.logEvent("Apollo.Config.NotFound", Cat.logEvent("Apollo.Config.NotFound",
assembleKey(appId, clusterName, namespace, dataCenter)); assembleKey(appId, clusterName, originalNamespace, dataCenter));
return null; return null;
} }
...@@ -91,24 +99,35 @@ public class ConfigController { ...@@ -91,24 +99,35 @@ public class ConfigController {
// Client side configuration is the same with server side, return 304 // Client side configuration is the same with server side, return 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
Cat.logEvent("Apollo.Config.NotModified", Cat.logEvent("Apollo.Config.NotModified",
assembleKey(appId, appClusterNameLoaded, namespace, dataCenter)); assembleKey(appId, appClusterNameLoaded, originalNamespace, dataCenter));
return null; return null;
} }
ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, namespace, mergedReleaseKey); ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, originalNamespace, mergedReleaseKey);
apolloConfig.setConfigurations(mergeReleaseConfigurations(releases)); apolloConfig.setConfigurations(mergeReleaseConfigurations(releases));
Cat.logEvent("Apollo.Config.Found", assembleKey(appId, appClusterNameLoaded, namespace, dataCenter)); Cat.logEvent("Apollo.Config.Found", assembleKey(appId, appClusterNameLoaded, originalNamespace, dataCenter));
return apolloConfig; return apolloConfig;
} }
private boolean namespaceBelongsToAppId(String appId, String namespaceName) {
//Every app has an 'application' namespace
if (Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespaceName)) {
return true;
}
AppNamespace appNamespace = appNamespaceService.findOne(appId, namespaceName);
return appNamespace != null;
}
/** /**
* @param applicationId the application which uses public config * @param applicationId the application which uses public config
* @param namespace the namespace * @param namespace the namespace
* @param dataCenter the datacenter * @param dataCenter the datacenter
*/ */
private Release findPublicConfig(String applicationId, String clusterName, String namespace, String dataCenter) { private Release findPublicConfig(String applicationId, String clusterName, String namespace, String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace); AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace);
//check whether the namespace's appId equals to current one //check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) { if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
......
...@@ -16,6 +16,7 @@ import com.ctrip.framework.apollo.biz.message.Topics; ...@@ -16,6 +16,7 @@ import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseMessageService; import com.ctrip.framework.apollo.biz.service.ReleaseMessageService;
import com.ctrip.framework.apollo.biz.utils.EntityManagerUtil; import com.ctrip.framework.apollo.biz.utils.EntityManagerUtil;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.dianping.cat.Cat; import com.dianping.cat.Cat;
...@@ -60,6 +61,9 @@ public class NotificationController implements ReleaseMessageListener { ...@@ -60,6 +61,9 @@ public class NotificationController implements ReleaseMessageListener {
@Autowired @Autowired
private EntityManagerUtil entityManagerUtil; private EntityManagerUtil entityManagerUtil;
@Autowired
private NamespaceUtil namespaceUtil;
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification( public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification(
@RequestParam(value = "appId") String appId, @RequestParam(value = "appId") String appId,
...@@ -68,10 +72,13 @@ public class NotificationController implements ReleaseMessageListener { ...@@ -68,10 +72,13 @@ public class NotificationController implements ReleaseMessageListener {
@RequestParam(value = "dataCenter", required = false) String dataCenter, @RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "notificationId", defaultValue = "-1") long notificationId, @RequestParam(value = "notificationId", defaultValue = "-1") long notificationId,
@RequestParam(value = "ip", required = false) String clientIp) { @RequestParam(value = "ip", required = false) String clientIp) {
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
Set<String> watchedKeys = assembleWatchKeys(appId, cluster, namespace, dataCenter); Set<String> watchedKeys = assembleWatchKeys(appId, cluster, namespace, dataCenter);
//Listen on more namespaces, since it's not the default namespace //Listen on more namespaces if it's a public namespace
if (!Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespace)) { if (!namespaceBelongsToAppId(appId, namespace)) {
watchedKeys.addAll(this.findPublicConfigWatchKey(appId, cluster, namespace, dataCenter)); watchedKeys.addAll(this.findPublicConfigWatchKey(appId, cluster, namespace, dataCenter));
} }
...@@ -124,7 +131,7 @@ public class NotificationController implements ReleaseMessageListener { ...@@ -124,7 +131,7 @@ public class NotificationController implements ReleaseMessageListener {
private Set<String> findPublicConfigWatchKey(String applicationId, String clusterName, private Set<String> findPublicConfigWatchKey(String applicationId, String clusterName,
String namespace, String namespace,
String dataCenter) { String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace); AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace);
//check whether the namespace's appId equals to current one //check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) { if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
...@@ -187,6 +194,17 @@ public class NotificationController implements ReleaseMessageListener { ...@@ -187,6 +194,17 @@ public class NotificationController implements ReleaseMessageListener {
logger.info("Notification completed"); logger.info("Notification completed");
} }
private boolean namespaceBelongsToAppId(String appId, String namespaceName) {
//Every app has an 'application' namespace
if (Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespaceName)) {
return true;
}
AppNamespace appNamespace = appNamespaceService.findOne(appId, namespaceName);
return appNamespace != null;
}
private void logWatchedKeysToCat(Set<String> watchedKeys, String eventName) { private void logWatchedKeysToCat(Set<String> watchedKeys, String eventName) {
for (String watchedKey : watchedKeys) { for (String watchedKey : watchedKeys) {
Cat.logEvent(eventName, watchedKey); Cat.logEvent(eventName, watchedKey);
......
package com.ctrip.framework.apollo.configservice.util;
import org.springframework.stereotype.Component;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Component
public class NamespaceUtil {
public String filterNamespaceName(String namespaceName) {
if (namespaceName.toLowerCase().endsWith(".properties")) {
int dotIndex = namespaceName.lastIndexOf(".");
return namespaceName.substring(0, dotIndex);
}
return namespaceName;
}
}
...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.configservice.controller.ConfigControllerTest; ...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.configservice.controller.ConfigControllerTest;
import com.ctrip.framework.apollo.configservice.controller.NotificationControllerTest; import com.ctrip.framework.apollo.configservice.controller.NotificationControllerTest;
import com.ctrip.framework.apollo.configservice.integration.ConfigControllerIntegrationTest; import com.ctrip.framework.apollo.configservice.integration.ConfigControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.integration.NotificationControllerIntegrationTest; import com.ctrip.framework.apollo.configservice.integration.NotificationControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtilTest;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
...@@ -11,7 +12,8 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -11,7 +12,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses({ConfigControllerTest.class, NotificationControllerTest.class, @SuiteClasses({ConfigControllerTest.class, NotificationControllerTest.class,
ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class}) ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class,
NamespaceUtilTest.class})
public class AllTests { public class AllTests {
} }
...@@ -4,12 +4,13 @@ import com.google.common.base.Joiner; ...@@ -4,12 +4,13 @@ import com.google.common.base.Joiner;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage; import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.Topics; import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseMessageService; import com.ctrip.framework.apollo.biz.service.ReleaseMessageService;
import com.ctrip.framework.apollo.biz.utils.EntityManagerUtil; import com.ctrip.framework.apollo.biz.utils.EntityManagerUtil;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
...@@ -51,6 +52,9 @@ public class NotificationControllerTest { ...@@ -51,6 +52,9 @@ public class NotificationControllerTest {
private ReleaseMessageService releaseMessageService; private ReleaseMessageService releaseMessageService;
@Mock @Mock
private EntityManagerUtil entityManagerUtil; private EntityManagerUtil entityManagerUtil;
@Mock
private NamespaceUtil namespaceUtil;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>> private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>
deferredResults; deferredResults;
...@@ -60,6 +64,7 @@ public class NotificationControllerTest { ...@@ -60,6 +64,7 @@ public class NotificationControllerTest {
ReflectionTestUtils.setField(controller, "appNamespaceService", appNamespaceService); ReflectionTestUtils.setField(controller, "appNamespaceService", appNamespaceService);
ReflectionTestUtils.setField(controller, "releaseMessageService", releaseMessageService); ReflectionTestUtils.setField(controller, "releaseMessageService", releaseMessageService);
ReflectionTestUtils.setField(controller, "entityManagerUtil", entityManagerUtil); ReflectionTestUtils.setField(controller, "entityManagerUtil", entityManagerUtil);
ReflectionTestUtils.setField(controller, "namespaceUtil", namespaceUtil);
someAppId = "someAppId"; someAppId = "someAppId";
someCluster = "someCluster"; someCluster = "someCluster";
...@@ -70,6 +75,9 @@ public class NotificationControllerTest { ...@@ -70,6 +75,9 @@ public class NotificationControllerTest {
someNotificationId = 1; someNotificationId = 1;
someClientIp = "someClientIp"; someClientIp = "someClientIp";
when(namespaceUtil.filterNamespaceName(defaultNamespace)).thenReturn(defaultNamespace);
when(namespaceUtil.filterNamespaceName(somePublicNamespace)).thenReturn(somePublicNamespace);
deferredResults = deferredResults =
(Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>) ReflectionTestUtils (Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>) ReflectionTestUtils
.getField(controller, "deferredResults"); .getField(controller, "deferredResults");
...@@ -95,6 +103,55 @@ public class NotificationControllerTest { ...@@ -95,6 +103,55 @@ public class NotificationControllerTest {
} }
} }
@Test
public void testPollNotificationWithDefaultNamespaceAsFile() throws Exception {
String namespace = String.format("%s.%s", defaultNamespace, "properties");
when(namespaceUtil.filterNamespaceName(namespace)).thenReturn(defaultNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, namespace, someDataCenter,
someNotificationId, someClientIp);
List<String> clusters =
Lists.newArrayList(someCluster, someDataCenter, ConfigConsts.CLUSTER_NAME_DEFAULT);
assertEquals(clusters.size(), deferredResults.size());
for (String cluster : clusters) {
String key =
Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, cluster, defaultNamespace);
assertTrue(deferredResults.get(key).contains(deferredResult));
}
}
@Test
public void testPollNotificationWithPrivateNamespaceAsFile() throws Exception {
String namespace = String.format("someNamespace.xml");
AppNamespace appNamespace = mock(AppNamespace.class);
when(namespaceUtil.filterNamespaceName(namespace)).thenReturn(namespace);
when(appNamespaceService.findOne(someAppId, namespace)).thenReturn(appNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, namespace, someDataCenter,
someNotificationId, someClientIp);
List<String> clusters =
Lists.newArrayList(someCluster, someDataCenter, ConfigConsts.CLUSTER_NAME_DEFAULT);
assertEquals(clusters.size(), deferredResults.size());
for (String cluster : clusters) {
String key =
Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, cluster, namespace);
assertTrue(deferredResults.get(key).contains(deferredResult));
}
}
@Test @Test
public void testPollNotificationWithDefaultNamespaceWithNotificationIdOutDated() throws Exception { public void testPollNotificationWithDefaultNamespaceWithNotificationIdOutDated() throws Exception {
long notificationId = someNotificationId + 1; long notificationId = someNotificationId + 1;
...@@ -156,7 +213,6 @@ public class NotificationControllerTest { ...@@ -156,7 +213,6 @@ public class NotificationControllerTest {
.join(someAppId, cluster, defaultNamespace); .join(someAppId, cluster, defaultNamespace);
assertTrue(deferredResults.get(key).contains(deferredResult)); assertTrue(deferredResults.get(key).contains(deferredResult));
} }
} }
@Test @Test
...@@ -165,7 +221,7 @@ public class NotificationControllerTest { ...@@ -165,7 +221,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace = AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace); assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace)) when(appNamespaceService.findPublicNamespaceByName(somePublicNamespace))
.thenReturn(somePublicAppNamespace); .thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>> DeferredResult<ResponseEntity<ApolloConfigNotification>>
...@@ -193,6 +249,46 @@ public class NotificationControllerTest { ...@@ -193,6 +249,46 @@ public class NotificationControllerTest {
} }
} }
@Test
public void testPollNotificationWithPublicNamespaceAsFile() throws Exception {
String somePublicNamespaceAsFile = String.format("%s.%s", somePublicNamespace, "xml");
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(namespaceUtil.filterNamespaceName(somePublicNamespaceAsFile))
.thenReturn(somePublicNamespace);
when(appNamespaceService.findPublicNamespaceByName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
when(appNamespaceService.findOne(someAppId, somePublicNamespace)).thenReturn(null);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
deferredResult = controller
.pollNotification(someAppId, someCluster, somePublicNamespaceAsFile, someDataCenter,
someNotificationId, someClientIp);
List<String> clusters =
Lists.newArrayList(someCluster, someDataCenter, ConfigConsts.CLUSTER_NAME_DEFAULT);
assertEquals(clusters.size() * 2, deferredResults.size());
for (String cluster : clusters) {
String publicKey =
Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppId, cluster, somePublicNamespace);
assertTrue(deferredResults.get(publicKey).contains(deferredResult));
}
for (String cluster : clusters) {
String publicKey =
Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(somePublicAppId, cluster, somePublicNamespace);
assertTrue(deferredResults.get(publicKey).contains(deferredResult));
}
}
@Test @Test
public void testPollNotificationWithPublicNamespaceWithNotificationIdOutDated() throws Exception { public void testPollNotificationWithPublicNamespaceWithNotificationIdOutDated() throws Exception {
long notificationId = someNotificationId + 1; long notificationId = someNotificationId + 1;
...@@ -206,7 +302,7 @@ public class NotificationControllerTest { ...@@ -206,7 +302,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace = AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace); assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace)) when(appNamespaceService.findPublicNamespaceByName(somePublicNamespace))
.thenReturn(somePublicAppNamespace); .thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>> DeferredResult<ResponseEntity<ApolloConfigNotification>>
...@@ -253,7 +349,7 @@ public class NotificationControllerTest { ...@@ -253,7 +349,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace = AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace); assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace)) when(appNamespaceService.findPublicNamespaceByName(somePublicNamespace))
.thenReturn(somePublicAppNamespace); .thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>> DeferredResult<ResponseEntity<ApolloConfigNotification>>
......
...@@ -47,6 +47,20 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -47,6 +47,20 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("v1", result.getConfigurations().get("k1")); assertEquals("v1", result.getConfigurations().get("k1"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigFileWithDefaultClusterAndDefaultNamespaceOK() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION + ".properties");
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-RELEASE-KEY1", result.getReleaseKey());
assertEquals("v1", result.getConfigurations().get("k1"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -61,6 +75,21 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -61,6 +75,21 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("v2", result.getConfigurations().get("k2")); assertEquals("v2", result.getConfigurations().get("k2"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigFileWithNamespaceOK() throws Exception {
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, someNamespace + ".xml");
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-RELEASE-KEY5", result.getReleaseKey());
assertEquals("v1-file", result.getConfigurations().get("k1"));
assertEquals("v2-file", result.getConfigurations().get("k2"));
}
@Test @Test
public void testQueryConfigError() throws Exception { public void testQueryConfigError() throws Exception {
String someNamespaceNotExists = "someNamespaceNotExists"; String someNamespaceNotExists = "someNamespaceNotExists";
...@@ -168,6 +197,24 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -168,6 +197,24 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals(somePublicNamespace, result.getNamespaceName()); assertEquals(somePublicNamespace, result.getNamespaceName());
assertEquals("override-v1", result.getConfigurations().get("k1")); assertEquals("override-v1", result.getConfigurations().get("k1"));
assertEquals("default-v2", result.getConfigurations().get("k2")); assertEquals("default-v2", result.getConfigurations().get("k2"));
}
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPrivateConfigFileWithPublicNamespaceExists() throws Exception {
String namespaceName = "anotherNamespace";
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}",
ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespaceName);
ApolloConfig result = response.getBody();
assertEquals("TEST-RELEASE-KEY6", result.getReleaseKey());
assertEquals(someAppId, result.getAppId());
assertEquals(ConfigConsts.CLUSTER_NAME_DEFAULT, result.getCluster());
assertEquals(namespaceName, result.getNamespaceName());
assertEquals("v1-file", result.getConfigurations().get("k1"));
assertEquals(null, result.getConfigurations().get("k2"));
} }
} }
...@@ -65,6 +65,47 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati ...@@ -65,6 +65,47 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
assertNotEquals(0, notification.getNotificationId()); assertNotEquals(0, notification.getNotificationId());
} }
@Test(timeout = 5000L)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPollNotificationWithDefaultNamespaceAsFile() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(assembleKey(someAppId, someCluster, defaultNamespace), stop);
ResponseEntity<ApolloConfigNotification> result = restTemplate.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, defaultNamespace + ".properties");
stop.set(true);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(defaultNamespace, notification.getNamespaceName());
assertNotEquals(0, notification.getNotificationId());
}
@Test(timeout = 5000L)
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPollNotificationWithPrivateNamespaceAsFile() throws Exception {
String namespace = "someNamespace.xml";
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(assembleKey(someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespace), stop);
ResponseEntity<ApolloConfigNotification> result = restTemplate
.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, namespace);
stop.set(true);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(namespace, notification.getNamespaceName());
assertNotEquals(0, notification.getNotificationId());
}
@Test(timeout = 5000L) @Test(timeout = 5000L)
@Sql(scripts = "/integration-test/test-release-message.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release-message.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -144,6 +185,30 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati ...@@ -144,6 +185,30 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
assertNotEquals(0, notification.getNotificationId()); assertNotEquals(0, notification.getNotificationId());
} }
@Test(timeout = 5000L)
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPollNotificationWthPublicNamespaceAsFile() throws Exception {
String publicAppId = "somePublicAppId";
String someDC = "someDC";
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(assembleKey(publicAppId, someDC, somePublicNamespace), stop);
ResponseEntity<ApolloConfigNotification> result = restTemplate
.getForEntity(
"{baseurl}/notifications?appId={appId}&cluster={clusterName}&namespace={namespace}&dataCenter={dataCenter}",
ApolloConfigNotification.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace + ".properties", someDC);
stop.set(true);
ApolloConfigNotification notification = result.getBody();
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(somePublicNamespace, notification.getNamespaceName());
assertNotEquals(0, notification.getNotificationId());
}
@Test(timeout = 5000L) @Test(timeout = 5000L)
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-message.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release-message.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
......
package com.ctrip.framework.apollo.configservice.util;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class NamespaceUtilTest {
private NamespaceUtil namespaceUtil;
@Before
public void setUp() throws Exception {
namespaceUtil = new NamespaceUtil();
}
@Test
public void testFilterNamespaceName() throws Exception {
String someName = "a.properties";
assertEquals("a", namespaceUtil.filterNamespaceName(someName));
}
@Test
public void testFilterNamespaceNameUnchanged() throws Exception {
String someName = "a.xml";
assertEquals(someName, namespaceUtil.filterNamespaceName(someName));
}
@Test
public void testFilterNamespaceNameWithMultiplePropertiesSuffix() throws Exception {
String someName = "a.properties.properties";
assertEquals("a.properties", namespaceUtil.filterNamespaceName(someName));
}
@Test
public void testFilterNamespaceNameWithRandomCase() throws Exception {
String someName = "AbC.ProPErties";
assertEquals("AbC", namespaceUtil.filterNamespaceName(someName));
}
@Test
public void testFilterNamespaceNameWithRandomCaseUnchanged() throws Exception {
String someName = "AbCD.xMl";
assertEquals(someName, namespaceUtil.filterNamespaceName(someName));
}
}
...@@ -6,16 +6,22 @@ INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'someCluster'); ...@@ -6,16 +6,22 @@ INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'someCluster');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'default'); INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'someDC'); INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'someDC');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'application'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'application', false);
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'someNamespace'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'someNamespace', true);
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'application'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'someNamespace.xml', false);
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'somePublicNamespace'); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'anotherNamespace', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('somePublicAppId', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('somePublicAppId', 'somePublicNamespace', true);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('somePublicAppId', 'anotherNamespace', true);
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'application'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'application');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'someNamespace.xml');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'anotherNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'someCluster', 'someNamespace'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'someCluster', 'someNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'default', 'application'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'default', 'application');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'someDC', 'somePublicNamespace'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'someDC', 'somePublicNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'somePublicNamespace'); INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'somePublicNamespace');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('somePublicAppId', 'default', 'anotherNamespace');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (990, 'TEST-RELEASE-KEY1', 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}'); VALUES (990, 'TEST-RELEASE-KEY1', 'INTEGRATION-TEST-DEFAULT','First Release','someAppId', 'default', 'application', '{"k1":"v1"}');
...@@ -25,3 +31,9 @@ INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, Namespac ...@@ -25,3 +31,9 @@ INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, Namespac
VALUES (992, 'TEST-RELEASE-KEY3', 'INTEGRATION-TEST-PUBLIC-DEFAULT','First Release','somePublicAppId', 'default', 'somePublicNamespace', '{"k1":"default-v1", "k2":"default-v2"}'); VALUES (992, 'TEST-RELEASE-KEY3', 'INTEGRATION-TEST-PUBLIC-DEFAULT','First Release','somePublicAppId', 'default', 'somePublicNamespace', '{"k1":"default-v1", "k2":"default-v2"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (993, 'TEST-RELEASE-KEY4', 'INTEGRATION-TEST-PUBLIC-NAMESPACE','First Release','somePublicAppId', 'someDC', 'somePublicNamespace', '{"k1":"someDC-v1", "k2":"someDC-v2"}'); VALUES (993, 'TEST-RELEASE-KEY4', 'INTEGRATION-TEST-PUBLIC-NAMESPACE','First Release','somePublicAppId', 'someDC', 'somePublicNamespace', '{"k1":"someDC-v1", "k2":"someDC-v2"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (989, 'TEST-RELEASE-KEY5', 'INTEGRATION-TEST-PRIVATE-CONFIG-FILE','First Release','someAppId', 'default', 'someNamespace.xml', '{"k1":"v1-file", "k2":"v2-file"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (988, 'TEST-RELEASE-KEY6', 'INTEGRATION-TEST-PRIVATE-CONFIG-FILE','First Release','someAppId', 'default', 'anotherNamespace', '{"k1":"v1-file"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (987, 'TEST-RELEASE-KEY7', 'INTEGRATION-TEST-PUBLIC-CONFIG-FILE','First Release','somePublicAppId', 'default', 'anotherNamespace', '{"k2":"v2-file"}');
...@@ -5,4 +5,5 @@ public interface ConfigConsts { ...@@ -5,4 +5,5 @@ public interface ConfigConsts {
String CLUSTER_NAME_DEFAULT = "default"; String CLUSTER_NAME_DEFAULT = "default";
String CLUSTER_NAMESPACE_SEPARATOR = "+"; String CLUSTER_NAMESPACE_SEPARATOR = "+";
String APOLLO_CLUSTER_KEY = "apollo.cluster"; String APOLLO_CLUSTER_KEY = "apollo.cluster";
String CONFIG_FILE_CONTENT_KEY = "content";
} }
package com.ctrip.framework.apollo.core.dto; package com.ctrip.framework.apollo.core.dto;
public class AppNamespaceDTO extends BaseDTO{ public class AppNamespaceDTO extends BaseDTO{
private long id; private long id;
...@@ -9,6 +10,10 @@ public class AppNamespaceDTO extends BaseDTO{ ...@@ -9,6 +10,10 @@ public class AppNamespaceDTO extends BaseDTO{
private String comment; private String comment;
private String format;
private boolean isPublic = false;
public long getId() { public long getId() {
return id; return id;
} }
...@@ -33,6 +38,22 @@ public class AppNamespaceDTO extends BaseDTO{ ...@@ -33,6 +38,22 @@ public class AppNamespaceDTO extends BaseDTO{
this.appId = appId; this.appId = appId;
} }
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public boolean isPublic() {
return isPublic;
}
public void setPublic(boolean aPublic) {
isPublic = aPublic;
}
public String getComment() { public String getComment() {
return comment; return comment;
} }
......
package com.ctrip.framework.apollo.core.enums;
import com.ctrip.framework.apollo.core.utils.StringUtils;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public enum ConfigFileFormat {
Properties("properties"), XML("xml");
private String value;
ConfigFileFormat(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static ConfigFileFormat fromString(String value){
if (StringUtils.isEmpty(value)){
throw new IllegalArgumentException("value can not be empty");
}
switch (value){
case "properties":
return Properties;
case "xml":
return XML;
}
throw new IllegalArgumentException(value + " can not map enum");
}
}
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
......
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloConfigFileDemo {
private static final Logger logger = LoggerFactory.getLogger(ApolloConfigDemo.class);
private ConfigFile configFile;
private String namespace = "application";
public ApolloConfigFileDemo() {
configFile = ConfigService.getConfigFile(namespace, ConfigFileFormat.XML);
}
private void print() {
if (!configFile.hasContent()) {
System.out.println("No config file content found for " + namespace);
return;
}
System.out.println("=== Config File Content for " + namespace + " is as follows: ");
System.out.println(configFile.getContent());
}
public static void main(String[] args) throws IOException {
ApolloConfigFileDemo apolloConfigFileDemo = new ApolloConfigFileDemo();
System.out.println(
"Apollo Config File Demo. Please input print to get the config file content.");
while (true) {
System.out.print("> ");
String input = new BufferedReader(new InputStreamReader(System.in)).readLine();
if (input == null || input.length() == 0) {
continue;
}
input = input.trim();
if (input.equalsIgnoreCase("print")) {
apolloConfigFileDemo.print();
}
if (input.equalsIgnoreCase("quit")) {
System.exit(0);
}
}
}
}
package com.ctrip.framework.apollo.portal.auth; package com.ctrip.framework.apollo.portal.auth;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.portal.constant.PermissionType; import com.ctrip.framework.apollo.portal.constant.PermissionType;
import com.ctrip.framework.apollo.portal.service.RolePermissionService; import com.ctrip.framework.apollo.portal.service.RolePermissionService;
import com.ctrip.framework.apollo.portal.util.RoleUtils; import com.ctrip.framework.apollo.portal.util.RoleUtils;
...@@ -15,36 +16,45 @@ public class PermissionValidator { ...@@ -15,36 +16,45 @@ public class PermissionValidator {
@Autowired @Autowired
private RolePermissionService rolePermissionService; private RolePermissionService rolePermissionService;
public boolean hasModifyNamespacePermission(String appId, String namespaceName){ public boolean hasModifyNamespacePermission(String appId, String namespaceName) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(), return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.MODIFY_NAMESPACE, PermissionType.MODIFY_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName)); RoleUtils.buildNamespaceTargetId(appId, namespaceName));
} }
public boolean hasReleaseNamespacePermission(String appId, String namespaceName){ public boolean hasReleaseNamespacePermission(String appId, String namespaceName) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(), return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.RELEASE_NAMESPACE, PermissionType.RELEASE_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName)); RoleUtils.buildNamespaceTargetId(appId, namespaceName));
} }
public boolean hasAssignRolePermission(String appId){ public boolean hasAssignRolePermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(), return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.ASSIGN_ROLE, PermissionType.ASSIGN_ROLE,
appId); appId);
} }
public boolean hasCreateNamespacePermission(String appId){ public boolean hasCreateNamespacePermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(), return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.CREATE_NAMESPACE, PermissionType.CREATE_NAMESPACE,
appId); appId);
} }
public boolean hasCreateClusterPermission(String appId){ public boolean hasCreateClusterPermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(), return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.CREATE_CLUSTER, PermissionType.CREATE_CLUSTER,
appId); appId);
} }
public boolean hasCreateAppNamespacePermission(String appId, AppNamespace appNamespace) {
boolean isPublicAppNamespace = appNamespace.isPublic();
if (isPublicAppNamespace){
return hasCreateNamespacePermission(appId);
}else {
return rolePermissionService.isSuperAdmin(userInfoHolder.getUser().getUserId());
}
}
} }
...@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.portal.entity.form.NamespaceSyncModel; ...@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.portal.entity.form.NamespaceSyncModel;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel; import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.service.ConfigService; import com.ctrip.framework.apollo.portal.service.ConfigService;
import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -33,11 +34,13 @@ public class ConfigController { ...@@ -33,11 +34,13 @@ public class ConfigController {
@Autowired @Autowired
private ConfigService configService; private ConfigService configService;
@Autowired
private ServerConfigService serverConfigService;
@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName)") @PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT, consumes = { @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT, consumes = {
"application/json"}) "application/json"})
public void modifyItems(@PathVariable String appId, @PathVariable String env, public void modifyItemsByText(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName, @PathVariable String clusterName, @PathVariable String namespaceName,
@RequestBody NamespaceTextModel model) { @RequestBody NamespaceTextModel model) {
...@@ -131,4 +134,5 @@ public class ConfigController { ...@@ -131,4 +134,5 @@ public class ConfigController {
return item != null && !StringUtils.isContainEmpty(item.getKey()); return item != null && !StringUtils.isContainEmpty(item.getKey());
} }
} }
...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.common.entity.App; ...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.utils.InputValidator; import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO; import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.exception.BadRequestException; import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
...@@ -11,6 +12,7 @@ import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; ...@@ -11,6 +12,7 @@ import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceCreationModel; import com.ctrip.framework.apollo.portal.entity.form.NamespaceCreationModel;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent; import com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent;
import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
import com.ctrip.framework.apollo.portal.service.AppService; import com.ctrip.framework.apollo.portal.service.AppService;
import com.ctrip.framework.apollo.portal.service.NamespaceService; import com.ctrip.framework.apollo.portal.service.NamespaceService;
...@@ -46,15 +48,18 @@ public class NamespaceController { ...@@ -46,15 +48,18 @@ public class NamespaceController {
private UserInfoHolder userInfoHolder; private UserInfoHolder userInfoHolder;
@Autowired @Autowired
private NamespaceService namespaceService; private NamespaceService namespaceService;
@Autowired
private AppNamespaceService appNamespaceService;
@RequestMapping("/appnamespaces/public") @RequestMapping("/appnamespaces/public")
public List<AppNamespace> findPublicAppNamespaces() { public List<AppNamespace> findPublicAppNamespaces() {
return namespaceService.findPublicAppNamespaces(); return appNamespaceService.findPublicAppNamespaces();
} }
@PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)") @PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)")
@RequestMapping(value = "/apps/{appId}/namespaces", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/namespaces", method = RequestMethod.POST)
public ResponseEntity<Void> createNamespace(@PathVariable String appId, @RequestBody List<NamespaceCreationModel> models) { public ResponseEntity<Void> createNamespace(@PathVariable String appId,
@RequestBody List<NamespaceCreationModel> models) {
checkModel(!CollectionUtils.isEmpty(models)); checkModel(!CollectionUtils.isEmpty(models));
...@@ -73,24 +78,31 @@ public class NamespaceController { ...@@ -73,24 +78,31 @@ public class NamespaceController {
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
@RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
public void createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) { public void createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) {
checkArgument(appNamespace.getAppId(), appNamespace.getName()); checkArgument(appNamespace.getAppId(), appNamespace.getName());
if (!InputValidator.isValidClusterNamespace(appNamespace.getName())) { if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE)); throw new BadRequestException(String.format("Namespace格式错误: %s",
InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
+ InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
} }
//add app org id as prefix //add app org id as prefix
App app = appService.load(appId); App app = appService.load(appId);
appNamespace.setName(String.format("%s.%s", app.getOrgId(), appNamespace.getName())); if (appNamespace.formatAsEnum() == ConfigFileFormat.Properties) {
appNamespace.setName(String.format("%s.%s", app.getOrgId(), appNamespace.getName()));
} else {
appNamespace.setName(String.format("%s.%s.%s", app.getOrgId(), appNamespace.getName(), appNamespace.getFormat()));
}
String operator = userInfoHolder.getUser().getUserId(); String operator = userInfoHolder.getUser().getUserId();
if (StringUtils.isEmpty(appNamespace.getDataChangeCreatedBy())) { if (StringUtils.isEmpty(appNamespace.getDataChangeCreatedBy())) {
appNamespace.setDataChangeCreatedBy(operator); appNamespace.setDataChangeCreatedBy(operator);
} }
appNamespace.setDataChangeLastModifiedBy(operator); appNamespace.setDataChangeLastModifiedBy(operator);
AppNamespace createdAppNamespace = namespaceService.createAppNamespaceInLocal(appNamespace); AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace);
publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace)); publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace));
...@@ -100,7 +112,7 @@ public class NamespaceController { ...@@ -100,7 +112,7 @@ public class NamespaceController {
public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env, public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName) { @PathVariable String clusterName) {
return namespaceService.findNampspaces(appId, Env.valueOf(env), clusterName); return namespaceService.findNamespaces(appId, Env.valueOf(env), clusterName);
} }
} }
...@@ -2,15 +2,12 @@ package com.ctrip.framework.apollo.portal.controller; ...@@ -2,15 +2,12 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.core.dto.NamespaceLockDTO; import com.ctrip.framework.apollo.core.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.exception.ServiceException;
import com.ctrip.framework.apollo.portal.service.NamespaceLockService; import com.ctrip.framework.apollo.portal.service.NamespaceLockService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
@RestController @RestController
public class NamespaceLockController { public class NamespaceLockController {
...@@ -22,15 +19,7 @@ public class NamespaceLockController { ...@@ -22,15 +19,7 @@ public class NamespaceLockController {
public NamespaceLockDTO getNamespaceLock(@PathVariable String appId, @PathVariable String env, public NamespaceLockDTO getNamespaceLock(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){ @PathVariable String clusterName, @PathVariable String namespaceName){
try {
return namespaceLockService.getNamespaceLock(appId, Env.valueOf(env), clusterName, namespaceName); return namespaceLockService.getNamespaceLock(appId, Env.valueOf(env), clusterName, namespaceName);
} catch (HttpClientErrorException e){
if (e.getStatusCode() == HttpStatus.NOT_FOUND){
return null;
}
throw new ServiceException("service error", e);
}
} }
} }
...@@ -61,6 +61,15 @@ public class PermissionController { ...@@ -61,6 +61,15 @@ public class PermissionController {
return ResponseEntity.ok().body(permissionCondition); return ResponseEntity.ok().body(permissionCondition);
} }
@RequestMapping("/permissions/root")
public ResponseEntity<PermissionCondition> hasRootPermission(){
PermissionCondition permissionCondition = new PermissionCondition();
permissionCondition.setHasPermission(rolePermissionService.isSuperAdmin(userInfoHolder.getUser().getUserId()));
return ResponseEntity.ok().body(permissionCondition);
}
@RequestMapping("/apps/{appId}/namespaces/{namespaceName}/role_users") @RequestMapping("/apps/{appId}/namespaces/{namespaceName}/role_users")
public NamespaceRolesAssignedUsers getNamespaceRoles(@PathVariable String appId, @PathVariable String namespaceName){ public NamespaceRolesAssignedUsers getNamespaceRoles(@PathVariable String appId, @PathVariable String namespaceName){
......
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.form;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
...@@ -11,8 +12,10 @@ public class NamespaceTextModel implements Verifiable { ...@@ -11,8 +12,10 @@ public class NamespaceTextModel implements Verifiable {
private String clusterName; private String clusterName;
private String namespaceName; private String namespaceName;
private int namespaceId; private int namespaceId;
private String format;
private String configText; private String configText;
@Override @Override
public boolean isInvalid(){ public boolean isInvalid(){
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName) || namespaceId <= 0; return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName) || namespaceId <= 0;
...@@ -65,4 +68,11 @@ public class NamespaceTextModel implements Verifiable { ...@@ -65,4 +68,11 @@ public class NamespaceTextModel implements Verifiable {
this.configText = configText; this.configText = configText;
} }
public ConfigFileFormat getFormat() {
return ConfigFileFormat.fromString(this.format);
}
public void setFormat(String format) {
this.format = format;
}
} }
package com.ctrip.framework.apollo.portal.entity.vo; package com.ctrip.framework.apollo.portal.entity.vo;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO; import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import java.util.List; import java.util.List;
...@@ -8,6 +9,7 @@ public class NamespaceVO { ...@@ -8,6 +9,7 @@ public class NamespaceVO {
private NamespaceDTO namespace; private NamespaceDTO namespace;
private int itemModifiedCnt; private int itemModifiedCnt;
private List<ItemVO> items; private List<ItemVO> items;
private String format;
public NamespaceDTO getNamespace() { public NamespaceDTO getNamespace() {
...@@ -34,6 +36,14 @@ public class NamespaceVO { ...@@ -34,6 +36,14 @@ public class NamespaceVO {
this.items = items; this.items = items;
} }
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public static class ItemVO{ public static class ItemVO{
private ItemDTO item; private ItemDTO item;
private boolean isModified; private boolean isModified;
......
package com.ctrip.framework.apollo.portal.listener; package com.ctrip.framework.apollo.portal.listener;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.ExceptionUtils;
import com.ctrip.framework.apollo.core.dto.AppDTO; import com.ctrip.framework.apollo.core.dto.AppDTO;
import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO; import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.PortalSettings; import com.ctrip.framework.apollo.portal.PortalSettings;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.service.RoleInitializationService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -28,6 +28,8 @@ public class CreationListener { ...@@ -28,6 +28,8 @@ public class CreationListener {
private AdminServiceAPI.AppAPI appAPI; private AdminServiceAPI.AppAPI appAPI;
@Autowired @Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI; private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private RoleInitializationService roleInitializationService;
@EventListener @EventListener
public void onAppCreationEvent(AppCreationEvent event) { public void onAppCreationEvent(AppCreationEvent event) {
...@@ -53,6 +55,11 @@ public class CreationListener { ...@@ -53,6 +55,11 @@ public class CreationListener {
logger.error("call namespaceAPI.createOrUpdateAppNamespace error. [{app}, {env}]", dto.getAppId(), env, e); logger.error("call namespaceAPI.createOrUpdateAppNamespace error. [{app}, {env}]", dto.getAppId(), env, e);
} }
} }
//如果是私有的app namespace 要默认初始化权限
if (!dto.isPublic()) {
roleInitializationService.initNamespaceRoles(dto.getAppId(), dto.getName());
}
} }
} }
...@@ -12,6 +12,8 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa ...@@ -12,6 +12,8 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
AppNamespace findByName(String namespaceName); AppNamespace findByName(String namespaceName);
List<AppNamespace> findByNameNot(String namespaceName); AppNamespace findByNameAndIsPublic(String namespaceName, boolean isPublic);
List<AppNamespace> findByIsPublicTrue();
} }
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.exception.ServiceException;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.repository.AppNamespaceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
@Service
public class AppNamespaceService {
@Autowired
private UserInfoHolder userInfoHolder;
@Autowired
private AppNamespaceRepository appNamespaceRepository;
/**
* 公共的app ns,能被其它项目关联到的app ns
*/
public List<AppNamespace> findPublicAppNamespaces() {
return appNamespaceRepository.findByIsPublicTrue();
}
public AppNamespace findPublicAppNamespace(String namespaceName) {
return appNamespaceRepository.findByNameAndIsPublic(namespaceName, true);
}
public AppNamespace findByAppIdAndName(String appId, String namespaceName) {
return appNamespaceRepository.findByAppIdAndName(appId, namespaceName);
}
@Transactional
public void createDefaultAppNamespace(String appId) {
if (!isAppNamespaceNameUnique(appId, appId)) {
throw new ServiceException("appnamespace not unique");
}
AppNamespace appNs = new AppNamespace();
appNs.setAppId(appId);
appNs.setName(ConfigConsts.NAMESPACE_APPLICATION);
appNs.setComment("default app namespace");
String userId = userInfoHolder.getUser().getUserId();
appNs.setDataChangeCreatedBy(userId);
appNs.setDataChangeLastModifiedBy(userId);
appNamespaceRepository.save(appNs);
}
public boolean isAppNamespaceNameUnique(String appId, String namespaceName) {
Objects.requireNonNull(appId, "AppId must not be null");
Objects.requireNonNull(namespaceName, "Namespace must not be null");
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
}
@Transactional
public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace) {
// unique check
if (appNamespace.isPublic() &&
appNamespaceRepository.findByNameAndIsPublic(appNamespace.getName(), true) != null) {
throw new BadRequestException(appNamespace.getName() + "已存在");
}
if (!appNamespace.isPublic() &&
appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName()) != null) {
throw new BadRequestException(appNamespace.getName() + "已存在");
}
return appNamespaceRepository.save(appNamespace);
}
}
...@@ -8,7 +8,6 @@ import java.util.List; ...@@ -8,7 +8,6 @@ import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
...@@ -34,7 +33,7 @@ public class AppService { ...@@ -34,7 +33,7 @@ public class AppService {
@Autowired @Autowired
private ClusterService clusterService; private ClusterService clusterService;
@Autowired @Autowired
private NamespaceService namespaceService; private AppNamespaceService appNamespaceService;
@Autowired @Autowired
private RoleInitializationService roleInitializationService; private RoleInitializationService roleInitializationService;
...@@ -92,7 +91,7 @@ public class AppService { ...@@ -92,7 +91,7 @@ public class AppService {
throw new BadRequestException(String.format("app id %s already exists!", app.getAppId())); throw new BadRequestException(String.format("app id %s already exists!", app.getAppId()));
} else { } else {
App createdApp = appRepository.save(app); App createdApp = appRepository.save(app);
namespaceService.createDefaultAppNamespace(appId); appNamespaceService.createDefaultAppNamespace(appId);
//role //role
roleInitializationService.initAppRoles(createdApp); roleInitializationService.initAppRoles(createdApp);
return createdApp; return createdApp;
......
...@@ -4,12 +4,14 @@ package com.ctrip.framework.apollo.portal.service; ...@@ -4,12 +4,14 @@ package com.ctrip.framework.apollo.portal.service;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets; import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
...@@ -43,8 +45,14 @@ public class ConfigService { ...@@ -43,8 +45,14 @@ public class ConfigService {
private AdminServiceAPI.ItemAPI itemAPI; private AdminServiceAPI.ItemAPI itemAPI;
@Autowired @Autowired
private AdminServiceAPI.ReleaseAPI releaseAPI; private AdminServiceAPI.ReleaseAPI releaseAPI;
@Autowired
@Qualifier("fileTextResolver")
private ConfigTextResolver fileTextResolver;
@Autowired @Autowired
private ConfigTextResolver resolver; @Qualifier("propertyResolver")
private ConfigTextResolver propertyResolver;
/** /**
...@@ -60,6 +68,7 @@ public class ConfigService { ...@@ -60,6 +68,7 @@ public class ConfigService {
long namespaceId = model.getNamespaceId(); long namespaceId = model.getNamespaceId();
String configText = model.getConfigText(); String configText = model.getConfigText();
ConfigTextResolver resolver = model.getFormat() == ConfigFileFormat.Properties ? propertyResolver : fileTextResolver;
ItemChangeSets changeSets = resolver.resolve(namespaceId, configText, ItemChangeSets changeSets = resolver.resolve(namespaceId, configText,
itemAPI.findItems(appId, env, clusterName, namespaceName)); itemAPI.findItems(appId, env, clusterName, namespaceName));
if (changeSets.isEmpty()) { if (changeSets.isEmpty()) {
......
...@@ -13,7 +13,6 @@ public class NamespaceLockService { ...@@ -13,7 +13,6 @@ public class NamespaceLockService {
@Autowired @Autowired
private AdminServiceAPI.NamespaceLockAPI namespaceLockAPI; private AdminServiceAPI.NamespaceLockAPI namespaceLockAPI;
public NamespaceLockDTO getNamespaceLock(String appId, Env env, String clusterName, String namespaceName){ public NamespaceLockDTO getNamespaceLock(String appId, Env env, String clusterName, String namespaceName){
return namespaceLockAPI.getNamespaceLockOwner(appId, env, clusterName, namespaceName); return namespaceLockAPI.getNamespaceLockOwner(appId, env, clusterName, namespaceName);
......
...@@ -5,41 +5,33 @@ import com.google.gson.Gson; ...@@ -5,41 +5,33 @@ import com.google.gson.Gson;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.ExceptionUtils; import com.ctrip.framework.apollo.common.utils.ExceptionUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO; import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.dto.ReleaseDTO; import com.ctrip.framework.apollo.core.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.exception.ServiceException;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.PortalSettings;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.repository.AppNamespaceRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
@Service @Service
public class NamespaceService { public class NamespaceService {
private Logger logger = LoggerFactory.getLogger(NamespaceService.class); private Logger logger = LoggerFactory.getLogger(NamespaceService.class);
private Gson gson = new Gson();
@Autowired @Autowired
private UserInfoHolder userInfoHolder; private UserInfoHolder userInfoHolder;
...@@ -49,19 +41,13 @@ public class NamespaceService { ...@@ -49,19 +41,13 @@ public class NamespaceService {
private AdminServiceAPI.ReleaseAPI releaseAPI; private AdminServiceAPI.ReleaseAPI releaseAPI;
@Autowired @Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI; private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private PortalSettings portalSettings;
@Autowired
private AppNamespaceRepository appNamespaceRepository;
@Autowired @Autowired
private RoleInitializationService roleInitializationService; private RoleInitializationService roleInitializationService;
@Autowired
private Gson gson = new Gson(); private AppNamespaceService appNamespaceService;
public List<AppNamespace> findPublicAppNamespaces() {
return appNamespaceRepository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION);
}
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) { public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
if (StringUtils.isEmpty(namespace.getDataChangeCreatedBy())) { if (StringUtils.isEmpty(namespace.getDataChangeCreatedBy())) {
...@@ -74,48 +60,11 @@ public class NamespaceService { ...@@ -74,48 +60,11 @@ public class NamespaceService {
return createdNamespace; return createdNamespace;
} }
@Transactional
public void createDefaultAppNamespace(String appId) {
if (!isAppNamespaceNameUnique(appId, appId)) {
throw new ServiceException("appnamespace not unique");
}
AppNamespace appNs = new AppNamespace();
appNs.setAppId(appId);
appNs.setName(ConfigConsts.NAMESPACE_APPLICATION);
appNs.setComment("default app namespace");
String userId = userInfoHolder.getUser().getUserId();
appNs.setDataChangeCreatedBy(userId);
appNs.setDataChangeLastModifiedBy(userId);
appNamespaceRepository.save(appNs);
}
public boolean isAppNamespaceNameUnique(String appId, String namespaceName) {
Objects.requireNonNull(appId, "AppId must not be null");
Objects.requireNonNull(namespaceName, "Namespace must not be null");
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
}
@Transactional
public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace) {
//not unique
if (appNamespaceRepository.findByName(appNamespace.getName()) != null){
throw new BadRequestException(appNamespace.getName() + "已存在");
}
AppNamespace managedAppNamespace = appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName());
//update
if (managedAppNamespace != null){
BeanUtils.copyEntityProperties(appNamespace, managedAppNamespace);
return appNamespaceRepository.save(managedAppNamespace);
}else {
return appNamespaceRepository.save(appNamespace);
}
}
/** /**
* load cluster all namespace info with items * load cluster all namespace info with items
*/ */
public List<NamespaceVO> findNampspaces(String appId, Env env, String clusterName) { public List<NamespaceVO> findNamespaces(String appId, Env env, String clusterName) {
List<NamespaceDTO> namespaces = namespaceAPI.findNamespaceByCluster(appId, env, clusterName); List<NamespaceDTO> namespaces = namespaceAPI.findNamespaceByCluster(appId, env, clusterName);
if (namespaces == null || namespaces.size() == 0) { if (namespaces == null || namespaces.size() == 0) {
...@@ -144,6 +93,15 @@ public class NamespaceService { ...@@ -144,6 +93,15 @@ public class NamespaceService {
NamespaceVO namespaceVO = new NamespaceVO(); NamespaceVO namespaceVO = new NamespaceVO();
namespaceVO.setNamespace(namespace); namespaceVO.setNamespace(namespace);
//先从当前appId下面找,包含私有的和公共的
AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespace.getNamespaceName());
//再从公共的app namespace里面找
if (appNamespace == null) {
appNamespace = appNamespaceService.findPublicAppNamespace(namespace.getNamespaceName());
}
namespaceVO.setFormat(appNamespace.getFormat());
List<NamespaceVO.ItemVO> itemVos = new LinkedList<>(); List<NamespaceVO.ItemVO> itemVos = new LinkedList<>();
namespaceVO.setItems(itemVos); namespaceVO.setItems(itemVos);
......
...@@ -199,7 +199,7 @@ public class RolePermissionService implements InitializingBean { ...@@ -199,7 +199,7 @@ public class RolePermissionService implements InitializingBean {
return false; return false;
} }
private boolean isSuperAdmin(String userId) { public boolean isSuperAdmin(String userId) {
return superAdminUsers.contains(userId); return superAdminUsers.contains(userId);
} }
......
package com.ctrip.framework.apollo.portal.service.txtresolver;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Component("fileTextResolver")
public class FileTextResolver implements ConfigTextResolver {
@Override
public ItemChangeSets resolve(long namespaceId, String configText, List<ItemDTO> baseItems) {
ItemChangeSets changeSets = new ItemChangeSets();
if (StringUtils.isEmpty(configText)) {
return changeSets;
}
if (CollectionUtils.isEmpty(baseItems)) {
changeSets.addCreateItem(createItem(namespaceId, configText));
} else {
ItemDTO beforeItem = baseItems.get(0);
if (!configText.equals(beforeItem.getValue())) {//update
changeSets.addUpdateItem(createItem(namespaceId, configText));
}
}
return changeSets;
}
private ItemDTO createItem(long namespaceId, String value) {
ItemDTO item = new ItemDTO();
item.setNamespaceId(namespaceId);
item.setValue(value);
item.setKey(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
return item;
}
}
...@@ -18,7 +18,7 @@ import java.util.Set; ...@@ -18,7 +18,7 @@ import java.util.Set;
* update comment and blank item implement by create new item and delete old item. * update comment and blank item implement by create new item and delete old item.
* update normal key/value item implement by update. * update normal key/value item implement by update.
*/ */
@Component @Component("propertyResolver")
public class PropertyResolver implements ConfigTextResolver { public class PropertyResolver implements ConfigTextResolver {
private static final String KV_SEPARATOR = "="; private static final String KV_SEPARATOR = "=";
......
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
<script type="application/javascript" src="scripts/services/UserService.js"></script> <script type="application/javascript" src="scripts/services/UserService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
<script type="application/javascript" src="scripts/services/OrganizationService.js"></script> <script type="application/javascript" src="scripts/services/OrganizationService.js"></script>
<script type="application/javascript" src="scripts/directive.js"></script> <script type="application/javascript" src="scripts/directive/directive.js"></script>
<script type="application/javascript" src="scripts/controller/AppController.js"></script> <script type="application/javascript" src="scripts/controller/AppController.js"></script>
</body> </body>
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
</h4> </h4>
</div> </div>
<div class="col-md-5 text-right"> <div class="col-md-5 text-right">
<a type="button" class="btn btn-success" data-dismiss="modal" <a type="button" class="btn btn-info" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回 href="/config.html?#appid={{pageContext.appId}}">返回到项目首页
</a> </a>
</div> </div>
</div> </div>
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
<script type="application/javascript" src="../scripts/AppUtils.js"></script> <script type="application/javascript" src="../scripts/AppUtils.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script> <script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive.js"></script> <script type="application/javascript" src="../scripts/directive/directive.js"></script>
<script type="application/javascript" src="../scripts/controller/role/AppRoleController.js"></script> <script type="application/javascript" src="../scripts/controller/role/AppRoleController.js"></script>
</body> </body>
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<h4>创建集群</h4> <h4>创建集群</h4>
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<a type="button" class="btn btn-primary" href="/config.html?#/appid={{appId}}">返回 <a type="button" class="btn btn-info" href="/config.html?#/appid={{appId}}">返回到项目首页
</a> </a>
</div> </div>
</div> </div>
...@@ -106,7 +106,7 @@ ...@@ -106,7 +106,7 @@
<script type="application/javascript" src="scripts/services/UserService.js"></script> <script type="application/javascript" src="scripts/services/UserService.js"></script>
<script type="application/javascript" src="scripts/services/ClusterService.js"></script> <script type="application/javascript" src="scripts/services/ClusterService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
<script type="application/javascript" src="scripts/directive.js"></script> <script type="application/javascript" src="scripts/directive/directive.js"></script>
<script type="application/javascript" src="scripts/controller/ClusterController.js"></script> <script type="application/javascript" src="scripts/controller/ClusterController.js"></script>
</body> </body>
......
...@@ -34,8 +34,8 @@ ...@@ -34,8 +34,8 @@
<button type="button" class="btn btn-success" ng-show="syncItemStep == 2 && hasDiff" <button type="button" class="btn btn-success" ng-show="syncItemStep == 2 && hasDiff"
ng-click="syncItems()">同步 ng-click="syncItems()">同步
</button> </button>
<button type="button" class="btn btn-success" data-dismiss="modal" ng-show="syncItemStep == 3" <button type="button" class="btn btn-info" data-dismiss="modal" ng-show="syncItemStep == 3"
ng-click="backToAppHomePage()">返回 ng-click="backToAppHomePage()">返回到项目首页
</button> </button>
</div> </div>
</div> </div>
...@@ -212,6 +212,6 @@ ...@@ -212,6 +212,6 @@
<script type="application/javascript" src="../scripts/controller/config/SyncConfigController.js"></script> <script type="application/javascript" src="../scripts/controller/config/SyncConfigController.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script> <script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive.js"></script> <script type="application/javascript" src="../scripts/directive/directive.js"></script>
</body> </body>
</html> </html>
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<div class="row"> <div class="row">
<div class="col-md-6">新建Namespace</div> <div class="col-md-6">新建Namespace</div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<button type="button" class="btn btn-success" ng-show="step == 2" ng-click="back()">返回 <button type="button" class="btn btn-info" ng-click="back()">返回到项目首页
</button> </button>
</div> </div>
</div> </div>
...@@ -69,8 +69,28 @@ ...@@ -69,8 +69,28 @@
ng-required="type == 'create'"> ng-required="type == 'create'">
</div> </div>
</div> </div>
<div class="col-sm-2" ng-if="hasRootPermission">
<select class="form-control" ng-model="appNamespace.format">
<option value="properties">properties</option>
<option value="xml">xml</option>
</select>
</div>
<span ng-bind="concatNamespace()" style="line-height: 34px;"></span> <span ng-bind="concatNamespace()" style="line-height: 34px;"></span>
</div> </div>
<div class="form-group" ng-show="type == 'create' && hasRootPermission">
<label class="col-sm-3 control-label">
<apollorequiredfiled></apollorequiredfiled>
类型</label>
<div class="col-sm-4">
<label class="radio-inline">
<input type="radio" name="namespaceType" value="true" ng-value="true" ng-model="appNamespace.isPublic"> public
</label>
<label class="radio-inline">
<input type="radio" name="namespaceType" value="false" ng-value="false" ng-model="appNamespace.isPublic"> private
</label>
</div>
</div>
<div class="form-group" ng-show="type == 'create'"> <div class="form-group" ng-show="type == 'create'">
<label class="col-sm-3 control-label">备注</label> <label class="col-sm-3 control-label">备注</label>
<div class="col-sm-7"> <div class="col-sm-7">
...@@ -128,10 +148,11 @@ ...@@ -128,10 +148,11 @@
<script type="application/javascript" src="scripts/services/EnvService.js"></script> <script type="application/javascript" src="scripts/services/EnvService.js"></script>
<script type="application/javascript" src="scripts/services/UserService.js"></script> <script type="application/javascript" src="scripts/services/UserService.js"></script>
<script type="application/javascript" src="scripts/services/NamespaceService.js"></script> <script type="application/javascript" src="scripts/services/NamespaceService.js"></script>
<script type="application/javascript" src="scripts/services/PermissionService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
<!--directive--> <!--directive-->
<script type="application/javascript" src="scripts/directive.js"></script> <script type="application/javascript" src="scripts/directive/directive.js"></script>
<script type="application/javascript" src="scripts/controller/NamespaceController.js"></script> <script type="application/javascript" src="scripts/controller/NamespaceController.js"></script>
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
</h4> </h4>
</div> </div>
<div class="col-md-5 text-right"> <div class="col-md-5 text-right">
<a type="button" class="btn btn-success" data-dismiss="modal" <a type="button" class="btn btn-info" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回 href="/config.html?#appid={{pageContext.appId}}">返回到项目首页
</a> </a>
</div> </div>
</div> </div>
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
<script type="application/javascript" src="../scripts/AppUtils.js"></script> <script type="application/javascript" src="../scripts/AppUtils.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script> <script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive.js"></script> <script type="application/javascript" src="../scripts/directive/directive.js"></script>
<script type="application/javascript" src="../scripts/controller/role/NamespaceRoleController.js"></script> <script type="application/javascript" src="../scripts/controller/role/NamespaceRoleController.js"></script>
</body> </body>
......
namespace_module.controller("LinkNamespaceController", namespace_module.controller("LinkNamespaceController",
['$scope', '$location', '$window', 'toastr', 'AppService', 'AppUtil', 'NamespaceService', ['$scope', '$location', '$window', 'toastr', 'AppService', 'AppUtil', 'NamespaceService',
function ($scope, $location, $window, toastr, AppService, AppUtil, NamespaceService) { 'PermissionService',
function ($scope, $location, $window, toastr, AppService, AppUtil, NamespaceService,
PermissionService) {
var params = AppUtil.parseParams($location.$$url); var params = AppUtil.parseParams($location.$$url);
$scope.appId = params.appid; $scope.appId = params.appid;
...@@ -8,6 +10,10 @@ namespace_module.controller("LinkNamespaceController", ...@@ -8,6 +10,10 @@ namespace_module.controller("LinkNamespaceController",
$scope.step = 1; $scope.step = 1;
PermissionService.has_root_permission().then(function (result) {
$scope.hasRootPermission = result.hasPermission;
});
NamespaceService.find_public_namespaces().then(function (result) { NamespaceService.find_public_namespaces().then(function (result) {
var publicNamespaces = []; var publicNamespaces = [];
result.forEach(function (item) { result.forEach(function (item) {
...@@ -35,7 +41,13 @@ namespace_module.controller("LinkNamespaceController", ...@@ -35,7 +41,13 @@ namespace_module.controller("LinkNamespaceController",
$scope.appNamespace = { $scope.appNamespace = {
appId: $scope.appId, appId: $scope.appId,
name: '', name: '',
comment: '' comment: '',
isPublic: false,
format: 'xml'
};
$scope.switchNSType = function (type) {
$scope.appNamespace.isPublic = type;
}; };
$scope.concatNamespace = function () { $scope.concatNamespace = function () {
...@@ -95,7 +107,13 @@ namespace_module.controller("LinkNamespaceController", ...@@ -95,7 +107,13 @@ namespace_module.controller("LinkNamespaceController",
function (result) { function (result) {
$scope.step = 2; $scope.step = 2;
setInterval(function () { setInterval(function () {
$window.location.reload(); if ($scope.appNamespace.isPublic) {
$window.location.reload();
} else {//private的直接link并且跳转到授权页面
$window.location.href =
"/namespace/role.html?#/appid=" + $scope.appId
+ "&namespaceName=" + $scope.appNamespace.name;
}
}, 1000); }, 1000);
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "创建失败"); toastr.error(AppUtil.errorMsg(result), "创建失败");
......
...@@ -34,6 +34,7 @@ application_module.controller("ConfigBaseInfoController", ...@@ -34,6 +34,7 @@ application_module.controller("ConfigBaseInfoController",
var node = {}; var node = {};
//first nav //first nav
node.text = env.env; node.text = env.env;
// node.icon = 'glyphicon glyphicon-console';
var clusterNodes = []; var clusterNodes = [];
//如果env下面只有一个default集群则不显示集群列表 //如果env下面只有一个default集群则不显示集群列表
...@@ -57,8 +58,10 @@ application_module.controller("ConfigBaseInfoController", ...@@ -57,8 +58,10 @@ application_module.controller("ConfigBaseInfoController",
} }
clusterNode.text = cluster.name; clusterNode.text = cluster.name;
// clusterNode.icon = 'glyphicon glyphicon-object-align-vertical';
parentNode.push(node.text); parentNode.push(node.text);
clusterNode.tags = parentNode; clusterNode.tags = ['集群'];
clusterNode.parentNode = parentNode;
clusterNodes.push(clusterNode); clusterNodes.push(clusterNode);
}); });
} }
...@@ -73,13 +76,14 @@ application_module.controller("ConfigBaseInfoController", ...@@ -73,13 +76,14 @@ application_module.controller("ConfigBaseInfoController",
levels: 99, levels: 99,
expandIcon: '', expandIcon: '',
collapseIcon: '', collapseIcon: '',
showTags: true,
onNodeSelected: function (event, data) { onNodeSelected: function (event, data) {
if (!data.tags) {//first nav node if (!data.parentNode) {//first nav node
$rootScope.pageContext.env = data.text; $rootScope.pageContext.env = data.text;
$rootScope.pageContext.clusterName = $rootScope.pageContext.clusterName =
'default'; 'default';
} else {//second cluster node } else {//second cluster node
$rootScope.pageContext.env = data.tags[0]; $rootScope.pageContext.env = data.parentNode[0];
$rootScope.pageContext.clusterName = $rootScope.pageContext.clusterName =
data.text; data.text;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
directive_module.directive('apollonav', function ($compile, $window, toastr, AppUtil, AppService, EnvService, UserService) { directive_module.directive('apollonav', function ($compile, $window, toastr, AppUtil, AppService, EnvService, UserService) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../views/common/nav.html', templateUrl: '../../views/common/nav.html',
transclude: true, transclude: true,
replace: true, replace: true,
link: function (scope, element, attrs) { link: function (scope, element, attrs) {
...@@ -120,7 +120,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App ...@@ -120,7 +120,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App
directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) { directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../views/component/env-selector.html', templateUrl: '../../views/component/env-selector.html',
transclude: true, transclude: true,
replace: true, replace: true,
scope: { scope: {
...@@ -218,7 +218,7 @@ directive_module.directive('apollorequiredfiled', function ($compile, $window) { ...@@ -218,7 +218,7 @@ directive_module.directive('apollorequiredfiled', function ($compile, $window) {
directive_module.directive('apolloconfirmdialog', function ($compile, $window) { directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../views/component/confirm-dialog.html', templateUrl: '../../views/component/confirm-dialog.html',
transclude: true, transclude: true,
replace: true, replace: true,
scope: { scope: {
...@@ -244,7 +244,7 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) { ...@@ -244,7 +244,7 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
directive_module.directive('apolloentrance', function ($compile, $window) { directive_module.directive('apolloentrance', function ($compile, $window) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../views/component/entrance.html', templateUrl: '../../views/component/entrance.html',
transclude: true, transclude: true,
replace: true, replace: true,
scope: { scope: {
...@@ -262,7 +262,7 @@ directive_module.directive('apolloentrance', function ($compile, $window) { ...@@ -262,7 +262,7 @@ directive_module.directive('apolloentrance', function ($compile, $window) {
directive_module.directive('apollouserselector', function ($compile, $window) { directive_module.directive('apollouserselector', function ($compile, $window) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../views/component/user-selector.html', templateUrl: '../../views/component/user-selector.html',
transclude: true, transclude: true,
replace: true, replace: true,
scope: { scope: {
......
...@@ -72,7 +72,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -72,7 +72,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
return d.promise; return d.promise;
}, },
modify_items: function (appId, env, clusterName, namespaceName, configText, namespaceId, comment) { modify_items: function (appId, env, clusterName, namespaceName, model) {
var d = $q.defer(); var d = $q.defer();
config_source.modify_items({ config_source.modify_items({
appId: appId, appId: appId,
...@@ -80,11 +80,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -80,11 +80,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
clusterName: clusterName, clusterName: clusterName,
namespaceName: namespaceName namespaceName: namespaceName
}, },
{ model, function (result) {
configText: configText,
namespaceId: namespaceId,
comment: comment
}, function (result) {
d.resolve(result); d.resolve(result);
}, function (result) { }, function (result) {
......
...@@ -5,6 +5,7 @@ appService.service('NamespaceLockService', ['$resource', '$q', function ($resour ...@@ -5,6 +5,7 @@ appService.service('NamespaceLockService', ['$resource', '$q', function ($resour
url: 'apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/lock' url: 'apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/lock'
} }
}); });
return { return {
get_namespace_lock: function (appId, env, clusterName, namespaceName) { get_namespace_lock: function (appId, env, clusterName, namespaceName) {
var d = $q.defer(); var d = $q.defer();
......
...@@ -8,6 +8,10 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource, ...@@ -8,6 +8,10 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource,
method: 'GET', method: 'GET',
url: '/apps/:appId/namespaces/:namespaceName/permissions/:permissionType' url: '/apps/:appId/namespaces/:namespaceName/permissions/:permissionType'
}, },
has_root_permission:{
method: 'GET',
url: '/permissions/root'
},
get_namespace_role_users: { get_namespace_role_users: {
method: 'GET', method: 'GET',
url: '/apps/:appId/namespaces/:namespaceName/role_users' url: '/apps/:appId/namespaces/:namespaceName/role_users'
...@@ -110,6 +114,17 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource, ...@@ -110,6 +114,17 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource,
has_release_namespace_permission: function (appId, namespaceName) { has_release_namespace_permission: function (appId, namespaceName) {
return hasNamespacePermission(appId, namespaceName, 'ReleaseNamespace'); return hasNamespacePermission(appId, namespaceName, 'ReleaseNamespace');
}, },
has_root_permission: function () {
var d = $q.defer();
permission_resource.has_root_permission({ },
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
assign_modify_namespace_role: function (appId, namespaceName, user) { assign_modify_namespace_role: function (appId, namespaceName, user) {
return assignNamespaceRoleToUser(appId, namespaceName, 'ModifyNamespace', user); return assignNamespaceRoleToUser(appId, namespaceName, 'ModifyNamespace', user);
}, },
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
<script src="vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script> <script src="vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script type="application/javascript" src="scripts/app.js"></script> <script type="application/javascript" src="scripts/app.js"></script>
<script type="application/javascript" src="scripts/directive.js"></script> <script type="application/javascript" src="scripts/directive/directive.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
<script type="application/javascript" src="scripts/services/AppService.js"></script> <script type="application/javascript" src="scripts/services/AppService.js"></script>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="http://conf.ctripcorp.com/pages/viewpage.action?pageId=98435462" target="_blank"> <li><a href="http://conf.ctripcorp.com/display/FRAM/Apollo" target="_blank">
Help</span> Help</span>
</a></li> </a></li>
<li class="dropdown"> <li class="dropdown">
......
...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.core.ConfigConsts; ...@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets; import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO; import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
...@@ -21,6 +22,7 @@ import org.junit.runner.RunWith; ...@@ -21,6 +22,7 @@ import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -47,6 +49,7 @@ public class ConfigServiceTest { ...@@ -47,6 +49,7 @@ public class ConfigServiceTest {
@Before @Before
public void setup() { public void setup() {
ReflectionTestUtils.setField(configService, "propertyResolver", resolver);
} }
@Test @Test
...@@ -61,7 +64,7 @@ public class ConfigServiceTest { ...@@ -61,7 +64,7 @@ public class ConfigServiceTest {
model.setClusterName(clusterName); model.setClusterName(clusterName);
model.setAppId(appId); model.setAppId(appId);
model.setConfigText("a=b\nb=c\nc=d\nd=e"); model.setConfigText("a=b\nb=c\nc=d\nd=e");
model.setFormat(ConfigFileFormat.Properties.getValue());
List<ItemDTO> itemDTOs = mockBaseItemHas3Key(); List<ItemDTO> itemDTOs = mockBaseItemHas3Key();
ItemChangeSets changeSets = new ItemChangeSets(); ItemChangeSets changeSets = new ItemChangeSets();
changeSets.addCreateItem(new ItemDTO("d", "c", "", 4)); changeSets.addCreateItem(new ItemDTO("d", "c", "", 4));
......
...@@ -9,6 +9,7 @@ import com.ctrip.framework.apollo.portal.service.txtresolver.ConfigTextResolver; ...@@ -9,6 +9,7 @@ import com.ctrip.framework.apollo.portal.service.txtresolver.ConfigTextResolver;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -17,6 +18,7 @@ import java.util.List; ...@@ -17,6 +18,7 @@ import java.util.List;
public class PropertyResolverTest extends AbstractPortalTest { public class PropertyResolverTest extends AbstractPortalTest {
@Autowired @Autowired
@Qualifier("propertyResolver")
private ConfigTextResolver resolver; private ConfigTextResolver resolver;
@Test @Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment