From 35deaebd3406cddbbdffa8c91ee9a0cd9c0e1fe2 Mon Sep 17 00:00:00 2001 From: briancaruso Date: Tue, 5 Apr 2011 14:52:48 +0000 Subject: [PATCH] Adding changes for Deepak to review. --- .../n3editing/controller/ProcessRdfForm.java | 572 ++++++++++++++++++ 1 file changed, 572 insertions(+) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfForm.java diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfForm.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfForm.java new file mode 100644 index 000000000..03ecf5982 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfForm.java @@ -0,0 +1,572 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.controller; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.Field; +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.shared.Lock; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.IndividualImpl; +import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.InsertException; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.dao.jena.DependentResourceDeleteJena; +import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.EditConfiguration; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.EditN3Generator; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.EditN3Utils; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.EditSubmission; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.ModelChangePreprocessor; + +/** + * This servlet will process EditConfigurations with query parameters + * to perform an edit. + * + * The general steps involved are: + * get edit configuration + * get edit submission + * + * + */ +public class ProcessRdfForm extends VitroHttpServlet{ + + //bdc34: How will we change this so it is not a jsp? + public static final String NO_EDITCONFIG_FOUND_JSP = "/edit/messages/noEditConfigFound.jsp" ; + + //bdc34: this is likely to become a servlet instead of a jsp + // you can get a servlet from the context. + public static final String POST_EDIT_CLEANUP_JSP = "postEditCleanUp.jsp"; + + private Log log = LogFactory.getLog(ProcessRdfForm.class); + + //we should get this from the application, ConfigurationProperties? + static String defaultUriPrefix = "http://vivo.library.cornell.edu/ns/0.1#individual"; + + /* entity to return to may be a variable */ + List entToReturnTo = new ArrayList(1); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws + ServletException, IOException{ + + VitroRequest vreq = new VitroRequest(request); + RequestDispatcher requestDispatcher; + + //get the EditConfiguration + EditConfiguration editConfiguration = getEditConfiguration(request); + if(editConfiguration == null){ + requestDispatcher = request.getRequestDispatcher(NO_EDITCONFIG_FOUND_JSP); + requestDispatcher.forward(request, response); + return; + } + + //get the EditSubmission + EditSubmission submission = new EditSubmission(vreq.getParameterMap(), editConfiguration); + EditSubmission.putEditSubmissionInSession(request.getSession(), submission); + + boolean hasErrors = processValidationErrors(vreq, editConfiguration, submission, response); + if( hasErrors) + return; //processValidationErrors() already forwarded to redisplay the form with validation messages + + OntModel queryModel = editConfiguration.getQueryModelSelector().getModel(request, getServletContext()); + + // get the model to write to here in case a preprocessor has switched the write layer + OntModel writeModel = editConfiguration.getWriteModelSelector().getModel(request,getServletContext()); + + AdditionsAndRetractions changes; + if(editConfiguration.isUpdate()){ + changes = editExistingResource(editConfiguration, submission); + }else{ + changes = createNewResource(editConfiguration, submission); + } + + applyChanges(changes, editConfiguration, request, queryModel, writeModel); + + /* what about entity to return to? */ + + requestDispatcher = vreq.getRequestDispatcher(POST_EDIT_CLEANUP_JSP); + requestDispatcher.forward(vreq, response); + } + + private void applyChanges(AdditionsAndRetractions changes, EditConfiguration editConfiguration, HttpServletRequest request, OntModel queryModel, OntModel writeModel) { + //make a model with all the assertions and a model with all the + //retractions, do a diff on those and then only add those to the jenaOntModel + Model allPossibleAssertions = ModelFactory.createDefaultModel(); + Model allPossibleRetractions = ModelFactory.createDefaultModel(); + + for( Model model : changes.getAdditions() ) { + allPossibleAssertions.add( model ); + } + for( Model model : changes.getRetractions() ){ + allPossibleRetractions.add( model ); + } + + //find the minimal change set + Model actualAssertions = allPossibleAssertions.difference( allPossibleRetractions ); + Model actualRetractions = allPossibleRetractions.difference( allPossibleAssertions ); + + //Add retractions for dependent resource delete if that is configured and + //if there are any dependent resources. + if( editConfiguration.isUseDependentResourceDelete() ){ + Model depResRetractions = + DependentResourceDeleteJena + .getDependentResourceDeleteForChange(actualAssertions,actualRetractions,queryModel); + actualRetractions.add( depResRetractions ); + } + + //execute any modelChangePreprocessors + List modelChangePreprocessors = editConfiguration.getModelChangePreprocessors(); + if ( modelChangePreprocessors != null ) { + for ( ModelChangePreprocessor pp : modelChangePreprocessors ) { + pp.preprocess( actualRetractions, actualAssertions, request ); + } + } + + //side effect: modify the write model with the changes + String editorUri = EditN3Utils.getEditorUri(new VitroRequest(request)); + Lock lock = null; + try{ + lock = writeModel.getLock(); + lock.enterCriticalSection(Lock.WRITE); + writeModel.getBaseModel().notifyEvent(new EditEvent(editorUri,true)); + writeModel.add( actualAssertions ); + writeModel.remove( actualRetractions ); + }catch(Throwable t){ + log.error("error adding edit change n3required model to in memory model \n"+ t.getMessage() ); + }finally{ + writeModel.getBaseModel().notifyEvent(new EditEvent(editorUri,false)); + lock.leaveCriticalSection(); + } + + //Here we are trying to get the entity to return to URL set up correctly. + //The problme is that subInURI will add < and > around URIs for the N3 syntax. + //for the URL to return to, replace < and > from URI additions. + //This should happen somewhere else... + if( entToReturnTo.size() >= 1 && entToReturnTo.get(0) != null){ + request.setAttribute("entityToReturnTo", + entToReturnTo.get(0).trim().replaceAll("<","").replaceAll(">","")); + } + } + + private EditConfiguration getEditConfiguration(HttpServletRequest request) { + + HttpSession session = request.getSession(); + EditConfiguration editConfiguration = EditConfiguration.getConfigFromSession(session, request); + + return editConfiguration; + } + + @SuppressWarnings("static-access") + private AdditionsAndRetractions createNewResource(EditConfiguration editConfiguration , EditSubmission submission){ + List errorMessages = new ArrayList(); + + EditN3Generator n3Subber = editConfiguration.getN3Generator(); + + if(log.isDebugEnabled()){ + log.debug("creating a new relation " + editConfiguration.getPredicateUri()); + } + + //handle creation of a new object property and maybe a resource + List n3Required = editConfiguration.getN3Required(); + List n3Optional = editConfiguration.getN3Optional(); + + /* ********** URIs and Literals on Form/Parameters *********** */ + //sub in resource uris off form + n3Required = n3Subber.subInUris(submission.getUrisFromForm(), n3Required); + n3Optional = n3Subber.subInUris(submission.getUrisFromForm(), n3Optional); + if(log.isDebugEnabled()) { + Utilities.logRequiredOpt("substituted in URIs off from ",n3Required,n3Optional); + } + entToReturnTo = n3Subber.subInUris(submission.getUrisFromForm(), entToReturnTo); + + //sub in literals from form + n3Required = n3Subber.subInLiterals(submission.getLiteralsFromForm(), n3Required); + n3Optional = n3Subber.subInLiterals(submission.getLiteralsFromForm(), n3Optional); + if(log.isDebugEnabled()) { + Utilities.logRequiredOpt("substituted in literals off from ",n3Required,n3Optional); + } + + /* ****************** URIs and Literals in Scope ************** */ + n3Required = n3Subber.subInUris( editConfiguration.getUrisInScope(), n3Required); + n3Optional = n3Subber.subInUris( editConfiguration.getUrisInScope(), n3Optional); + if(log.isDebugEnabled()) { + Utilities.logRequiredOpt("substituted in URIs from scope ",n3Required,n3Optional); + } + entToReturnTo = n3Subber.subInUris(editConfiguration.getUrisInScope(), entToReturnTo); + + n3Required = n3Subber.subInLiterals( editConfiguration.getLiteralsInScope(), n3Required); + n3Optional = n3Subber.subInLiterals( editConfiguration.getLiteralsInScope(), n3Optional); + if(log.isDebugEnabled()) { + Utilities.logRequiredOpt("substituted in Literals from scope ",n3Required,n3Optional); + } + + //deal with required N3 + List requiredNewModels = new ArrayList(); + for(String n3 : n3Required){ + try{ + Model model = ModelFactory.createDefaultModel(); + StringReader reader = new StringReader(n3); + model.read(reader, "", "N3"); + requiredNewModels.add(model); + } catch(Throwable t){ + errorMessages.add("error processing required n3 string \n" + t.getMessage() + '\n' + "n3: \n" + n3); + } + } + + if(!errorMessages.isEmpty()){ + String error = "problems processing required n3: \n"; + for(String errorMsg: errorMessages){ + error += errorMsg + '\n'; + } + if(log.isDebugEnabled()){ + log.debug(error); + } + } + List requiredAssertions = requiredNewModels; + + //deal with optional N3 + List optionalNewModels = new ArrayList(); + for(String n3 : n3Optional){ + try{ + Model model = ModelFactory.createDefaultModel(); + StringReader reader = new StringReader(n3); + model.read(reader, "", "N3"); + optionalNewModels.add(model); + }catch(Throwable t){ + //if an optional N3 block fails to parse it will be ignored +// errorMessages.add("error processing optional n3 string \n"+ +// t.getMessage() + '\n' + +// "n3: \n" + n3); + } + } + requiredAssertions.addAll( optionalNewModels ); + + AdditionsAndRetractions delta = new AdditionsAndRetractions(); + delta.setAdditions(requiredAssertions); + delta.setRetractions(Collections.emptyList()); + return delta; + } + + @SuppressWarnings("static-access") + private AdditionsAndRetractions editExistingResource(EditConfiguration editConfiguration, EditSubmission submission) { + + Map> fieldAssertions = Utilities.fieldsToAssertionMap(editConfiguration.getFields()); + Map> fieldRetractions = Utilities.fieldsToRetractionMap(editConfiguration.getFields()); + EditN3Generator n3Subber = editConfiguration.getN3Generator(); + + if(editConfiguration.getEntityToReturnTo() != null){ + entToReturnTo.add(" " + editConfiguration.getEntityToReturnTo() + " "); + } + + /* ********** URIs and Literals on Form/Parameters *********** */ + fieldAssertions = n3Subber.substituteIntoValues(submission.getUrisFromForm(), submission.getLiteralsFromForm(), fieldAssertions); + if(log.isDebugEnabled()) { + Utilities.logAddRetract("substituted in literals from form",fieldAssertions,fieldRetractions); + } + entToReturnTo = n3Subber.subInUris(submission.getUrisFromForm(),entToReturnTo); + + /* ****************** URIs and Literals in Scope ************** */ + fieldAssertions = n3Subber.substituteIntoValues(editConfiguration.getUrisInScope(), editConfiguration.getLiteralsInScope(), fieldAssertions ); + fieldRetractions = n3Subber.substituteIntoValues(editConfiguration.getUrisInScope(), editConfiguration.getLiteralsInScope(), fieldRetractions); + if(log.isDebugEnabled()) { + Utilities.logAddRetract("substituted in URIs and Literals from scope",fieldAssertions,fieldRetractions); + } + entToReturnTo = n3Subber.subInUris(editConfiguration.getUrisInScope(),entToReturnTo); + + //do edits ever need new resources? (YES) +/* Map varToNewResource = newToUriMap(editConfiguration.getNewResources(),wdf); + fieldAssertions = n3Subber.substituteIntoValues(varToNewResource, null, fieldAssertions); + if(log.isDebugEnabled()) { + Utilities.logAddRetract("substituted in URIs for new resources",fieldAssertions,fieldRetractions); + } + entToReturnTo = n3Subber.subInUris(varToNewResource,entToReturnTo); +*/ + //editing an existing statement + List requiredFieldAssertions = new ArrayList(); + List requiredFieldRetractions = new ArrayList(); + + List errorMessages = new ArrayList(); + + for(String fieldName : fieldAssertions.keySet()){ + List assertions = fieldAssertions.get(fieldName); + List retractions = fieldRetractions.get(fieldName); + + for(String n3: assertions){ + try{ + Model model = ModelFactory.createDefaultModel(); + StringReader reader = new StringReader(n3); + model.read(reader, "", "N3"); + }catch(Throwable t){ + String errMsg = "error processing N3 assertion string from field " + fieldName + "\n" + + t.getMessage() + '\n' + "n3: \n" + n3; + errorMessages.add(errMsg); + if(log.isDebugEnabled()){ + log.debug(errMsg); + } + } + } + + for(String n3 : retractions){ + try{ + Model model = ModelFactory.createDefaultModel(); + StringReader reader = new StringReader(n3); + model.read(reader, "", "N3"); + requiredFieldRetractions.add(model); + }catch(Throwable t){ + String errMsg = "error processing N3 retraction string from field " + fieldName + "\n"+ + t.getMessage() + '\n' + "n3: \n" + n3; + errorMessages.add(errMsg); + if(log.isDebugEnabled()){ + log.debug(errMsg); + } + } + } + } + +// requiredAssertions = requiredFieldAssertions; +// requiredRetractions = requiredFieldRetractions; +// optionalAssertions = Collections.emptyList(); + + throw new Error("need to be implemented by Deepak"); + + } + + private boolean processValidationErrors(VitroRequest vreq, + EditConfiguration editConfiguration, EditSubmission submission, + HttpServletResponse response) throws ServletException, IOException { + + Map errors = submission.getValidationErrors(); + + if(errors != null && !errors.isEmpty()){ + String form = editConfiguration.getFormUrl(); + vreq.setAttribute("formUrl", form); + vreq.setAttribute("view", vreq.getParameter("view")); + + RequestDispatcher requestDispatcher = vreq.getRequestDispatcher(editConfiguration.getFormUrl()); + requestDispatcher.forward(vreq, response); + return true; + } + return false; + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws + ServletException, IOException{ + doGet(request, response); + } + + public static class Utilities { + + private static Log log = LogFactory.getLog(ProcessRdfForm.class); + static Random random = new Random(); + + public static Map> fieldsToAssertionMap( Map fields){ + Map> out = new HashMap>(); + for( String fieldName : fields.keySet()){ + Field field = fields.get(fieldName); + + List copyOfN3 = new ArrayList(); + for( String str : field.getAssertions()){ + copyOfN3.add(str); + } + out.put( fieldName, copyOfN3 ); + } + return out; + } + + public static Map> fieldsToRetractionMap( Map fields){ + Map> out = new HashMap>(); + for( String fieldName : fields.keySet()){ + Field field = fields.get(fieldName); + + List copyOfN3 = new ArrayList(); + for( String str : field.getRetractions()){ + copyOfN3.add(str); + } + out.put( fieldName, copyOfN3 ); + } + return out; + } + + public static Map newToUriMap(Map newResources, WebappDaoFactory wdf){ + HashMap newVarsToUris = new HashMap(); + HashSet newUris = new HashSet(); + for( String key : newResources.keySet()){ + String prefix = newResources.get(key); + String uri = makeNewUri(prefix, wdf); + while( newUris.contains(uri) ){ + uri = makeNewUri(prefix,wdf); + } + newVarsToUris.put(key,uri); + newUris.add(uri); + } + return newVarsToUris; + } + + + public static String makeNewUri(String prefix, WebappDaoFactory wdf){ + if( prefix == null || prefix.length() == 0 ){ + String uri = null; + try{ + uri = wdf.getIndividualDao().getUnusedURI(null); + }catch(InsertException ex){ + log.error("could not create uri"); + } + return uri; + } + + String goodURI = null; + int attempts = 0; + while( goodURI == null && attempts < 30 ){ + Individual ind = new IndividualImpl(); + ind.setURI( prefix + random.nextInt() ); + try{ + goodURI = wdf.getIndividualDao().getUnusedURI(ind); + }catch(InsertException ex){ + log.debug("could not create uri"); + } + attempts++; + } + if( goodURI == null ) + log.error("could not create uri for prefix " + prefix); + return goodURI; + + } + + private static boolean logAddRetract(String msg, Map>add, Map>retract){ + log.debug(msg); + if( add != null ) log.debug( "assertions: " + add.toString() ); + if( retract != null ) log.debug( "retractions: " + retract.toString() ); + return true; + } + + private static boolean logRequiredOpt(String msg, Listrequired, Listoptional){ + log.debug(msg); + if( required != null ) log.debug( "required: " + required.toString() ); + if( optional != null ) log.debug( "optional: " + optional.toString() ); + return true; + } + + + /* What are the posibilities and what do they mean? + field is a Uri: + orgValue formValue + null null Optional object property, maybe a un-filled out checkbox or radio button. + non-null null There was an object property and it was unset on the form + null non-null There was an objProp that was not set and is now set. + non-null non-null If they are the same then there was no edit, if the differ then form field was changed + + field is a Literal: + orgValue formValue + null null Optional value that was not set. + non-null null Optional value that was unset on form + null non-null Optional value that was unset but was set on form + non-null non-null If same, there was no edit, if different, there was a change to the form field. + + What about checkboxes? + */ + private boolean hasFieldChanged(String fieldName, EditConfiguration editConfig, EditSubmission submission) { + String orgValue = editConfig.getUrisInScope().get(fieldName); + String newValue = submission.getUrisFromForm().get(fieldName); + + // see possibilities list in comments just above + if (orgValue == null && newValue != null) { + log.debug("Note: Setting previously null object property for field '"+fieldName+"' to new value ["+newValue+"]"); + return true; + } + + if( orgValue != null && newValue != null){ + if( orgValue.equals(newValue)) + return false; + else + return true; + } + + //This does NOT use the semantics of the literal's Datatype or the language. + Literal orgLit = editConfig.getLiteralsInScope().get(fieldName); + Literal newLit = submission.getLiteralsFromForm().get(fieldName); + + if( orgLit != null ) { + orgValue = orgLit.getValue().toString(); + } + if( newLit != null ) { + newValue = newLit.getValue().toString(); + } + + // added for links, where linkDisplayRank will frequently come in null + if (orgValue == null && newValue != null) { + return true; + } + + if( orgValue != null && newValue != null ){ + if( orgValue.equals(newValue)) { + return false; + } + + else { + return true; + } + } + //value wasn't set originally because the field is optional + return false; + } + + private void dump(String name, Object fff){ + XStream xstream = new XStream(new DomDriver()); + System.out.println( "*******************************************************************" ); + System.out.println( name ); + System.out.println(xstream.toXML( fff )); + } + + + } + + + private String subInValues(String in /* what else is needed? */){ + + return in; + } + + + private class AdditionsAndRetractions { + List additions; + List retractions; + + public List getAdditions() { + return additions; + } + public void setAdditions(List additions) { + this.additions = additions; + } + public List getRetractions() { + return retractions; + } + public void setRetractions(List retractions) { + this.retractions = retractions; + } + } +}