org.collectionspace.chain.csp.webui.record.RecordCreateUpdate.java Source code

Java tutorial

Introduction

Here is the source code for org.collectionspace.chain.csp.webui.record.RecordCreateUpdate.java

Source

/* Copyright 2010 University of Cambridge
 * Licensed under the Educational Community License (ECL), Version 2.0. You may not use this file except in 
 * compliance with this License.
 *
 * You may obtain a copy of the ECL 2.0 License at https://source.collectionspace.org/collection-space/LICENSE.txt
 */
package org.collectionspace.chain.csp.webui.record;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.collectionspace.chain.csp.config.ConfigException;
import org.collectionspace.chain.csp.schema.FieldSet;
import org.collectionspace.chain.csp.schema.Instance;
import org.collectionspace.chain.csp.schema.Record;
import org.collectionspace.chain.csp.schema.Spec;
import org.collectionspace.chain.csp.webui.authorities.AuthoritiesVocabulariesInitialize;
import org.collectionspace.chain.csp.webui.main.Request;
import org.collectionspace.chain.csp.webui.main.WebMethodWithOps;
import org.collectionspace.chain.csp.webui.main.WebUI;
import org.collectionspace.chain.csp.webui.misc.Generic;
import org.collectionspace.chain.csp.webui.nuispec.CacheTermList;
import org.collectionspace.csp.api.persistence.ExistException;
import org.collectionspace.csp.api.persistence.Storage;
import org.collectionspace.csp.api.persistence.UnderlyingStorageException;
import org.collectionspace.csp.api.persistence.UnimplementedException;
import org.collectionspace.csp.api.ui.Operation;
import org.collectionspace.csp.api.ui.UIException;
import org.collectionspace.csp.api.ui.UIRequest;
import org.collectionspace.csp.helper.core.ResponseCache;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RecordCreateUpdate implements WebMethodWithOps {
    private static final Logger log = LoggerFactory.getLogger(RecordCreateUpdate.class);
    protected String url_base, base;
    protected boolean create;
    protected Record record;
    protected AuthoritiesVocabulariesInitialize avi;
    protected Spec spec;
    protected RecordRead reader;
    protected RecordSearchList searcher;
    protected CacheTermList ctl;

    protected static final String BLOBS_SERVICE_URL_PATTERN = "/cspace-services/blobs/";

    public RecordCreateUpdate(Record r, boolean create) {
        this.spec = r.getSpec();
        this.record = r;
        this.url_base = r.getWebURL();
        this.base = r.getID();
        this.create = create;
        this.reader = new RecordRead(r);
        this.avi = new AuthoritiesVocabulariesInitialize(r, false);
        this.reader.configure(spec);
        this.searcher = new RecordSearchList(r, RecordSearchList.MODE_LIST);
    }

    private void deleteAllRelations(Storage storage, String csid)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException {
        JSONObject r = new JSONObject();
        r.put("src", base + "/" + csid);
        // XXX needs pagination support CSPACE-1819
        JSONObject data = storage.getPathsJSON("relations/main", r);
        String[] paths = (String[]) data.get("listItems");
        for (String relation : paths) {
            storage.deleteJSON("relations/main/" + relation);
        }
    }

    private void setRelations(Storage storage, String csid, JSONArray relations)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException {
        deleteAllRelations(storage, csid);
        for (int i = 0; i < relations.length(); i++) {
            // Extract data from miniobject
            JSONObject in = relations.getJSONObject(i);
            String dst_type = spec.getRecordByWebUrl(in.getString("recordtype")).getID();
            String dst_id = in.getString("csid");
            String type = in.getString("relationshiptype");
            // Create relation
            JSONObject r = new JSONObject();
            r.put("src", base + "/" + csid);
            r.put("dst", dst_type + "/" + dst_id);
            r.put("type", type);
            storage.autocreateJSON("relations/main", r, null);
        }
    }

    public String sendJSON(Storage storage, String path, JSONObject data, JSONObject restrictions)
            throws ExistException, UnimplementedException, UnderlyingStorageException, JSONException {
        final String WORKFLOW_TRANSITION = "workflowTransition";
        final String WORKFLOW_TRANSITION_LOCK = "lock";

        JSONObject fields = data.optJSONObject("fields");
        JSONArray relations = data.optJSONArray("relations");
        if (path != null) {
            // Update
            if (fields != null)
                storage.updateJSON(base + "/" + path, fields, restrictions);
        } else {
            // Create
            if (fields != null)
                path = storage.autocreateJSON(base, fields, restrictions);
        }
        if (relations != null)
            setRelations(storage, path, relations);
        if (record.supportsLocking() && data.has(WORKFLOW_TRANSITION)
                && WORKFLOW_TRANSITION_LOCK.equalsIgnoreCase(data.getString(WORKFLOW_TRANSITION))) {
            // If any problem, will throw exception.
            storage.transitionWorkflowJSON(base + "/" + path, WORKFLOW_TRANSITION_LOCK);
        }
        return path;
    }

    private JSONObject permJSON(String permValue) throws JSONException {
        JSONObject perm = new JSONObject();
        perm.put("name", permValue);
        return perm;
    }

    private JSONObject getPerm(Storage storage, Record recordForPermResource, String resourceName, String permLevel)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException, UIException {
        JSONObject permitem = new JSONObject();

        if (permLevel.equals("none")) {
            return permitem;
        }

        JSONArray actions = new JSONArray();
        JSONObject permR = permJSON("READ");
        JSONObject permC = permJSON("CREATE");
        JSONObject permU = permJSON("UPDATE");
        JSONObject permD = permJSON("DELETE");
        JSONObject permL = permJSON("SEARCH");

        ///cspace-services/authorization/permissions?res=acquisition&actGrp=CRUDL
        String queryString = "CRUDL";

        if (permLevel.equals(Generic.READ_PERMISSION)) {
            queryString = "RL";
            actions.put(permR);
            actions.put(permL);
        } else if (permLevel.equals(Generic.WRITE_PERMISSION)) {
            queryString = "CRUL";
            actions.put(permC);
            actions.put(permR);
            actions.put(permU);
            actions.put(permL);
        } else if (permLevel.equals(Generic.DELETE_PERMISSION) || permLevel.equals(Generic.LOCK_PERMISSION)) {
            actions.put(permC);
            actions.put(permR);
            actions.put(permU);
            if (recordForPermResource != null && recordForPermResource.hasSoftDeleteMethod()) {
                // Delete is handled in the workflow perms
                queryString = "CRUL";
            } else {
                queryString = "CRUDL";
                actions.put(permD);
            }
            actions.put(permL); // Keep this here to preserve CRUDL order of actions
        } else {
            log.warn("RecordCreateDelete.getPerm passed unknown permLevel: " + permLevel);
        }

        String permbase = spec.getRecordByWebUrl("permission").getID();

        permitem = getPermID(storage, Generic.ResourceNameServices(spec, resourceName), queryString, permbase,
                actions);
        return permitem;
    }

    private JSONObject getWorkflowPerm(Storage storage, Record recordForPermResource, String resourceName,
            String permLevel, String workflowTransition)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException, UIException {
        JSONObject permitem = new JSONObject();

        if (permLevel.equals("none")) {
            return permitem;
        }

        JSONArray actions = new JSONArray();
        JSONObject permC = permJSON("CREATE");
        JSONObject permR = permJSON("READ");
        JSONObject permU = permJSON("UPDATE");
        JSONObject permD = permJSON("DELETE");
        JSONObject permL = permJSON("SEARCH");
        actions.put(permR);
        actions.put(permL);

        ///cspace-services/authorization/permissions?res=acquisition&actGrp=CRUDL
        String queryString = "RL";
        String permbase = spec.getRecordByWebUrl("permission").getID();
        boolean hasRights = false;

        if (workflowTransition.equals(DELETE_WORKFLOW_TRANSITION)) {
            // permLevel delete or lock includes this
            hasRights = permLevel.equals(Generic.DELETE_PERMISSION) || permLevel.equals(Generic.LOCK_PERMISSION);
        } else if (workflowTransition.equals(LOCK_WORKFLOW_TRANSITION)) {
            // permLevel lock includes this
            // UI does not yet support admin of the lock perm, so 
            //hasRights = permLevel.equals(Generic.LOCK_PERMISSION);
            // Assumes this is only called for records that actually support locking...
            hasRights = permLevel.equals(Generic.DELETE_PERMISSION) || permLevel.equals(Generic.UPDATE_PERMISSION)
                    || permLevel.equals(Generic.LOCK_PERMISSION);
        } else {
            log.warn("RecordCreateUpdate.getWorkflowPerm passed unknown workflowTransition: " + workflowTransition);
        }
        if (hasRights) {
            actions.put(permC);
            actions.put(permU);
            actions.put(permD);
            // They do not really get DELETE rights on a workflow, but that is what the services models by
            // default, so let's stick with that 
            queryString = "CRUDL";
        }
        // Workflow resources all have leading slashes.
        String resource = Generic.ResourceNameServices(spec, resourceName) + WORKFLOW_SUB_RESOURCE
                + workflowTransition;
        if (!resource.startsWith("/"))
            resource = "/" + resource;
        permitem = getPermID(storage, resource, queryString, permbase, actions);
        return permitem;
    }

    private JSONObject getPermID(Storage storage, String name, String queryString, String permbase,
            JSONArray actions)
            throws JSONException, UIException, ExistException, UnimplementedException, UnderlyingStorageException {

        JSONObject permitem = new JSONObject();
        JSONObject permrestrictions = new JSONObject();
        permrestrictions.put("keywords", name);
        permrestrictions.put("queryTerm", "actGrp");
        permrestrictions.put("queryString", queryString);
        JSONObject data = searcher.getJSON(storage, permrestrictions, "items", permbase);

        String permid = "";
        JSONArray items = data.getJSONArray("items");
        for (int i = 0; i < items.length(); i++) {
            JSONObject item = items.getJSONObject(i);
            String resourcename = item.getString("summary");
            String actionGroup = item.getString("number");
            //need to do a double check as the query is an inexact match
            if (resourcename.equals(name) && actionGroup.equals(queryString)) {
                permid = item.getString("csid");
            }
        }
        if (permid.equals("")) {

            //create the permission
            /**
             * {
            "effect": "PERMIT",
            "resourceName": "testthing2",
                
            "action":[{"name":"CREATE"},{"name":"READ"},{"name":"UPDATE"},{"name":"DELETE"},{"name":"SEARCH"}]
            }
             */

            JSONObject permission_add = new JSONObject();
            permission_add.put("effect", "PERMIT");
            permission_add.put("description", "created because we couldn't find a match");
            permission_add.put("resourceName", name);
            permission_add.put("actionGroup", queryString);
            permission_add.put("action", actions);

            permid = storage.autocreateJSON(spec.getRecordByWebUrl("permission").getID(), permission_add, null);

        }

        if (!permid.equals("")) {
            permitem.put("resourceName", name);
            permitem.put("permissionId", permid);
            permitem.put("actionGroup", queryString);
        }
        return permitem;

    }

    private void assignTerms(Storage storage, String path, JSONObject data)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException, UIException {
        JSONObject fields = data.optJSONObject("fields");
        String insId = "";

        if (fields.has("terms")) {
            Record vr = this.spec.getRecord("vocab");
            Record thisr = spec.getRecord("vocab");
            String sid = fields.getString("shortIdentifier");
            String name = fields.getString("displayName");
            insId = "vocab-" + sid;
            if (create) {
                Map<String, String> options = new HashMap<String, String>();
                options.put("id", insId);
                options.put("title", name);
                options.put("web-url", sid);
                options.put("title-ref", sid);

                Instance ins = new Instance(thisr, options);
                vr.addInstance(ins);
            }
            ctl.get(storage, sid, vr, 0);
        }

    }

    private final static String DELETE_WORKFLOW_TRANSITION = Generic.DELETE_PERMISSION;
    private final static String LOCK_WORKFLOW_TRANSITION = Generic.LOCK_PERMISSION;
    private static final String PUBLISH_URL_SUFFIX = "publish";

    private void assignPermissions(Storage storage, String path, JSONObject data)
            throws JSONException, ExistException, UnimplementedException, UnderlyingStorageException, UIException {
        JSONObject fields = data.optJSONObject("fields");

        JSONArray permdata = new JSONArray();
        JSONObject permcheck = new JSONObject();
        if (fields.has("permissions")) {
            JSONArray permissions = fields.getJSONArray("permissions");
            for (int i = 0; i < permissions.length(); i++) {
                JSONObject perm = permissions.getJSONObject(i);

                Record recordForPermResource = Generic.RecordNameServices(spec, perm.getString("resourceName"));
                if (recordForPermResource != null) {
                    if (recordForPermResource.hasSoftDeleteMethod()) {
                        JSONObject permitem = getWorkflowPerm(storage, recordForPermResource,
                                perm.getString("resourceName"), perm.getString("permission"),
                                DELETE_WORKFLOW_TRANSITION);
                        if (permitem.has("permissionId")) {
                            if (permcheck.has(permitem.getString("resourceName"))) {
                                //ignore as we have duplicate name - eek
                                log.warn(
                                        "RecordCreateUpdate.assignPermissions got duplicate workflow/delete permission for: "
                                                + permitem.getString("resourceName"));
                            } else {
                                permcheck.put(permitem.getString("resourceName"), permitem);
                                permdata.put(permitem);
                            }
                        }
                    }
                    if (recordForPermResource.supportsLocking()) {
                        JSONObject permitem = getWorkflowPerm(storage, recordForPermResource,
                                perm.getString("resourceName"), perm.getString("permission"),
                                LOCK_WORKFLOW_TRANSITION);
                        if (permitem.has("permissionId")) {
                            if (permcheck.has(permitem.getString("resourceName"))) {
                                //ignore as we have duplicate name - eek
                                log.warn(
                                        "RecordCreateUpdate.assignPermissions got duplicate workflow/lock permission for: "
                                                + permitem.getString("resourceName"));
                            } else {
                                permcheck.put(permitem.getString("resourceName"), permitem);
                                permdata.put(permitem);
                            }
                        }
                    }
                }
                JSONObject permitem = getPerm(storage, recordForPermResource, perm.getString("resourceName"),
                        perm.getString("permission"));
                if (permitem.has("permissionId")) {
                    if (permcheck.has(permitem.getString("resourceName"))) {
                        //ignore as we have duplicate name - eek
                        log.warn("RecordCreateUpdate.assignPermissions got duplicate permission for: "
                                + permitem.getString("resourceName"));
                    } else {
                        permcheck.put(permitem.getString("resourceName"), permitem);
                        permdata.put(permitem);
                    }
                }
            }
        }

        //log.info("permdata"+permdata.toString());
        JSONObject roledata = new JSONObject();
        roledata.put("roleName", fields.getString("roleName"));

        String[] ids = path.split("/");
        roledata.put("roleId", ids[ids.length - 1]);

        JSONObject accountrole = new JSONObject();
        JSONObject arfields = new JSONObject();
        arfields.put("role", roledata);
        arfields.put("permission", permdata);
        accountrole.put("fields", arfields);
        //log.info("WAAA"+arfields.toString());
        if (fields != null)
            path = storage.autocreateJSON(spec.getRecordByWebUrl("permrole").getID(), arfields, null);
    }

    private boolean setPayloadField(String fieldName, JSONObject payloadOut, JSONObject fieldsSrc,
            JSONObject dataSrc, String defaultValue) throws JSONException {
        boolean result = true;

        if (fieldsSrc != null && fieldsSrc.has(fieldName)) {
            payloadOut.put(fieldName, fieldsSrc.getString(fieldName));
        } else if (dataSrc != null && dataSrc.has(fieldName)) {
            payloadOut.put(fieldName, dataSrc.getString(fieldName));
        } else if (defaultValue != null) {
            payloadOut.put(fieldName, defaultValue);
        } else {
            result = false;
        }

        return result;
    }

    /*
     * Set a field in the payloadOut object with a value from fieldSrc or if not present there then from dataSrc
     */
    private boolean setPayloadField(String fieldName, JSONObject payloadOut, JSONObject fieldsSrc,
            JSONObject dataSrc) throws JSONException {
        return setPayloadField(fieldName, payloadOut, fieldsSrc, dataSrc, null);
    }

    private void store_set(Storage storage, UIRequest request, String path) throws UIException {
        try {
            JSONObject restrictions = new JSONObject();
            JSONObject data = request.getJSONBody();

            if (this.base.equals("role")) {
                JSONObject fields = data.optJSONObject("fields");
                if ((fields.optString("roleName") == null || fields.optString("roleName").equals(""))
                        && fields.optString("displayName") != null) {
                    String test = fields.optString("displayName");
                    test = test.toUpperCase();
                    test.replaceAll("\\W", "_");
                    fields.put("roleName", "ROLE_" + test);
                    data.put("fields", fields);
                }
                // If we are updating a role, then we need to clear the userperms cache
                // Note that creating a role does not impact things until we assign it
                if (!create) {
                    ResponseCache.clearCache(ResponseCache.USER_PERMS_CACHE);
                }
            }

            if (this.record.getID().equals("media")) {
                JSONObject fields = data.optJSONObject("fields");
                // Handle linked media references
                if (!fields.has("blobCsid") || StringUtils.isEmpty(fields.getString("blobCsid"))) { // If has blobCsid, already has media link so do nothing more
                    // No media, so consider the source
                    // "sourceUrl" is not a declared field in the app layer config, but the UI passes it in
                    // Can consider mapping srcUri to this if want to clean that up
                    if (fields.has("sourceUrl")) {
                        // We have a source - see where it is from
                        String uri = fields.getString("sourceUrl");
                        if (uri.contains(BLOBS_SERVICE_URL_PATTERN)) {
                            // This is an uploaded blob, so just pull the csid and set into blobCsid
                            String[] parts = uri.split(BLOBS_SERVICE_URL_PATTERN); // Split to get CSID
                            String[] bits = parts[1].split("/"); // Strip off anything trailing the CSID
                            fields.put("blobCsid", bits[0]);
                        } else { // This must be an external Url source
                            // External Source is handled as params to the CREATE/UPDATE of the media record
                            restrictions.put(Record.BLOB_SOURCE_URL, uri);
                            // Tell the Services to delete the original after creating derivatives
                            restrictions.put(Record.BLOB_PURGE_ORIGINAL, Boolean.toString(true));
                        }
                        fields.remove("sourceUrl");
                        data.put("fields", fields);
                    }
                }
            }

            if (this.record.getID().equals("output")) {
                //
                // Invoke a report
                //
                ReportUtils.invokeReport(this, storage, request, path);
            } else if (this.record.getID().equals("batchoutput")) {
                //do a read instead of a create as reports are special and evil

                JSONObject fields = data.optJSONObject("fields");
                JSONObject payload = new JSONObject();
                payload.put("mode", "single");

                if (fields.has("mode")) {
                    payload.put("mode", fields.getString("mode"));
                }
                if (fields.has("docType")) {
                    String type = spec.getRecordByWebUrl(fields.getString("docType")).getServicesTenantSg();
                    payload.put("docType", type);
                }
                if (fields.has("singleCSID")) {
                    payload.put("singleCSID", fields.getString("singleCSID"));
                } else if (fields.has("groupCSID")) {
                    payload.put("singleCSID", fields.getString("csid"));
                }

                JSONObject out = storage.retrieveJSON(base + "/" + path, payload);

                byte[] data_array = (byte[]) out.get("getByteBody");
                String contentDisp = out.has("contentdisposition") ? out.getString("contentdisposition") : null;
                request.sendUnknown(data_array, out.getString("contenttype"), contentDisp);
                request.setCacheMaxAgeSeconds(0); // Ensure we do not cache report output.
                //request.sendJSONResponse(out);
                request.setOperationPerformed(create ? Operation.CREATE : Operation.UPDATE);
            } else {
                //
                // <Please document this clause.>
                //
                FieldSet displayNameFS = this.record.getDisplayNameField();
                String displayNameFieldName = (displayNameFS != null) ? displayNameFS.getID() : null;
                boolean remapDisplayName = false;
                String remapDisplayNameValue = null;
                boolean quickie = false;
                if (create) {
                    quickie = (data.has("_view") && data.getString("_view").equals("autocomplete"));
                    remapDisplayName = quickie && !"displayName".equals(displayNameFieldName);
                    // Check to see if displayName field needs remapping from UI
                    if (remapDisplayName) {
                        // Need to map the field for displayName, and put it into a proper structure
                        JSONObject fields = data.getJSONObject("fields");
                        remapDisplayNameValue = fields.getString("displayName");
                        if (remapDisplayNameValue != null) {
                            // This needs generalizing, in case the remapped name is nested
                            /*
                             * From vocab handling where we know where the termDisplayName is
                            FieldSet parentTermGroup = (FieldSet)displayNameFS.getParent();
                            JSONArray parentTermInfoArray = new JSONArray();
                            JSONObject termInfo = new JSONObject();
                            termInfo.put(displayNameFieldName, remapDisplayNameValue);
                            parentTermInfoArray.put(termInfo);
                            */
                            fields.put(displayNameFieldName, remapDisplayNameValue);
                            fields.remove("displayName");
                        }
                    }
                    path = sendJSON(storage, null, data, restrictions); // REM - We needed a way to send query params, so I'm adding "restrictions" here
                    data.put("csid", path);
                    data.getJSONObject("fields").put("csid", path);
                    // Is this needed???
                    /*
                    String refName = data.getJSONObject("fields").getString("refName");
                    data.put("urn", refName);
                    data.getJSONObject("fields").put("urn", refName);
                    // This seems wrong - especially when we create from existing.
                    if(remapDisplayName){
                       JSONObject newdata = new JSONObject();
                       newdata.put("urn", refName);
                       newdata.put("displayName",quickieDisplayName);
                       data = newdata;
                    }
                     */
                } else {
                    path = sendJSON(storage, path, data, restrictions);
                }

                if (path == null) {
                    throw new UIException("Insufficient data for create (no fields?)");
                }

                if (this.base.equals("role")) {
                    assignPermissions(storage, path, data);
                }
                if (this.base.equals("termlist")) {
                    assignTerms(storage, path, data);
                }

                data = reader.getJSON(storage, path); // We do a GET now to read back what we created.
                if (quickie) {
                    JSONObject newdata = new JSONObject();
                    JSONObject fields = data.getJSONObject("fields");
                    String displayName = fields.getString(remapDisplayName ? displayNameFieldName : "displayName");
                    newdata.put("displayName", remapDisplayNameValue);
                    String refName = fields.getString("refName");
                    newdata.put("urn", refName);
                    data = newdata;
                }

                request.sendJSONResponse(data);
                request.setOperationPerformed(create ? Operation.CREATE : Operation.UPDATE);
                if (create)
                    request.setSecondaryRedirectPath(new String[] { url_base, path });
            }
        } catch (JSONException x) {
            throw new UIException("Failed to parse JSON: " + x, x);
        } catch (ExistException x) {
            UIException uiexception = new UIException(x.getMessage(), 0, "", x);
            request.sendJSONResponse(uiexception.getJSON());
        } catch (UnimplementedException x) {
            throw new UIException("Unimplemented exception: " + x, x);
        } catch (UnderlyingStorageException x) {
            UIException uiexception = new UIException(x.getMessage(), x.getStatus(), x.getUrl(), x);
            request.setStatus(x.getStatus());
            request.setFailure(true, uiexception);
            request.sendJSONResponse(uiexception.getJSON());
        } catch (Exception x) {
            throw new UIException(x);
        }

    }

    @Override
    public void run(Object in, String[] tail) throws UIException {
        Request q = (Request) in;
        ctl = new CacheTermList(q.getCache());
        store_set(q.getStorage(), q.getUIRequest(), StringUtils.join(tail, "/"));
    }

    public void configure() throws ConfigException {
    }

    @Override
    public void configure(WebUI ui, Spec spec) {
        this.searcher.configure(spec);
    }

    @Override
    public Operation getOperation() {
        return create ? Operation.CREATE : Operation.UPDATE;
    }

    @Override
    public String getBase() {
        return base;
    }

    @Override
    public Spec getSpec() {
        // TODO Auto-generated method stub
        return spec;
    }
}