/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.xd.dirt.module.store;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.collections.MapUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.xd.dirt.core.DeploymentUnitStatus;
import org.springframework.xd.dirt.core.ModuleDeploymentsPath;
import org.springframework.xd.dirt.stream.JobRepository;
import org.springframework.xd.dirt.stream.StreamRepository;
import org.springframework.xd.dirt.util.PagingUtility;
import org.springframework.xd.dirt.zookeeper.Paths;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnection;
import org.springframework.xd.dirt.zookeeper.ZooKeeperUtils;
import org.springframework.xd.module.ModuleType;
/**
* ZooKeeper backed repository for runtime info about deployed modules.
*
* @author Ilayaperumal Gopinathan
* @author David Turanski
* @author Gary Russell
*/
public class ZooKeeperModuleMetadataRepository implements ModuleMetadataRepository {
private static final String XD_MODULE_PROPERTIES_PREFIX = "xd.";
private final ZooKeeperConnection zkConnection;
private final StreamRepository streamRepository;
private final JobRepository jobRepository;
private final PagingUtility<ModuleMetadata> pagingUtility = new PagingUtility<ModuleMetadata>();
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
public ZooKeeperModuleMetadataRepository(ZooKeeperConnection zkConnection,
StreamRepository streamRepository,
JobRepository jobRepository) {
this.zkConnection = zkConnection;
this.streamRepository = streamRepository;
this.jobRepository = jobRepository;
}
@Override
public Iterable<ModuleMetadata> findAll(Sort sort) {
// todo: add support for sort
return findAll();
}
@Override
public Page<ModuleMetadata> findAll(Pageable pageable) {
return pagingUtility.getPagedData(pageable, findAll());
}
/**
* {@inheritDoc}
*/
@Override
public ModuleMetadata findOne(String containerId, String moduleId) {
return findOne(new ModuleMetadata.Id(containerId, moduleId));
}
/**
* {@inheritDoc}
*/
@Override
public Page<ModuleMetadata> findAllByContainerId(Pageable pageable, String containerId) {
Assert.hasText(containerId, "containerId is required");
return pagingUtility.getPagedData(pageable, findAllByContainerId(containerId));
}
/**
* {@inheritDoc}
*/
@Override
public Page<ModuleMetadata> findAllByModuleId(Pageable pageable, String moduleId) {
Assert.hasText(moduleId, "moduleId is required");
List<ModuleMetadata> results = new ArrayList<ModuleMetadata>();
for (String containerId : getAvailableContainerIds()) {
ModuleMetadata metadata = findOne(new ModuleMetadata.Id(containerId, moduleId));
if (metadata != null) {
results.add(metadata);
}
}
return pagingUtility.getPagedData(pageable, results);
}
/**
* {@inheritDoc}
* <p>
* Find the module metadata for the modules that are deployed into the
* given container and module metadata id.
*
* @param id unique id for module deployment
*
* @return {@link ModuleMetadata} of the module.
*/
@Override
public ModuleMetadata findOne(ModuleMetadata.Id id) {
Assert.notNull(id, "id is required");
String moduleDeploymentPath = moduleDeploymentPath(id);
String metadataPath = Paths.build(moduleDeploymentPath, Paths.METADATA);
ModuleMetadata metadata = null;
try {
byte[] data = zkConnection.getClient().getData().forPath(metadataPath);
if (data != null) {
Map<String, String> metadataMap = ZooKeeperUtils.bytesToMap(data);
new ModuleDeploymentsPath(moduleDeploymentPath);
ModuleType moduleType = id.getModuleType();
DeploymentUnitStatus status = moduleType == ModuleType.job
? jobRepository.getDeploymentStatus(id.getUnitName())
: streamRepository.getDeploymentStatus(id.getUnitName());
metadata = new ModuleMetadata(id,
getResolvedModuleOptions(metadataMap),
getDeploymentProperties(moduleDeploymentPath),
status.getState());
}
}
catch (Exception e) {
// NoNodeException - this node does not exist, will return null
ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class);
}
return metadata;
}
/**
* Get the deployment properties associated with this module metadata path.
*
* @param moduleDeploymentsPath the module deployment path
* @return deployment properties
*/
private Properties getDeploymentProperties(String moduleDeploymentsPath) {
Map<String, String> deploymentProperties = new HashMap<String, String>();
try {
deploymentProperties = ZooKeeperUtils.bytesToMap(zkConnection.getClient().getData()
.forPath(moduleDeploymentsPath));
}
catch (Exception e) {
ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class);
}
return MapUtils.toProperties(deploymentProperties);
}
/**
* Resolve the module option value using the module metadata.
*
* @param metadataMap the values map from ZK module metadata
* @return the resolved option values
*/
private Properties getResolvedModuleOptions(Map<String, String> metadataMap) {
Map<String, String> optionsMap = new HashMap<String, String>();
for (Map.Entry<String, String> entry : metadataMap.entrySet()) {
String propertyKey = entry.getKey();
String propertyValue = entry.getValue();
if (!propertyKey.startsWith(XD_MODULE_PROPERTIES_PREFIX)
&& !StringUtils.isEmpty(propertyValue)) {
if (propertyValue.startsWith(PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX) &&
propertyValue.endsWith(PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX)) {
// For a property ${module.property}, we just extract "module.property" and
// check if the metadataMap has a value for it.
String placeholderKey = propertyValue.substring(2, propertyValue.length() - 1);
if (metadataMap.get(placeholderKey) != null) {
propertyValue = metadataMap.get(placeholderKey);
}
}
optionsMap.put(propertyKey, propertyValue);
}
}
return MapUtils.toProperties(optionsMap);
}
@Override
public boolean exists(ModuleMetadata.Id id) {
try {
return zkConnection.getClient().checkExists()
.forPath(moduleDeploymentPath(id)) != null;
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
@Override
public List<ModuleMetadata> findAll() {
List<ModuleMetadata> results = new ArrayList<ModuleMetadata>();
try {
for (String containerId : getAvailableContainerIds()) {
List<ModuleMetadata.Id> modules = getDeployedModules(containerId);
for (ModuleMetadata.Id moduleId : modules) {
ModuleMetadata metadata = findOne(moduleId);
if (metadata != null) {
results.add(metadata);
}
}
}
return results;
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
/**
* Get all the available containers' ids.
*
* @return the list of all available containers' ids.
*/
private List<String> getAvailableContainerIds() {
try {
return zkConnection.getClient().getChildren().forPath(Paths.CONTAINERS);
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
/**
* Find all the modules that are deployed into this container.
*
* @param containerId the containerId
* @return {@link ModuleMetadata} of the modules deployed into this container.
*/
public List<ModuleMetadata> findAllByContainerId(String containerId) {
Assert.hasText(containerId, "containerId is required");
List<ModuleMetadata.Id> deployedModules = getDeployedModules(containerId);
logger.debug("deployedModules: {}", deployedModules);
List<ModuleMetadata> results = new ArrayList<ModuleMetadata>(deployedModules.size());
for (ModuleMetadata.Id moduleId : deployedModules) {
ModuleMetadata metadata = findOne(moduleId);
logger.debug("found metadata: {}", metadata);
if (metadata != null) {
results.add(metadata);
}
}
return results;
}
/**
* Get all the deployed modules by the given containerId.
*
* @param containerId the containerId to filter
* @return the list of moduleIds of the deployed modules.
*/
private List<ModuleMetadata.Id> getDeployedModules(String containerId) {
List<ModuleMetadata.Id> ids = new ArrayList<ModuleMetadata.Id>();
try {
CuratorFramework client = zkConnection.getClient();
if (client.checkExists().forPath(Paths.build(Paths.CONTAINERS, containerId)) != null) {
List<String> qualifiedIds = client.getChildren().forPath(containerAllocationPath(containerId));
for (String qualifiedId : qualifiedIds) {
ids.add(new ModuleMetadata.Id(containerId, qualifiedId));
}
}
}
catch (Exception e) {
ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class);
}
return ids;
}
@Override
public Iterable<ModuleMetadata> findAll(Iterable<ModuleMetadata.Id> ids) {
List<ModuleMetadata> results = new ArrayList<ModuleMetadata>();
for (ModuleMetadata.Id id : ids) {
ModuleMetadata entity = findOne(id);
if (entity != null) {
results.add(entity);
}
}
return results;
}
@Override
public Iterable<ModuleMetadata> findAllInRange(ModuleMetadata.Id from,
boolean fromInclusive, ModuleMetadata.Id to, boolean toInclusive) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public long count() {
long count = 0;
try {
List<String> containerIds = zkConnection.getClient().getChildren().forPath(Paths.MODULE_DEPLOYMENTS);
for (String containerId : containerIds) {
Stat stat = zkConnection.getClient().checkExists().forPath(containerAllocationPath(containerId));
if (stat != null) {
count+= stat.getNumChildren();
}
}
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
return count;
}
/**
* Return the module allocation path for the given container id.
*
* @param id container id
* @return path for module allocations for the container
*/
private String containerAllocationPath(String id) {
return Paths.build(Paths.MODULE_DEPLOYMENTS, Paths.ALLOCATED, id);
}
/**
* Return the module deployment path for the module indicated
* by the provided {@link org.springframework.xd.dirt.module.store.ModuleMetadata.Id}.
*
* @param id id for module instance
* @return path for module allocation
*/
private String moduleDeploymentPath(ModuleMetadata.Id id) {
return Paths.build(Paths.MODULE_DEPLOYMENTS, Paths.ALLOCATED, id.getContainerId(),
id.getFullyQualifiedId());
}
@Override
public <S extends ModuleMetadata> S save(S entity) {
throw new UnsupportedOperationException();
}
@Override
public <S extends ModuleMetadata> Iterable<S> save(Iterable<S> entities) {
throw new UnsupportedOperationException();
}
@Override
public void delete(ModuleMetadata.Id id) {
throw new UnsupportedOperationException();
}
@Override
public void delete(ModuleMetadata entity) {
throw new UnsupportedOperationException();
}
@Override
public void delete(Iterable<? extends ModuleMetadata> entities) {
throw new UnsupportedOperationException();
}
@Override
public void deleteAll() {
throw new UnsupportedOperationException();
}
}