/*
* Copyright (C) 2012 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.Callback;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DSCallback;
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.RestDataSource;
import com.smartgwt.client.rpc.RPCResponse;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.FieldType;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import cz.cas.lib.proarc.common.mods.custom.ModsConstants;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.ErrorHandler;
import cz.cas.lib.proarc.webapp.client.ds.DigitalObjectDataSource.DigitalObject;
import cz.cas.lib.proarc.webapp.client.ds.MetaModelDataSource.MetaModelRecord;
import cz.cas.lib.proarc.webapp.client.ds.mods.IdentifierDataSource;
import cz.cas.lib.proarc.webapp.shared.rest.DigitalObjectResourceApi;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
/**
* Data source for MODS/JSON custom mapping. Later it should be fully dynamic
* and pluggable.
*
* <pre>
* {@code
* {pid:"uuid:1",
* timestamp:"0",
* data:{
* identifier:[{type:"uuid", value:"1"}],
* pageType:"Blank"
* }
* }
* </pre>
*
* <p>See reasons for NOT using GWT-RPC http://forums.smartclient.com/showthread.php?t=8159#aGWTRPC,
* http://code.google.com/p/smartgwt/issues/detail?id=303
*
* @author Jan Pokorsky
*/
public final class ModsCustomDataSource extends RestDataSource implements ModsConstants {
private static final Logger LOG = Logger.getLogger(ModsCustomDataSource.class.getName());
public static final String ID = "ModsCustomDataSource";
public static final String FIELD_PID = DigitalObjectResourceApi.DIGITALOBJECT_PID;
public static final String FIELD_BATCHID = DigitalObjectResourceApi.BATCHID_PARAM;
public static final String FIELD_EDITOR = DigitalObjectResourceApi.MODS_CUSTOM_EDITORID;
public static final String FIELD_TIMESTAMP = DigitalObjectResourceApi.TIMESTAMP_PARAM;
public static final String FIELD_DATA = DigitalObjectResourceApi.MODS_CUSTOM_CUSTOMJSONDATA;
// follows custom field names
// custom field names are defined by ModsConstants for now
public ModsCustomDataSource() {
setID(ID);
setDataFormat(DSDataFormat.JSON);
setDataURL(RestConfig.URL_DIGOBJECT_MODS_CUSTOM);
DataSourceField fieldPid = new DataSourceField(FIELD_PID, FieldType.TEXT);
fieldPid.setPrimaryKey(true);
fieldPid.setRequired(true);
DataSourceField fieldTimestamp = new DataSourceField(FIELD_TIMESTAMP, FieldType.TEXT);
fieldTimestamp.setRequired(true);
fieldTimestamp.setHidden(true);
DataSourceField fieldEditor = new DataSourceField(MetaModelDataSource.FIELD_EDITOR, FieldType.TEXT);
fieldEditor.setRequired(true);
fieldEditor.setHidden(true);
DataSourceField fieldData = new DataSourceField(FIELD_DATA, FieldType.ANY);
fieldData.setTypeAsDataSource(new DataSource() {
{
DataSourceField identifiers = new DataSourceField(FIELD_IDENTIFIERS, FieldType.ANY);
identifiers.setTypeAsDataSource(IdentifierDataSource.getInstance());
setFields(identifiers);
}
});
setFields(fieldPid, fieldTimestamp, fieldEditor, fieldData);
setOperationBindings(RestConfig.createUpdateOperation());
setRequestProperties(RestConfig.createRestRequest(getDataFormat()));
}
public static ModsCustomDataSource getInstance() {
ModsCustomDataSource ds = (ModsCustomDataSource) DataSource.get(ID);
ds = ds != null ? ds : new ModsCustomDataSource();
return ds;
}
public static String getDefaultPageType() {
return "NormalPage";
}
public void fetchDescription(final DigitalObject dobj, final Callback<DescriptionMetadata, String> cb) {
fetchDescription(dobj, cb, true);
}
public void fetchDescription(final DigitalObject dobj, final Callback<DescriptionMetadata, String> cb, boolean showPrompt) {
MetaModelRecord model = dobj.getModel();
Criteria criteria = new Criteria(MetaModelDataSource.FIELD_EDITOR, model.getEditorId());
criteria.addCriteria(FIELD_PID, dobj.getPid());
String batchId = dobj.getBatchId();
if (batchId != null) {
criteria.addCriteria(FIELD_BATCHID, batchId);
}
DSRequest request = new DSRequest();
request.setShowPrompt(showPrompt);
fetchData(criteria, new DSCallback() {
@Override
public void execute(DSResponse response, Object rawData, DSRequest request) {
String errorMsg;
if (RestConfig.isStatusOk(response)) {
Record[] data = response.getData();
if (data != null && data.length == 1) {
Record customRecord = data[0];
cb.onSuccess(new DescriptionMetadata(customRecord));
return ;
} else {
errorMsg = "No record found! " + dobj;
}
} else {
errorMsg = "Fetch failed! " + dobj;
}
cb.onFailure(errorMsg);
}
}, request);
}
public void saveXmlDescription(DigitalObject dobj, String xml, DescriptionSaveHandler callback) {
saveXmlDescription(dobj, xml, -1, callback);
}
public void saveXmlDescription(DigitalObject dobj, String xml, long timestamp, DescriptionSaveHandler callback) {
Record update = new Record();
dobj.toCriteria();
update.setAttribute(FIELD_PID, dobj.getPid());
if (dobj.getBatchId() != null) {
update.setAttribute(FIELD_BATCHID, dobj.getBatchId());
}
if (xml == null || xml.isEmpty()) {
return ;
}
update.setAttribute(DigitalObjectResourceApi.MODS_CUSTOM_CUSTOMXMLDATA, xml);
// timestamp -1 stands for rewrite without concurrency check
update.setAttribute(FIELD_TIMESTAMP, timestamp);
update.setAttribute(FIELD_EDITOR, dobj.getModel().getEditorId());
callback.setUpdateRecord(update);
updateData(update, callback, callback.getUpdateRequest());
}
public void saveDescription(DescriptionMetadata update, DescriptionSaveHandler callback, Boolean showPrompt) {
Record customRecord = update.getWrapper();
callback.getUpdateRequest().setShowPrompt(showPrompt);
callback.setUpdateRecord(customRecord);
updateData(customRecord, callback, callback.getUpdateRequest());
}
public static class DescriptionMetadata {
private Record wrapper;
/**
* Constructs the object from a record fetched by the {@link ModsCustomDataSource}.
*/
public DescriptionMetadata(Record wrapper) {
this.wrapper = wrapper;
}
public Record getWrapper() {
return wrapper;
}
public DigitalObject getDigitalObject() {
return DigitalObject.create(wrapper);
}
public Record getDescription() {
Record customModsRecord = wrapper.getAttributeAsRecord(ModsCustomDataSource.FIELD_DATA);
return customModsRecord;
}
public void setDescription(Record r) {
wrapper.setAttribute(ModsCustomDataSource.FIELD_DATA, r);
}
}
/**
* A helper class to handle save responses.
*/
public static class DescriptionSaveHandler implements DSCallback {
private DSResponse response;
/** The template of the request. */
private DSRequest updateRequest;
/** The used request by query. */
private DSRequest sentRequest;
/** Data to save. */
private Record updateRecord;
private final ClientMessages i18n;
public DescriptionSaveHandler() {
this(GWT.<ClientMessages>create(ClientMessages.class));
}
public DescriptionSaveHandler(ClientMessages i18n) {
this.i18n = i18n;
}
public DSResponse getResponse() {
return response;
}
public Record getUpdateRecord() {
return updateRecord;
}
public DSRequest getUpdateRequest() {
if (updateRequest == null) {
updateRequest = new DSRequest();
updateRequest.setWillHandleError(true);
}
return updateRequest;
}
public void setUpdateRecord(Record updateRecord) {
this.updateRecord = updateRecord;
}
@Override
public void execute(DSResponse response, Object rawData, DSRequest request) {
this.response = response;
this.sentRequest = request;
if (RestConfig.isStatusOk(response)) {
Record[] data = response.getData();
if (data != null && data.length == 1) {
Record customRecord = data[0];
onSave(new DescriptionMetadata(customRecord));
}
} else if (response.getStatus() == RPCResponse.STATUS_VALIDATION_ERROR) {
onValidationError();
} else if (RestConfig.isConcurrentModification(response)) { // concurrency conflict
onConcurrencyError();
} else {
onError();
}
}
protected void onSave(DescriptionMetadata dm) {
}
protected void onValidationError() {
String msg = i18n.SaveAction_IgnoreRemoteInvalid_Msg(getValidationMessage());
SC.ask(i18n.SaveAction_Title(), msg, new BooleanCallback() {
@Override
public void execute(Boolean value) {
// save again
if (value != null && value) {
updateRecord.setAttribute(DigitalObjectResourceApi.MODS_CUSTOM_IGNOREVALIDATION, true);
getInstance().updateData(updateRecord, DescriptionSaveHandler.this, updateRequest);
}
}
});
}
protected void onConcurrencyError() {
}
protected void onError() {
ErrorHandler.warn(response, sentRequest);
}
/**
* Gets a {@code <li>} list of validation messages.
*/
public String getValidationMessage() {
Map<?,?> errors = response.getErrors();
StringBuilder sb = new StringBuilder(1024);
for (Entry<?,?> entry : errors.entrySet()) {
Object errMsgs = entry.getValue();
if (errMsgs instanceof List) {
for (Object errMsg : (List) errMsgs) {
sb.append("<li>").append(errMsg).append("</li>");
}
} else {
sb.append("<li>").append(errMsgs).append("</li>");
}
}
if (sb.length() == 0) {
sb.append(response.getDataAsString());
} else {
sb.insert(0, "<ul>");
sb.append("</ul>");
}
if (sb.length() == 0) {
sb.append(response.getHttpResponseText());
}
return sb.toString();
}
}
}