/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You 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 com.esri.gpt.catalog.arcims;
import com.esri.gpt.catalog.publication.PublicationRecord;
import com.esri.gpt.catalog.schema.MetadataDocument;
import com.esri.gpt.catalog.schema.Schema;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.collection.StringSet;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Provides functionality to send metadata document modification events
* (publish/delete) to a remote CSW repository.
*/
class CswRemoteRepository {
/** class variables ========================================================= */
/** The Logger. */
private static Logger LOGGER = Logger.getLogger(CswRemoteRepository.class.getName());
/** instance variables ====================================================== */
private String cswURL = "";
private boolean isActive = false;
private RequestContext requestContext;
private StringSet schemaKeys = new StringSet();
private boolean sendDelete = true;
/** constructors ============================================================ */
/**
* Constructs with a supplied request context.
* @param requestContext the active request context
*/
protected CswRemoteRepository(RequestContext requestContext) {
this.requestContext = requestContext;
StringAttributeMap params = requestContext.getCatalogConfiguration().getParameters();
this.cswURL = Val.chkStr(params.getValue("cswRemoteRepository.url"));
String sKeys = Val.chkStr(params.getValue("cswRemoteRepository.schemaKeys"));
String[] aKeys = Val.tokenize(sKeys,",");
for (String sKey: aKeys) schemaKeys.add(sKey);
this.isActive = ((cswURL.length() > 0) && (schemaKeys.size() > 0));
this.sendDelete = Val.chkBool(params.getValue("cswRemoteRepository.sendDelete"),true);
}
/** methods ================================================================= */
/**
* Indicates if a remote repository has been configured.
* @return true if a remote repository has been configured.
*/
protected boolean isActive() {
return this.isActive;
}
/**
* Deletes a collection of identifiers from the remote repository.
* @param identifiers the collection of identifiers to delete.
*/
private void delete(StringSet identifiers) throws IOException {
// make and send the CSW request
StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("\r\n<csw:Transaction xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\"");
sb.append(" xmlns:ogc=\"http://www.opengis.net/ogc\">");
sb.append("\r\n<csw:Delete>");
sb.append("\r\n<csw:Constraint version=\"1.1.0\">");
sb.append("\r\n<ogc:Filter>");
sb.append("\r\n<ogc:Or>");
for (String identifier: identifiers) {
sb.append("\r\n<ogc:PropertyIsEqualTo>");
sb.append("\r\n<ogc:PropertyName>").append("dc:identifier").append("</ogc:PropertyName>");
sb.append("\r\n<ogc:Literal>").append(identifier).append("</ogc:Literal>");
sb.append("\r\n</ogc:PropertyIsEqualTo>");
}
sb.append("\r\n</ogc:Or>");
sb.append("\r\n</ogc:Filter>");
sb.append("\r\n</csw:Constraint>");
sb.append("\r\n</csw:Delete>");
sb.append("\r\n</csw:Transaction>");
sendRequest(sb.toString());
}
/**
* Informs the remote repository of record deletions.
* @param identifiers the file identifiers associated with the deleted records
*/
protected void onRecordsDeleted(StringSet identifiers) {
if (!this.isActive() || !this.sendDelete) return;
try {
if ((identifiers == null) || (identifiers.size() == 0)) return;
delete(identifiers);
} catch (Exception e) {
LOGGER.log(Level.SEVERE,"Deletion from remote service failed.",e);
}
}
/**
* Informs the remote repository of record publications.
* @param schema the evaluated schema associated with the document
* @param record the publication record
*/
protected void onRecordUpdated(Schema schema, PublicationRecord record) {
if (!isActive()) return;
try {
if (!schemaKeys.containsString(schema.getKey())) return;
publish(schema,record.getSourceXml());
} catch (Exception e) {
LOGGER.log(Level.SEVERE,"Publication to remote service failed.",e);
}
}
/**
* Informs the remote repository of record publications.
* @param xml the raw XML associated with the record
* @param record the publication record
*/
protected void onRecordUpdated(String xml) {
if (!this.isActive()) return;
try {
MetadataDocument mdDoc = new MetadataDocument();
Schema schema = mdDoc.prepareForView(requestContext,xml);
xml = mdDoc.prepareForFullViewing(xml);
publish(schema,xml);
} catch (Exception e) {
LOGGER.log(Level.SEVERE,"Publication to remote service failed.",e);
}
}
/**
* Publishes a document to the remote repository.
* @param schema the evaluated schema associated with the document
* @param xml the document's XML
* @throws IOException if an i/o exception occurs
* @throws ParserConfigurationException if a configuration exception occurs
* @throws SAXException if an exception occurs during XML parsing
* @throws TransformerException if an exception occurs during XML transformation
*/
private void publish(Schema schema, String xml)
throws IOException, ParserConfigurationException, SAXException, TransformerException {
if (!schemaKeys.containsString(schema.getKey())) return;
// remove the processing instruction and schema location attribute from the metadata xml
Document dom = DomUtil.makeDomFromString(xml,true);
for (int i=0;i<dom.getDocumentElement().getAttributes().getLength();i++) {
Attr attr = (Attr)dom.getDocumentElement().getAttributes().item(i);
if (attr.getLocalName().equalsIgnoreCase("schemaLocation")) {
dom.getDocumentElement().removeAttributeNode(attr);
break;
}
}
StringWriter result = new StringWriter();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
transformer.transform(new DOMSource(dom),new StreamResult(result));
xml = Val.chkStr(result.toString());
// make and send the CSW request
StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("\r\n<csw:Transaction xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\">");
sb.append("\r\n<csw:Insert>");
sb.append("\r\n").append(xml);
sb.append("\r\n</csw:Insert>");
sb.append("\r\n</csw:Transaction>");
sendRequest(sb.toString());
}
/**
* Fully reads the characters from an InputStream.
* @param strm the InputStream
* @return the characters read
* @throws IOException if an exception occurs
*/
private StringBuffer readCharacters(InputStream strm)
throws IOException {
StringBuffer sb = new StringBuffer();
BufferedReader br = null;
InputStreamReader ir = null;
try {
char cbuf[] = new char[2048];
int n = 0;
int nLen = cbuf.length;
ir = new InputStreamReader(strm, "UTF-8");
br = new BufferedReader(ir);
while ((n = br.read(cbuf, 0, nLen)) > 0) {
sb.append(cbuf, 0, n);
}
} finally {
try {if (br != null) br.close();} catch (Exception ef) {}
try {if (ir != null) ir.close();} catch (Exception ef) {}
}
return sb;
}
/**
* Sends data to the open HTTP connection.
* @param httpCon the HTTP connection
* @param data the data to send
* @throws IOException if the send fails
*/
private void sendData(HttpURLConnection httpCon, String data)
throws IOException {
OutputStream sendStream = null;
try {
httpCon.setRequestProperty("Content-Type","text/xml; charset=UTF-8");
httpCon.setRequestProperty("Content-Length",""+data.length());
sendStream = httpCon.getOutputStream();
sendStream.write(data.getBytes("UTF-8"));
sendStream.flush();
} finally {
try {
if (sendStream != null) {
sendStream.close();
}
} catch (Exception ef) {
}
}
}
/**
* Sends a request to the CSW end-point.
* @param cswRequest the request to send
* @throws IOException if an exception occurs
*/
private void sendRequest(String cswRequest) throws IOException {
HttpURLConnection httpCon = null;
InputStream responseStream = null;
try {
if (LOGGER.isLoggable(Level.FINER)) {
StringBuffer sb = new StringBuffer();
sb.append("Sending CSW request\n");
sb.append(" url=").append(cswURL);
sb.append("\n").append(cswRequest);
LOGGER.finer(sb.toString());
}
//if (true) return;
URL url = new URL(cswURL);
httpCon = (HttpURLConnection) url.openConnection();
httpCon.setRequestMethod("POST");
httpCon.setConnectTimeout(10000);
httpCon.setDoInput(true);
httpCon.setDoOutput(true);
httpCon.setUseCaches(false); // turn off document caching
httpCon.setRequestProperty("Connection", "Close"); // Disable keep-alive
sendData(httpCon,cswRequest);
int nResponseCode = httpCon.getResponseCode();
if (LOGGER.isLoggable(Level.FINER)) {
responseStream = httpCon.getInputStream();
String sResponse = readCharacters(responseStream).toString();
StringBuffer sb = new StringBuffer();
sb.append("Read CSW response\n");
sb.append(" url=").append(cswURL);
sb.append(" responseCode=").append(nResponseCode);
sb.append("\n").append(sResponse);
LOGGER.finer(sb.toString());
}
} finally {
try {if (responseStream != null) responseStream.close();} catch (Exception ef) {}
}
}
}