/* * Copyright (C) 2011 Jan Pokorsky * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.cas.lib.proarc.webapp.client.ds; import com.google.gwt.core.client.JavaScriptObject; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.DataSource; import com.smartgwt.client.data.DataSourceField; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.XMLTools; import com.smartgwt.client.data.XmlNamespaces; import com.smartgwt.client.types.DSDataFormat; import com.smartgwt.client.types.DSOperationType; import com.smartgwt.client.types.FieldType; import com.smartgwt.client.util.JSOHelper; import cz.cas.lib.proarc.webapp.client.ClientUtils; import cz.cas.lib.proarc.webapp.shared.rest.DigitalObjectResourceApi; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; /** * * @author Jan Pokorsky */ public class DcRecordDataSource extends DataSource { private static final String FIX_SINGLE_STRING_VALUE = "fixSingleStringValue"; private static final Logger LOG = Logger.getLogger(DcRecordDataSource.class.getName()); public static final String ID = "DcRecordDataSource"; public static final String PREFIX_DCR = "dcr"; public static final String PREFIX_DC = "dc"; public static final String PREFIX_OAI_DC = "oai_dc"; private static final HashMap<String, String> XML_NAMESPACE_MAP = new HashMap<String, String>(); private static final XmlNamespaces XML_NAMESPACES = new XmlNamespaces(); static { XML_NAMESPACE_MAP.put(PREFIX_OAI_DC, DigitalObjectResourceApi.DUBLINCORERECORD_NS_OAIDC); XML_NAMESPACE_MAP.put(PREFIX_DC, DigitalObjectResourceApi.DUBLINCORERECORD_NS_DC); XML_NAMESPACE_MAP.put(PREFIX_DCR, DigitalObjectResourceApi.DUBLINCORERECORD_NS); for (Map.Entry<String, String> entry : XML_NAMESPACE_MAP.entrySet()) { XML_NAMESPACES.addNamespace(entry.getKey(), entry.getValue()); } } public static final QName FIELD_PID = new QName(XML_NAMESPACE_MAP, DigitalObjectResourceApi.DUBLINCORERECORD_PID, PREFIX_DCR); public static final QName FIELD_TIMESTAMP = new QName(XML_NAMESPACE_MAP, DigitalObjectResourceApi.DUBLINCORERECORD_TIMESTAMP, PREFIX_DCR); public static final QName FIELD_DC = new QName(XML_NAMESPACE_MAP, DigitalObjectResourceApi.DUBLINCORERECORD_DC, PREFIX_OAI_DC); public static final QName FIELD_CREATOR = new QName(XML_NAMESPACE_MAP, "creator", PREFIX_DC); public static final QName FIELD_CONTRIBUTOR = new QName(XML_NAMESPACE_MAP, "contributor", PREFIX_DC); public static final QName FIELD_COVERAGE = new QName(XML_NAMESPACE_MAP, "coverage", PREFIX_DC); public static final QName FIELD_DATE = new QName(XML_NAMESPACE_MAP, "date", PREFIX_DC); public static final QName FIELD_DESCRIPTION = new QName(XML_NAMESPACE_MAP, "description", PREFIX_DC); public static final QName FIELD_FORMAT = new QName(XML_NAMESPACE_MAP, "format", PREFIX_DC); public static final QName FIELD_IDENTIFIER = new QName(XML_NAMESPACE_MAP, "identifier", PREFIX_DC); public static final QName FIELD_LANGUAGE = new QName(XML_NAMESPACE_MAP, "language", PREFIX_DC); public static final QName FIELD_PUBLISHER = new QName(XML_NAMESPACE_MAP, "publisher", PREFIX_DC); public static final QName FIELD_RELATION = new QName(XML_NAMESPACE_MAP, "relation", PREFIX_DC); public static final QName FIELD_RIGHTS = new QName(XML_NAMESPACE_MAP, "rights", PREFIX_DC); public static final QName FIELD_SOURCE = new QName(XML_NAMESPACE_MAP, "source", PREFIX_DC); public static final QName FIELD_SUBJECT = new QName(XML_NAMESPACE_MAP, "subject", PREFIX_DC); public static final QName FIELD_TITLE = new QName(XML_NAMESPACE_MAP, "title", PREFIX_DC); public static final QName FIELD_TYPE = new QName(XML_NAMESPACE_MAP, "type", PREFIX_DC); public static final String FIELD_XML_LANG = "xml:lang"; /** SmartGWT field name for text inside ComplexType element with attributes */ public static final String FIELD_XML_TEXT_CONTENT = "xmlTextContent"; public static DcRecordDataSource getInstance() { DcRecordDataSource ds = (DcRecordDataSource) DataSource.get(ID); ds = ds != null ? ds : new DcRecordDataSource(); return ds; } public DcRecordDataSource() { setID(ID); setDataFormat(DSDataFormat.XML); // setDataURL("ds/dsRecord.xml"); setDataURL(RestConfig.URL_DIGOBJECT_DC); setXmlNamespaces(XML_NAMESPACES); setGlobalNamespaces(XML_NAMESPACE_MAP); setRecordXPath("/dcr:dcRecord"); setTagName("dcr:dcRecord"); // setSendExtraFields(false); // setDropExtraFields(true); DataSourceField fieldPid = new DataSourceField(FIELD_PID.getQualifiedName(), FieldType.TEXT); fieldPid.setPrimaryKey(true); fieldPid.setRequired(true); fieldPid.setValueXPath(FIELD_PID.getQualifiedName()); DataSourceField fieldTimestamp = new DataSourceField(FIELD_TIMESTAMP.getQualifiedName(), FieldType.TEXT); fieldTimestamp.setRequired(true); fieldTimestamp.setHidden(true); fieldTimestamp.setValueXPath(FIELD_TIMESTAMP.getQualifiedName()); DataSourceField fieldDc = new DataSourceField(FIELD_DC.getQualifiedName(), FieldType.TEXT); fieldDc.setRequired(true); fieldDc.setTypeAsDataSource(getDcDataSource()); fieldDc.setValueXPath(FIELD_DC.getQualifiedName()); setFields(fieldPid, fieldTimestamp, fieldDc); setOperationBindings(RestConfig.createUpdateOperation()); setRequestProperties(RestConfig.createRestRequest(getDataFormat())); } @Override protected void transformResponse(DSResponse response, DSRequest request, Object data) { // transform oai_dc:dc element to properly resolve namespaces Object dcNode = XMLTools.selectNodes(data, FIELD_DC.getQualifiedName(), XML_NAMESPACE_MAP); DataSource ds = getDcDataSource(); Record[] recordsFromXML = new Record[0]; // XXX test // Record[] recordsFromXML = ds.recordsFromXML(dcNode); Record[] responseData = response.getData(); if (responseData.length > 0) { fixDcRecord(responseData[0].getAttributeAsRecord(FIELD_DC.getQualifiedName())); // LOG.fine(ClientUtils.dump(responseData[0], "transformResponse.responseData:")); } LOG.fine(ClientUtils.format("transformResponse: responseData.length: %s, recordsFromXML.length: %s", responseData.length, recordsFromXML.length)); for (int i = 0; i < recordsFromXML.length; i++) { // LOG.fine(ClientUtils.dump(recordsFromXML[i], "")); fixDcRecord(recordsFromXML[i]); responseData[i].setAttribute(FIELD_DC.getQualifiedName(), recordsFromXML[i]); } super.transformResponse(response, request, data); } @Override protected Object transformRequest(DSRequest dsRequest) { if (dsRequest.getOperationType() == DSOperationType.UPDATE) { // Uff, it seems the only way how to dispose of garbage from resulting XML // is to copy relevant attributes to a new record recursively. Record r = Record.getOrCreateRef(dsRequest.getData()); Record update = new Record(); update.setAttribute(FIELD_PID.getQualifiedName(), r.getAttribute(FIELD_PID.getQualifiedName())); update.setAttribute(FIELD_TIMESTAMP.getQualifiedName(), r.getAttribute(FIELD_TIMESTAMP.getQualifiedName())); Record oaiDc = r.getAttributeAsRecord(FIELD_DC.getQualifiedName()); Record updateOaiDc = new Record(); for (String fieldName : getDcDataSource().getFieldNames()) { updateOaiDc.setAttribute(fieldName, oaiDc.getAttributeAsObject(fieldName)); } update.setAttribute(FIELD_DC.getQualifiedName(), updateOaiDc); LOG.fine(ClientUtils.dump(update, "transformRequest")); dsRequest.setData(update); } return super.transformRequest(dsRequest); } public static DataSource getDcDataSource() { DataSource result = DataSource.get("DcDataSource"); if (result != null) { return result; } else { return createDcDataSource(); } } private static DataSource createDcDataSource() { final DataSource dsDC = new DataSource() { @Override protected void transformResponse(DSResponse response, DSRequest request, Object data) { // data is a xml Document // WARNING: it is com.google.gwt.dom.client.Document not com.google.gwt.xml.client.Document!!! // following fix transforms parsed result that contains various object types to array of Records for each Record's attribute // Maybe we should create own data result from data parameter using // XMLTools or XMLParser instead of fixing default result. // In order to get data as string use DataSource.setDataFormat(DSDataFormat.CUSTOM) // or setDataProtocol(DSProtocol.CLIENTCUSTOM). // Do not use XMLTools.loadXMLSchema as it does not work well with tricky ComplexTypes. LOG.fine(ClientUtils.format("transformResponse.data: %s, class: %s", data, data.getClass())); Record[] rs = response.getData(); if (rs != null) { for (Record r : rs) { fixDcRecord(r); } } super.transformResponse(response, request, data); } }; dsDC.setID("DcDataSource"); // dsDC.setDataURL("ds/dc.xml"); // dsDC.setDataURL("ds/dc_drobnustky.xml"); dsDC.setXmlNamespaces(XML_NAMESPACES); dsDC.setGlobalNamespaces(XML_NAMESPACE_MAP); // dsDC.setRecordXPath("oai_dc:dc"); // dsDC.setRecordXPath("/dc"); dsDC.setTagName(FIELD_DC.getQualifiedName()); dsDC.setSendExtraFields(false); dsDC.setDropExtraFields(true); dsDC.setFields( createDcDataSourceField(FIELD_TITLE), createDcDataSourceField(FIELD_CREATOR), createDcDataSourceField(FIELD_SUBJECT), createDcDataSourceField(FIELD_DESCRIPTION), createDcDataSourceField(FIELD_PUBLISHER), createDcDataSourceField(FIELD_CONTRIBUTOR), createDcDataSourceField(FIELD_DATE), createDcDataSourceField(FIELD_TYPE), createDcDataSourceField(FIELD_FORMAT), createDcDataSourceField(FIELD_IDENTIFIER), createDcDataSourceField(FIELD_SOURCE), createDcDataSourceField(FIELD_LANGUAGE), createDcDataSourceField(FIELD_RELATION), createDcDataSourceField(FIELD_COVERAGE), createDcDataSourceField(FIELD_RIGHTS) ); return dsDC; } private static DataSourceField createDcDataSourceField(QName name) { DataSourceField field = new DataSourceField(name.getQualifiedName(), FieldType.TEXT); field.setTypeAsDataSource(createDcElementDataSource(name.getQualifiedName())); field.setRequired(true); field.setAttribute(FIX_SINGLE_STRING_VALUE, true); field.setValueXPath(name.getQualifiedName()); return field; } /** * Holds element text content and its attributes */ private static DataSource createDcElementDataSource(String name) { DataSource ds = new DataSource(); // ds.setID("DcElementDataSource"); ds.setTagName(name); DataSourceField lang = new DataSourceField(FIELD_XML_LANG, FieldType.TEXT); lang.setXmlAttribute(true); ds.setFields(lang); ds.setXmlNamespaces(XML_NAMESPACES); ds.setGlobalNamespaces(XML_NAMESPACE_MAP); return ds; } /** * Ensures that subelements of {@code oai_dc:dc} are represented as list of JavaScriptObjects * in order to allow to easy handling by ListGrid. * * @param r {@code oai_dc:dc} record */ public static void fixDcRecord(Record r) { if (r == null) { return ; } DataSourceField[] fields = getDcDataSource().getFields(); for (DataSourceField field : fields) { Boolean fix = field.getAttributeAsBoolean(FIX_SINGLE_STRING_VALUE); if (fix != null && fix) { fixField(r, field); } } } private static void fixField(Record r, DataSourceField field) { String fieldName = field.getName(); Object fieldValue = r.getAttributeAsObject(fieldName); LOG.finest(ClientUtils.format("fixField: %s, fieldValue: %s, class: %s", fieldName, fieldValue, ClientUtils.safeGetClass(fieldValue))); if (JSOHelper.isJSO(fieldValue)) { JavaScriptObject jso = r.getAttributeAsJavaScriptObject(fieldName); if (JSOHelper.isArray(jso)) { int arrayLength = JSOHelper.arrayLength(jso); for (int i = 0; i < arrayLength; i++) { Object arrayItem = JSOHelper.arrayGetObject(jso, i); // transforms String object to Record fix(arrayItem, jso, i, field); } } else { // transforms single JSO to array: JSO -> [JSO] JavaScriptObject array = JSOHelper.arrayConvert(new JavaScriptObject[] {jso}); r.setAttribute(fieldName, array); LOG.fine(ClientUtils.format("fix.field: %s, JavaScriptObject -> [JavaScriptObject] fix", fieldName)); } } else if (JSOHelper.isJavaString(fieldValue)) { JavaScriptObject fix = JSOHelper.createObject(); JSOHelper.setAttribute(fix, FIELD_XML_TEXT_CONTENT, fieldValue); JavaScriptObject array = JSOHelper.arrayConvert(new JavaScriptObject[] {fix}); r.setAttribute(fieldName, array); LOG.fine(ClientUtils.format("fix.field: %s, String -> [JavaScriptObject] fix", fieldName)); } } private static void fix(Object arrayItem, JavaScriptObject jso, int i, DataSourceField field) { LOG.finest(ClientUtils.format("fix.field: %s, index: %s, arrayItem: %s, class: %s", field.getName(), i, arrayItem, arrayItem.getClass())); if (arrayItem instanceof String) { JavaScriptObject fix = JSOHelper.createObject(); JSOHelper.setAttribute(fix, FIELD_XML_TEXT_CONTENT, arrayItem); JSOHelper.setArrayValue(jso, i, fix); LOG.fine(ClientUtils.format("fix.field: %s, index: %s, String -> JavaScriptObject fix", field.getName(), i)); } } public static final class QName { private final String namespace; private final String localPart; private final String prefix; public QName(String localPart) { this((String) null, localPart, null); } public QName(Map<String, String> nsMap, String localPart, String prefix) { this(nsMap.get(prefix), localPart, prefix); } public QName(String namespace, String localPart, String prefix) { if (localPart == null) { throw new NullPointerException("localPart"); } if (namespace != null && namespace.trim().isEmpty()) { namespace = null; } if (prefix != null && prefix.trim().isEmpty()) { prefix = null; } if (prefix != null && namespace == null) { throw new IllegalArgumentException("Missing namespace for prefix: " + prefix); } this.namespace = namespace; this.localPart = localPart; this.prefix = prefix; } public String getQualifiedName() { return (prefix == null) ? localPart : prefix + ':' + localPart; } public String getLocalPart() { return localPart; } public String getNamespace() { return namespace; } public String getPrefix() { return prefix; } } }