diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java index 3c67fe17c..4ef527d1e 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java @@ -19,6 +19,7 @@ import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants; +import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective; import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualLocalNameMethod; import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualPlaceholderImageUrlMethod; import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualProfileUrlMethod; @@ -154,6 +155,7 @@ public class FreemarkerConfiguration extends Configuration { map.put("dump", new freemarker.ext.dump.DumpDirective()); map.put("dumpAll", new freemarker.ext.dump.DumpAllDirective()); map.put("help", new freemarker.ext.dump.HelpDirective()); + map.put("shortView", new IndividualShortViewDirective()); return map; } 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 new file mode 100644 index 000000000..3abb738ce --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java @@ -0,0 +1,74 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.services.shortview; + +import static edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.ShortViewContext.SEARCH; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; + +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter; + +/** + * TODO + * + * Get rid of this when the Application Ontology is implemented. + */ +public class FakeApplicationOntologyService { + private static final String FACULTY_MEMBER_CLASS_URI = "http://vivoweb.org/ontology/core#FacultyMember"; + + /** + * Return the template name and DataGetter instances associated with this + * class and this short view context. If none, return null. + */ + public TemplateAndDataGetters getShortViewProperties(String classUri, + String contextName) { +// if ((SEARCH.name().equals(contextName)) +// && (classUri.equals(FACULTY_MEMBER_CLASS_URI))) { +// return new TemplateAndDataGetters("view-search-faculty.ftl", new FakeFacultyDataGetter()); +// } else { + 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 static class FakeFacultyDataGetter implements DataGetter { + @Override + public Map getData(ServletContext context, + VitroRequest vreq, Map valueMap) { + Map map = new HashMap(); + Map extras = new HashMap(); + extras.put("departmentName", "Department of Redundancy Department"); + map.put("extra", extras); + return map; + } + + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewService.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewService.java new file mode 100644 index 000000000..5ba8ccf25 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewService.java @@ -0,0 +1,96 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.services.shortview; + +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; + +/** + * Define a service that will produce HTML snippets for short views on + * Individuals. + */ +public interface ShortViewService { + + /** + * Render the short view template that applies to this individual in this + * context. The data in the modelMap can be used to populate the template, + * along with any additional data returned by custom data getters. + * + * If there are any problems, return a dummy piece of text that includes the + * label of the individual. Never return null or empty string. + * + * This method should not be called from within an ongoing Freemarker + * process. In that case, use getShortViewInfo() instead. + */ + String renderShortView(Individual individual, ShortViewContext context, + Map modelMap, VitroRequest vreq); + + /** + * What template should be used to render the short view of this individual + * in this context? What data is available from custom data getters? + * + * Ask the Application Ontology for short view specifications on each of the + * most specific classes for this individual. If more than one such class + * has an applicable short view, the class with with the first URI + * (alphabetically) will be used. + */ + TemplateAndSupplementalData getShortViewInfo(Individual individual, + ShortViewContext svContext, VitroRequest vreq); + + /** + * The information associated with a particular short view. + */ + public interface TemplateAndSupplementalData { + /** + * The name of the template to be used in the short view. + * + * Either the custom view assigned to the individual and context, or the + * default view. Never empty or null, but it might refer to a template + * that can't be located. + */ + String getTemplateName(); + + /** + * The results of any custom data getters were associated with this + * individual in this short view context. + * + * May be empty, but never null. + */ + Map getSupplementalData(); + } + + /** + * The available contexts for short views. + */ + public enum ShortViewContext { + SEARCH("view-search-default.ftl"); + + private final String defaultTemplateName; + + ShortViewContext(String defaultTemplateName) { + this.defaultTemplateName = defaultTemplateName; + } + + public String getDefaultTemplateName() { + return defaultTemplateName; + } + + public static ShortViewContext fromString(String string) { + for (ShortViewContext c : ShortViewContext.values()) { + if (c.name().equalsIgnoreCase(string)) { + return c; + } + } + return null; + } + + public static String valueList() { + return StringUtils.join(ShortViewContext.values(), ", "); + } + } + +} 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 new file mode 100644 index 000000000..7fd3038fe --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceImpl.java @@ -0,0 +1,121 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +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; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.ServletContext; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.services.shortview.FakeApplicationOntologyService.TemplateAndDataGetters; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter; + +/** + * The basic implementation of ShortViewService + */ +public class ShortViewServiceImpl implements ShortViewService { + private static final Map EMPTY_MAP = Collections.emptyMap(); + + private final FakeApplicationOntologyService faker; + + public ShortViewServiceImpl(FakeApplicationOntologyService faker) { + this.faker = faker; + } + + @Override + public String renderShortView(Individual individual, + ShortViewContext context, Map modelMap, + VitroRequest vreq) { + + TemplateAndSupplementalData tsd = getShortViewInfo(individual, context, + vreq); + + // TODO Auto-generated method stub + throw new RuntimeException( + "ShortViewService.renderShortView() not implemented."); + } + + @Override + public TemplateAndSupplementalData getShortViewInfo(Individual individual, + ShortViewContext svContext, VitroRequest vreq) { + TemplateAndDataGetters tdg = fetchTemplateAndDataGetters(individual, + svContext); + Map gotData = runDataGetters(tdg.getDataGetters(), vreq); + return new TemplateAndSupplementalDataImpl(tdg.getTemplateName(), + gotData); + } + + /** Get most specific classes from Individual, sorted by alpha. */ + private SortedSet figureMostSpecificClassUris(Individual individual) { + SortedSet classUris = new TreeSet(); + List stmts = individual + .getObjectPropertyStatements(VitroVocabulary.MOST_SPECIFIC_TYPE); + for (ObjectPropertyStatement stmt : stmts) { + classUris.add(stmt.getObjectURI()); + } + return classUris; + } + + /** Find the template and data getters for this individual in this context. */ + private TemplateAndDataGetters fetchTemplateAndDataGetters( + Individual individual, ShortViewContext svContext) { + List classUris = new ArrayList(); + classUris.addAll(figureMostSpecificClassUris(individual)); + + for (String classUri : classUris) { + TemplateAndDataGetters tdg = faker.getShortViewProperties(classUri, + svContext.name()); + if (tdg != null) { + return tdg; + } + } + + // Didn't find one? Use the default values. + return new TemplateAndDataGetters(svContext.getDefaultTemplateName()); + } + + /** Build a data map from the combined results of all data getters. */ + private Map runDataGetters(Set dataGetters, + VitroRequest vreq) { + ServletContext ctx = vreq.getSession().getServletContext(); + + Map gotData = new HashMap(); + for (DataGetter dg : dataGetters) { + gotData.putAll(dg.getData(ctx, vreq, EMPTY_MAP)); + } + return gotData; + } + + private static class TemplateAndSupplementalDataImpl implements + TemplateAndSupplementalData { + private final String templateName; + private final Map customData; + + public TemplateAndSupplementalDataImpl(String templateName, + Map customData) { + this.templateName = templateName; + this.customData = customData; + } + + @Override + public String getTemplateName() { + return templateName; + } + + @Override + public Map getSupplementalData() { + return customData; + } + + } +} 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 new file mode 100644 index 000000000..d140901e4 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/shortview/ShortViewServiceSetup.java @@ -0,0 +1,39 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.services.shortview; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Set up the ShortViewService. + */ +public class ShortViewServiceSetup implements ServletContextListener { + private static final String ATTRIBUTE_NAME = ShortViewService.class + .getName(); + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + StartupStatus ss = StartupStatus.getBean(ctx); + + ShortViewServiceImpl svs = new ShortViewServiceImpl( + new FakeApplicationOntologyService()); + ctx.setAttribute(ATTRIBUTE_NAME, svs); + + ss.info(this, + "Started the Short View Service with a ShortViewServiceImpl"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + sce.getServletContext().removeAttribute(ATTRIBUTE_NAME); + } + + public static ShortViewService getService(ServletContext ctx) { + return (ShortViewService) ctx.getAttribute(ATTRIBUTE_NAME); + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/BaseTemplateDirectiveModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/BaseTemplateDirectiveModel.java index 41536e766..a1500a2c5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/BaseTemplateDirectiveModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/BaseTemplateDirectiveModel.java @@ -4,19 +4,17 @@ package edu.cornell.mannlib.vitro.webapp.web.directives; import java.io.IOException; import java.io.StringWriter; -import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import freemarker.core.Environment; +import freemarker.template.SimpleScalar; import freemarker.template.Template; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; -import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModelException; -import freemarker.template.utility.DeepUnwrap; public abstract class BaseTemplateDirectiveModel implements TemplateDirectiveModel { @@ -48,4 +46,41 @@ public abstract class BaseTemplateDirectiveModel implements TemplateDirectiveMod return template; } + // ---------------------------------------------------------------------- + // Convenience methods for parsing the parameter map + // ---------------------------------------------------------------------- + + /** Get the parameter, or throw an exception. */ + protected String getRequiredSimpleScalarParameter(Map params, + String name) throws TemplateModelException { + Object o = params.get(name); + if (o == null) { + throw new TemplateModelException("The '" + name + + "' parameter is required" + "."); + } + + if (!(o instanceof SimpleScalar)) { + throw new TemplateModelException("The '" + name + + "' parameter must be a string value."); + } + + return o.toString(); + } + + /** Get the parameter, or "null" if the parameter is not provided. */ + protected String getOptionalSimpleScalarParameter(Map params, + String name) throws TemplateModelException { + Object o = params.get(name); + if (o == null) { + return null; + } + + if (!(o instanceof SimpleScalar)) { + throw new TemplateModelException("The '" + name + + "' parameter must be a string value."); + } + + return o.toString(); + } + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/EmailDirective.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/EmailDirective.java index 58ca99ada..913e69782 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/EmailDirective.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/EmailDirective.java @@ -14,7 +14,6 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage; import freemarker.core.Environment; -import freemarker.template.SimpleScalar; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; @@ -60,21 +59,6 @@ public class EmailDirective extends BaseTemplateDirectiveModel { } } - private String getOptionalSimpleScalarParameter(Map params, - String name) throws TemplateModelException { - Object o = params.get(name); - if (o == null) { - return null; - } - - if (!(o instanceof SimpleScalar)) { - throw new TemplateModelException("The '" + name + "' parameter " - + "for the email directive must be a string value."); - } - - return o.toString(); - } - @Override public Map help(String name) { Map map = new LinkedHashMap(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/IndividualShortViewDirective.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/IndividualShortViewDirective.java new file mode 100644 index 000000000..981bafbf7 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/IndividualShortViewDirective.java @@ -0,0 +1,152 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.web.directives; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService; +import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewServiceSetup; +import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.ShortViewContext; +import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.TemplateAndSupplementalData; +import freemarker.core.Environment; +import freemarker.template.ObjectWrapper; +import freemarker.template.Template; +import freemarker.template.TemplateDirectiveBody; +import freemarker.template.TemplateException; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +/** + * Find the short-view template for the specified Individual in the specified + * context. Get any required data, and render the template to HTML. + */ +public class IndividualShortViewDirective extends BaseTemplateDirectiveModel { + private static final Log log = LogFactory + .getLog(IndividualShortViewDirective.class); + + @Override + public void execute(Environment env, Map params, TemplateModel[] loopVars, + TemplateDirectiveBody body) throws TemplateException, IOException { + // Get the Individual URI and check it. + String individualUri = getRequiredSimpleScalarParameter(params, "uri"); + Individual individual = getIndividual(individualUri); + if (individual == null) { + throw new TemplateModelException( + "Can't find individual for URI: \"" + individualUri + "\""); + } + + // Get the view context and check it. + String vcString = getRequiredSimpleScalarParameter(params, + "viewContext"); + ShortViewContext viewContext = ShortViewContext.fromString(vcString); + if (viewContext == null) { + throw new TemplateModelException( + "viewContext must be one of these: " + + ShortViewContext.valueList()); + } + + // Find the details of the short view and include it in the output. + renderShortView(individual, viewContext); + } + + private Individual getIndividual(String individualUri) { + Environment env = Environment.getCurrentEnvironment(); + HttpServletRequest request = (HttpServletRequest) env + .getCustomAttribute("request"); + VitroRequest vreq = new VitroRequest(request); + WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + IndividualDao iDao = wdf.getIndividualDao(); + return iDao.getIndividualByURI(individualUri); + } + + private void renderShortView(Individual individual, + ShortViewContext svContext) { + Environment env = Environment.getCurrentEnvironment(); + + ServletContext ctx = (ServletContext) env.getCustomAttribute("context"); + VitroRequest vreq = new VitroRequest( + (HttpServletRequest) env.getCustomAttribute("request")); + ShortViewService svs = ShortViewServiceSetup.getService(ctx); + TemplateAndSupplementalData svInfo = svs.getShortViewInfo(individual, + svContext, vreq); + + ObjectWrapper objectWrapper = env.getConfiguration().getObjectWrapper(); + + for (String name : svInfo.getSupplementalData().keySet()) { + Object value = svInfo.getSupplementalData().get(name); + try { + env.setVariable(name, objectWrapper.wrap(value)); + } catch (TemplateModelException e) { + log.error("Failed to wrap supplemental data '" + name + "' = '" + + value + "'", e); + } + } + + try { + Template template = env.getTemplateForInclusion( + svInfo.getTemplateName(), null, true); + env.include(template); + } catch (IOException e) { + log.error("Could not load template '" + svInfo.getTemplateName() + + "'", e); + renderErrorMessage(individual); + } catch (TemplateException e) { + log.error("Could not process template '" + svInfo.getTemplateName() + + "'", e); + renderErrorMessage(individual); + } + } + + /** If there is a problem rendering the custom view, do this instead. */ + private void renderErrorMessage(Individual individual) { + Environment env = Environment.getCurrentEnvironment(); + try { + env.getOut().append( + "Can't process the custom short view for " + + individual.getName() + ""); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + @Override + public Map help(String name) { + Map map = new LinkedHashMap(); + + map.put("effect", "Find the short view that applies " + + "to this individual in this context -- a template and " + + "optional DataGetters. " + + "Execute the DataGetters and render the template."); + + map.put("comments", + "The path should be an absolute path, starting with \"/\"."); + + Map params = new HashMap(); + params.put("uri", "The URI of the individual being displayed."); + params.put("viewContext", + "One of these: " + ShortViewContext.valueList()); + map.put("parameters", params); + + List examples = new ArrayList(); + examples.add("<img src=\"<@shortView uri=individual.uri viewContext=\"SEARCH\" />\" />"); + map.put("examples", examples); + + return map; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/BaseIndividualSearchResult.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/BaseIndividualSearchResult.java index 1356a8fa3..7fa0fce6d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/BaseIndividualSearchResult.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/BaseIndividualSearchResult.java @@ -45,6 +45,10 @@ public abstract class BaseIndividualSearchResult extends BaseTemplateModel { /* Template properties */ + public String getUri() { + return individual.getURI(); + } + public String getProfileUrl() { return UrlBuilder.getIndividualProfileUrl(individual, vreq); } @@ -59,10 +63,6 @@ public abstract class BaseIndividualSearchResult extends BaseTemplateModel { return types.values(); } - public String getSearchView() { - return getView(ClassView.SEARCH); - } - public String getSnippet() { return individual.getSearchSnippet(); } diff --git a/webapp/web/WEB-INF/resources/startup_listeners.txt b/webapp/web/WEB-INF/resources/startup_listeners.txt index eb004d336..7396c2ea7 100644 --- a/webapp/web/WEB-INF/resources/startup_listeners.txt +++ b/webapp/web/WEB-INF/resources/startup_listeners.txt @@ -48,6 +48,8 @@ edu.cornell.mannlib.vitro.webapp.auth.policy.RootUserPolicy$Setup edu.cornell.mannlib.vitro.webapp.auth.policy.RestrictHomeMenuItemEditingPolicy$Setup +edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewServiceSetup + # The Solr index uses a "public" permission, so the PropertyRestrictionPolicyHelper # and the PermissionRegistry must already be set up. edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup diff --git a/webapp/web/templates/freemarker/body/search/search-pagedResults.ftl b/webapp/web/templates/freemarker/body/search/search-pagedResults.ftl index 5401b0940..e3deedc13 100644 --- a/webapp/web/templates/freemarker/body/search/search-pagedResults.ftl +++ b/webapp/web/templates/freemarker/body/search/search-pagedResults.ftl @@ -43,7 +43,7 @@
    <#list individuals as individual>
  • - <#include "${individual.searchView}"> + <@shortView uri=individual.uri viewContext="search" />