diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java index 7171c73a6..c3a0b6be4 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java @@ -4,9 +4,13 @@ package edu.cornell.mannlib.vitro.webapp.services.shortview; import static edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.ShortViewContext.BROWSE; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -19,28 +23,264 @@ import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModelSpec; import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.ResIterator; import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.DisplayVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.VClassDao; +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.ShortViewContext; import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter; import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.SparqlQueryDataGetter; /** - * TODO + * Read a config file that describes the short views. Read it into a model and + * scan the model to determine what each view consists of (data getter URIs, + * template names), what context each view applies to, and what classes map to + * each view. * - * Get rid of this when the Application Ontology is implemented. + * Data getters must be SparqlQueryDataGetters, and must be described in the + * same config file. + * + * TODO Get rid of this when the Application Ontology is implemented. */ public class FakeApplicationOntologyService { private static final Log log = LogFactory .getLog(FakeApplicationOntologyService.class); - private static final String FACULTY_MEMBER_CLASS_URI = "http://vivoweb.org/ontology/core#FacultyMember"; - private static final String PEOPLE_CLASSGROUP_URI = "http://vivoweb.org/ontology#vitroClassGrouppeople"; + public static final String FILE_OF_SHORT_VIEW_INFO = "/WEB-INF/resources/shortview_config.n3"; + + private static final String NS = "http://vitro.mannlib.cornell.edu/ontologies/display/1.1#"; + private static final String HAS_TEMPLATE = NS + "hasTemplate"; + private static final String CUSTOM_VIEW = NS + "customViewForIndividual"; + private static final String APPLIES_TO = NS + "appliesToContext"; + private static final String HAS_DATA_GETTER = NS + "hasDataGetter"; + private static final String HAS_VIEW = NS + "hasCustomView"; + private static final String RDF_TYPE = VitroVocabulary.RDF_TYPE; + + private final OntModel viewModel; + private final Map> classUriToViewSpecs; + + /** + * Load the model from the config file, and inspect it for Views and + * mappings. + * + * Keep the model - we'll need it when its time to create the DataGetters + * (on each request). + */ + public FakeApplicationOntologyService(ServletContext ctx) + throws ShortViewConfigException { + this.viewModel = createModelFromFile(ctx); + + Map viewSpecsByUri = createViewSpecs(); + this.classUriToViewSpecs = createClassMappings(viewSpecsByUri); + + if (log.isDebugEnabled()) { + log.debug("Mapping: " + classUriToViewSpecs); + } + } + + /** + * If we fail to parse the config file, use this constructor instead, to + * simulate an empty config file. + */ + public FakeApplicationOntologyService() { + this.viewModel = ModelFactory + .createOntologyModel(OntModelSpec.OWL_DL_MEM); + this.classUriToViewSpecs = new HashMap>(); + + log.debug("Created empty FakeApplicationOntologyService."); + } + + /** + * Load the short view config file into an OntModel. + */ + private OntModel createModelFromFile(ServletContext ctx) + throws ShortViewConfigException { + InputStream stream = ctx.getResourceAsStream(FILE_OF_SHORT_VIEW_INFO); + if (stream == null) { + throw new ShortViewConfigException("The short view config file " + + "doesn't exist in the servlet context: '" + + FILE_OF_SHORT_VIEW_INFO + "'"); + } + + OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM); + try { + m.read(stream, null, "N3"); + } catch (Exception e) { + throw new ShortViewConfigException( + "Parsing error in the short view config file.", e); + } finally { + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + log.debug("Loaded " + m.size() + " statements"); + return m; + } + + /** + * Find all of the views. + */ + private Map createViewSpecs() + throws ShortViewConfigException { + Property rdfType = viewModel.getProperty(RDF_TYPE); + Property appliesTo = viewModel.getProperty(APPLIES_TO); + Property dataGetter = viewModel.getProperty(HAS_DATA_GETTER); + Property template = viewModel.getProperty(HAS_TEMPLATE); + Resource customView = viewModel.getResource(CUSTOM_VIEW); + + ResIterator views = viewModel.listResourcesWithProperty(rdfType, + customView); + try { + Map map = new HashMap(); + while (views.hasNext()) { + Resource view = views.next(); + List contextNames = getDataPropertyValues(view, + appliesTo, "context"); + List contexts = contextsFromNames(view, + contextNames); + List dataGetterUris = getObjectPropertyValues(view, + dataGetter, "data getter"); + String tn = getDataProperty(view, template); + map.put(view.getURI(), new ViewSpec(view.getURI(), contexts, + dataGetterUris, tn)); + } + return map; + } finally { + views.close(); + } + } + + /** + * Got a list of context names. Make sure that each one actually represents + * a known context. + */ + private List contextsFromNames(Resource view, + List contextNames) throws ShortViewConfigException { + List list = new ArrayList(); + for (String name : contextNames) { + ShortViewContext context = ShortViewContext.fromString(name); + if (context == null) { + throw new ShortViewConfigException("Unrecognized context '" + + name + "' for view '" + view.getURI() + "'"); + } + list.add(context); + } + return list; + } + + /** + * Create a map of classes to views. + */ + private Map> createClassMappings( + Map viewSpecsByUri) + throws ShortViewConfigException { + Property hasView = viewModel.getProperty(HAS_VIEW); + + StmtIterator stmts = viewModel.listStatements(null, hasView, + (RDFNode) null); + try { + Map> map = new HashMap>(); + while (stmts.hasNext()) { + Statement s = stmts.next(); + + String classUri = s.getSubject().getURI(); + + RDFNode node = s.getObject(); + if (!node.isResource()) { + throw new ShortViewConfigException("The hasCustomView" + + " property for '" + classUri + + "' must be a resource."); + } + String viewUri = node.asResource().getURI(); + + ViewSpec view = viewSpecsByUri.get(viewUri); + if (view == null) { + throw new ShortViewConfigException("On '" + classUri + + "', the view '" + viewUri + "' does not exist."); + } + + if (!map.containsKey(classUri)) { + map.put(classUri, new ArrayList()); + } + map.get(classUri).add(view); + } + return map; + } finally { + stmts.close(); + } + } + + private String getDataProperty(Resource subject, Property predicate) + throws ShortViewConfigException { + Statement s = viewModel.getProperty(subject, predicate); + if (s == null) { + throw new ShortViewConfigException("The required property '" + + predicate.getURI() + "' is not present for '" + + subject.getURI() + "'"); + } + RDFNode node = s.getObject(); + if (!node.isLiteral()) + throw new ShortViewConfigException("The value of '" + + predicate.getURI() + "' for '" + subject.getURI() + + "' must be a literal."); + return node.asLiteral().getString(); + } + + private List getDataPropertyValues(Resource subject, + Property predicate, String label) throws ShortViewConfigException { + StmtIterator stmts = viewModel.listStatements(subject, predicate, + (RDFNode) null); + try { + List list = new ArrayList(); + while (stmts.hasNext()) { + Statement stmt = stmts.next(); + RDFNode node = stmt.getObject(); + if (!node.isLiteral()) { + throw new ShortViewConfigException("The " + label + + " property for '" + subject.getURI() + + "' must be a literal."); + } + list.add(node.asLiteral().getString()); + } + return list; + } finally { + stmts.close(); + } + } + + private List getObjectPropertyValues(Resource subject, + Property predicate, String label) throws ShortViewConfigException { + StmtIterator stmts = viewModel.listStatements(subject, predicate, + (RDFNode) null); + try { + List list = new ArrayList(); + while (stmts.hasNext()) { + Statement stmt = stmts.next(); + RDFNode node = stmt.getObject(); + if (!node.isResource()) { + throw new ShortViewConfigException("The " + label + + " property for '" + subject.getURI() + + "' must be a resource."); + } + list.add(node.asResource().getURI()); + } + return list; + } finally { + stmts.close(); + } + } /** * Return the template name and DataGetter instances associated with this @@ -48,42 +288,42 @@ public class FakeApplicationOntologyService { */ public TemplateAndDataGetters getShortViewProperties(VitroRequest vreq, Individual individual, String classUri, String contextName) { + /* + * If we have a mapping for this class that applies to this context, + * construct the DataGetter instances and return them with the template. + */ + if (classUriToViewSpecs.containsKey(classUri)) { + for (ViewSpec view : classUriToViewSpecs.get(classUri)) { + for (ShortViewContext context : view.getContexts()) { + if (context.name().equalsIgnoreCase(contextName)) { + List dgList = new ArrayList(); + for (String dgUri : view.getDataGetterUris()) { + dgList.add(new SparqlQueryDataGetter(vreq, + viewModel, dgUri)); + } + return new TemplateAndDataGetters( + view.getTemplateName(), + dgList.toArray(new DataGetter[0])); + } + } + } + } + + /* + * Otherwise, check for this hard-coded kluge. Any class in the People + * class group gets a special view with preferred title. + */ if ((BROWSE.name().equals(contextName)) && (isClassInPeopleClassGroup(vreq.getWebappDaoFactory(), classUri))) { return new TemplateAndDataGetters("view-browse-people.ftl", new FakeVivoPeopleDataGetter(vreq, individual.getURI())); } - // A mockup of Tammy's use case. - // if ((SEARCH.name().equals(contextName)) - // && (classUri.equals(FACULTY_MEMBER_CLASS_URI))) { - // return new TemplateAndDataGetters("view-search-faculty.ftl", new - // FakeFacultyDataGetter()); - // } else { + + /* + * Otherwise, no custom view. + */ return null; - // } - } - - /** The info associated with a short view. */ - public static class TemplateAndDataGetters { - private final String templateName; - private final Set dataGetters; - - public TemplateAndDataGetters(String templateName, - DataGetter... dataGetters) { - this.templateName = templateName; - this.dataGetters = new HashSet( - Arrays.asList(dataGetters)); - } - - public String getTemplateName() { - return templateName; - } - - public Set getDataGetters() { - return dataGetters; - } - } private boolean isClassInPeopleClassGroup(WebappDaoFactory wadf, @@ -116,18 +356,85 @@ public class FakeApplicationOntologyService { return isPeople; } - private static class FakeFacultyDataGetter implements DataGetter { + // ---------------------------------------------------------------------- + // Helper classes + // ---------------------------------------------------------------------- + + /** The info associated with a short view. */ + public static class TemplateAndDataGetters { + private final String templateName; + private final Set dataGetters; + + public TemplateAndDataGetters(String templateName, + DataGetter... dataGetters) { + this.templateName = templateName; + this.dataGetters = new HashSet( + Arrays.asList(dataGetters)); + } + + public String getTemplateName() { + return templateName; + } + + public Set getDataGetters() { + return dataGetters; + } + + } + + /** The view specifications that we read from the config file. */ + private class ViewSpec { + private final String uri; + private final List contexts; + private final List dataGetterUris; + private final String templateName; + + public ViewSpec(String uri, List contexts, + List dataGetterUris, String templateName) { + this.uri = uri; + this.contexts = contexts; + this.dataGetterUris = dataGetterUris; + this.templateName = templateName; + } + + public List getContexts() { + return contexts; + } + + public List getDataGetterUris() { + return dataGetterUris; + } + + public String getTemplateName() { + return templateName; + } + @Override - public Map getData(Map valueMap) { - Map map = new HashMap(); - Map extras = new HashMap(); - extras.put("departmentName", "Department of Redundancy Department"); - map.put("extra", extras); - return map; + public String toString() { + return "ViewSpec[uri='" + uri + "', contexts=" + contexts + + ", dataGetterUris=" + dataGetterUris + ", templateName='" + + templateName + "']"; + } + + } + + /** A custom exception that says something was wrong with the config file. */ + public class ShortViewConfigException extends Exception { + public ShortViewConfigException(String message) { + super(message); + } + + public ShortViewConfigException(String message, Throwable cause) { + super(message, cause); } } + private static final String PEOPLE_CLASSGROUP_URI = "http://vivoweb.org/ontology#vitroClassGrouppeople"; + /** + * A special data getter to support the kluge case of browsing an individual + * that belongs to the People class group. + * * A SPARQL query data getter that initializes itself from its own private * "display model". The query finds a preferred title for the individual. */ diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceImpl.java index 078ff45a8..99f6cd839 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceImpl.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceImpl.java @@ -3,7 +3,6 @@ package edu.cornell.mannlib.vitro.webapp.services.shortview; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,8 +30,10 @@ public class ShortViewServiceImpl implements ShortViewService { private static final Log log = LogFactory .getLog(ShortViewServiceImpl.class); - private static final Map EMPTY_MAP = Collections.emptyMap(); - + /* + * TODO this should use a real connection to the ApplicationOntology to find + * the short view to use for each individiual in a given context. + */ private final FakeApplicationOntologyService faker; public ShortViewServiceImpl(FakeApplicationOntologyService faker) { @@ -64,6 +65,7 @@ public class ShortViewServiceImpl implements ShortViewService { return fps.renderTemplate(templateName, fullModelMap, vreq); } catch (TemplateParsingException e) { + log.error(e, e); return "

Can't parse the short view template '" + templateName + "' for " + individual.getName() + "

"; } catch (Exception e) { @@ -78,7 +80,7 @@ public class ShortViewServiceImpl implements ShortViewService { ShortViewContext svContext, VitroRequest vreq) { TemplateAndDataGetters tdg = fetchTemplateAndDataGetters(individual, svContext, vreq); - Map gotData = runDataGetters(tdg.getDataGetters()); + Map gotData = runDataGetters(tdg.getDataGetters(), individual); return new TemplateAndSupplementalDataImpl(tdg.getTemplateName(), gotData); } @@ -113,10 +115,12 @@ public class ShortViewServiceImpl implements ShortViewService { } /** Build a data map from the combined results of all data getters. */ - private Map runDataGetters(Set dataGetters) { + private Map runDataGetters(Set dataGetters, Individual individual) { + Map valueMap = new HashMap(); + valueMap.put("individualUri", individual.getURI()); Map gotData = new HashMap(); for (DataGetter dg : dataGetters) { - gotData.putAll(dg.getData(EMPTY_MAP)); + gotData.putAll(dg.getData(valueMap)); } return gotData; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceSetup.java index d140901e4..8b48e7aaf 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceSetup.java @@ -6,6 +6,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; +import edu.cornell.mannlib.vitro.webapp.services.shortview.FakeApplicationOntologyService.ShortViewConfigException; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; /** @@ -20,8 +21,16 @@ public class ShortViewServiceSetup implements ServletContextListener { ServletContext ctx = sce.getServletContext(); StartupStatus ss = StartupStatus.getBean(ctx); - ShortViewServiceImpl svs = new ShortViewServiceImpl( - new FakeApplicationOntologyService()); + FakeApplicationOntologyService faker; + try { + faker = new FakeApplicationOntologyService(ctx); + } catch (ShortViewConfigException e) { + ss.warning(this, "Failed to load the shortview_config.n3 file -- " + + e.getMessage(), e); + faker = new FakeApplicationOntologyService(); + } + + ShortViewServiceImpl svs = new ShortViewServiceImpl(faker); ctx.setAttribute(ATTRIBUTE_NAME, svs); ss.info(this, diff --git a/webapp/web/WEB-INF/resources/shortview_config.n3 b/webapp/web/WEB-INF/resources/shortview_config.n3 new file mode 100644 index 000000000..ace0ff7ad --- /dev/null +++ b/webapp/web/WEB-INF/resources/shortview_config.n3 @@ -0,0 +1,13 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +# +# Short View configuration +# +# This file allows a Vitro administrator to configure short view templates and data getters +# for individuals of particular classes in particular contexts. This is a transitional +# implementation, and should be replaced when the work on the Application and Display +# Ontology is complete. +# +# Find out how to use this file at +# https://sourceforge.net/apps/mediawiki/vivo/index.php?title=Using_Short_Views_in_Release_1.5 +#