/* * Copyright 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.zookeeper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.api.BackgroundPathAndBytesable; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.xd.dirt.module.ModuleDependencyRepository; import org.springframework.xd.dirt.stream.StreamDefinition; import org.springframework.xd.dirt.stream.StreamDefinitionRepository; import org.springframework.xd.dirt.stream.StreamDefinitionRepositoryUtils; 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.ModuleDefinition; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; /** * @author Mark Fisher */ // todo: the StreamDefinitionRepository abstraction can be removed once we are fully zk-enabled since we do not need to // support multiple impls at that point public class ZooKeeperStreamDefinitionRepository implements StreamDefinitionRepository, InitializingBean { /** * The key used in serialized properties to hold the raw definition of a stream. */ private static final String DEFINITION_KEY = "definition"; /** * The key used in serialized properties to hold the parsed module definitions that make up a stream. */ private static final String MODULE_DEFINITIONS_KEY = "moduleDefinitions"; private final static TypeReference<List<ModuleDefinition>> MODULE_DEFINITIONS_LIST = new TypeReference<List<ModuleDefinition>>() {}; private final Logger logger = LoggerFactory.getLogger(ZooKeeperStreamDefinitionRepository.class); private final ZooKeeperConnection zkConnection; private final ModuleDependencyRepository moduleDependencyRepository; private final PagingUtility<StreamDefinition> pagingUtility = new PagingUtility<StreamDefinition>(); private final RepositoryConnectionListener connectionListener = new RepositoryConnectionListener(); private final ObjectWriter objectWriter = new ObjectMapper().writerWithType(MODULE_DEFINITIONS_LIST); private final ObjectReader objectReader = new ObjectMapper().reader(MODULE_DEFINITIONS_LIST); @Autowired public ZooKeeperStreamDefinitionRepository(ZooKeeperConnection zkConnection, ModuleDependencyRepository moduleDependencyRepository) { this.zkConnection = zkConnection; this.moduleDependencyRepository = moduleDependencyRepository; } @Override public void afterPropertiesSet() throws Exception { zkConnection.addListener(connectionListener); if (zkConnection.isConnected()) { // already connected, invoke the callback directly connectionListener.onConnect(zkConnection.getClient()); } } @Override public Iterable<StreamDefinition> findAll(Sort sort) { throw new UnsupportedOperationException("Auto-generated method stub"); } @Override public Page<StreamDefinition> findAll(Pageable pageable) { return pagingUtility.getPagedData(pageable, findAll()); } @Override public <S extends StreamDefinition> Iterable<S> save(Iterable<S> entities) { List<S> results = new ArrayList<S>(); for (S entity : entities) { results.add(this.save(entity)); } return results; } @Override public <S extends StreamDefinition> S save(S entity) { try { Map<String, String> map = new HashMap<>(); map.put(DEFINITION_KEY, entity.getDefinition()); map.put(MODULE_DEFINITIONS_KEY, objectWriter.writeValueAsString(entity.getModuleDefinitions())); CuratorFramework client = zkConnection.getClient(); String path = Paths.build(Paths.STREAMS, entity.getName()); byte[] binary = ZooKeeperUtils.mapToBytes(map); BackgroundPathAndBytesable<?> op = client.checkExists().forPath(path) == null ? client.create() : client.setData(); op.forPath(path, binary); logger.trace("Saved stream {} with properties {}", path, map); StreamDefinitionRepositoryUtils.saveDependencies(moduleDependencyRepository, entity); } catch (Exception e) { // NodeExistsException indicates that we tried to create the // path just after another thread/jvm successfully created it ZooKeeperUtils.wrapAndThrowIgnoring(e, NodeExistsException.class); } return entity; } @Override public StreamDefinition findOne(String id) { try { byte[] bytes = zkConnection.getClient().getData().forPath(Paths.build(Paths.STREAMS, id)); if (bytes == null) { return null; } Map<String, String> map = ZooKeeperUtils.bytesToMap(bytes); StreamDefinition streamDefinition = new StreamDefinition(id, map.get(DEFINITION_KEY)); if (map.get(MODULE_DEFINITIONS_KEY) != null) { List<ModuleDefinition> moduleDefinitions = objectReader.readValue(map.get(MODULE_DEFINITIONS_KEY)); streamDefinition.setModuleDefinitions(moduleDefinitions); } return streamDefinition; } catch (Exception e) { //NoNodeException - the definition does not exist ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class); } return null; } @Override public boolean exists(String id) { try { return (null != zkConnection.getClient().checkExists().forPath(Paths.build(Paths.STREAMS, id))); } catch (Exception e) { throw ZooKeeperUtils.wrapThrowable(e); } } @Override public List<StreamDefinition> findAll() { try { return this.findAll(zkConnection.getClient().getChildren().forPath(Paths.STREAMS)); } catch (Exception e) { throw ZooKeeperUtils.wrapThrowable(e); } } @Override public List<StreamDefinition> findAll(Iterable<String> ids) { List<StreamDefinition> results = new ArrayList<StreamDefinition>(); for (String id : ids) { StreamDefinition sd = this.findOne(id); if (sd != null) { results.add(sd); } } return results; } @Override public long count() { try { Stat stat = zkConnection.getClient().checkExists().forPath(Paths.STREAMS); return stat == null ? 0 : stat.getNumChildren(); } catch (Exception e) { throw ZooKeeperUtils.wrapThrowable(e); } } @Override public void delete(String id) { logger.trace("Deleting stream {}", id); String path = Paths.build(Paths.STREAMS, id); try { zkConnection.getClient().delete().deletingChildrenIfNeeded().forPath(path); } catch (Exception e) { //NoNodeException - nothing to delete ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class); } } @Override public void delete(StreamDefinition entity) { StreamDefinitionRepositoryUtils.deleteDependencies(moduleDependencyRepository, entity); this.delete(entity.getName()); } @Override public void delete(Iterable<? extends StreamDefinition> entities) { for (StreamDefinition streamDefinition : entities) { this.delete(streamDefinition); } } @Override public void deleteAll() { try { delete(findAll()); } catch (Exception e) { //NoNodeException - nothing to delete ZooKeeperUtils.wrapAndThrowIgnoring(e, NoNodeException.class); } } @Override public Iterable<StreamDefinition> findAllInRange(String from, boolean fromInclusive, String to, boolean toInclusive) { throw new UnsupportedOperationException("Auto-generated method stub"); } }