package org.zstack.core.rest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusEventListener;
import org.zstack.header.Component;
import org.zstack.header.apimediator.ApiMediatorConstant;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.*;
import org.zstack.header.rest.RESTApiFacade;
import org.zstack.header.rest.RestAPIResponse;
import org.zstack.header.rest.RestAPIState;
import org.zstack.header.rest.RestAPIVO;
import org.zstack.header.search.APISearchMessage;
import org.zstack.utils.ExceptionDSL;
import org.zstack.utils.Utils;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.*;
public class RESTApiFacadeImpl implements RESTApiFacade, CloudBusEventListener, Component {
private static final CLogger logger = Utils.getLogger(RESTApiFacadeImpl.class);
private EntityManagerFactory entityManagerFactory;
private Set<String> basePkgNames;
private List<String> processingRequests = Collections.synchronizedList(new ArrayList<String>(100));
@Autowired
private CloudBus bus;
void init() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Set<APIEvent> boundEvents = new HashSet<APIEvent>(100);
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.resetFilters(false);
scanner.addIncludeFilter(new AssignableTypeFilter(APIEvent.class));
for (String pkg : getBasePkgNames()) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg)) {
Class<?> clazz = Class.forName(bd.getBeanClassName());
if (clazz == APIEvent.class) {
continue;
}
APIEvent evt = (APIEvent) clazz.newInstance();
boundEvents.add(evt);
}
}
for (APIEvent e : boundEvents) {
bus.subscribeEvent(this, e);
}
}
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
private RestAPIVO persist(APIMessage msg) {
RestAPIVO vo = new RestAPIVO();
vo.setUuid(msg.getId());
vo.setApiMessageName(msg.getMessageName());
vo.setState(RestAPIState.Processing);
EntityManager mgr = getEntityManager();
EntityTransaction tran = mgr.getTransaction();
try {
tran.begin();
mgr.persist(vo);
mgr.flush();
mgr.refresh(vo);
tran.commit();
return vo;
} catch (Exception e) {
ExceptionDSL.exceptionSafe(tran::rollback);
throw new CloudRuntimeException(e);
} finally {
ExceptionDSL.exceptionSafe(mgr::close);
}
}
@Override
public RestAPIResponse send(APIMessage msg) {
assert !(msg instanceof APIListMessage) && !(msg instanceof APISearchMessage) : "You must invoke call(APIMessage) for APIListMessage or APISearchMsg, the message you pass is "
+ msg.getMessageName();
RestAPIResponse rsp = new RestAPIResponse();
RestAPIVO vo = persist(msg);
processingRequests.add(vo.getUuid());
rsp.setCreatedDate(vo.getCreateDate());
rsp.setState(vo.getState().toString());
rsp.setUuid(vo.getUuid());
msg.setServiceId(ApiMediatorConstant.SERVICE_ID);
bus.send(msg);
return rsp;
}
@Override
public RestAPIResponse call(APIMessage msg) {
RestAPIResponse rsp = new RestAPIResponse();
rsp.setCreatedDate(new Date());
msg.setServiceId(ApiMediatorConstant.SERVICE_ID);
MessageReply reply = bus.call(msg);
rsp.setFinishedDate(new Date());
rsp.setState(RestAPIState.Done.toString());
rsp.setResult(RESTApiDecoder.dump(reply));
return rsp;
}
private RestAPIVO find(String uuid) {
EntityManager mgr = getEntityManager();
EntityTransaction tran = mgr.getTransaction();
try {
tran.begin();
RestAPIVO vo = mgr.find(RestAPIVO.class, uuid);
tran.commit();
return vo;
} catch (Exception e) {
tran.rollback();
throw new CloudRuntimeException(e);
} finally {
mgr.close();
}
}
@Override
public RestAPIResponse getResult(String uuid) {
RestAPIVO vo = find(uuid);
if (vo == null) {
return null;
}
RestAPIResponse rsp = new RestAPIResponse();
rsp.setCreatedDate(vo.getCreateDate());
rsp.setFinishedDate(vo.getLastOpDate());
rsp.setResult(vo.getResult());
rsp.setState(vo.getState().toString());
rsp.setUuid(vo.getUuid());
return rsp;
}
private synchronized EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
private boolean update(APIEvent e) {
String sql = "update RestAPIVO r set r.result = :result, r.state = :state where r.uuid = :uuid";
EntityManager mgr = getEntityManager();
EntityTransaction tran = mgr.getTransaction();
try {
tran.begin();
Query query = mgr.createQuery(sql);
query.setParameter("result", RESTApiDecoder.dump(e));
query.setParameter("state", RestAPIState.Done);
query.setParameter("uuid", e.getApiId());
int ret = query.executeUpdate();
tran.commit();
return ret > 0;
} catch (Exception ex) {
tran.rollback();
throw new CloudRuntimeException(ex);
} finally {
mgr.close();
}
}
@Override
public boolean handleEvent(Event e) {
try {
if (e instanceof APIEvent) {
APIEvent ae = (APIEvent) e;
if (processingRequests.contains(ae.getApiId())) {
boolean ret = update(ae);
processingRequests.remove(ae.getApiId());
if (!ret) {
logger.warn(String.format("Cannot find RestAPIVO[uuid:%s], something wrong happened", ae.getApiId()));
}
}
} else {
bus.dealWithUnknownMessage(e);
}
} catch (Exception ex) {
logger.warn(ex.getMessage(), ex);
}
return false;
}
public Set<String> getBasePkgNames() {
if (basePkgNames == null) {
basePkgNames = new HashSet<String>();
basePkgNames.add("org.zstack");
}
return basePkgNames;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
}