/*
* 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.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.KeeperException;
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.core.DeploymentUnitStatus;
import org.springframework.xd.dirt.stream.Job;
import org.springframework.xd.dirt.stream.JobDefinition;
import org.springframework.xd.dirt.stream.JobRepository;
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;
/**
* Job instance repository. It should only return values for Jobs that are deployed.
*
* @author Mark Fisher
* @author David Turanski
*/
// todo: the JobRepository abstraction can be removed once we are fully zk-enabled since we do not need to
// support multiple impls at that point
public class ZooKeeperJobRepository implements JobRepository, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperJobRepository.class);
private final ZooKeeperConnection zkConnection;
private final PagingUtility<Job> pagingUtility = new PagingUtility<Job>();
private final RepositoryConnectionListener connectionListener = new RepositoryConnectionListener();
@Autowired
public ZooKeeperJobRepository(ZooKeeperConnection zkConnection) {
this.zkConnection = zkConnection;
}
@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<Job> findAll(Sort sort) {
// todo: implement sort support
return findAll();
}
@Override
public Page<Job> findAll(Pageable pageable) {
return pagingUtility.getPagedData(pageable, findAll());
}
@Override
public <S extends Job> S save(S entity) {
// job instances are "saved" when a JobDeploymentListener deploys a job
return entity;
}
@Override
public <S extends Job> Iterable<S> save(Iterable<S> entities) {
// job instances are "saved" when a JobDeploymentListener deploys a job
return entities;
}
@Override
public Job findOne(String id) {
CuratorFramework client = zkConnection.getClient();
String path = Paths.build(Paths.JOBS, id);
try {
Stat definitionStat = client.checkExists().forPath(path);
if (definitionStat != null) {
byte[] data = zkConnection.getClient().getData().forPath(path);
Map<String, String> map = ZooKeeperUtils.bytesToMap(data);
Job job = new Job(new JobDefinition(id, map.get("definition")));
Stat deployStat = client.checkExists().forPath(Paths.build(Paths.JOB_DEPLOYMENTS, id));
if (deployStat != null) {
job.setStartedAt(new Date(deployStat.getCtime()));
job.setStatus(getDeploymentStatus(id));
return job;
}
}
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
return null;
}
@Override
public boolean exists(String id) {
return null != findOne(id);
}
@Override
public List<Job> findAll() {
try {
return findAll(zkConnection.getClient().getChildren().forPath(Paths.JOBS));
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
@Override
public List<Job> findAll(Iterable<String> ids) {
List<Job> results = new ArrayList<Job>();
try {
for (String jobName : ids) {
Job job = findOne(jobName);
if (job != null) {
results.add(job);
}
}
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
return results;
}
@Override
public long count() {
try {
Stat stat = zkConnection.getClient().checkExists().forPath(Paths.JOB_DEPLOYMENTS);
return stat != null ? stat.getNumChildren() : 0;
}
catch (Exception e) {
throw ZooKeeperUtils.wrapThrowable(e);
}
}
@Override
public void delete(String id) {
CuratorFramework client = zkConnection.getClient();
try {
client.setData().forPath(
Paths.build(Paths.JOB_DEPLOYMENTS, id, Paths.STATUS),
ZooKeeperUtils.mapToBytes(new DeploymentUnitStatus(
DeploymentUnitStatus.State.undeploying).toMap()));
}
catch (Exception e) {
logger.warn("Exception while transitioning job '{}' state to {}", id,
DeploymentUnitStatus.State.undeploying, e);
}
try {
client.delete().deletingChildrenIfNeeded()
.forPath(Paths.build(Paths.JOB_DEPLOYMENTS, id));
}
catch (Exception e) {
//NoNodeException - nothing to delete
ZooKeeperUtils.wrapAndThrowIgnoring(e, KeeperException.NoNodeException.class);
}
}
@Override
public void delete(Job entity) {
delete(entity.getDefinition().getName());
}
@Override
public void delete(Iterable<? extends Job> entities) {
for (Job job : entities) {
delete(job);
}
}
@Override
public void deleteAll() {
try {
List<String> children = zkConnection.getClient().getChildren().forPath(Paths.JOB_DEPLOYMENTS);
for (String child : children) {
delete(child);
}
}
catch (Exception e) {
//NoNodeException - nothing to delete
ZooKeeperUtils.wrapAndThrowIgnoring(e, KeeperException.NoNodeException.class);
}
}
@Override
public Iterable<Job> findAllInRange(String from, boolean fromInclusive, String to, boolean toInclusive) {
List<Job> all = findAll();
if (CollectionUtils.isEmpty(all)) {
return Collections.emptyList();
}
Collections.sort(all);
List<Job> results = new ArrayList<Job>();
for (Job job : all) {
if (job.getDefinition().getName().compareTo(to) > 1) {
break;
}
if (job.getDefinition().getName().compareTo(from) < 0) {
continue;
}
results.add(job);
}
return results;
}
@Override
public DeploymentUnitStatus getDeploymentStatus(String s) {
String path = Paths.build(Paths.JOB_DEPLOYMENTS, s, Paths.STATUS);
byte[] statusBytes = null;
try {
statusBytes = zkConnection.getClient().getData().forPath(path);
}
catch (Exception e) {
// missing node means this job has not been deployed
ZooKeeperUtils.wrapAndThrowIgnoring(e, KeeperException.NoNodeException.class);
}
return (statusBytes == null)
? new DeploymentUnitStatus(DeploymentUnitStatus.State.undeployed)
: new DeploymentUnitStatus(ZooKeeperUtils.bytesToMap(statusBytes));
}
}