package org.constellation.admin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.lang3.StringUtils;
import org.apache.sis.xml.MarshallerPool;
import org.constellation.ServiceDef;
import org.constellation.admin.dto.ServiceDTO;
import org.constellation.admin.exception.ConstellationException;
import org.constellation.admin.util.DefaultServiceConfiguration;
import org.constellation.business.IServiceBusiness;
import org.constellation.configuration.ConfigDirectory;
import org.constellation.configuration.ConfigurationException;
import org.constellation.configuration.Instance;
import org.constellation.configuration.ServiceStatus;
import org.constellation.configuration.TargetNotFoundException;
import org.constellation.dto.Details;
import org.constellation.database.api.ConstellationPersistenceException;
import org.constellation.database.api.jooq.tables.pojos.CstlUser;
import org.constellation.database.api.jooq.tables.pojos.Service;
import org.constellation.database.api.jooq.tables.pojos.ServiceDetails;
import org.constellation.database.api.jooq.tables.pojos.ServiceExtraConfig;
import org.constellation.database.api.repository.DataRepository;
import org.constellation.database.api.repository.DatasetRepository;
import org.constellation.database.api.repository.LayerRepository;
import org.constellation.database.api.repository.ServiceRepository;
import org.constellation.database.api.repository.UserRepository;
import org.constellation.generic.database.GenericDatabaseMarshallerPool;
import org.constellation.security.SecurityManager;
import org.constellation.util.Util;
import org.constellation.ws.WSEngine;
import org.constellation.ws.Worker;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.util.FileUtilities;
import org.springframework.context.annotation.Primary;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Optional;
@Component
@Primary
public class ServiceBusiness implements IServiceBusiness {
@Inject
private SecurityManager securityManager;
@Inject
private UserRepository userRepository;
@Inject
private ServiceRepository serviceRepository;
@Inject
private DataRepository dataRepository;
@Inject
private DatasetRepository datasetRepository;
@Inject
private LayerRepository layerRepository;
/**
* Creates a new service instance.
*
* @param serviceType
* @param identifier
* The identifier of the service.
* @param details
* the service metadata (can be null).
* @param configuration
* the service configuration (can be null).
*
* @return the configuration object just setted.
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
public Object create(final String serviceType, final String identifier, Object configuration, Details details)
throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
if (configuration == null) {
configuration = DefaultServiceConfiguration.getDefaultConfiguration(serviceType);
}
Optional<CstlUser> user = userRepository.findOne(securityManager.getCurrentUserLogin());
final String config = getStringFromObject(configuration, GenericDatabaseMarshallerPool.getInstance());
final Service service = new Service();
service.setConfig(config);
service.setDate(new Date().getTime());
service.setType(ServiceDef.Specification.valueOf(serviceType.toUpperCase()).name().toLowerCase());
if (user.isPresent()) {
service.setOwner(user.get().getId());
}
service.setIdentifier(identifier);
service.setStatus(ServiceStatus.STOPPED.toString());
// TODO metadata-Iso
if (details == null) {
final InputStream in = Util
.getResourceAsStream("org/constellation/xml/" + service.getType().toUpperCase() + "Capabilities.xml");
if (in != null) {
try {
final Unmarshaller u = GenericDatabaseMarshallerPool.getInstance().acquireUnmarshaller();
details = (Details) u.unmarshal(in);
details.setIdentifier(service.getIdentifier());
details.setLang("eng"); // default value
GenericDatabaseMarshallerPool.getInstance().recycle(u);
in.close();
} catch (JAXBException | IOException ex) {
throw new ConfigurationException(ex);
}
} else {
throw new ConfigurationException("Unable to find the capabilities skeleton from resource.");
}
} else if (details.getLang() == null) {
details.setLang("eng");// default value
}
final String versions;
if (details.getVersions() != null) {
versions = StringUtils.join(details.getVersions(), "µ");
} else {
versions = "";
}
service.setVersions(versions);
int serviceId = serviceRepository.create(service);
setInstanceDetails(serviceType, identifier, details, details.getLang(), true);
return configuration;
}
/**
* Starts a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service identifier
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
@PreAuthorize("@cstlAuth.hasAccessToService(#serviceType, #identifier, 'start')")
//@PreAuthorize("@cstlAuth.hasAccessToService(#serviceType, #identifier, 'start') and @cstlAuth.hasAccessToService(#serviceType, #identifier, 'stop')")
public void start(final String serviceType, final String identifier) throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
try {
final Worker worker = WSEngine.buildWorker(serviceType, identifier);
if (worker != null) {
WSEngine.addServiceInstance(serviceType, identifier, worker);
if (!worker.isStarted()) {
service.setStatus("ERROR");
serviceRepository.update(service);
throw new ConfigurationException("Unable to start the instance " + identifier + ".");
}
service.setStatus("STARTED");
serviceRepository.update(service);
} else {
throw new ConfigurationException("The instance " + identifier + " can not be instanciated.");
}
} catch (IllegalArgumentException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
} else {
throw new TargetNotFoundException(serviceType + " service instance with identifier \"" + identifier
+ "\" not found. There is not configuration in the database.");
}
}
@Override
@Transactional
public void start(final String serviceType) throws ConfigurationException {
final List<Service> services = serviceRepository.findByType(serviceType);
for (Service service : services) {
try {
final String identifier = service.getIdentifier();
final Worker worker = WSEngine.buildWorker(serviceType, identifier);
if (worker != null) {
WSEngine.addServiceInstance(serviceType, identifier, worker);
if (!worker.isStarted()) {
service.setStatus("ERROR");
serviceRepository.update(service);
}
service.setStatus("STARTED");
serviceRepository.update(service);
} else {
throw new ConfigurationException("The instance " + identifier + " can not be instanciated.");
}
} catch (IllegalArgumentException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
}
}
/**
* Stops a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service identifier
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
@PreAuthorize("@cstlAuth.hasAccessToService(#serviceType, #identifier, 'stop')")
public void stop(final String serviceType, final String identifier) throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
if (WSEngine.serviceInstanceExist(serviceType, identifier)) {
WSEngine.shutdownInstance(serviceType, identifier);
service.setStatus("STOPPED");
serviceRepository.update(service);
} else {
throw new ConfigurationException("Instance " + identifier + " doesn't exist.");
}
} else {
throw new TargetNotFoundException(serviceType + " service instance with identifier \"" + identifier
+ "\" not found. There is not configuration in the database.");
}
}
/**
* Restarts a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service identifier
* @param closeFirst
* indicates if the service should be closed before trying to
* restart it
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Transactional
public void restart(final String serviceType, final String identifier, final boolean closeFirst) throws ConfigurationException {
if (identifier == null || "".equals(identifier)) {
buildWorkers(serviceType, null, closeFirst);
} else {
if (WSEngine.serviceInstanceExist(serviceType, identifier)) {
buildWorkers(serviceType, identifier, closeFirst);
} else {
start(serviceType, identifier);
}
}
}
/**
* Renames a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the current service identifier
* @param newIdentifier
* the new service identifier
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
public void rename(final String serviceType, final String identifier, final String newIdentifier) throws ConfigurationException {
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
final Service newService = serviceRepository.findByIdentifierAndType(newIdentifier, serviceType);
if (newService == null) {
service.setIdentifier(newIdentifier);
serviceRepository.update(service);
// we stop the current worker
WSEngine.shutdownInstance(serviceType, identifier);
// start the new one
final Worker newWorker = WSEngine.buildWorker(serviceType, newIdentifier);
if (newWorker == null) {
throw new ConfigurationException("The instance " + newIdentifier
+ " can be started, maybe there is no configuration directory with this name.");
} else {
WSEngine.addServiceInstance(serviceType, newIdentifier, newWorker);
if (!newWorker.isStarted()) {
throw new ConfigurationException("unable to start the renamed instance");
}
}
} else {
throw new ConfigurationException("already existing instance:" + newIdentifier);
}
} else {
throw new ConfigurationException("no existing instance:" + identifier);
}
}
/**
* Deletes a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service identifier
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
public void delete(final String serviceType, final String identifier) throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
final Service service = getServiceByIdentifierAndType(serviceType, identifier);
if (service != null) {
// unregister the service instance if exist
if (WSEngine.serviceInstanceExist(serviceType, identifier)) {
WSEngine.shutdownInstance(serviceType, identifier);
}
if (serviceType.equalsIgnoreCase("csw")) {
dataRepository.removeAllDataFromCSW(service.getId());
datasetRepository.removeAllDatasetFromCSW(service.getId());
}
// delete from database
serviceRepository.delete(service.getId());
// delete folder
final File instanceDir = ConfigDirectory.getInstanceDirectory(serviceType, identifier);
if (instanceDir.isDirectory()) {
FileUtilities.deleteDirectory(instanceDir);
}
} else {
throw new ConfigurationException("There is no instance:" + identifier + " to delete");
}
}
@Override
@Transactional
public void deleteAll() throws ConfigurationException {
final List<Service> services = serviceRepository.findAll();
for (Service service : services) {
delete(service.getType(), service.getIdentifier());
}
}
/**
* Configures a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service identifier.
* @param configuration
* the service configuration (depending on implementation).
* @param details
* the service metadata.
*
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Transactional
public void configure(final String serviceType, final String identifier, Details details, Object configuration)
throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
if (configuration == null) {
configuration = DefaultServiceConfiguration.getDefaultConfiguration(serviceType);
}
// write configuration file.
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service == null) {
throw new ConfigurationException("Service " + serviceType + ':' + identifier + " not found.");
} else {
service.setConfig(getStringFromObject(configuration, GenericDatabaseMarshallerPool.getInstance()));
if (details != null) {
setInstanceDetails(serviceType, identifier, details, details.getLang(), true);
} else {
details = getInstanceDetails(service.getId(), "eng");
}
service.setVersions(StringUtils.join(details.getVersions(), "µ"));
serviceRepository.update(service);
}
}
/**
* Returns the configuration object of a service instance.
*
* @param serviceType
* The service type (WMS, WFS, ...)
* @param identifier
* the service
*
* @return a configuration {@link Object} (depending on implementation)
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
public Object getConfiguration(final String serviceType, final String identifier) throws ConfigurationException {
if (identifier == null || identifier.isEmpty()) {
throw new ConfigurationException("Service instance identifier can't be null or empty.");
}
try {
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
final String confXml = service.getConfig();
return getObjectFromString(confXml, GenericDatabaseMarshallerPool.getInstance());
}
} catch (JAXBException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
return null;
}
@Override
public Object getExtraConfiguration(final String serviceType, final String identifier, final String fileName)
throws ConfigurationException {
return getExtraConfiguration(serviceType, identifier, fileName, GenericDatabaseMarshallerPool.getInstance());
}
@Override
public Object getExtraConfiguration(final String serviceType, final String identifier, final String fileName, final MarshallerPool pool)
throws ConfigurationException {
try {
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
final ServiceExtraConfig conf = serviceRepository.getExtraConfig(service.getId(), fileName);
if (conf != null) {
final String content = conf.getContent();
if (content != null) {
return getObjectFromString(content, pool);
}
}
}
} catch (JAXBException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
return null;
}
@Override
@Transactional
public void setExtraConfiguration(final String serviceType, final String identifier, final String fileName, final Object config,
final MarshallerPool pool) {
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
if (service != null) {
final String content = getStringFromObject(config, pool);
final ServiceExtraConfig conf = new ServiceExtraConfig(service.getId(), fileName, content);
serviceRepository.updateExtraFile(service, fileName, content);
}
}
/**
* Returns all service instances (for current specification) status.
*
* @param spec
* @return a {@link Map} of {@link ServiceStatus} status
*/
@Override
public Map<String, ServiceStatus> getStatus(final String spec) {
final List<Service> services = serviceRepository.findByType(spec);
final Map<String, ServiceStatus> status = new HashMap<>();
for (Service service : services) {
status.put(service.getIdentifier(), ServiceStatus.valueOf(service.getStatus()));
}
return status;
}
@Override
public List<String> getServiceIdentifiers(final String spec) {
return serviceRepository.findIdentifiersByType(spec);
}
/**
* Create new worker instance in service directory.
*
* @param serviceType
* @param identifier
* @param closeInstance
* @throws ProcessException
*/
private void buildWorkers(final String serviceType, final String identifier, final boolean closeInstance) throws ConfigurationException {
/*
* Single refresh
*/
if (identifier != null) {
if (closeInstance) {
WSEngine.shutdownInstance(serviceType, identifier);
}
try {
final Worker worker = WSEngine.buildWorker(serviceType, identifier);
if (worker != null) {
WSEngine.addServiceInstance(serviceType, identifier, worker);
if (!worker.isStarted()) {
throw new ConfigurationException("Unable to start the instance " + identifier + ".");
}
} else {
throw new ConfigurationException("The instance " + identifier
+ " can't be started, maybe there is no configuration directory with this name.");
}
} catch (IllegalArgumentException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
/*
* Multiple refresh
*/
} else {
final Map<String, Worker> workersMap = new HashMap<>();
if (closeInstance) {
WSEngine.destroyInstances(serviceType);
}
for (String instanceID : getServiceIdentifiers(serviceType)) {
try {
final Worker worker = WSEngine.buildWorker(serviceType, instanceID);
if (worker != null) {
workersMap.put(instanceID, worker);
} else {
throw new ConfigurationException("The instance " + instanceID
+ " can be started, maybe there is no configuration directory with this name.");
}
} catch (IllegalArgumentException ex) {
throw new ConfigurationException(ex.getMessage(), ex);
}
}
WSEngine.setServiceInstances(serviceType, workersMap);
}
}
@Override
public Service getServiceByIdentifierAndType(String serviceType, String identifier) {
return serviceRepository.findByIdentifierAndType(identifier, serviceType);
}
/**
* Returns a service instance metadata.
*
* @param serviceType
* The type of the service.
* @param identifier
* the service identifier
* @return
* @throws TargetNotFoundException
* if the service with specified identifier does not exist
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
public Details getInstanceDetails(final String serviceType, final String identifier, final String language)
throws ConfigurationException {
final Service service = this.ensureExistingInstance(serviceType, identifier);
return getInstanceDetails(service.getId(), language);
}
public Details getInstanceDetails(final int serviceId, String language) throws ConfigurationException {
try {
ServiceDetails details;
if (language == null) {
details = serviceRepository.getServiceDetailsForDefaultLang(serviceId);
} else {
details = serviceRepository.getServiceDetails(serviceId, language);
}
if (details == null) {
details = serviceRepository.getServiceDetailsForDefaultLang(serviceId);
}
return (Details) getObjectFromString(details.getContent(), GenericDatabaseMarshallerPool.getInstance());
} catch (JAXBException ex) {
throw new ConstellationException(ex);
}
}
/**
* Updates a service instance metadata.
*
* @param serviceType
* The type of the service.
* @param identifier
* the service identifier
* @param details
* the service metadata
* @throws TargetNotFoundException
* if the service with specified identifier does not exist
* @throws org.constellation.configuration.ConfigurationException
* if the operation has failed for any reason
*/
@Override
@Transactional
public void setInstanceDetails(final String serviceType, final String identifier, final Details details, final String language,
final boolean default_) throws ConfigurationException {
final Service service = this.ensureExistingInstance(serviceType, identifier);
if (service != null) {
final String xml = getStringFromObject(details, GenericDatabaseMarshallerPool.getInstance());
final ServiceDetails serviceDetails = new ServiceDetails(service.getId(), language, xml, default_);
serviceRepository.createOrUpdateServiceDetails(serviceDetails);
}
}
/**
* Ensure that a service instance really exists.
*
* @param spec
* The service type.
* @param identifier
* the service identifier
* @throws TargetNotFoundException
* if the service with specified identifier does not exist
*/
@Override
public Service ensureExistingInstance(final String spec, final String identifier) throws TargetNotFoundException {
Service service = serviceRepository.findByIdentifierAndType(identifier, spec);
if (!WSEngine.serviceInstanceExist(spec.toUpperCase(), identifier)) {
if (service == null) {
throw new TargetNotFoundException(spec + " service instance with identifier \"" + identifier
+ "\" not found. There is not configuration in the database.");
}
}
return service;
}
private String getStringFromObject(final Object obj, final MarshallerPool pool) {
String config = null;
if (obj != null) {
try {
final StringWriter sw = new StringWriter();
final Marshaller m = pool.acquireMarshaller();
m.marshal(obj, sw);
pool.recycle(m);
config = sw.toString();
} catch (JAXBException e) {
throw new ConstellationPersistenceException(e);
}
}
return config;
}
private Object getObjectFromString(final String xml, final MarshallerPool pool) throws JAXBException {
if (xml != null) {
final Unmarshaller u = pool.acquireUnmarshaller();
final Object config = u.unmarshal(new StringReader(xml));
pool.recycle(u);
return config;
}
return null;
}
@Override
public List<ServiceDTO> getAllServices(String lang) throws ConfigurationException {
List<ServiceDTO> serviceDTOs = new ArrayList<>();
List<Service> services = serviceRepository.findAll();
for (Service service : services) {
final Details details = getInstanceDetails(service.getId(), lang);
final ServiceDTO serviceDTO = convertIntoServiceDto(service, details);
serviceDTOs.add(serviceDTO);
}
return serviceDTOs;
}
@Override
public List<ServiceDTO> getAllServicesByType(String lang, String type) throws ConfigurationException {
List<ServiceDTO> serviceDTOs = new ArrayList<>();
List<Service> services = serviceRepository.findByType(type);
for (Service service : services) {
final Details details = getInstanceDetails(service.getId(), lang);
final ServiceDTO serviceDTO = convertIntoServiceDto(service, details);
serviceDTOs.add(serviceDTO);
}
return serviceDTOs;
}
private ServiceDTO convertIntoServiceDto(final Service service, final Details details) {
final ServiceDTO serviceDTO = new ServiceDTO();
serviceDTO.setOwner(service.getOwner());
serviceDTO.setConfig(service.getConfig());
serviceDTO.setDate(new Date(service.getDate()));
serviceDTO.setDescription(details != null ? details.getDescription() : "");
serviceDTO.setId(service.getId());
serviceDTO.setIdentifier(service.getIdentifier());
serviceDTO.setStatus(service.getStatus());
serviceDTO.setTitle(details != null ? details.getName() : "");
serviceDTO.setType(service.getType());
serviceDTO.setVersions(service.getVersions());
return serviceDTO;
}
@Override
public Instance getI18nInstance(String serviceType, String identifier, String lang) {
Instance instance = new Instance();
final Service service = serviceRepository.findByIdentifierAndType(identifier, serviceType);
try {
final Details details = getInstanceDetails(serviceType, identifier, lang);
instance.setId(service.getId());
instance.set_abstract(details.getDescription());
instance.setIdentifier(service.getIdentifier());
int layersNumber = layerRepository.findByServiceId(service.getId()).size();
instance.setLayersNumber(layersNumber);
instance.setName(details.getName());
instance.setType(service.getType());
instance.setVersions(Arrays.asList(service.getVersions().split("µ")));
instance.setStatus(ServiceStatus.valueOf(service.getStatus()));
return instance;
} catch (ConfigurationException e) {
throw new ConstellationException(e);
}
}
}