/** * Copyright (C) 2012-2017 52°North Initiative for Geospatial Open Source * Software GmbH * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * If the program is linked with libraries which are licensed under one of * the following licenses, the combination of the program with the linked * library is not considered a "derivative work" of the program: * * - Apache License, version 2.0 * - Apache Software License, version 1.0 * - GNU Lesser General Public License, version 3 * - Mozilla Public License, versions 1.0, 1.1 and 2.0 * - Common Development and Distribution License (CDDL), version 1.0 * * Therefore the distribution of the program linked with libraries licensed * under the aforementioned licenses, is permitted by the copyright holders * if the distribution is compliant with both the GNU General Public * License version 2 and the aforementioned licenses. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. */ package org.n52.sos.ds.hibernate; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.Transaction; import org.joda.time.DateTime; import org.n52.sos.coding.CodingRepository; import org.n52.sos.ds.AbstractInsertResultDAO; import org.n52.sos.ds.FeatureQueryHandler; import org.n52.sos.ds.FeatureQueryHandlerQueryObject; import org.n52.sos.ds.HibernateDatasourceConstants; import org.n52.sos.ds.hibernate.dao.AbstractObservationDAO; import org.n52.sos.ds.hibernate.dao.DaoFactory; import org.n52.sos.ds.hibernate.dao.ObservationConstellationDAO; import org.n52.sos.ds.hibernate.dao.ResultTemplateDAO; import org.n52.sos.ds.hibernate.entities.Codespace; import org.n52.sos.ds.hibernate.entities.FeatureOfInterest; import org.n52.sos.ds.hibernate.entities.ObservationConstellation; import org.n52.sos.ds.hibernate.entities.Procedure; import org.n52.sos.ds.hibernate.entities.ResultTemplate; import org.n52.sos.ds.hibernate.entities.Unit; import org.n52.sos.ds.hibernate.util.ResultHandlingHelper; import org.n52.sos.ds.hibernate.util.observation.HibernateObservationUtilities; import org.n52.sos.exception.ows.InvalidParameterValueException; import org.n52.sos.exception.ows.NoApplicableCodeException; import org.n52.sos.exception.ows.concrete.DateTimeParseException; import org.n52.sos.ogc.gml.AbstractFeature; import org.n52.sos.ogc.gml.CodeWithAuthority; import org.n52.sos.ogc.gml.time.Time; import org.n52.sos.ogc.gml.time.TimeInstant; import org.n52.sos.ogc.gml.time.TimePeriod; import org.n52.sos.ogc.om.AbstractPhenomenon; import org.n52.sos.ogc.om.MultiObservationValues; import org.n52.sos.ogc.om.OmConstants; import org.n52.sos.ogc.om.OmObservableProperty; import org.n52.sos.ogc.om.OmObservation; import org.n52.sos.ogc.om.OmObservationConstellation; import org.n52.sos.ogc.om.features.samplingFeatures.SamplingFeature; import org.n52.sos.ogc.om.values.SweDataArrayValue; import org.n52.sos.ogc.ows.OwsExceptionReport; import org.n52.sos.ogc.sensorML.SensorML; import org.n52.sos.ogc.sos.CapabilitiesExtension; import org.n52.sos.ogc.sos.CapabilitiesExtensionKey; import org.n52.sos.ogc.sos.CapabilitiesExtensionProvider; import org.n52.sos.ogc.sos.Sos2Constants; import org.n52.sos.ogc.sos.SosConstants; import org.n52.sos.ogc.sos.SosInsertionCapabilities; import org.n52.sos.ogc.sos.SosProcedureDescription; import org.n52.sos.ogc.sos.SosResultEncoding; import org.n52.sos.ogc.sos.SosResultStructure; import org.n52.sos.ogc.swe.SweAbstractDataComponent; import org.n52.sos.ogc.swe.SweConstants; import org.n52.sos.ogc.swe.SweDataArray; import org.n52.sos.ogc.swe.SweDataRecord; import org.n52.sos.ogc.swe.SweField; import org.n52.sos.ogc.swe.encoding.SweAbstractEncoding; import org.n52.sos.ogc.swe.encoding.SweTextEncoding; import org.n52.sos.ogc.swe.simpleType.SweAbstractSimpleType; import org.n52.sos.ogc.swe.simpleType.SweQuantity; import org.n52.sos.request.InsertResultRequest; import org.n52.sos.response.InsertResultResponse; import org.n52.sos.service.Configurator; import org.n52.sos.util.DateTimeHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * Implementation of the abstract class AbstractInsertResultDAO * * @since 4.0.0 * */ public class InsertResultDAO extends AbstractInsertResultDAO implements CapabilitiesExtensionProvider { private static final Logger LOGGER = LoggerFactory.getLogger(InsertResultDAO.class); private static final int FLUSH_THRESHOLD = 50; private final HibernateSessionHolder sessionHolder = new HibernateSessionHolder(); /** * constructor */ public InsertResultDAO() { super(SosConstants.SOS); } @Override public String getDatasourceDaoIdentifier() { return HibernateDatasourceConstants.ORM_DATASOURCE_DAO_IDENTIFIER; } @Override public synchronized InsertResultResponse insertResult(final InsertResultRequest request) throws OwsExceptionReport { final InsertResultResponse response = new InsertResultResponse(); response.setService(request.getService()); response.setVersion(request.getVersion()); Session session = null; Transaction transaction = null; Map<String,Codespace> codespaceCache = Maps.newHashMap(); Map<String,Unit> unitCache = Maps.newHashMap(); try { session = sessionHolder.getSession(); final ResultTemplate resultTemplate = new ResultTemplateDAO().getResultTemplateObject(request.getTemplateIdentifier(), session); transaction = session.beginTransaction(); final OmObservation o = getSingleObservationFromResultValues(response.getVersion(), resultTemplate, request.getResultValues(), session); response.setObservation(o); final List<OmObservation> observations = getSingleObservationsFromObservation(o); final ObservationConstellation obsConst = new ObservationConstellationDAO().getObservationConstellation( resultTemplate.getProcedure(), resultTemplate.getObservableProperty(), resultTemplate.getOffering(), session); int insertion = 0; final int size = observations.size(); final AbstractObservationDAO observationDAO = DaoFactory.getInstance().getObservationDAO(); LOGGER.debug("Start saving {} observations.", size); for (final OmObservation observation : observations) { observationDAO.insertObservationSingleValue(obsConst, resultTemplate.getFeatureOfInterest(), observation, codespaceCache, unitCache, Sets.newHashSet(obsConst.getOffering()), session); if ((++insertion % FLUSH_THRESHOLD) == 0) { session.flush(); session.clear(); LOGGER.debug("Saved {}/{} observations.", insertion, size); } } LOGGER.debug("Saved {} observations.", size); transaction.commit(); } catch (final HibernateException he) { if (transaction != null) { transaction.rollback(); } // XXX exception text throw new NoApplicableCodeException().causedBy(he); } finally { sessionHolder.returnSession(session); } return response; } /** * Create OmObservation from result values * * @param version * Service version * @param resultTemplate * Associated result template * @param resultValues * Result values * @param session * Hibernate session * @return OmObservation from result values * @throws OwsExceptionReport * If an error occurs during the processing */ private OmObservation getSingleObservationFromResultValues(final String version, final ResultTemplate resultTemplate, final String resultValues, final Session session) throws OwsExceptionReport { final SosResultEncoding resultEncoding = new SosResultEncoding(resultTemplate.getResultEncoding()); final SosResultStructure resultStructure = new SosResultStructure(resultTemplate.getResultStructure()); final String[] blockValues = getBlockValues(resultValues, resultEncoding.getEncoding()); final OmObservation singleObservation = getObservation(resultTemplate, blockValues, resultStructure.getResultStructure(), resultEncoding.getEncoding(), session); final AbstractFeature feature = getSosAbstractFeature(resultTemplate.getFeatureOfInterest(), version, session); singleObservation.getObservationConstellation().setFeatureOfInterest(feature); return singleObservation; } /** * Get internal feature from FeatureOfInterest entity * * @param featureOfInterest * @param version * Service version * @param session * Hibernate session * @return Internal feature representation * @throws OwsExceptionReport * If an error occurs during requesting */ protected AbstractFeature getSosAbstractFeature(final FeatureOfInterest featureOfInterest, final String version, final Session session) throws OwsExceptionReport { final FeatureQueryHandler featureQueryHandler = Configurator.getInstance().getFeatureQueryHandler(); FeatureQueryHandlerQueryObject queryObject = new FeatureQueryHandlerQueryObject() .addFeatureIdentifier(featureOfInterest.getIdentifier()) .setConnection(session) .setVersion(version); return featureQueryHandler.getFeatureByID(queryObject); } /** * Unfold internal observation from result values to single internal * observations * * @param observation * Internal observaiton to unfold * @return List with single interal observations * @throws OwsExceptionReport * If an error occurs during unfolding */ protected List<OmObservation> getSingleObservationsFromObservation(final OmObservation observation) throws OwsExceptionReport { try { return HibernateObservationUtilities.unfoldObservation(observation); } catch (final Exception e) { throw new InvalidParameterValueException() .causedBy(e) .at(Sos2Constants.InsertResultParams.resultValues) .withMessage( "The resultValues format does not comply to the resultStructure of the resultTemplate!"); } } /** * Get internal ObservationConstellation from result template * * @param resultTemplate * @param session * Hibernate session * @return Internal ObservationConstellation */ private OmObservationConstellation getSosObservationConstellation(final ResultTemplate resultTemplate, final Session session) { final List<ObservationConstellation> obsConsts = new ObservationConstellationDAO().getObservationConstellationsForOfferings( resultTemplate.getProcedure(), resultTemplate.getObservableProperty(), Sets.newHashSet(resultTemplate.getOffering()), session); final Set<String> offerings = Sets.newHashSet(resultTemplate.getOffering().getIdentifier()); String observationType = null; for (ObservationConstellation obsConst : obsConsts) { if (observationType == null) { observationType = obsConst.getObservationType().getObservationType(); } } final SosProcedureDescription procedure = createProcedure(resultTemplate.getProcedure()); final AbstractPhenomenon observablePropety = new OmObservableProperty(resultTemplate.getObservableProperty().getIdentifier()); final AbstractFeature feature = new SamplingFeature(new CodeWithAuthority(resultTemplate.getFeatureOfInterest().getIdentifier())); return new OmObservationConstellation(procedure, observablePropety, offerings, feature, observationType); } /** * Create internal ProcedureDescription from Procedure entity * * @param hProcedure * Procedure entity * @return Internal ProcedureDescription */ private SosProcedureDescription createProcedure(final Procedure hProcedure) { final SensorML procedure = new SensorML(); procedure.setIdentifier(hProcedure.getIdentifier()); return procedure; } /** * Get internal observation * * @param resultTemplate * Associated ResultTemplate * @param blockValues * Block values from result values * @param resultStructure * Associated ResultStructure * @param encoding * Associated ResultEncoding * @param session * Hibernate session * @return Internal observation * @throws OwsExceptionReport * If processing fails */ private OmObservation getObservation(final ResultTemplate resultTemplate, final String[] blockValues, final SweAbstractDataComponent resultStructure, final SweAbstractEncoding encoding, final Session session) throws OwsExceptionReport { final int resultTimeIndex = ResultHandlingHelper.hasResultTime(resultStructure); final int phenomenonTimeIndex = ResultHandlingHelper.hasPhenomenonTime(resultStructure); final SweDataRecord record = setRecordFrom(resultStructure); final Map<Integer, String> observedProperties = new HashMap<Integer, String>(record.getFields().size() - 1); final Map<Integer, String> units = new HashMap<Integer, String>(record.getFields().size() - 1); int j = 0; for (final SweField swefield : record.getFields()) { if (j != resultTimeIndex && j != phenomenonTimeIndex) { if (swefield.getElement() instanceof SweAbstractSimpleType<?>) { final Integer index = Integer.valueOf(j); final SweAbstractSimpleType<?> sweAbstractSimpleType = (SweAbstractSimpleType<?>) swefield.getElement(); if (sweAbstractSimpleType instanceof SweQuantity) { /* TODO units for other SosSweSimpleTypes? */ units.put(index, ((SweQuantity) sweAbstractSimpleType).getUom()); } observedProperties.put(index, swefield.getElement().getDefinition()); } else { throw new NoApplicableCodeException().withMessage("The swe:Field element of type {} is not yet supported!", swefield.getElement().getClass().getName()); } } ++j; } // TODO support for compositePhenomenon // if (observedProperties.size() > 1) { // } final MultiObservationValues<SweDataArray> sosValues = createObservationValueFrom(blockValues, record, encoding, resultTimeIndex, phenomenonTimeIndex); final OmObservation observation = new OmObservation(); observation.setObservationConstellation(getSosObservationConstellation(resultTemplate, session)); observation.setResultType(OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION); observation.setValue(sosValues); return observation; } /** * Create internal observation value * * @param blockValues * Block values from result values * @param recordFromResultStructure * Associated ResultStructure * @param encoding * Associated Result encoding * @param resultTimeIndex * Result time position * @param phenomenonTimeIndex * Phenomenon time positions * @return Internal observation value * @throws OwsExceptionReport * If processing fails */ private MultiObservationValues<SweDataArray> createObservationValueFrom(final String[] blockValues, final SweAbstractDataComponent recordFromResultStructure, final SweAbstractEncoding encoding, final int resultTimeIndex, final int phenomenonTimeIndex) throws OwsExceptionReport { final SweDataArray dataArray = new SweDataArray(); dataArray.setElementType(recordFromResultStructure); dataArray.setEncoding(encoding); final SweDataArrayValue dataArrayValue = new SweDataArrayValue(); dataArrayValue.setValue(dataArray); for (final String block : blockValues) { final String[] singleValues = getSingleValues(block, encoding); if (singleValues != null && singleValues.length > 0) { dataArrayValue.addBlock(Arrays.asList(singleValues)); } } final MultiObservationValues<SweDataArray> sosValues = new MultiObservationValues<SweDataArray>(); sosValues.setValue(dataArrayValue); return sosValues; } // TODO move to helper class /** * Get internal time object from time String * * @param timeString * Time String to parse * @return Internal time object * @throws OwsExceptionReport * If an error occurs */ private Time getPhenomenonTime(final String timeString) throws OwsExceptionReport { try { Time phenomenonTime; if (timeString.contains("/")) { final String[] times = timeString.split("/"); final DateTime start = DateTimeHelper.parseIsoString2DateTime(times[0].trim()); final DateTime end = DateTimeHelper.parseIsoString2DateTime(times[1].trim()); phenomenonTime = new TimePeriod(start, end); } else { final DateTime dateTime = DateTimeHelper.parseIsoString2DateTime(timeString.trim()); phenomenonTime = new TimeInstant(dateTime); } return phenomenonTime; } catch (final DateTimeParseException dte) { throw dte.at("phenomenonTime"); } } /** * Get single values from a block value * * @param block * Block value * @param encoding * ResultEncoding * @return Single value array */ private String[] getSingleValues(final String block, final SweAbstractEncoding encoding) { if (encoding instanceof SweTextEncoding) { final SweTextEncoding textEncoding = (SweTextEncoding) encoding; return separateValues(block, textEncoding.getTokenSeparator()); } return null; } /** * Get block values from result values * * @param resultValues * Result values * @param encoding * ResultEncoding * @return Block value array */ private String[] getBlockValues(final String resultValues, final SweAbstractEncoding encoding) { if (encoding instanceof SweTextEncoding) { final SweTextEncoding textEncoding = (SweTextEncoding) encoding; final String[] blockValues = separateValues(resultValues, textEncoding.getBlockSeparator()); return checkForCountValue(blockValues, textEncoding.getTokenSeparator()); } return null; } /** * Check if the block values from result values contains a preceding count * value * * @param blockValues * Block values from result values * @param tokenSeparator * Token separator * @return Block value array without preceding count value */ private String[] checkForCountValue(final String[] blockValues, final String tokenSeparator) { if (blockValues != null && blockValues.length > 0) { if (blockValues[0].contains(tokenSeparator)) { return blockValues; } else { final String[] blockValuesWithoutCount = new String[blockValues.length - 1]; System.arraycopy(blockValues, 1, blockValuesWithoutCount, 0, blockValuesWithoutCount.length); return blockValuesWithoutCount; } } return null; } /** * Separate values from String with separator * * @param values * Value String * @param separator * Separator * @return Separated values as array */ private String[] separateValues(final String values, final String separator) { return values.split(separator); } @Override public CapabilitiesExtension getExtension() { final SosInsertionCapabilities insertionCapabilities = new SosInsertionCapabilities(); insertionCapabilities.addFeatureOfInterestTypes(getCache().getFeatureOfInterestTypes()); insertionCapabilities.addObservationTypes(getCache().getObservationTypes()); insertionCapabilities.addProcedureDescriptionFormats(CodingRepository.getInstance() .getSupportedProcedureDescriptionFormats(SosConstants.SOS, Sos2Constants.SERVICEVERSION)); // TODO dynamic insertionCapabilities.addSupportedEncoding(SweConstants.ENCODING_TEXT); return insertionCapabilities; } @Override public CapabilitiesExtensionKey getCapabilitiesExtensionKey() { return new CapabilitiesExtensionKey(SosConstants.SOS, Sos2Constants.SERVICEVERSION); } @Override public boolean hasRelatedOperation() { return true; } @Override public String getRelatedOperation() { return getOperationName(); } }