Commit 5c6081a0 authored by lepdou's avatar lepdou

namespace as file

parent 060126fd
......@@ -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.ItemDTO;
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.Before;
......@@ -42,13 +43,14 @@ public class NamespaceLockAspect {
@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());
}
@Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)")
public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemChangeSets changeSet) {
ItemChangeSets changeSet) {
acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy());
}
......@@ -59,7 +61,8 @@ public class NamespaceLockAspect {
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()) {
return;
}
......@@ -90,17 +93,15 @@ public class NamespaceLockAspect {
//lock success
} catch (DataIntegrityViolationException e) {
//lock fail
acquireLockFail(namespace, currentUser);
} catch (Exception e){
namespaceLock = namespaceLockService.findLock(namespaceId);
checkLock(namespace, namespaceLock, currentUser);
} catch (Exception e) {
logger.error("try lock error", e);
throw e;
}
} else {
//check lock owner is current user
String lockOwner = namespaceLock.getDataChangeCreatedBy();
if (!lockOwner.equals(currentUser)) {
acquireLockFail(namespace, currentUser);
}
checkLock(namespace, namespaceLock, currentUser);
}
}
......@@ -112,15 +113,19 @@ public class NamespaceLockAspect {
namespaceLockService.tryLock(lock);
}
private void acquireLockFail(Namespace namespace, String currentUser){
NamespaceLock namespaceLock = namespaceLockService.findLock(namespace.getId());
if (namespaceLock == null){
acquireLock(namespace, currentUser);
private void checkLock(Namespace namespace, NamespaceLock namespaceLock,
String currentUser) {
if (namespaceLock == null) {
throw new ServiceException(
String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
}
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);
}
}
}
......@@ -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.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import java.util.List;
......@@ -20,18 +21,6 @@ public class AppNamespaceController {
@Autowired
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)
public AppNamespaceDTO createOrUpdate( @RequestBody AppNamespaceDTO appNamespace){
......@@ -39,8 +28,7 @@ public class AppNamespaceController {
AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
if (managedEntity != null){
BeanUtils.copyEntityProperties(entity, managedEntity);
entity = appNamespaceService.update(managedEntity);
throw new BadRequestException("app namespaces already exist.");
}else {
entity = appNamespaceService.createAppNamespace(entity, entity.getDataChangeCreatedBy());
}
......
......@@ -32,8 +32,7 @@ public class ClusterController {
Cluster entity = BeanUtils.transfrom(Cluster.class, dto);
Cluster managedEntity = clusterService.findOne(appId, entity.getName());
if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity);
entity = clusterService.update(managedEntity);
throw new BadRequestException("cluster already exist.");
} else {
entity = clusterService.save(entity);
}
......
......@@ -33,8 +33,7 @@ public class NamespaceController {
Namespace entity = BeanUtils.transfrom(Namespace.class, dto);
Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName());
if (managedEntity != null) {
BeanUtils.copyEntityProperties(entity, managedEntity);
entity = namespaceService.update(managedEntity);
throw new BadRequestException("namespace already exist.");
} else {
entity = namespaceService.save(entity);
}
......
......@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.common.entity.BaseEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
import org.hibernate.annotations.SQLDelete;
......@@ -22,6 +23,7 @@ public class Item extends BaseEntity {
private String key;
@Column(name = "value")
@Lob
private String value;
@Column(name = "comment")
......
......@@ -10,8 +10,8 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
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 {
clusterService.createDefaultCluster(appId, createBy);
namespaceService.createDefaultNamespace(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
namespaceService.createPrivateNamespace(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
return app;
}
......
......@@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository;
......@@ -22,7 +24,10 @@ public class AppNamespaceService {
@Autowired
private AppNamespaceRepository appNamespaceRepository;
@Autowired
private NamespaceService namespaceService;
@Autowired
private ClusterService clusterService;
@Autowired
private AuditService auditService;
......@@ -32,9 +37,13 @@ public class AppNamespaceService {
return Objects.isNull(appNamespaceRepository.findByAppIdAndName(appId, namespaceName));
}
public AppNamespace findByNamespaceName(String namespaceName) {
public AppNamespace findPublicByNamespaceName(String namespaceName) {
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){
......@@ -68,16 +77,28 @@ public class AppNamespaceService {
appNamespace.setDataChangeCreatedBy(createBy);
appNamespace.setDataChangeLastModifiedBy(createBy);
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,
createBy);
return appNamespace;
}
public List<AppNamespace> findPublicAppNamespaces(){
return appNamespaceRepository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION);
}
public AppNamespace update(AppNamespace appNamespace){
AppNamespace managedNs = appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName());
BeanUtils.copyEntityProperties(appNamespace, managedNs);
......
......@@ -22,12 +22,13 @@ public class ClusterService {
@Autowired
private ClusterRepository clusterRepository;
@Autowired
private AuditService auditService;
@Autowired
private NamespaceService namespaceService;
@Autowired
private AppNamespaceService appNamespaceService;
public boolean isClusterNameUnique(String appId, String clusterName) {
Objects.requireNonNull(appId, "AppId must not be null");
......@@ -59,7 +60,7 @@ public class ClusterService {
entity.setId(0);//protection
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,
cluster.getDataChangeCreatedBy());
......
......@@ -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.common.utils.BeanUtils;
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.stereotype.Service;
......@@ -27,18 +28,22 @@ public class ItemService {
@Autowired
private AuditService auditService;
@Autowired
private ServerConfigService serverConfigService;
@Transactional
public void delete(long id, String operator) {
public Item delete(long id, String operator) {
Item item = itemRepository.findOne(id);
if (item == null) {
return;
throw new IllegalArgumentException("item not exist. ID:" + id);
}
item.setDeleted(true);
item.setDataChangeLastModifiedBy(operator);
itemRepository.save(item);
Item deletedItem = itemRepository.save(item);
auditService.audit(Item.class.getSimpleName(), id, Audit.OP.DELETE, operator);
return deletedItem;
}
public Item findOne(String appId, String clusterName, String namespaceName, String key) {
......@@ -88,6 +93,8 @@ public class ItemService {
@Transactional
public Item save(Item entity) {
checkItemValueLength(entity.getValue());
entity.setId(0);//protection
Item item = itemRepository.save(entity);
......@@ -99,6 +106,7 @@ public class ItemService {
@Transactional
public Item update(Item item) {
checkItemValueLength(item.getValue());
Item managedItem = itemRepository.findOne(item.getId());
BeanUtils.copyEntityProperties(item, managedItem);
managedItem = itemRepository.save(managedItem);
......@@ -109,4 +117,12 @@ public class ItemService {
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;
}
}
......@@ -28,6 +28,9 @@ public class ItemSetService {
@Autowired
private CommitService commitService;
@Autowired
private ItemService itemService;
@Transactional
public ItemChangeSets updateSet(String appId, String clusterName,
......@@ -38,10 +41,9 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) {
for (ItemDTO item : changeSet.getCreateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item);
entity.setId(0);//protection
entity.setDataChangeCreatedBy(operator);
entity.setDataChangeLastModifiedBy(operator);
Item createdItem = itemRepository.save(entity);
Item createdItem = itemService.save(entity);
configChangeContentBuilder.createItem(createdItem);
}
auditService.audit("ItemSet", null, Audit.OP.INSERT, operator);
......@@ -50,11 +52,11 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getUpdateItems())) {
for (ItemDTO item : changeSet.getUpdateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item);
Item managedItem = itemRepository.findOne(entity.getId());
Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedItem);
BeanUtils.copyEntityProperties(entity, managedItem);
managedItem.setDataChangeLastModifiedBy(operator);
Item updatedItem = itemRepository.save(managedItem);
Item beforeUpdateItem = itemRepository.findOne(entity.getId());
entity.setDataChangeLastModifiedBy(operator);
Item updatedItem = itemService.update(entity);
configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem);
}
......@@ -63,25 +65,24 @@ public class ItemSetService {
if (!CollectionUtils.isEmpty(changeSet.getDeleteItems())) {
for (ItemDTO item : changeSet.getDeleteItems()) {
Item entity = BeanUtils.transfrom(Item.class, item);
entity.setDeleted(true);
entity.setDataChangeLastModifiedBy(operator);
Item deletedItem = itemRepository.save(entity);
Item deletedItem = itemService.delete(item.getId(), operator);
configChangeContentBuilder.deleteItem(deletedItem);
}
auditService.audit("ItemSet", null, Audit.OP.DELETE, operator);
}
String configChangeContent = configChangeContentBuilder.build();
if (!StringUtils.isEmpty(configChangeContent)){
createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(), changeSet.getDataChangeLastModifiedBy());
if (!StringUtils.isEmpty(configChangeContent)) {
createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(),
changeSet.getDataChangeLastModifiedBy());
}
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.setAppId(appId);
......
......@@ -5,6 +5,7 @@ import com.ctrip.framework.apollo.biz.repository.NamespaceLockRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class NamespaceLockService {
......@@ -17,10 +18,12 @@ public class NamespaceLockService {
return namespaceLockRepository.findByNamespaceId(namespaceId);
}
@Transactional
public NamespaceLock tryLock(NamespaceLock lock){
return namespaceLockRepository.save(lock);
}
@Transactional
public void unlock(Long namespaceId){
namespaceLockRepository.deleteByNamespaceId(namespaceId);
}
......
......@@ -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.Namespace;
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.core.ConfigConsts;
import com.ctrip.framework.apollo.core.exception.ServiceException;
@Service
......@@ -20,9 +20,10 @@ public class NamespaceService {
@Autowired
private NamespaceRepository namespaceRepository;
@Autowired
private AuditService auditService;
@Autowired
private AppNamespaceService appNamespaceService;
public boolean isNamespaceUnique(String appId, String cluster, String namespace) {
Objects.requireNonNull(appId, "AppId must not be null");
......@@ -91,19 +92,21 @@ public class NamespaceService {
}
@Transactional
public void createDefaultNamespace(String appId, String clusterName, String createBy) {
if (!isNamespaceUnique(appId, clusterName, appId)) {
throw new ServiceException("namespace not unique");
public void createPrivateNamespace(String appId, String clusterName, String createBy) {
//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);
}
}
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Validator {
@Autowired
private ServerConfigService serverConfigService;
public boolean checkItemValueLength(String value){
int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.value.length.limit", "65536"));
if (!StringUtils.isEmpty(value) && value.length() > lengthLimit){
throw new BadRequestException("value too long. length limit:" + lengthLimit);
}
return true;
}
}
......@@ -2,9 +2,7 @@ package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.BizTestConfiguration;
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.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -13,7 +11,8 @@ import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
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)
@SpringApplicationConfiguration(classes = BizTestConfiguration.class)
......@@ -25,9 +24,16 @@ public class AppNamespaceRepositoryTest {
private AppNamespaceRepository repository;
@Test
public void testFindAllPublicAppNamespaces(){
List<AppNamespace> appNamespaceList = repository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION);
Assert.assertEquals(4, appNamespaceList.size());
public void testFindByNameAndIsPublicTrue() throws Exception {
AppNamespace appNamespace = repository.findByNameAndIsPublicTrue("fx.apollo.config");
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) VALUES ('100003171', 'fx.apollo.config');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'fx.apollo.admin');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'fx.apollo.portal');
INSERT INTO AppNamespace (AppID, Name) VALUES ('fxhermesproducer', 'fx.hermes.producer');
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'fx.apollo.config', true);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'fx.apollo.admin', true);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003173', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003173', 'fx.apollo.portal', true);
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;
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.internals.ConfigManager;
import com.ctrip.framework.apollo.spi.ConfigFactory;
......@@ -38,10 +39,13 @@ public class ConfigService {
* @return config instance
*/
public static Config getConfig(String namespace) {
return getManager().getConfig(namespace);
}
public static ConfigFile getConfigFile(String namespacePrefix, ConfigFileFormat configFileFormat) {
return getManager().getConfigFile(namespacePrefix, configFileFormat);
}
private static ConfigManager getManager() {
try {
return s_instance.m_container.lookup(ConfigManager.class);
......@@ -77,6 +81,12 @@ public class ConfigService {
public Config create(String namespace) {
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;
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)
......@@ -12,4 +14,12 @@ public interface ConfigManager {
* @return the config instance for the namespace
*/
public Config getConfig(String namespace);
/**
* Get the config file instance for the namespace specified.
* @param namespacePrefix the namespace
* @param configFileFormat the config file format
* @return the config file instance for the namespace
*/
public ConfigFile getConfigFile(String namespacePrefix, ConfigFileFormat configFileFormat);
}
......@@ -3,6 +3,8 @@ package com.ctrip.framework.apollo.internals;
import com.google.common.collect.Maps;
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.ConfigFactoryManager;
......@@ -20,6 +22,7 @@ public class DefaultConfigManager implements ConfigManager {
private ConfigFactoryManager m_factoryManager;
private Map<String, Config> m_configs = Maps.newConcurrentMap();
private Map<String, ConfigFile> m_configFiles = Maps.newConcurrentMap();
@Override
public Config getConfig(String namespace) {
......@@ -40,4 +43,25 @@ public class DefaultConfigManager implements ConfigManager {
return config;
}
@Override
public ConfigFile getConfigFile(String namespacePrefix, ConfigFileFormat configFileFormat) {
String namespace = String.format("%s.%s", namespacePrefix, configFileFormat.getValue());
ConfigFile configFile = m_configFiles.get(namespace);
if (configFile == null) {
synchronized (this) {
configFile = m_configFiles.get(namespace);
if (configFile == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespace);
configFile = factory.createConfigFile(namespace, configFileFormat);
m_configFiles.put(namespace, configFile);
}
}
}
return configFile;
}
}
......@@ -70,6 +70,9 @@ public class LocalFileConfigRepository extends AbstractConfigRepository
try {
String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir();
Path path = Paths.get(defaultCacheDir);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
if (Files.exists(path) && Files.isWritable(path)) {
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;
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)
......@@ -13,4 +15,11 @@ public interface ConfigFactory {
* @return the newly created config instance
*/
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;
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.LocalFileConfigRepository;
import com.ctrip.framework.apollo.internals.PropertiesConfigFile;
import com.ctrip.framework.apollo.internals.RemoteConfigRepository;
import com.ctrip.framework.apollo.internals.XmlConfigFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -23,6 +28,19 @@ public class DefaultConfigFactory implements ConfigFactory {
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 localFileConfigRepository =
new LocalFileConfigRepository(namespace);
......
......@@ -5,8 +5,10 @@ import com.ctrip.framework.apollo.integration.ConfigIntegrationTest;
import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest;
import com.ctrip.framework.apollo.internals.DefaultConfigTest;
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.SimpleConfigTest;
import com.ctrip.framework.apollo.internals.XmlConfigFileTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest;
......@@ -21,7 +23,7 @@ import org.junit.runners.Suite.SuiteClasses;
ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class,
DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class,
RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class,
ConfigIntegrationTest.class, ExceptionUtilTest.class
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class
})
public class AllTests {
......
......@@ -176,6 +176,11 @@ public abstract class BaseIntegrationTest extends ComponentTestCase {
public int getLongPollQPS() {
return 200;
}
@Override
public String getDefaultLocalCacheDir() {
return ClassLoaderUtil.getClassPath();
}
}
/**
......
package com.ctrip.framework.apollo;
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.spi.ConfigFactory;
import com.ctrip.framework.apollo.util.ConfigUtil;
......@@ -62,6 +63,20 @@ public class ConfigServiceTest extends ComponentTestCase {
assertEquals(null, config.getProperty("unknown", null));
}
@Test
public void testMockConfigFactoryForConfigFile() throws Exception {
String someNamespacePrefix = "mock";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
String someNamespace =
String.format("%s.%s", someNamespacePrefix, someConfigFileFormat.getValue());
defineComponent(ConfigFactory.class, someNamespace, MockConfigFactory.class);
ConfigFile configFile = ConfigService.getConfigFile(someNamespacePrefix, someConfigFileFormat);
assertEquals(someNamespace, configFile.getNamespace());
assertEquals(someNamespace + ":" + someConfigFileFormat.getValue(), configFile.getContent());
}
private static class MockConfig extends AbstractConfig {
private final String m_namespace;
......@@ -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 {
@Override
public Config create(String namespace) {
return new MockConfig(namespace);
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return new MockConfigFile(namespace, configFileFormat);
}
}
public static class MockConfigUtil extends ConfigUtil {
......
......@@ -57,6 +57,9 @@ public class ConfigIntegrationTest extends BaseIntegrationTest {
defaultNamespace = ConfigConsts.NAMESPACE_APPLICATION;
someReleaseKey = "1";
configDir = new File(ClassLoaderUtil.getClassPath() + "config-cache");
if (configDir.exists()) {
configDir.delete();
}
configDir.mkdirs();
}
......
package com.ctrip.framework.apollo.internals;
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.ConfigFactoryManager;
import com.ctrip.framework.apollo.spi.ConfigRegistry;
import org.junit.Before;
import org.junit.Test;
......@@ -11,18 +14,21 @@ import org.unidal.lookup.ComponentTestCase;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class DefaultConfigManagerTest extends ComponentTestCase {
private DefaultConfigManager defaultConfigManager;
private static String someConfigContent;
@Before
public void setUp() throws Exception {
super.setUp();
defineComponent(ConfigFactoryManager.class, MockConfigManager.class);
defaultConfigManager = (DefaultConfigManager) lookup(ConfigManager.class);
someConfigContent = "someContent";
}
@Test
......@@ -48,6 +54,34 @@ public class DefaultConfigManagerTest extends ComponentTestCase {
config, equalTo(anotherConfig));
}
@Test
public void testGetConfigFile() throws Exception {
String someNamespacePrefix = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
ConfigFile configFile =
defaultConfigManager.getConfigFile(someNamespacePrefix, someConfigFileFormat);
assertEquals(someConfigFileFormat, configFile.getConfigFileFormat());
assertEquals(someConfigContent, configFile.getContent());
}
@Test
public void testGetConfigFileMultipleTimesWithSameNamespace() throws Exception {
String someNamespacePrefix = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;
ConfigFile someConfigFile =
defaultConfigManager.getConfigFile(someNamespacePrefix, someConfigFileFormat);
ConfigFile anotherConfigFile =
defaultConfigManager.getConfigFile(someNamespacePrefix, 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 {
@Override
......@@ -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;
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.Test;
......@@ -75,6 +77,12 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) {
return null;
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
};
@Override
......@@ -96,6 +104,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) {
return null;
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}
public static class AnotherConfigFactory implements ConfigFactory {
......@@ -103,6 +116,11 @@ public class DefaultConfigFactoryManagerTest extends ComponentTestCase {
public Config create(String namespace) {
return null;
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}
}
package com.ctrip.framework.apollo.spi;
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.LocalFileConfigRepository;
import com.ctrip.framework.apollo.internals.PropertiesConfigFile;
import com.ctrip.framework.apollo.internals.XmlConfigFile;
import org.junit.Before;
import org.junit.Test;
......@@ -14,6 +18,7 @@ import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
......@@ -51,4 +56,30 @@ public class DefaultConfigFactoryTest extends ComponentTestCase {
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;
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.Test;
......@@ -46,5 +48,10 @@ public class DefaultConfigRegistryTest extends ComponentTestCase {
public Config create(String namespace) {
return null;
}
@Override
public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
return null;
}
}
}
package com.ctrip.framework.apollo.common.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import org.bouncycastle.util.Strings;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "AppNamespace")
@SQLDelete(sql = "Update AppNamespace set isDeleted = 1 where id = ?")
......@@ -20,6 +23,12 @@ public class AppNamespace extends BaseEntity {
@Column(name = "AppId", nullable = false)
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")
private String comment;
......@@ -47,8 +56,28 @@ public class AppNamespace extends BaseEntity {
this.name = name;
}
public boolean isPublic() {
return isPublic;
}
public void setPublic(boolean aPublic) {
isPublic = aPublic;
}
public ConfigFileFormat getFormatAsEnum() {
return ConfigFileFormat.fromString(this.format);
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String toString() {
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;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -8,12 +10,24 @@ import java.util.regex.Pattern;
*/
public class InputValidator {
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 APP_NAMESPACE_VALIDATOR = "[a-zA-z0-9._-]+(?<!\\.(json|yml|yaml|xml|properties))$";
private static final Pattern CLUSTER_NAMESPACE_PATTERN =
Pattern.compile(CLUSTER_NAMESPACE_VALIDATOR);
private static final Pattern APP_NAMESPACE_PATTERN =
Pattern.compile(APP_NAMESPACE_VALIDATOR);
public static boolean isValidClusterNamespace(String input) {
Matcher matcher = CLUSTER_NAMESPACE_PATTERN.matcher(input);
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;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
public class RequestPrecondition {
private static String CONTAIN_EMPTY_ARGUMENT = "request payload should not be contain empty.";
......@@ -12,6 +13,7 @@ public class RequestPrecondition {
private static String ILLEGAL_NUMBER = "number should be positive";
public static void checkArgument(String... args) {
checkArgument(!StringUtils.isContainEmpty(args), CONTAIN_EMPTY_ARGUMENT);
}
......
......@@ -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.service.AppNamespaceService;
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.dto.ApolloConfig;
import com.dianping.cat.Cat;
......@@ -41,6 +42,8 @@ public class ConfigController {
private ConfigService configService;
@Autowired
private AppNamespaceService appNamespaceService;
@Autowired
private NamespaceUtil namespaceUtil;
private static final Gson gson = new Gson();
private static final Type configurationTypeReference =
......@@ -55,6 +58,11 @@ public class ConfigController {
@RequestParam(value = "releaseKey", defaultValue = "-1") String clientSideReleaseKey,
@RequestParam(value = "ip", required = false) String clientIp,
HttpServletResponse response) throws IOException {
String originalNamespace = namespace;
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
List<Release> releases = Lists.newLinkedList();
Release currentAppRelease = loadConfig(appId, clusterName, namespace, dataCenter);
......@@ -66,8 +74,8 @@ public class ConfigController {
appClusterNameLoaded = currentAppRelease.getClusterName();
}
//if namespace is not 'application', should check if it's a public configuration
if (!Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespace)) {
//if namespace does not belong to this appId, should check if there is a public configuration
if (!namespaceBelongsToAppId(appId, namespace)) {
Release publicRelease = this.findPublicConfig(appId, clusterName, namespace, dataCenter);
if (!Objects.isNull(publicRelease)) {
releases.add(publicRelease);
......@@ -78,9 +86,9 @@ public class ConfigController {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
String.format(
"Could not load configurations with appId: %s, clusterName: %s, namespace: %s",
appId, clusterName, namespace));
appId, clusterName, originalNamespace));
Cat.logEvent("Apollo.Config.NotFound",
assembleKey(appId, clusterName, namespace, dataCenter));
assembleKey(appId, clusterName, originalNamespace, dataCenter));
return null;
}
......@@ -91,24 +99,35 @@ public class ConfigController {
// Client side configuration is the same with server side, return 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
Cat.logEvent("Apollo.Config.NotModified",
assembleKey(appId, appClusterNameLoaded, namespace, dataCenter));
assembleKey(appId, appClusterNameLoaded, originalNamespace, dataCenter));
return null;
}
ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, namespace, mergedReleaseKey);
ApolloConfig apolloConfig = new ApolloConfig(appId, appClusterNameLoaded, originalNamespace, mergedReleaseKey);
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;
}
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 namespace the namespace
* @param dataCenter the datacenter
*/
private Release findPublicConfig(String applicationId, String clusterName, String namespace, String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace);
AppNamespace appNamespace = appNamespaceService.findPublicByNamespaceName(namespace);
//check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
......
......@@ -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.ReleaseMessageService;
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.dto.ApolloConfigNotification;
import com.dianping.cat.Cat;
......@@ -60,6 +61,9 @@ public class NotificationController implements ReleaseMessageListener {
@Autowired
private EntityManagerUtil entityManagerUtil;
@Autowired
private NamespaceUtil namespaceUtil;
@RequestMapping(method = RequestMethod.GET)
public DeferredResult<ResponseEntity<ApolloConfigNotification>> pollNotification(
@RequestParam(value = "appId") String appId,
......@@ -68,10 +72,13 @@ public class NotificationController implements ReleaseMessageListener {
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "notificationId", defaultValue = "-1") long notificationId,
@RequestParam(value = "ip", required = false) String clientIp) {
//strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace);
Set<String> watchedKeys = assembleWatchKeys(appId, cluster, namespace, dataCenter);
//Listen on more namespaces, since it's not the default namespace
if (!Objects.equals(ConfigConsts.NAMESPACE_APPLICATION, namespace)) {
//Listen on more namespaces if it's a public namespace
if (!namespaceBelongsToAppId(appId, namespace)) {
watchedKeys.addAll(this.findPublicConfigWatchKey(appId, cluster, namespace, dataCenter));
}
......@@ -124,7 +131,7 @@ public class NotificationController implements ReleaseMessageListener {
private Set<String> findPublicConfigWatchKey(String applicationId, String clusterName,
String namespace,
String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findByNamespaceName(namespace);
AppNamespace appNamespace = appNamespaceService.findPublicByNamespaceName(namespace);
//check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) {
......@@ -187,6 +194,17 @@ public class NotificationController implements ReleaseMessageListener {
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) {
for (String watchedKey : watchedKeys) {
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;
import com.ctrip.framework.apollo.configservice.controller.NotificationControllerTest;
import com.ctrip.framework.apollo.configservice.integration.ConfigControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.integration.NotificationControllerIntegrationTest;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtilTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
......@@ -11,7 +12,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ConfigControllerTest.class, NotificationControllerTest.class,
ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class})
ConfigControllerIntegrationTest.class, NotificationControllerIntegrationTest.class,
NamespaceUtilTest.class})
public class AllTests {
}
......@@ -4,12 +4,13 @@ import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
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.message.Topics;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseMessageService;
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.dto.ApolloConfigNotification;
......@@ -51,6 +52,9 @@ public class NotificationControllerTest {
private ReleaseMessageService releaseMessageService;
@Mock
private EntityManagerUtil entityManagerUtil;
@Mock
private NamespaceUtil namespaceUtil;
private Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>
deferredResults;
......@@ -60,6 +64,7 @@ public class NotificationControllerTest {
ReflectionTestUtils.setField(controller, "appNamespaceService", appNamespaceService);
ReflectionTestUtils.setField(controller, "releaseMessageService", releaseMessageService);
ReflectionTestUtils.setField(controller, "entityManagerUtil", entityManagerUtil);
ReflectionTestUtils.setField(controller, "namespaceUtil", namespaceUtil);
someAppId = "someAppId";
someCluster = "someCluster";
......@@ -70,6 +75,9 @@ public class NotificationControllerTest {
someNotificationId = 1;
someClientIp = "someClientIp";
when(namespaceUtil.filterNamespaceName(defaultNamespace)).thenReturn(defaultNamespace);
when(namespaceUtil.filterNamespaceName(somePublicNamespace)).thenReturn(somePublicNamespace);
deferredResults =
(Multimap<String, DeferredResult<ResponseEntity<ApolloConfigNotification>>>) ReflectionTestUtils
.getField(controller, "deferredResults");
......@@ -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
public void testPollNotificationWithDefaultNamespaceWithNotificationIdOutDated() throws Exception {
long notificationId = someNotificationId + 1;
......@@ -156,7 +213,6 @@ public class NotificationControllerTest {
.join(someAppId, cluster, defaultNamespace);
assertTrue(deferredResults.get(key).contains(deferredResult));
}
}
@Test
......@@ -165,7 +221,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace))
when(appNamespaceService.findPublicByNamespaceName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
......@@ -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.findPublicByNamespaceName(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
public void testPollNotificationWithPublicNamespaceWithNotificationIdOutDated() throws Exception {
long notificationId = someNotificationId + 1;
......@@ -206,7 +302,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace))
when(appNamespaceService.findPublicByNamespaceName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
......@@ -253,7 +349,7 @@ public class NotificationControllerTest {
AppNamespace somePublicAppNamespace =
assmbleAppNamespace(somePublicAppId, somePublicNamespace);
when(appNamespaceService.findByNamespaceName(somePublicNamespace))
when(appNamespaceService.findPublicByNamespaceName(somePublicNamespace))
.thenReturn(somePublicAppNamespace);
DeferredResult<ResponseEntity<ApolloConfigNotification>>
......
......@@ -47,6 +47,20 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
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
@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)
......@@ -61,6 +75,21 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
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
public void testQueryConfigError() throws Exception {
String someNamespaceNotExists = "someNamespaceNotExists";
......@@ -168,6 +197,24 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals(somePublicNamespace, result.getNamespaceName());
assertEquals("override-v1", result.getConfigurations().get("k1"));
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
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)
@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)
......@@ -144,6 +185,30 @@ public class NotificationControllerIntegrationTest extends AbstractBaseIntegrati
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)
@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)
......
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');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('somePublicAppId', 'someDC');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'someNamespace');
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('somePublicAppId', 'somePublicNamespace');
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'someNamespace', true);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('someAppId', 'someNamespace.xml', false);
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', '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 ('somePublicAppId', 'default', 'application');
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 ('somePublicAppId', 'default', 'anotherNamespace');
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"}');
......@@ -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"}');
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"}');
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 {
String CLUSTER_NAME_DEFAULT = "default";
String CLUSTER_NAMESPACE_SEPARATOR = "+";
String APOLLO_CLUSTER_KEY = "apollo.cluster";
String FILE_NAMESPACE_KEY_NAME = "content";
}
package com.ctrip.framework.apollo.core.dto;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
public class AppNamespaceDTO extends BaseDTO{
private long id;
......@@ -9,6 +12,10 @@ public class AppNamespaceDTO extends BaseDTO{
private String comment;
private String format;
private boolean isPublic = false;
public long getId() {
return id;
}
......@@ -33,6 +40,26 @@ public class AppNamespaceDTO extends BaseDTO{
this.appId = appId;
}
public ConfigFileFormat getFormatAsEnum() {
return ConfigFileFormat.fromString(this.format);
}
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() {
return comment;
}
......
package com.ctrip.framework.apollo.core.enums;
/**
* @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){
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.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigFile;
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.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 namespacePrefix = "application";
public ApolloConfigFileDemo() {
configFile = ConfigService.getConfigFile(namespacePrefix, ConfigFileFormat.XML);
}
private void print() {
if (!configFile.hasContent()) {
System.out.println("No config file content found for " + namespacePrefix);
return;
}
System.out.println("=== Config File Content for " + namespacePrefix + " 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;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.portal.constant.PermissionType;
import com.ctrip.framework.apollo.portal.service.RolePermissionService;
import com.ctrip.framework.apollo.portal.util.RoleUtils;
......@@ -15,36 +16,45 @@ public class PermissionValidator {
@Autowired
private RolePermissionService rolePermissionService;
public boolean hasModifyNamespacePermission(String appId, String namespaceName){
public boolean hasModifyNamespacePermission(String appId, String namespaceName) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.MODIFY_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName));
PermissionType.MODIFY_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName));
}
public boolean hasReleaseNamespacePermission(String appId, String namespaceName){
public boolean hasReleaseNamespacePermission(String appId, String namespaceName) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.RELEASE_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName));
}
public boolean hasAssignRolePermission(String appId){
public boolean hasAssignRolePermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.ASSIGN_ROLE,
appId);
}
public boolean hasCreateNamespacePermission(String appId){
public boolean hasCreateNamespacePermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.CREATE_NAMESPACE,
appId);
}
public boolean hasCreateClusterPermission(String appId){
public boolean hasCreateClusterPermission(String appId) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.CREATE_CLUSTER,
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;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel;
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.http.HttpStatus;
......@@ -33,11 +34,13 @@ public class ConfigController {
@Autowired
private ConfigService configService;
@Autowired
private ServerConfigService serverConfigService;
@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT, consumes = {
"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,
@RequestBody NamespaceTextModel model) {
......@@ -131,4 +134,5 @@ public class ConfigController {
return item != null && !StringUtils.isContainEmpty(item.getKey());
}
}
......@@ -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.utils.InputValidator;
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.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
......@@ -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.vo.NamespaceVO;
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.NamespaceService;
......@@ -46,15 +48,18 @@ public class NamespaceController {
private UserInfoHolder userInfoHolder;
@Autowired
private NamespaceService namespaceService;
@Autowired
private AppNamespaceService appNamespaceService;
@RequestMapping("/appnamespaces/public")
public List<AppNamespace> findPublicAppNamespaces() {
return namespaceService.findPublicAppNamespaces();
return appNamespaceService.findPublicAppNamespaces();
}
@PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)")
@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));
......@@ -73,24 +78,31 @@ public class NamespaceController {
return ResponseEntity.ok().build();
}
@PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
@RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
public void createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) {
checkArgument(appNamespace.getAppId(), appNamespace.getName());
if (!InputValidator.isValidClusterNamespace(appNamespace.getName())) {
throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
throw new BadRequestException(String.format("Namespace格式错误: %s",
InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
+ InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
}
//add app org id as prefix
App app = appService.load(appId);
appNamespace.setName(String.format("%s.%s", app.getOrgId(), appNamespace.getName()));
if (appNamespace.getFormatAsEnum() == 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();
if (StringUtils.isEmpty(appNamespace.getDataChangeCreatedBy())) {
appNamespace.setDataChangeCreatedBy(operator);
}
appNamespace.setDataChangeLastModifiedBy(operator);
AppNamespace createdAppNamespace = namespaceService.createAppNamespaceInLocal(appNamespace);
AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace);
publisher.publishEvent(new AppNamespaceCreationEvent(createdAppNamespace));
......@@ -100,7 +112,7 @@ public class NamespaceController {
public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName) {
return namespaceService.findNampspaces(appId, Env.valueOf(env), clusterName);
return namespaceService.findNamespaces(appId, Env.valueOf(env), clusterName);
}
}
......@@ -61,6 +61,15 @@ public class PermissionController {
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")
public NamespaceRolesAssignedUsers getNamespaceRoles(@PathVariable String appId, @PathVariable String namespaceName){
......
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.utils.StringUtils;
......@@ -11,8 +12,10 @@ public class NamespaceTextModel implements Verifiable {
private String clusterName;
private String namespaceName;
private int namespaceId;
private String format;
private String configText;
@Override
public boolean isInvalid(){
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName) || namespaceId <= 0;
......@@ -65,4 +68,11 @@ public class NamespaceTextModel implements Verifiable {
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;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import java.util.List;
......@@ -8,6 +9,7 @@ public class NamespaceVO {
private NamespaceDTO namespace;
private int itemModifiedCnt;
private List<ItemVO> items;
private String format;
public NamespaceDTO getNamespace() {
......@@ -34,6 +36,14 @@ public class NamespaceVO {
this.items = items;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public static class ItemVO{
private ItemDTO item;
private boolean isModified;
......
package com.ctrip.framework.apollo.portal.listener;
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.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.PortalSettings;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.service.RoleInitializationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -28,6 +28,8 @@ public class CreationListener {
private AdminServiceAPI.AppAPI appAPI;
@Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private RoleInitializationService roleInitializationService;
@EventListener
public void onAppCreationEvent(AppCreationEvent event) {
......@@ -53,6 +55,11 @@ public class CreationListener {
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,10 @@ public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNa
AppNamespace findByName(String namespaceName);
AppNamespace findByNameAndIsPublic(String namespaceName, boolean isPublic);
List<AppNamespace> findByNameNot(String namespaceName);
List<AppNamespace> findByNameNotAndIsPublic(String namespaceName, boolean isPublic);
}
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
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
* @return
*/
public List<AppNamespace> findPublicAppNamespaces() {
return appNamespaceRepository.findByNameNotAndIsPublic(ConfigConsts.NAMESPACE_APPLICATION, true);
}
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) {
//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);
}
}
}
......@@ -8,7 +8,6 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpStatusCodeException;
......@@ -34,7 +33,7 @@ public class AppService {
@Autowired
private ClusterService clusterService;
@Autowired
private NamespaceService namespaceService;
private AppNamespaceService appNamespaceService;
@Autowired
private RoleInitializationService roleInitializationService;
......@@ -92,7 +91,7 @@ public class AppService {
throw new BadRequestException(String.format("app id %s already exists!", app.getAppId()));
} else {
App createdApp = appRepository.save(app);
namespaceService.createDefaultAppNamespace(appId);
appNamespaceService.createDefaultAppNamespace(appId);
//role
roleInitializationService.initAppRoles(createdApp);
return createdApp;
......
......@@ -4,12 +4,14 @@ package com.ctrip.framework.apollo.portal.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException;
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.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
......@@ -43,8 +45,14 @@ public class ConfigService {
private AdminServiceAPI.ItemAPI itemAPI;
@Autowired
private AdminServiceAPI.ReleaseAPI releaseAPI;
@Autowired
@Qualifier("fileTextResolver")
private ConfigTextResolver fileTextResolver;
@Autowired
private ConfigTextResolver resolver;
@Qualifier("propertyResolver")
private ConfigTextResolver propertyResolver;
/**
......@@ -60,6 +68,7 @@ public class ConfigService {
long namespaceId = model.getNamespaceId();
String configText = model.getConfigText();
ConfigTextResolver resolver = model.getFormat() == ConfigFileFormat.Properties ? propertyResolver : fileTextResolver;
ItemChangeSets changeSets = resolver.resolve(namespaceId, configText,
itemAPI.findItems(appId, env, clusterName, namespaceName));
if (changeSets.isEmpty()) {
......
......@@ -5,41 +5,33 @@ import com.google.gson.Gson;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
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.NamespaceDTO;
import com.ctrip.framework.apollo.core.dto.ReleaseDTO;
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.portal.PortalSettings;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.repository.AppNamespaceRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Service
public class NamespaceService {
private Logger logger = LoggerFactory.getLogger(NamespaceService.class);
private Gson gson = new Gson();
@Autowired
private UserInfoHolder userInfoHolder;
......@@ -49,19 +41,13 @@ public class NamespaceService {
private AdminServiceAPI.ReleaseAPI releaseAPI;
@Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private PortalSettings portalSettings;
@Autowired
private AppNamespaceRepository appNamespaceRepository;
@Autowired
private RoleInitializationService roleInitializationService;
private Gson gson = new Gson();
@Autowired
private AppNamespaceService appNamespaceService;
public List<AppNamespace> findPublicAppNamespaces() {
return appNamespaceRepository.findByNameNot(ConfigConsts.NAMESPACE_APPLICATION);
}
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
if (StringUtils.isEmpty(namespace.getDataChangeCreatedBy())) {
......@@ -74,48 +60,11 @@ public class NamespaceService {
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
*/
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);
if (namespaces == null || namespaces.size() == 0) {
......@@ -144,6 +93,15 @@ public class NamespaceService {
NamespaceVO namespaceVO = new NamespaceVO();
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<>();
namespaceVO.setItems(itemVos);
......
......@@ -199,7 +199,7 @@ public class RolePermissionService implements InitializingBean {
return false;
}
private boolean isSuperAdmin(String userId) {
public boolean isSuperAdmin(String 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.FILE_NAMESPACE_KEY_NAME);
return item;
}
}
......@@ -18,7 +18,7 @@ import java.util.Set;
* update comment and blank item implement by create new item and delete old item.
* update normal key/value item implement by update.
*/
@Component
@Component("propertyResolver")
public class PropertyResolver implements ConfigTextResolver {
private static final String KV_SEPARATOR = "=";
......
......@@ -88,7 +88,7 @@
<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/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>
</body>
......
......@@ -24,8 +24,8 @@
</h4>
</div>
<div class="col-md-5 text-right">
<a type="button" class="btn btn-success" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回
<a type="button" class="btn btn-info" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回到项目首页
</a>
</div>
</div>
......@@ -101,7 +101,7 @@
<script type="application/javascript" src="../scripts/AppUtils.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>
</body>
......
......@@ -25,7 +25,7 @@
<h4>创建集群</h4>
</div>
<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>
</div>
</div>
......@@ -106,7 +106,7 @@
<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/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>
</body>
......
......@@ -34,8 +34,8 @@
<button type="button" class="btn btn-success" ng-show="syncItemStep == 2 && hasDiff"
ng-click="syncItems()">同步
</button>
<button type="button" class="btn btn-success" data-dismiss="modal" ng-show="syncItemStep == 3"
ng-click="backToAppHomePage()">返回
<button type="button" class="btn btn-info" data-dismiss="modal" ng-show="syncItemStep == 3"
ng-click="backToAppHomePage()">返回到项目首页
</button>
</div>
</div>
......@@ -212,6 +212,6 @@
<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/directive.js"></script>
<script type="application/javascript" src="../scripts/directive/directive.js"></script>
</body>
</html>
......@@ -24,7 +24,7 @@
<div class="row">
<div class="col-md-6">新建Namespace</div>
<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-show="step == 2" ng-click="back()">返回到项目首页
</button>
</div>
</div>
......@@ -69,8 +69,28 @@
ng-required="type == 'create'">
</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>
</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'">
<label class="col-sm-3 control-label">备注</label>
<div class="col-sm-7">
......@@ -128,10 +148,11 @@
<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/NamespaceService.js"></script>
<script type="application/javascript" src="scripts/services/PermissionService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script>
<!--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>
......
......@@ -24,8 +24,8 @@
</h4>
</div>
<div class="col-md-5 text-right">
<a type="button" class="btn btn-success" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回
<a type="button" class="btn btn-info" data-dismiss="modal"
href="/config.html?#appid={{pageContext.appId}}">返回到项目首页
</a>
</div>
</div>
......@@ -131,7 +131,7 @@
<script type="application/javascript" src="../scripts/AppUtils.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>
</body>
......
namespace_module.controller("LinkNamespaceController",
['$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);
$scope.appId = params.appid;
......@@ -8,6 +10,10 @@ namespace_module.controller("LinkNamespaceController",
$scope.step = 1;
PermissionService.has_root_permission().then(function (result) {
$scope.hasRootPermission = result.hasPermission;
});
NamespaceService.find_public_namespaces().then(function (result) {
var publicNamespaces = [];
result.forEach(function (item) {
......@@ -35,7 +41,13 @@ namespace_module.controller("LinkNamespaceController",
$scope.appNamespace = {
appId: $scope.appId,
name: '',
comment: ''
comment: '',
isPublic: false,
format: 'xml'
};
$scope.switchNSType = function (type) {
$scope.appNamespace.isPublic = type;
};
$scope.concatNamespace = function () {
......@@ -95,7 +107,13 @@ namespace_module.controller("LinkNamespaceController",
function (result) {
$scope.step = 2;
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);
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "创建失败");
......
......@@ -34,6 +34,7 @@ application_module.controller("ConfigBaseInfoController",
var node = {};
//first nav
node.text = env.env;
// node.icon = 'glyphicon glyphicon-console';
var clusterNodes = [];
//如果env下面只有一个default集群则不显示集群列表
......@@ -57,8 +58,10 @@ application_module.controller("ConfigBaseInfoController",
}
clusterNode.text = cluster.name;
// clusterNode.icon = 'glyphicon glyphicon-object-align-vertical';
parentNode.push(node.text);
clusterNode.tags = parentNode;
clusterNode.tags = ['集群'];
clusterNode.parentNode = parentNode;
clusterNodes.push(clusterNode);
});
}
......@@ -73,13 +76,14 @@ application_module.controller("ConfigBaseInfoController",
levels: 99,
expandIcon: '',
collapseIcon: '',
showTags: true,
onNodeSelected: function (event, data) {
if (!data.tags) {//first nav node
if (!data.parentNode) {//first nav node
$rootScope.pageContext.env = data.text;
$rootScope.pageContext.clusterName =
'default';
} else {//second cluster node
$rootScope.pageContext.env = data.tags[0];
$rootScope.pageContext.env = data.parentNode[0];
$rootScope.pageContext.clusterName =
data.text;
}
......
......@@ -2,7 +2,7 @@
directive_module.directive('apollonav', function ($compile, $window, toastr, AppUtil, AppService, EnvService, UserService) {
return {
restrict: 'E',
templateUrl: '../views/common/nav.html',
templateUrl: '../../views/common/nav.html',
transclude: true,
replace: true,
link: function (scope, element, attrs) {
......@@ -120,7 +120,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App
directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) {
return {
restrict: 'E',
templateUrl: '../views/component/env-selector.html',
templateUrl: '../../views/component/env-selector.html',
transclude: true,
replace: true,
scope: {
......@@ -218,7 +218,7 @@ directive_module.directive('apollorequiredfiled', function ($compile, $window) {
directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
return {
restrict: 'E',
templateUrl: '../views/component/confirm-dialog.html',
templateUrl: '../../views/component/confirm-dialog.html',
transclude: true,
replace: true,
scope: {
......@@ -244,7 +244,7 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
directive_module.directive('apolloentrance', function ($compile, $window) {
return {
restrict: 'E',
templateUrl: '../views/component/entrance.html',
templateUrl: '../../views/component/entrance.html',
transclude: true,
replace: true,
scope: {
......@@ -262,7 +262,7 @@ directive_module.directive('apolloentrance', function ($compile, $window) {
directive_module.directive('apollouserselector', function ($compile, $window) {
return {
restrict: 'E',
templateUrl: '../views/component/user-selector.html',
templateUrl: '../../views/component/user-selector.html',
transclude: true,
replace: true,
scope: {
......
......@@ -72,7 +72,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
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();
config_source.modify_items({
appId: appId,
......@@ -80,11 +80,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
clusterName: clusterName,
namespaceName: namespaceName
},
{
configText: configText,
namespaceId: namespaceId,
comment: comment
}, function (result) {
model, function (result) {
d.resolve(result);
}, function (result) {
......
......@@ -5,6 +5,7 @@ appService.service('NamespaceLockService', ['$resource', '$q', function ($resour
url: 'apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/lock'
}
});
return {
get_namespace_lock: function (appId, env, clusterName, namespaceName) {
var d = $q.defer();
......
......@@ -8,6 +8,10 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource,
method: 'GET',
url: '/apps/:appId/namespaces/:namespaceName/permissions/:permissionType'
},
has_root_permission:{
method: 'GET',
url: '/permissions/root'
},
get_namespace_role_users: {
method: 'GET',
url: '/apps/:appId/namespaces/:namespaceName/role_users'
......@@ -110,6 +114,17 @@ appService.service('PermissionService', ['$resource', '$q', function ($resource,
has_release_namespace_permission: function (appId, namespaceName) {
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) {
return assignNamespaceRoleToUser(appId, namespaceName, 'ModifyNamespace', user);
},
......
......@@ -78,7 +78,7 @@
<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/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/services/AppService.js"></script>
......
......@@ -10,7 +10,7 @@
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<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>
</a></li>
<li class="dropdown">
......
......@@ -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.ItemDTO;
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.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
......@@ -21,6 +22,7 @@ import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
import java.util.List;
......@@ -47,6 +49,7 @@ public class ConfigServiceTest {
@Before
public void setup() {
ReflectionTestUtils.setField(configService, "propertyResolver", resolver);
}
@Test
......@@ -61,7 +64,7 @@ public class ConfigServiceTest {
model.setClusterName(clusterName);
model.setAppId(appId);
model.setConfigText("a=b\nb=c\nc=d\nd=e");
model.setFormat(ConfigFileFormat.Properties.getValue());
List<ItemDTO> itemDTOs = mockBaseItemHas3Key();
ItemChangeSets changeSets = new ItemChangeSets();
changeSets.addCreateItem(new ItemDTO("d", "c", "", 4));
......
package com.ctrip.framework.apollo.portal;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
import com.ctrip.framework.apollo.portal.service.NamespaceService;
import com.ctrip.framework.apollo.portal.service.txtresolver.PropertyResolver;
......@@ -20,6 +23,7 @@ import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
......@@ -33,6 +37,8 @@ public class NamespaceServiceTest {
private AdminServiceAPI.ItemAPI itemAPI;
@Mock
private PropertyResolver resolver;
@Mock
private AppNamespaceService appNamespaceService;
@InjectMocks
private NamespaceService namespaceService;
......@@ -47,18 +53,21 @@ public class NamespaceServiceTest {
String clusterName = "default";
String namespaceName = "application";
AppNamespace applicationAppNamespace = mock(AppNamespace.class);
AppNamespace hermesAppNamespace = mock(AppNamespace.class);
NamespaceDTO application = new NamespaceDTO();
application.setId(1);
application.setClusterName(clusterName);
application.setAppId(appId);
application.setNamespaceName(namespaceName);
NamespaceDTO hermas = new NamespaceDTO();
hermas.setId(2);
hermas.setClusterName("default");
hermas.setAppId(appId);
hermas.setNamespaceName("hermas");
List<NamespaceDTO> namespaces = Arrays.asList(application, hermas);
NamespaceDTO hermes = new NamespaceDTO();
hermes.setId(2);
hermes.setClusterName("default");
hermes.setAppId(appId);
hermes.setNamespaceName("hermes");
List<NamespaceDTO> namespaces = Arrays.asList(application, hermes);
ReleaseDTO someRelease = new ReleaseDTO();
someRelease.setConfigurations("{\"a\":\"123\",\"b\":\"123\"}");
......@@ -69,12 +78,17 @@ public class NamespaceServiceTest {
ItemDTO i4 = new ItemDTO("c", "1", "", 4);
List<ItemDTO> someItems = Arrays.asList(i1, i2, i3, i4);
when(applicationAppNamespace.getFormat()).thenReturn(ConfigFileFormat.Properties.getValue());
when(hermesAppNamespace.getFormat()).thenReturn(ConfigFileFormat.XML.getValue());
when(appNamespaceService.findByAppIdAndName(appId, namespaceName))
.thenReturn(applicationAppNamespace);
when(appNamespaceService.findPublicAppNamespace("hermes")).thenReturn(hermesAppNamespace);
when(namespaceAPI.findNamespaceByCluster(appId, Env.DEV, clusterName)).thenReturn(namespaces);
when(releaseAPI.loadLatestRelease(appId, Env.DEV, clusterName, namespaceName)).thenReturn(someRelease);
when(releaseAPI.loadLatestRelease(appId, Env.DEV, clusterName, "hermas")).thenReturn(someRelease);
when(releaseAPI.loadLatestRelease(appId, Env.DEV, clusterName, "hermes")).thenReturn(someRelease);
when(itemAPI.findItems(appId, Env.DEV, clusterName, namespaceName)).thenReturn(someItems);
List<NamespaceVO> namespaceVOs = namespaceService.findNampspaces(appId, Env.DEV, clusterName);
List<NamespaceVO> namespaceVOs = namespaceService.findNamespaces(appId, Env.DEV, clusterName);
assertEquals(2, namespaceVOs.size());
NamespaceVO namespaceVO = namespaceVOs.get(0);
assertEquals(4, namespaceVO.getItems().size());
......
......@@ -9,6 +9,7 @@ import com.ctrip.framework.apollo.portal.service.txtresolver.ConfigTextResolver;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays;
import java.util.Collections;
......@@ -17,6 +18,7 @@ import java.util.List;
public class PropertyResolverTest extends AbstractPortalTest {
@Autowired
@Qualifier("propertyResolver")
private ConfigTextResolver resolver;
@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