/*
*
* * Copyright 2011-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.module.store;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.xd.dirt.module.ModuleDependencyRepository;
import org.springframework.xd.dirt.module.ModuleRegistry;
import org.springframework.xd.dirt.module.NoSuchModuleException;
import org.springframework.xd.dirt.module.WritableModuleRegistry;
import org.springframework.xd.dirt.module.support.ModuleDefinitionRepositoryUtils;
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.CompositeModuleDefinition;
import org.springframework.xd.module.ModuleDefinition;
import org.springframework.xd.module.ModuleDefinitions;
import org.springframework.xd.module.ModuleType;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* An implementation of {@code WriteableModuleRegistry} dedicated to {@code CompositeModuleDefinition}s and that uses
* ZooKeeper as storage mechanism.
* <p>Writes each definition to a node, such as: {@code /xd/modules/[moduletype]/[modulename]} with the node data being
* a JSON representation of the module definition.</p>
*
* @author Mark Fisher
* @author David Turanski
* @author Eric Bottard
* @author Chris Lemper
*/
public class ZooKeeperComposedModuleDefinitionRegistry implements WritableModuleRegistry {
private final ModuleDependencyRepository moduleDependencyRepository;
/**
* A reference to the main module registry (which also typically contains this registry as a delegate),
* used to re-hydrate {@link org.springframework.xd.module.SimpleModuleDefinition}s with locations that
* make sense on this runtime(admin or container).
*/
private final ModuleRegistry mainModuleRegistry;
private final ZooKeeperConnection zooKeeperConnection;
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
public ZooKeeperComposedModuleDefinitionRegistry(
ModuleDependencyRepository moduleDependencyRepository,
ModuleRegistry mainModuleRegistry,
ZooKeeperConnection zooKeeperConnection) {
Assert.notNull(moduleDependencyRepository, "moduleDependencyRepository must not be null");
Assert.notNull(zooKeeperConnection, "zooKeeperConnection must not be null");
this.moduleDependencyRepository = moduleDependencyRepository;
this.mainModuleRegistry = mainModuleRegistry;
this.zooKeeperConnection = zooKeeperConnection;
}
@Override
public boolean delete(ModuleDefinition definition) {
Assert.notNull(definition, "'definition' cannot be null.");
if (!definition.isComposed()) {
return false;
}
String path = Paths.build(Paths.MODULES, definition.getType().toString(), definition.getName());
try {
// Delete actual definition
zooKeeperConnection.getClient().delete().deletingChildrenIfNeeded().forPath(path);
// As well as dependencies bookkeeping
List<ModuleDefinition> children = ((CompositeModuleDefinition) definition).getChildren();
for (ModuleDefinition child : children) {
ModuleDefinitionRepositoryUtils.deleteDependencies(moduleDependencyRepository, child,
dependencyKey(definition));
}
}
catch (KeeperException.NoNodeException ignore) {
// We are not responsible for this definition
return false;
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
return true;
}
@Override
public boolean registerNew(ModuleDefinition definition) {
if (!definition.isComposed()) {
return false;
}
String path = Paths.build(Paths.MODULES, definition.getType().toString(),
definition.getName());
byte[] data = null;
try {
// Save actual definition
data = objectMapper.writeValueAsString(definition).getBytes("UTF-8");
zooKeeperConnection.getClient().create().creatingParentsIfNeeded().forPath(path, data);
// Also track dependencies
List<ModuleDefinition> childrenDefinitions = ((CompositeModuleDefinition) definition).getChildren();
for (ModuleDefinition child : childrenDefinitions) {
ModuleDefinitionRepositoryUtils.saveDependencies(moduleDependencyRepository, child,
dependencyKey(definition));
}
}
catch (KeeperException.NodeExistsException fallback) {
try {
zooKeeperConnection.getClient().setData().forPath(path, data);
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
return true;
}
@Override
public ModuleDefinition findDefinition(String name, ModuleType type) {
String path = Paths.build(Paths.MODULES, type.toString(), name);
try {
byte[] data = zooKeeperConnection.getClient().getData().forPath(path);
if (data.length == 0) {
return null;
}
ModuleDefinition deserializedDefinition = this.objectMapper.readValue(new String(data, "UTF-8"),
ModuleDefinition.class);
return relookup(deserializedDefinition);
}
catch (Exception e) {
// NoNodeException will return null
ZooKeeperUtils.wrapAndThrowIgnoring(e, KeeperException.NoNodeException.class);
}
// non-composed module
return null;
}
@Override
public List<ModuleDefinition> findDefinitions(String name) {
throw new UnsupportedOperationException("Not implemented (but never used)");
}
@Override
public List<ModuleDefinition> findDefinitions(ModuleType type) {
List<ModuleDefinition> results = new ArrayList<>();
String path = Paths.build(Paths.MODULES, type.toString());
try {
List<String> children = zooKeeperConnection.getClient().getChildren().forPath(path);
for (String child : children) {
byte[] data = zooKeeperConnection.getClient().getData().forPath(
Paths.build(Paths.MODULES, type.toString(), child));
// Check for data (only composed modules have definitions)
if (data != null && data.length > 0) {
ModuleDefinition composed = this.findDefinition(child, type);
if (composed != null) {
results.add(composed);
}
else {
throw new NoSuchModuleException(child, type);
}
}
}
}
catch (Exception e) {
ZooKeeperUtils.wrapAndThrowIgnoring(e, KeeperException.NoNodeException.class);
}
return results;
}
@Override
public List<ModuleDefinition> findDefinitions() {
List<ModuleDefinition> results = new ArrayList<>();
for (ModuleType type : ModuleType.values()) {
results.addAll(findDefinitions(type));
}
return results;
}
/**
* Generates the key used in the ModuleDependencyRepository.
*
* @param moduleDefinition the moduleDefinition being saved or deleted
* @return generated key
*/
private String dependencyKey(ModuleDefinition moduleDefinition) {
return String.format("module:%s:%s", moduleDefinition.getType(), moduleDefinition.getName());
}
/**
* Recursively re-lookup module definitions from the main module registry, so that locations for simple
* modules reflect the paths on the container/admin this code is running on.
*
* @param definition the ModuleDefinition to re-lookup
* @return module definition
*/
private ModuleDefinition relookup(ModuleDefinition definition) {
if (!definition.isComposed()) {
return mainModuleRegistry.findDefinition(definition.getName(), definition.getType());
}
else {
List<ModuleDefinition> children = new ArrayList<>();
CompositeModuleDefinition composite = (CompositeModuleDefinition) definition;
for (ModuleDefinition child : composite.getChildren()) {
children.add(relookup(child));
}
return ModuleDefinitions.composed(
composite.getName(), composite.getType(), composite.getDslDefinition(), children);
}
}
}