/*
* 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.metadata.io.mdweb;
import org.constellation.generic.database.Automatic;
import org.constellation.metadata.io.MDWebMetadataWriter;
import org.constellation.metadata.io.MetadataIoException;
import org.geotoolkit.csw.xml.CSWMarshallerPool;
import org.geotoolkit.lucene.index.AbstractIndexer;
import org.mdweb.io.MD_IOException;
import org.mdweb.model.schemas.Classe;
import org.mdweb.model.schemas.PrimitiveType;
import org.mdweb.model.schemas.Property;
import org.mdweb.model.storage.FullRecord;
import org.mdweb.model.storage.TextValue;
import org.mdweb.model.storage.Value;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.logging.Level;
import static org.geotoolkit.ows.xml.OWSExceptionCode.*;
// Constellation dependencies
// Geotoolkit dependencies
// MDWeb meta model dependencies
/**
* A CSW Metadata Writer specific for MDweb data source.
* It allows to write, update and delete metadatas, it also keep the lucene Index of the CSW up to date.
*
* @author Guilhem Legal (Geomatys)
*/
public class MDWebCSWMetadataWriter extends MDWebMetadataWriter {
/**
* An indexer lucene to add object into the index.
*/
protected final AbstractIndexer indexer;
/**
* Build a new metadata writer.
*
* @param MDReader an MDWeb database reader.
*/
public MDWebCSWMetadataWriter(final Automatic configuration, final AbstractIndexer index) throws MetadataIoException {
super(configuration);
this.indexer = index;
}
@Override
public boolean storeMetadata(Node obj) throws MetadataIoException {
return super.storeMetadata(obj);
}
/**
* {@inheritDoc}
*/
@Override
public void indexDocument(final FullRecord f) {
if (indexer != null) {
indexer.indexDocument(f);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean deleteMetadata(final String identifier) throws MetadataIoException {
final boolean success = super.deleteMetadata(identifier);
if (success) {
indexer.removeDocument(identifier);
}
return success;
}
/**
* {@inheritDoc}
*
* TODO move to {@link MDWebMetadataWriter}
*/
@Override
public boolean updateMetadata(final String metadataID, final Map<String, Object> properties) throws MetadataIoException {
LOGGER.log(logLevel, "metadataID: {0}", metadataID);
FullRecord f = null;
try {
f = mdWriter.getRecord(metadataID);
} catch (MD_IOException ex) {
throw new MetadataIoException("The service has throw an SQLException while updating the metadata: " + ex.getMessage(),
NO_APPLICABLE_CODE);
}
for (Entry<String, Object> property : properties.entrySet()) {
try {
final Object value;
if (property.getValue() instanceof Node) {
final Node n = (Node) property.getValue();
if (n instanceof Text) {
value = ((Text)property.getValue()).getTextContent();
// special case for LanguageCode
} else if (n.getLocalName().equals("LanguageCode")) {
final Node langAtt = n.getAttributes().getNamedItem("codeListValue");
if (langAtt == null) {
throw new MetadataIoException("missing codeListValue in languageCode node");
}
value = new Locale(langAtt.getNodeValue());
} else {
try {
final Unmarshaller u = CSWMarshallerPool.getInstance().acquireUnmarshaller();
value = u.unmarshal(n);
CSWMarshallerPool.getInstance().recycle(u);
} catch (JAXBException ex) {
throw new MetadataIoException(ex);
}
}
} else {
value = property.getValue();
}
final MixedPath mp = getMDWPathFromXPath(property.getKey());
LOGGER.log(Level.FINER, "IDValue: {0}", mp.idValue);
final List<Value> matchingValues = f.getValueFromNumberedPath(mp.path, mp.idValue);
// if there is no value for this path
if (matchingValues.isEmpty()) {
//1. if the path is erroned
// 1.1 we have a cardinality in a non multiple property
Property prop = mp.path.getProperty();
if (prop != null && prop.getMaximumOccurence() == 1 && mp.ordinal > 1) {
throw new MetadataIoException("The property: " + prop.getName() + " is not a collection", INVALID_PARAMETER_VALUE);
}
//2. We must build the values non existing yet.
// to do that we must find the highest value existing in the value tree,
// and build the chain to the new Value.
//
// TODO for now the algorithm can work only if the parent value exist
// because if we have to build a chain of value, how do we find the type of each element?
final String parentIdValue = mp.idValue.substring(0, mp.idValue.lastIndexOf(':'));
final List<Value> parentValues = f.getValueFromNumberedPath(mp.path.getParent(), parentIdValue);
if (parentValues != null && !parentValues.isEmpty()) {
final Map<Object, Value> alreadyWrite = new HashMap<>();
for (Value parentValue : parentValues) {
final List<Value> toInsert = addValueFromObject(f, value, mp.path, parentValue, alreadyWrite);
alreadyWrite.clear();
for (Value ins : toInsert) {
mdWriter.writeValue(ins);
}
}
} else {
throw new MetadataIoException("The service is not yet capable to build a value chain", NO_APPLICABLE_CODE);
}
// if the value(s) already exist
} else {
final Map<Object, Value> alreadyWrite = new HashMap<>();
for (Value v : matchingValues) {
if (v instanceof TextValue && value instanceof String) {
// TODO verify more Type
if (v.getType().equals(PrimitiveType.DATE)) {
try {
String timeValue = (String)value;
timeValue = timeValue.replaceAll("T", " ");
if (timeValue.indexOf('+') != -1) {
timeValue = timeValue.substring(0, timeValue.indexOf('+'));
}
LOGGER.finer(timeValue);
Timestamp.valueOf(timeValue);
} catch(IllegalArgumentException ex) {
throw new MetadataIoException("The type of the replacement value does not match with the value type : Date",
INVALID_PARAMETER_VALUE);
}
}
LOGGER.finer("textValue updated");
mdWriter.updateTextValue((TextValue) v, (String) value);
} else {
final Classe requestType = getClasseFromObject(value);
final Classe valueType = v.getType();
if (!Objects.equals(requestType, valueType)) {
String typeName = "null";
if (requestType != null) {
typeName = requestType.getName();
}
throw new MetadataIoException("The type of the replacement value (" + typeName +
") does not match with the value type :" + valueType.getName(),
INVALID_PARAMETER_VALUE);
} else {
LOGGER.finer("value updated");
mdWriter.deleteValue(v);
final List<Value> toInsert = addValueFromObject(f, value, mp.path, v.getParent(), alreadyWrite);
alreadyWrite.clear();
for (Value ins : toInsert) {
mdWriter.writeValue(ins);
}
}
}
}
}
} catch (MD_IOException | IllegalArgumentException ex) {
throw new MetadataIoException(ex);
}
indexer.removeDocument(metadataID);
indexer.indexDocument(f);
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void setLogLevel(final Level logLevel) {
super.setLogLevel(logLevel);
if (this.indexer != null) {
this.indexer.setLogLevel(logLevel);
}
}
@Override
public void destroy() {
super.destroy();
if (indexer != null) {
indexer.destroy();
}
}
}