Java tutorial
/* 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.authorities; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.httpclient.HttpStatus; import org.collectionspace.chain.csp.schema.Field; import org.collectionspace.chain.csp.schema.Instance; import org.collectionspace.chain.csp.schema.Option; import org.collectionspace.chain.csp.schema.Record; import org.collectionspace.chain.csp.schema.Spec; import org.collectionspace.chain.csp.webui.main.Request; import org.collectionspace.chain.csp.webui.main.WebMethod; import org.collectionspace.chain.csp.webui.main.WebUI; 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.TTYOutputter; import org.collectionspace.csp.api.ui.UIException; import org.collectionspace.csp.api.ui.UIRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * checks content of Vocabulary against a list and adds anything missing * eventually the service layer will have a function to do this so we wont have to do all the checking * * @author caret * */ public class AuthoritiesVocabulariesInitialize implements WebMethod { private static final Logger log = LoggerFactory.getLogger(AuthoritiesVocabulariesInitialize.class); private boolean append; private Instance fInstance; private Record r; private Spec spec; private boolean modifyResponse = true; private int failedPosts = 0; // If this value is > 0 after the init is complete than the default authorities and term lists have not been properly initialized. public AuthoritiesVocabulariesInitialize(Instance n, Boolean append, boolean modifyResponse) { this.append = append; this.fInstance = n; this.r = n.getRecord(); this.modifyResponse = modifyResponse; } public AuthoritiesVocabulariesInitialize(Record r, Boolean append) { this.append = append; this.r = r; this.fInstance = null; } public boolean success() { return failedPosts == 0; } private JSONObject getTermData(Storage storage, String auth_type, String inst_type, String csid) throws ExistException, UnimplementedException, UnderlyingStorageException, JSONException { //should be using cached data (hopefully) from previous getPathsJson call JSONObject out = storage.retrieveJSON(auth_type + "/" + inst_type + "/" + csid + "/view", new JSONObject()); return out; } private JSONObject list_vocab(JSONObject shortIdentifiers, Instance n, Storage storage, String param, Integer pageSize, Integer pageNum, Record thisr) throws ExistException, UnimplementedException, UnderlyingStorageException, JSONException { JSONObject restriction = new JSONObject(); if (param != null) { restriction.put(n.getRecord().getDisplayNameField().getID(), param); } if (pageNum != null) { restriction.put("pageNum", pageNum); } if (pageSize != null) { restriction.put("pageSize", pageSize); } // CSPACE-6371: When fetching existing vocabulary terms, include soft-deleted ones, so that terms // deleted through the UI are not re-added. restriction.put("deleted", true); String url = thisr.getID() + "/" + n.getTitleRef(); JSONObject data = null; try { data = storage.getPathsJSON(url, restriction); } catch (UnderlyingStorageException x) { JSONObject fields = new JSONObject( "{'displayName':'" + n.getTitle() + "','shortIdentifier':'" + n.getWebURL() + "'}"); if (thisr.getFieldFullList("termStatus") instanceof Field) { fields.put("termStatus", ((Field) thisr.getFieldFullList("termStatus")).getOptionDefault()); } String base = thisr.getID(); storage.autocreateJSON(base, fields, null); data = storage.getPathsJSON(url, restriction); } String[] results = (String[]) data.get("listItems"); /* Get a view of each */ for (String result : results) { //change csid into shortIdentifier JSONObject termData = getTermData(storage, thisr.getID(), n.getTitleRef(), result); shortIdentifiers.put(termData.getString("shortIdentifier"), result); } JSONObject alldata = new JSONObject(); alldata.put("shortIdentifiers", shortIdentifiers); alldata.put("pagination", data.getJSONObject("pagination")); return alldata; } public int createIfMissingAuthority(Storage storage, StringBuffer tty, Record record, Instance instance) throws ExistException, UnimplementedException, UIException, JSONException, UnderlyingStorageException { int result = HttpStatus.SC_OK; String url = record.getID() + "/" + instance.getTitleRef(); try { storage.getPathsJSON(url, new JSONObject()).toString(); if (tty != null) { log.debug("--- Instance " + instance.getID() + " exists."); tty.append("--- Instance " + instance.getID() + " exists.\n"); } } catch (UnderlyingStorageException e) { if (e.getStatus() == HttpStatus.SC_NOT_FOUND) { failedPosts++; // assume we're going to fail log.info("Need to create instance " + fInstance.getID()); if (tty != null) { tty.append("Need to create instance " + fInstance.getID() + '\n'); } JSONObject fields = new JSONObject("{'displayName':'" + instance.getTitle() + "', 'shortIdentifier':'" + instance.getWebURL() + "'}"); String base = record.getID(); storage.autocreateJSON(base, fields, null); failedPosts--; // We succeeded, so subtract our assumed failure log.info("Instance " + instance.getID() + " created."); if (tty != null) { tty.append("Instance " + instance.getID() + " created.\n"); } } else if (e.getStatus() == HttpStatus.SC_FORBIDDEN) { result = -1; // We don't have the permissions needed to see if the instance exists. } else { throw e; } } return result; } private void initializeVocab(Storage storage, UIRequest request, String path) throws UIException { try { if (fInstance == null) { // For now simply loop through all the instances one after the other. for (Instance instance : r.getAllInstances()) { log.info(instance.getID()); //does instance exist? if (createIfMissingAuthority(storage, null, this.r, instance) == -1) { log.warn(String.format( "The currently authenticated user does not have sufficient permission to determine if the '%s' authority/term-list is properly initialized.", instance.getID())); } resetvocabdata(storage, request, instance); } } else { log.info(fInstance.getID()); resetvocabdata(storage, request, this.fInstance); } } catch (JSONException e) { throw new UIException("Cannot generate JSON", e); } catch (ExistException e) { throw new UIException("Exist exception", e); } catch (UnimplementedException e) { throw new UIException("Unimplemented exception", e); } catch (UnderlyingStorageException x) { UIException uiexception = new UIException(x.getMessage(), x.getStatus(), x.getUrl(), x); request.sendJSONResponse(uiexception.getJSON()); } } private JSONObject getJSONResource(String in) throws IOException, JSONException { return new JSONObject(getResource(in)); } private String getResource(String in) throws IOException { File file = new File(in); if (!file.exists()) return null; InputStream stream = new FileInputStream(file); String data = IOUtils.toString(stream); stream.close(); return data; } public void doreset(Storage storage, Instance ins, JSONArray terms) throws JSONException, UIException, ExistException, UnimplementedException, UnderlyingStorageException { Option[] allOpts = ins.getAllOptions(); //remove all opts from instance if (allOpts != null && allOpts.length > 0) { for (Option opt : allOpts) { String name = opt.getName(); String shortIdentifier = opt.getID(); String sample = opt.getSample(); Boolean dfault = opt.isDefault(); ins.deleteOption(shortIdentifier, name, sample, dfault); } } for (int i = 0; i < terms.length(); i++) { JSONObject element = terms.getJSONObject(i); if (element.has("description")) ins.addOption(element.getString("shortIdentifier"), element.getString("displayName"), null, false, element.getString("description")); else ins.addOption(element.getString("shortIdentifier"), element.getString("displayName"), null, false); } allOpts = ins.getAllOptions(); fillVocab(storage, ins.getRecord(), ins, null, allOpts, false); } private void resetvocabdata(Storage storage, UIRequest request, Instance instance) throws UIException, ExistException, UnimplementedException, UnderlyingStorageException, JSONException { StringBuffer tty = new StringBuffer(); tty.append("Initializing Vocab " + instance.getID() + '\n'); //Where do we get the list from? //from Spec Option[] allOpts = instance.getAllOptions(); //but first check: do we have a path? Set<String> args = request.getAllRequestArgument(); if (args.contains("datapath")) { tty.append("Using Datapath \n"); //remove all opts from instance as we have a path if (allOpts != null && allOpts.length > 0) { tty.append("Removing all opts from instance as we have a path\n"); for (Option opt : allOpts) { String name = opt.getName(); String shortIdentifier = opt.getID(); String sample = opt.getSample(); Boolean dfault = opt.isDefault(); instance.deleteOption(shortIdentifier, name, sample, dfault); } } //add from path String value = request.getRequestArgument("datapath"); //log.info("getting data from path: "+value); try { tty.append("Getting data from path: " + value + '\n'); String names = getResource(value); for (String line : names.split("\n")) { line = line.trim(); String bits[] = line.split("\\|"); if (bits.length > 1) { instance.addOption(bits[0], bits[1], null, false); } else { instance.addOption(null, line, null, false); } } } catch (IOException e) { throw new UIException("IOException", e); } allOpts = instance.getAllOptions(); } fillVocab(storage, r, instance, tty, allOpts, this.append); if (this.modifyResponse == true) { TTYOutputter ttyOut = request.getTTYOutputter(); ttyOut.line(tty.toString()); } } public void fillVocab(Storage storage, Record thisr, Instance instance, StringBuffer tty, Option[] allOpts, Boolean appendit) throws UIException, ExistException, UnimplementedException, UnderlyingStorageException, JSONException { // // step away if we have nothing to add // if (allOpts != null && allOpts.length > 0) { // get list from Service layer JSONObject results = new JSONObject(); Integer pageNum = 0; Integer pageSize = 0; // Setting the pageSize to 0 means the Services layer will return ALL the terms in one request. // The "paging" code here is broken. It's made some false assumptions about the order in which terms are returned from the Services layer. // In short, setting the pageSize to 0 is a workaround to the bugs in the paging code. JSONObject fulldata = list_vocab(results, instance, storage, null, pageSize, pageNum, thisr); while (!fulldata.isNull("pagination")) { Integer total = fulldata.getJSONObject("pagination").getInt("totalItems"); pageSize = fulldata.getJSONObject("pagination").getInt("pageSize"); Integer itemsInPage = fulldata.getJSONObject("pagination").getInt("itemsInPage"); pageNum = fulldata.getJSONObject("pagination").getInt("pageNum"); results = fulldata.getJSONObject("shortIdentifiers"); pageNum++; // are there more results if (total > (pageSize * (pageNum))) { fulldata = list_vocab(results, instance, storage, null, pageSize, pageNum, thisr); } else { break; } } // compare results = fulldata.getJSONObject("shortIdentifiers"); for (Option opt : allOpts) { String name = opt.getName(); String shortIdentifier = opt.getID(); if (shortIdentifier == null || shortIdentifier.equals("")) { shortIdentifier = name.trim().replaceAll("\\W", "").toLowerCase(); } if (!results.has(shortIdentifier)) { // create it if term is not already present JSONObject data = new JSONObject(); data.put("displayName", name); data.put("description", opt.getDesc()); data.put("shortIdentifier", shortIdentifier); if (thisr.getFieldFullList("termStatus") instanceof Field) { data.put("termStatus", ((Field) thisr.getFieldFullList("termStatus")).getOptionDefault()); } String url = thisr.getID() + "/" + instance.getTitleRef(); failedPosts++; // assume we're going to fail and an exception will be thrown try { storage.autocreateJSON(url, data, null); failedPosts--; // we succeeded so we can remove our assumed failure if (tty != null) { String msg = String.format("Added term %s:'%s' with short ID='%s'.", instance.getTitleRef(), name, shortIdentifier); tty.append(msg + '\n'); log.info(msg); } } catch (UnderlyingStorageException e) { if (e.getStatus() == HttpStatus.SC_CONFLICT) { log.error( "Terms must have unique short identifiers. The CollectionSpace administrator needs to change the non-unique short identifier and restart CollectionSpace."); String msg = String.format( "CollectionSpace attempted to create the term %s:'%s' using a non-unique short identifier of '%s'.", instance.getTitleRef(), name, shortIdentifier); log.error(msg); log.error(String.format("Failed JSON payload:%s", data.toString())); } else { throw e; } } } else { // remove from results so can delete everything else if // necessary in next stage // though has issues with duplicates results.remove(shortIdentifier); String msg = String.format( "Tried to create term %s:'%s' with short ID='%s', but a term with that short ID already exists.", instance.getTitleRef(), name, shortIdentifier); log.debug(msg); } } if (!appendit) { // delete everything that is not in options Iterator<String> rit = results.keys(); while (rit.hasNext()) { String key = rit.next(); String csid = results.getString(key); failedPosts++; // assume we're going to fail and an exception will be thrown storage.deleteJSON(thisr.getID() + "/" + instance.getTitleRef() + "/" + csid); failedPosts--; // we succeeded so remove our assumed failure if (tty != null) { log.info("Deleting term " + key); tty.append("Deleting term " + key + '\n'); } } } } } @Override public void configure(WebUI ui, Spec spec) { this.spec = spec; } @Override public void run(Object in, String[] tail) throws UIException { Request q = (Request) in; initializeVocab(q.getStorage(), q.getUIRequest(), StringUtils.join(tail, "/")); } }