/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* 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.constellation.rest.api;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import org.apache.sis.xml.MarshallerPool;
import org.constellation.admin.dto.MapContextLayersDTO;
import org.constellation.admin.dto.MapContextStyledLayerDTO;
import org.constellation.business.IDataBusiness;
import org.constellation.business.IMapContextBusiness;
import org.constellation.business.IProviderBusiness;
import org.constellation.configuration.ConfigurationException;
import org.constellation.dto.ParameterValues;
import org.constellation.database.api.jooq.tables.pojos.CstlUser;
import org.constellation.database.api.jooq.tables.pojos.Data;
import org.constellation.database.api.jooq.tables.pojos.Mapcontext;
import org.constellation.database.api.jooq.tables.pojos.MapcontextStyledLayer;
import org.constellation.database.api.jooq.tables.pojos.Provider;
import org.constellation.database.api.repository.MapContextRepository;
import org.constellation.database.api.repository.UserRepository;
import org.constellation.provider.Providers;
import org.geotoolkit.georss.xml.v100.WhereType;
import org.geotoolkit.gml.xml.v311.DirectPositionType;
import org.geotoolkit.gml.xml.v311.EnvelopeType;
import org.geotoolkit.owc.xml.v10.MethodCodeType;
import org.geotoolkit.owc.xml.v10.OfferingType;
import org.geotoolkit.owc.xml.v10.OperationType;
import org.geotoolkit.wms.WebMapClient;
import org.geotoolkit.wms.xml.AbstractLayer;
import org.geotoolkit.wms.xml.AbstractWMSCapabilities;
import org.geotoolkit.wms.xml.WMSMarshallerPool;
import org.opengis.util.FactoryException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.w3._2005.atom.CategoryType;
import org.w3._2005.atom.DateTimeType;
import org.w3._2005.atom.EntryType;
import org.w3._2005.atom.FeedType;
import org.w3._2005.atom.IdType;
import org.w3._2005.atom.TextType;
import com.google.common.base.Optional;
/**
* Map context REST API.
*
* @author Cédric Briançon (Geomatys)
*/
@Component
@Path("/1/context/")
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public class MapContextRest {
private static final org.w3._2005.atom.ObjectFactory OBJ_ATOM_FACT = new org.w3._2005.atom.ObjectFactory();
private static final org.geotoolkit.owc.xml.v10.ObjectFactory OBJ_OWC_FACT = new org.geotoolkit.owc.xml.v10.ObjectFactory();
private static final org.geotoolkit.georss.xml.v100.ObjectFactory OBJ_GEORSS_FACT = new org.geotoolkit.georss.xml.v100.ObjectFactory();
@Inject
private IMapContextBusiness contextBusiness;
@Inject
private MapContextRepository contextRepository;
@Inject
private UserRepository userRepository;
@Inject
private IDataBusiness dataBusiness;
@Inject
private IProviderBusiness providerBusiness;
@GET
@Path("list")
public Response findAll() {
return Response.ok(contextRepository.findAll()).build();
}
@GET
@Path("list/layers")
public Response findAllMapContextLayers() {
return Response.ok(contextBusiness.findAllMapContextLayers()).build();
}
@PUT
@Transactional
public Response create(final MapContextLayersDTO mapContext,@Context HttpServletRequest req) {
//set owner
final Optional<CstlUser> cstlUser = userRepository.findOne(req.getUserPrincipal().getName());
if (!cstlUser.isPresent()) {
return Response.status(500).entity("operation not allowed without login").build();
}
mapContext.setOwner(cstlUser.get().getId());
final Mapcontext mapContextCreated = contextBusiness.create(mapContext);
return Response.ok(mapContextCreated).build();
}
@POST
@Transactional
public Response update(final MapContextLayersDTO mapContext) {
contextRepository.update(mapContext);
return Response.ok(mapContext).build();
}
@DELETE
@Path("{id}")
@Transactional
public Response delete(@PathParam("id") final int contextId) {
contextRepository.delete(contextId);
return Response.noContent().type(MediaType.TEXT_PLAIN_TYPE).build();
}
@POST
@Path("layers/{id}")
@Transactional
public Response setMapItems(@PathParam("id") final int contextId, final List<MapcontextStyledLayer> layers) {
contextBusiness.setMapItems(contextId, layers);
return Response.status(201).type(MediaType.TEXT_PLAIN_TYPE).build();
}
@GET
@Path("{id}/extent")
public Response getContextExtents(@PathParam("id") final int contextId) {
final ParameterValues values;
try {
values = contextBusiness.getExtent(contextId);
} catch (FactoryException ex) {
Providers.LOGGER.log(Level.WARNING, ex.getMessage(), ex);
return Response.ok("Failed to extract envelope for context "+contextId+". "+ex.getMessage()).status(500).build();
}
if (values == null) {
return Response.status(500).build();
}
return Response.ok(values).build();
}
@POST
@Path("extent/layers")
public Response getContextExtents(final List<MapcontextStyledLayer> layers) {
final ParameterValues values;
try {
values = contextBusiness.getExtentForLayers(layers);
} catch (FactoryException ex) {
Providers.LOGGER.log(Level.WARNING, ex.getMessage(), ex);
return Response.ok("Failed to extract envelope for these layers. "+ex.getMessage()).status(500).build();
}
if (values == null) {
return Response.status(500).entity("Cannot calculate envelope for layers, maybe the layers array sent is empty!").build();
}
return Response.ok(values).build();
}
@POST
@Path("external/capabilities/layers/{version}")
public Response getLayersForWms(@PathParam("version") final String version, final String url) throws IOException, JAXBException {
final WebMapClient client = (version != null && !version.isEmpty()) ?
new WebMapClient(new URL(url), version) : new WebMapClient(new URL(url));
final InputStream response = client.createGetCapabilities().getResponseStream();
final MarshallerPool pool = WMSMarshallerPool.getInstance();
final Unmarshaller unmarsh = pool.acquireUnmarshaller();
final Object obj = unmarsh.unmarshal(response);
pool.recycle(unmarsh);
if (!(obj instanceof AbstractWMSCapabilities)) {
throw new JAXBException("Unable to parse get capabilities response");
}
final List<AbstractLayer> layers = ((AbstractWMSCapabilities)obj).getLayers();
final List<AbstractLayer> finalList = new ArrayList<>();
for (final AbstractLayer layer : layers) {
// Remove layer groups, if any
if (layer.getLayer() != null && layer.getLayer().size() > 0) {
continue;
}
finalList.add(layer);
}
return Response.ok(finalList).build();
}
@GET
@Path("{id}/export")
@Produces("application/xml")
public Response export(@Context HttpServletRequest hsr, @PathParam("id") final int id) throws JAXBException {
final Mapcontext ctxt = contextRepository.findById(id);
final MapContextLayersDTO ctxtItem = contextBusiness.findMapContextLayers(id);
// Final object to return
final FeedType feed = new FeedType();
final List<Object> entriesToSet = feed.getAuthorOrCategoryOrContributor();
final IdType idFeed = new IdType();
idFeed.setValue(String.valueOf(id));
entriesToSet.add(OBJ_ATOM_FACT.createEntryTypeId(idFeed));
final TextType title = new TextType();
title.getContent().add(ctxt.getName());
entriesToSet.add(OBJ_ATOM_FACT.createEntryTypeTitle(title));
try {
final DateTimeType dateTime = new DateTimeType();
final Date date = new Date();
final GregorianCalendar gregCal = new GregorianCalendar();
gregCal.setTime(date);
dateTime.setValue(DatatypeFactory.newInstance().newXMLGregorianCalendar(gregCal));
entriesToSet.add(OBJ_ATOM_FACT.createEntryTypeUpdated(dateTime));
} catch (DatatypeConfigurationException ex) {
Providers.LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
if (ctxt.getWest() != null && ctxt.getNorth() != null && ctxt.getEast() != null && ctxt.getSouth() != null && ctxt.getCrs() != null) {
final DirectPositionType lowerCorner = new DirectPositionType(ctxt.getWest(), ctxt.getSouth());
final DirectPositionType upperCorner = new DirectPositionType(ctxt.getEast(), ctxt.getNorth());
final EnvelopeType envelope = new EnvelopeType(null, lowerCorner, upperCorner, ctxt.getCrs());
envelope.setSrsDimension(2);
final WhereType where = new WhereType();
where.setEnvelope(envelope);
entriesToSet.add(OBJ_GEORSS_FACT.createWhere(where));
}
for (final MapContextStyledLayerDTO styledLayer : ctxtItem.getLayers()) {
final boolean isExternal = (styledLayer.getExternalLayer() != null);
final String layerName = (isExternal) ? styledLayer.getExternalLayer() : styledLayer.getName();
final EntryType newEntry = new EntryType();
final List<Object> entryThings = newEntry.getAuthorOrCategoryOrContent();
final IdType idNewEntry = new IdType();
idNewEntry.setValue("Web Map Service Layer");
entryThings.add(OBJ_ATOM_FACT.createEntryTypeId(idNewEntry));
final TextType titleNewEntry = new TextType();
titleNewEntry.getContent().add(layerName);
entryThings.add(OBJ_ATOM_FACT.createEntryTypeTitle(titleNewEntry));
final org.w3._2005.atom.ContentType contentNewEntry = new org.w3._2005.atom.ContentType();
contentNewEntry.setType("html");
entryThings.add(OBJ_ATOM_FACT.createEntryTypeContent(contentNewEntry));
final CategoryType categoryNewEntry = new CategoryType();
categoryNewEntry.setScheme("http://www.opengis.net/spec/owc/active");
categoryNewEntry.setTerm("true");
entryThings.add(OBJ_ATOM_FACT.createEntryTypeCategory(categoryNewEntry));
final OfferingType offering = new OfferingType();
offering.setCode("http://www.opengis.net/spec/owc-atom/1.0/req/wms");
final String defStyle;
final String urlWms;
final String layerBBox;
if (isExternal) {
urlWms = styledLayer.getExternalServiceUrl();
defStyle = (styledLayer.getExternalStyle() != null) ? styledLayer.getExternalStyle().split(",")[0] : "";
layerBBox = styledLayer.getExternalLayerExtent();
} else {
final String reqUrl = hsr.getRequestURL().toString();
if(styledLayer.isIswms()){
urlWms = reqUrl.split("/api")[0] +"/WS/wms/"+ styledLayer.getServiceIdentifier();
}else {
urlWms = reqUrl.split("/api")[0] +"/api/1/portrayal/portray/style";
}
defStyle = (styledLayer.getExternalStyle() != null) ? styledLayer.getExternalStyle() : "";
ParameterValues extentValues = null;
try {
extentValues = contextBusiness.getExtentForLayers(Collections.singletonList(styledLayer.getMapcontextStyledLayer()));
} catch (FactoryException ex) {
Providers.LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
if (extentValues != null) {
layerBBox = extentValues.get("west") +","+ extentValues.get("south") +","+ extentValues.get("east") +","+ extentValues.get("north");
} else {
layerBBox = "-180,-90,180,90";
}
}
if(styledLayer.isIswms()){
final OperationType opCaps = new OperationType();
opCaps.setCode("GetCapabilities");
opCaps.setMethod(MethodCodeType.GET);
final StringBuilder capsUrl = new StringBuilder();
capsUrl.append(urlWms).append("?REQUEST=GetCapabilities&SERVICE=WMS");
opCaps.setHref(capsUrl.toString());
offering.getOperationOrContentOrStyleSet().add(OBJ_OWC_FACT.createOfferingTypeOperation(opCaps));
}
final OperationType opGetMap = new OperationType();
opGetMap.setCode("GetMap");
opGetMap.setMethod(MethodCodeType.GET);
final StringBuilder getMapUrl = new StringBuilder();
if(styledLayer.isIswms()){
//external wms or internal wms layer
getMapUrl.append(urlWms).append("?REQUEST=GetMap&SERVICE=WMS&FORMAT=image/png&TRANSPARENT=true&WIDTH=1024&HEIGHT=768&CRS=CRS:84&BBOX=")
.append(layerBBox)
.append("&LAYERS=").append(layerName)
.append("&STYLES=").append(defStyle)
.append("&VERSION=1.3.0");
}else {
//internal data
final Integer dataID = styledLayer.getDataId();
String layerDataName = layerName;
String provider="";
try {
final Data data = dataBusiness.findById(dataID);
final String namespace = data.getNamespace();
final String dataName = data.getName();
if(namespace!= null && !namespace.isEmpty()){
layerDataName = "{"+namespace+"}"+dataName;
}else {
layerDataName = dataName;
}
final Integer providerID = data.getProvider();
final Provider p = providerBusiness.getProvider(providerID);
provider = p.getIdentifier();
}catch(ConfigurationException ex){
Providers.LOGGER.log(Level.INFO, ex.getMessage(), ex);
}
getMapUrl.append(urlWms).append("?REQUEST=GetMap&SERVICE=WMS&FORMAT=image/png&TRANSPARENT=true&WIDTH=1024&HEIGHT=768&CRS=CRS:84&BBOX=")
.append(layerBBox)
.append("&LAYERS=").append(layerDataName)
.append("&STYLES=").append(defStyle)
.append("&SLD_VERSION=1.1.0")
.append("&PROVIDER=").append(provider)
.append("&VERSION=1.3.0");
if(defStyle!=null && !defStyle.isEmpty()){
getMapUrl.append("&SLDID=").append(defStyle)
.append("&SLDPROVIDER=sld");
}
}
opGetMap.setHref(getMapUrl.toString());
offering.getOperationOrContentOrStyleSet().add(OBJ_OWC_FACT.createOfferingTypeOperation(opGetMap));
entryThings.add(OBJ_OWC_FACT.createOffering(offering));
entriesToSet.add(OBJ_ATOM_FACT.createEntry(newEntry));
}
return Response.ok(feed, MediaType.APPLICATION_XML_TYPE).header("Content-Disposition", "attachment; filename=\"context-" + ctxt.getName() + ".xml\"").build();
}
}