/* * 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.sos.io.lucene; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.sis.storage.DataStoreException; import org.constellation.generic.database.Automatic; import org.geotoolkit.gml.xml.Envelope; import org.geotoolkit.lucene.IndexingException; import org.geotoolkit.lucene.SearchingException; import org.geotoolkit.lucene.filter.SpatialQuery; import org.geotoolkit.observation.ObservationFilter; import org.geotoolkit.observation.ObservationResult; import org.geotoolkit.observation.ObservationStoreException; import org.geotoolkit.sos.xml.ObservationOffering; import org.geotoolkit.sos.xml.ResponseModeType; import org.opengis.temporal.Instant; import org.opengis.temporal.Period; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import static org.constellation.sos.ws.SOSConstants.EVENT_TIME; import static org.constellation.sos.ws.SOSConstants.MEASUREMENT_QNAME; import static org.constellation.sos.ws.SOSUtils.getLuceneTimeValue; import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_PARAMETER_VALUE; /** * TODO * * @author Guilhem Legal (Geomatys) */ public class LuceneObservationFilter implements ObservationFilter { private StringBuilder luceneRequest; private LuceneObservationSearcher searcher; private static final String OR_OPERATOR = " OR "; public LuceneObservationFilter(final LuceneObservationFilter omFilter) throws DataStoreException { this.searcher = omFilter.searcher; } public LuceneObservationFilter(final Automatic configuration, final Map<String, Object> properties) throws DataStoreException { try { this.searcher = new LuceneObservationSearcher(configuration.getConfigurationDirectory(), ""); } catch (IndexingException ex) { throw new DataStoreException("IndexingException in LuceneObservationFilter constructor", ex); } } /** * {@inheritDoc} */ @Override public void initFilterObservation(final ResponseModeType requestMode, final QName resultModel) { if (resultModel.equals(MEASUREMENT_QNAME)) { luceneRequest = new StringBuilder("type:measurement "); } else { luceneRequest = new StringBuilder("type:observation "); } if (ResponseModeType.RESULT_TEMPLATE.equals(requestMode)) { luceneRequest.append("template:TRUE "); } else { luceneRequest.append("template:FALSE "); } } /** * {@inheritDoc} */ @Override public void initFilterGetResult(final String procedure, final QName resultModel) { if (resultModel.equals(MEASUREMENT_QNAME)) { luceneRequest = new StringBuilder("type:measurement AND template:FALSE AND procedure:\"" + procedure + "\" "); } else { luceneRequest = new StringBuilder("type:observation AND template:FALSE AND procedure:\"" + procedure + "\" "); } } /** * {@inheritDoc} */ @Override public void initFilterGetFeatureOfInterest() throws DataStoreException { // do nothing no implementes } /** * {@inheritDoc} */ @Override public void setProcedure(final List<String> procedures, final List<ObservationOffering> offerings) { luceneRequest.append(" ( "); boolean add = false; if (!procedures.isEmpty()) { for (String s : procedures) { if (s != null) { luceneRequest.append(" procedure:\"").append(s).append("\" OR "); add = true; } } } else { //if is not specified we use all the process of the offering for (ObservationOffering off : offerings) { for (String proc : off.getProcedures()) { luceneRequest.append(" procedure:\"").append(proc).append("\" OR "); add = true; } } } luceneRequest.delete(luceneRequest.length() - 3, luceneRequest.length()); if (add) { luceneRequest.append(") "); } } /** * {@inheritDoc} */ @Override public void setObservedProperties(final List<String> phenomenon) { luceneRequest.append(" AND( "); for (String p : phenomenon) { luceneRequest.append(" observed_property:\"").append(p).append('"').append(OR_OPERATOR); } luceneRequest.delete(luceneRequest.length() - 3, luceneRequest.length()); luceneRequest.append(") "); } /** * {@inheritDoc} */ @Override public void setFeatureOfInterest(final List<String> fois) { luceneRequest.append(" AND ("); for (String foi : fois) { luceneRequest.append("feature_of_interest:").append(foi).append(" OR "); } luceneRequest.delete(luceneRequest.length() - 3, luceneRequest.length()); luceneRequest.append(") "); } /** * {@inheritDoc} */ @Override public void setTimeEquals(final Object time) throws DataStoreException { if (time instanceof Period) { final Period tp = (Period) time; final String begin = getLuceneTimeValue(tp.getBeginning().getDate()); final String end = getLuceneTimeValue(tp.getEnding().getDate()); // we request directly a multiple observation or a period observation (one measure during a period) luceneRequest.append("AND ("); luceneRequest.append(" sampling_time_begin:").append(begin).append(" AND "); luceneRequest.append(" sampling_time_end:").append(end).append(") "); // if the temporal object is a timeInstant } else if (time instanceof Instant) { final Instant ti = (Instant) time; final String position = getLuceneTimeValue(ti.getDate()); luceneRequest.append("AND ("); // case 1 a single observation luceneRequest.append("(sampling_time_begin:'").append(position).append("' AND sampling_time_end:NULL)"); luceneRequest.append(OR_OPERATOR); //case 2 multiple observations containing a matching value luceneRequest.append("(sampling_time_begin: [19700000 ").append(position).append("] ").append(" AND sampling_time_end: [").append(position).append(" 30000000]))"); } else { throw new ObservationStoreException("TM_Equals operation require timeInstant or TimePeriod!", INVALID_PARAMETER_VALUE, EVENT_TIME); } } /** * {@inheritDoc} */ @Override public void setTimeBefore(final Object time) throws DataStoreException { // for the operation before the temporal object must be an timeInstant if (time instanceof Instant) { final Instant ti = (Instant) time; final String position = getLuceneTimeValue(ti.getDate()); luceneRequest.append("AND ("); // the single and multpile observations which begin after the bound luceneRequest.append("(sampling_time_begin: [19700000000000 ").append(position).append("]))"); } else { throw new ObservationStoreException("TM_Before operation require timeInstant!", INVALID_PARAMETER_VALUE, EVENT_TIME); } } /** * {@inheritDoc} */ @Override public void setTimeAfter(final Object time) throws DataStoreException { // for the operation after the temporal object must be an timeInstant if (time instanceof Instant) { final Instant ti = (Instant) time; final String position = getLuceneTimeValue(ti.getDate()); luceneRequest.append("AND ("); // the single and multpile observations which begin after the bound luceneRequest.append("(sampling_time_begin:[").append(position).append(" 30000000])"); luceneRequest.append(OR_OPERATOR); // the multiple observations overlapping the bound luceneRequest.append("(sampling_time_begin: [19700000 ").append(position).append("] AND sampling_time_end:[").append(position).append(" 30000000]))"); } else { throw new ObservationStoreException("TM_After operation require timeInstant!", INVALID_PARAMETER_VALUE, EVENT_TIME); } } /** * {@inheritDoc} */ @Override public void setTimeDuring(final Object time) throws DataStoreException { if (time instanceof Period) { final Period tp = (Period) time; final String begin = getLuceneTimeValue(tp.getBeginning().getDate()); final String end = getLuceneTimeValue(tp.getEnding().getDate()); luceneRequest.append("AND ("); // the multiple observations included in the period luceneRequest.append(" (sampling_time_begin:[").append(begin).append(" 30000000] AND sampling_time_end:[19700000 ").append(end).append("])"); luceneRequest.append(OR_OPERATOR); // the single observations included in the period luceneRequest.append(" (sampling_time_begin:[").append(begin).append(" 30000000] AND sampling_time_begin:[19700000 ").append(end).append("] AND sampling_time_end IS NULL)"); luceneRequest.append(OR_OPERATOR); // the multiple observations which overlaps the first bound luceneRequest.append(" (sampling_time_begin:[19700000 ").append(begin).append("] AND sampling_time_end:[19700000 ").append(end).append("] AND sampling_time_end:[").append(begin).append(" 30000000])"); luceneRequest.append(OR_OPERATOR); // the multiple observations which overlaps the second bound luceneRequest.append(" (sampling_time_begin:[").append(begin).append(" 30000000] AND sampling_time_end:[").append(end).append(" 30000000] AND sampling_time_begin:[19700000 ").append(end).append("])"); luceneRequest.append(OR_OPERATOR); // the multiple observations which overlaps the whole period luceneRequest.append(" (sampling_time_begin:[19700000 ").append(begin).append("] AND sampling_time_end:[").append(end).append(" 30000000]))"); } else { throw new ObservationStoreException("TM_During operation require TimePeriod!", INVALID_PARAMETER_VALUE, EVENT_TIME); } } /** * {@inheritDoc} */ @Override public void setOfferings(final List<ObservationOffering> offerings) throws DataStoreException { // not used in this implementations } /** * {@inheritDoc} */ @Override public List<ObservationResult> filterResult() throws DataStoreException { try { final SpatialQuery query = new SpatialQuery(luceneRequest.toString()); final SortField sf = new SortField("sampling_time_begin", SortField.Type.STRING, false); query.setSort(new Sort(sf)); return searcher.doResultSearch(query); } catch(SearchingException ex) { throw new DataStoreException("Search exception while filtering the observation", ex); } } /** * {@inheritDoc} */ @Override public Set<String> filterObservation() throws DataStoreException { try { return searcher.doSearch(new SpatialQuery(luceneRequest.toString())); } catch(SearchingException ex) { throw new DataStoreException("Search exception while filtering the observation", ex); } } /** * {@inheritDoc} */ @Override public String getInfos() { return "Constellation Lucene O&M Filter 0.9"; } /** * {@inheritDoc} */ @Override public boolean isBoundedObservation() { return false; } /** * {@inheritDoc} */ @Override public void setBoundingBox(Envelope e) throws DataStoreException { throw new DataStoreException("SetBoundingBox is not supported by this ObservationFilter implementation."); } /** * {@inheritDoc} */ @Override public void setResultEquals(String propertyName, String value) throws DataStoreException { throw new DataStoreException("setResultEquals is not supported by this ObservationFilter implementation."); } /** * {@inheritDoc} */ @Override public List<String> supportedQueryableResultProperties() { return new ArrayList<>(); } /** * {@inheritDoc} */ @Override public void refresh() throws DataStoreException { try { searcher.refresh(); } catch (IndexingException ex) { throw new DataStoreException("Indexing Exception while refreshing the lucene index", ex); } } /** * {@inheritDoc} */ @Override public void setLoglevel(final Level logLevel) { if (searcher != null) { searcher.setLogLevel(logLevel); } } @Override public void setTimeLatest() throws DataStoreException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void setTimeFirst() throws DataStoreException { throw new UnsupportedOperationException("Not supported yet."); } @Override public boolean isDefaultTemplateTime() { return true; } @Override public Set<String> filterFeatureOfInterest() throws DataStoreException { throw new DataStoreException("filterFeatureOfInterest is not supported by this ObservationFilter implementation."); } @Override public void destroy() { if (searcher != null) { searcher.destroy(); } } }