/*
* Copyright 2013-2014 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.stream;
import static org.springframework.xd.dirt.stream.ParsingContext.stream;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.BackgroundPathAndBytesable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.xd.dirt.server.admin.deployment.DeploymentHandler;
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.ModuleDefinition;
import org.springframework.xd.module.ModuleDescriptor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
/**
* Default implementation of {@link StreamDeployer} that uses provided
* {@link StreamDefinitionRepository} and {@link StreamRepository} to
* persist stream deployment and undeployment requests.
*
* @author Mark Fisher
* @author Gary Russell
* @author Andy Clement
* @author Eric Bottard
* @author Gunnar Hillert
* @author Patrick Peralta
* @author Ilayaperumal Gopinathan
*/
public class StreamDeployer extends AbstractInstancePersistingDeployer<StreamDefinition, Stream> {
private static final Logger logger = LoggerFactory.getLogger(StreamDeployer.class);
private final static TypeReference<List<ModuleDefinition>> MODULE_DEFINITIONS_LIST = new TypeReference<List<ModuleDefinition>>() {};
private final ObjectWriter objectWriter = new ObjectMapper().writerWithType(MODULE_DEFINITIONS_LIST);
private static final String DEFINITION_KEY = "definition";
private static final String MODULE_DEFINITIONS_KEY = "moduleDefinitions";
private final ZooKeeperConnection zkConnection;
/**
* Stream definition parser.
*/
private final XDParser parser;
/**
* Construct a StreamDeployer.
*
* @param zkConnection ZooKeeper connection
* @param repository repository for stream definitions
* @param streamRepository repository for stream instances
* @param parser stream definition parser
*/
public StreamDeployer(ZooKeeperConnection zkConnection, StreamDefinitionRepository repository,
StreamRepository streamRepository, XDParser parser, DeploymentHandler deploymentHandler) {
super(zkConnection, repository, streamRepository, parser, deploymentHandler, stream);
this.zkConnection = zkConnection;
this.parser = parser;
}
/**
* {@inheritDoc}
*/
@Override
protected Stream makeInstance(StreamDefinition definition) {
return new Stream(definition);
}
/**
* {@inheritDoc}
*/
@Override
protected StreamDefinition createDefinition(String name, String definition) {
return new StreamDefinition(name, definition);
}
/**
* {@inheritDoc}
*/
@Override
protected String getDeploymentPath(StreamDefinition definition) {
return Paths.build(Paths.STREAM_DEPLOYMENTS, definition.getName());
}
/**
* The migration code to run against existing stream definitions that don't have module definitions set already.
* The following method will update the module definitions for those stream definitions.
* See https://jira.spring.io/browse/XD-2854
*/
@PostConstruct
private void updateModuleDefinitions() {
if (this.zkConnection.getClient() != null) {
try {
CuratorFramework client = this.zkConnection.getClient();
if (client.checkExists().forPath(Paths.STREAMS) != null) {
for (StreamDefinition definition : findAll()) {
setModuleDefinitions(client, definition);
}
}
}
catch (Exception e) {
logger.error("Exception migrating stream definitions. This migration is done when the existing " +
"stream definitions that don't have module definitions set." , e);
}
}
}
/**
* Set the module definitions for the given stream definition.
* Module definitions are obtained by parsing the stream definition.
*
* @param client the curator framework client
* @param definition the stream definition
*/
private void setModuleDefinitions(CuratorFramework client, StreamDefinition definition) {
String streamName = definition.getName();
String path = Paths.build(Paths.STREAMS, streamName);
try {
byte[] bytes = client.getData().forPath(path);
if (bytes != null) {
Map<String, String> map = ZooKeeperUtils.bytesToMap(bytes);
// Prior to Spring XD 1.1.1/1.2, the JSON map for stream definitions
// contained a single key named "definition" with the value being
// the definition string. As of 1.1.1/1.2, the map contains an additional
// key "moduleDefinitions" which contains a JSON array of module
// information such as class name and module URL.
//
// The following will convert "old" stream definitions into the
// new format.
if (map.get(MODULE_DEFINITIONS_KEY) == null) {
List<ModuleDescriptor> moduleDescriptors = this.parser.parse(streamName,
definition.getDefinition(), definitionKind);
List<ModuleDefinition> moduleDefinitions = createModuleDefinitions(moduleDescriptors);
if (!moduleDefinitions.isEmpty()) {
map.put(DEFINITION_KEY, definition.getDefinition());
try {
map.put(MODULE_DEFINITIONS_KEY,
objectWriter.writeValueAsString(moduleDefinitions));
byte[] binary = ZooKeeperUtils.mapToBytes(map);
BackgroundPathAndBytesable<?> op = client.checkExists().forPath(path) == null
? client.create() : client.setData();
op.forPath(path, binary);
}
catch (JsonProcessingException jpe) {
logger.error("Exception writing module definitions " + moduleDefinitions +
" for the stream " + streamName, jpe);
}
}
}
}
}
catch (Exception e) {
logger.error("Exception when updating module definitions for the stream "
+ streamName, e);
}
}
}