diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e71b19cbd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Windows, Linux] + - Browser [e.g. chrome, safari] + - Tomcat version [e.g. 8, 9] + - VIVO version [e.g. 1.11.0, 1.12.0] + - Apache Solr or ElasticSearch version + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..aa9d3a4aa --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Build + +on: [ push, pull_request, workflow_dispatch ] + +jobs: + build: + runs-on: ubuntu-latest + + env: + MAVEN_OPTS: -Xmx1024M + + steps: + - uses: actions/checkout@v2 + + - name: Maven Cache + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-cache-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-cache-m2- + + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Maven Build + run: mvn clean install diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce9b09b50..000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: java -dist: trusty -sudo: false - -jdk: - - openjdk8 - - oraclejdk8 - -env: - # Give Maven 1GB of memory to work with - - MAVEN_OPTS=-Xmx1024M - -cache: - directories: - - .autoconf - - $HOME/.m2 - -install: - - "echo 'Skipping install stage, dependencies will be downloaded during build and test stages.'" - -script: - - "mvn clean package -Dmaven.test.skip=false" diff --git a/README.md b/README.md index 1125398ec..d65138c6a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # What is Vitro? -[![Build Status](https://travis-ci.org/vivo-project/Vitro.png?branch=develop)](https://travis-ci.org/vivo-project/Vitro) +[![Build](https://github.com/vivo-project/Vitro/workflows/Build/badge.svg)](https://github.com/vivo-project/Vitro/actions?query=workflow%3ABuild) Vitro is a general-purpose web-based ontology and instance editor with customizable public browsing. diff --git a/api/pom.xml b/api/pom.xml index c486a2ee1..bf032b4e4 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-api - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT jar org.vivoweb vitro-project - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. @@ -66,7 +66,7 @@ org.vivoweb vitro-dependencies - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom @@ -85,7 +85,7 @@ junit junit - 4.11 + 4.13.1 test diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java index 03798f6c4..a8fd8aa2f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java @@ -62,6 +62,7 @@ public class ApplicationSetup implements ServletContextListener { private void locateApplicationConfigFile() { Path path = this.vitroHomeDir.getPath().resolve(APPLICATION_SETUP_PATH); + if (!Files.exists(path)) { throw new IllegalStateException("'" + path + "' does not exist."); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java index e72436152..816bf7d51 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java @@ -37,7 +37,14 @@ public class RootUserPolicy implements PolicyIface { private static final Log log = LogFactory.getLog(RootUserPolicy.class); private static final String PROPERTY_ROOT_USER_EMAIL = "rootUser.emailAddress"; + /* + * UQAM Add-Feature For parameterization of rootUser + */ + private static final String PROPERTY_ROOT_USER_PASSWORD = "rootUser.password"; + private static final String PROPERTY_ROOT_USER_PASSWORD_CHANGE_REQUIRED = "rootUser.passwordChangeRequired"; + private static final String ROOT_USER_INITIAL_PASSWORD = "rootPassword"; + private static final String ROOT_USER_INITIAL_PASSWORD_CHANGE_REQUIRED = "true"; /** * This is the entire policy. If you are a root user, you are authorized. @@ -150,10 +157,12 @@ public class RootUserPolicy implements PolicyIface { ua.setEmailAddress(configuredRootUser); ua.setFirstName("root"); ua.setLastName("user"); + // UQAM Add-Feature using getRootPasswordFromConfig() ua.setArgon2Password(Authenticator.applyArgon2iEncoding( - ROOT_USER_INITIAL_PASSWORD)); + getRootPasswordFromConfig())); ua.setMd5Password(""); - ua.setPasswordChangeRequired(true); + // UQAM Add-Feature using getRootPasswdChangeRequiredFromConfig() + ua.setPasswordChangeRequired(getRootPasswdChangeRequiredFromConfig().booleanValue()); ua.setStatus(Status.ACTIVE); ua.setRootUser(true); @@ -191,7 +200,31 @@ public class RootUserPolicy implements PolicyIface { ss.warning(this, "For security, " + "it is best to delete unneeded root user accounts."); } + /* + * UQAM Add-Feature + * Add for getting rootUser.password property value from runtime.properties + */ + private String getRootPasswordFromConfig() { + String passwd = ConfigurationProperties.getBean(ctx).getProperty( + PROPERTY_ROOT_USER_PASSWORD); + if (passwd == null) { + passwd = ROOT_USER_INITIAL_PASSWORD; + } + return passwd; + } + /* + * UQAM Add-Feature + * Add for getting rootUser.passwordChangeRequired property value from runtime.properties + */ + private Boolean getRootPasswdChangeRequiredFromConfig() { + String passwdCR = ConfigurationProperties.getBean(ctx).getProperty( + PROPERTY_ROOT_USER_PASSWORD_CHANGE_REQUIRED); + if (passwdCR == null) { + passwdCR = ROOT_USER_INITIAL_PASSWORD_CHANGE_REQUIRED; + } + return new Boolean(passwdCR); + } @Override public void contextDestroyed(ServletContextEvent sce) { // Nothing to destroy diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ApplicationBean.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ApplicationBean.java index c083d44f3..563b1c9b4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ApplicationBean.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ApplicationBean.java @@ -20,6 +20,7 @@ public class ApplicationBean { public final static int VIVO_SEARCHBOX_SIZE = 20; private final static String DEFAULT_APPLICATION_NAME = "Vitro"; + private final static String DEFAULT_APPLICATION_AVAILABLE_LANGS_FILE = "available-langs"; private final static String DEFAULT_ROOT_LOGOTYPE_IMAGE = ""; private final static int DEFAULT_ROOT_LOGOTYPE_WIDTH = 0; private final static int DEFAULT_ROOT_LOGOTYPE_HEIGHT = 0; @@ -33,6 +34,7 @@ public class ApplicationBean { private boolean initialized = false; private String sessionIdStr = null; private String applicationName = DEFAULT_APPLICATION_NAME; + private String availableLangsFile = DEFAULT_APPLICATION_AVAILABLE_LANGS_FILE; private String rootLogotypeImage = DEFAULT_ROOT_LOGOTYPE_IMAGE; private int rootLogotypeWidth = DEFAULT_ROOT_LOGOTYPE_WIDTH; @@ -52,6 +54,7 @@ public class ApplicationBean { output += " initialized from DB: [" + initialized + "]\n"; output += " session id: [" + sessionIdStr + "]\n"; output += " application name: [" + applicationName + "]\n"; + output += " available langs file: [" + availableLangsFile + "]\n"; output += " root logotype image: [" + rootLogotypeImage + "]\n"; output += " root logotype width: [" + rootLogotypeWidth + "]\n"; output += " root logotype height: [" + rootLogotypeHeight + "]\n"; @@ -177,6 +180,10 @@ public class ApplicationBean { return ""; } + public String getAvailableLangsFile() { + return availableLangsFile; + } + /** * Directory to find the images. Subdirectories include css, jsp and site_icons. * Example: "themes/enhanced/" diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java index 49fe5e81d..ee27feef3 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java @@ -109,7 +109,7 @@ public abstract class ConfigurationProperties { throw new NullPointerException("bean may not be null."); } context.setAttribute(ATTRIBUTE_NAME, bean); - log.info(bean); + log.debug(bean); } /** Package access, so unit tests can call it. */ diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java index 773f43e87..54d0ac90d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java @@ -73,7 +73,7 @@ public class ConfigurationPropertiesSetup implements ServletContextListener { vitroHomeDir, vitroHomeDirConfig); File runtimePropertiesFile = locateRuntimePropertiesFile( - vitroHomeDir, vitroHomeDirConfig, ss); + vitroHomeDir, vitroHomeDirConfig, ss); stream = new FileInputStream(runtimePropertiesFile); Map preempts = createPreemptiveProperties( @@ -118,7 +118,6 @@ public class ConfigurationPropertiesSetup implements ServletContextListener { } } - private File locateRuntimePropertiesFile(File vitroHomeDir, File vitroHomeDirConfig, StartupStatus ss) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/admin/SparqlQueryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/admin/SparqlQueryController.java index 096263dab..5369d2626 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/admin/SparqlQueryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/admin/SparqlQueryController.java @@ -17,6 +17,8 @@ import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import edu.cornell.mannlib.vitro.webapp.i18n.I18n; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -75,8 +77,8 @@ public class SparqlQueryController extends FreemarkerHttpServlet { private static final String[] SAMPLE_QUERY = { // "", // "#", // - "# This example query gets 20 geographic locations", // - "# and (if available) their labels", // + "i18n:sparql_query_description_0", // + "i18n:sparql_query_description_1", // "#", // "SELECT ?geoLocation ?label", // "WHERE", // @@ -193,9 +195,11 @@ public class SparqlQueryController extends FreemarkerHttpServlet { @Override protected ResponseValues processRequest(VitroRequest vreq) throws Exception { + I18nBundle i18n = I18n.bundle(vreq); + Map bodyMap = new HashMap<>(); - bodyMap.put("sampleQuery", buildSampleQuery(buildPrefixList(vreq))); - bodyMap.put("title", "SPARQL Query"); + bodyMap.put("sampleQuery", buildSampleQuery(i18n, buildPrefixList(vreq))); + bodyMap.put("title", i18n.text("sparql_query_title")); bodyMap.put("submitUrl", UrlBuilder.getUrl("admin/sparqlquery")); return new TemplateResponseValues(TEMPLATE_NAME, bodyMap); } @@ -222,7 +226,7 @@ public class SparqlQueryController extends FreemarkerHttpServlet { return prefixList; } - private String buildSampleQuery(List prefixList) { + private String buildSampleQuery(I18nBundle i18n, List prefixList) { StringWriter sw = new StringWriter(); PrintWriter writer = new PrintWriter(sw); @@ -230,6 +234,10 @@ public class SparqlQueryController extends FreemarkerHttpServlet { writer.println(p); } for (String line : SAMPLE_QUERY) { + if (line.startsWith("i18n:")) { + // Get i18n translation + line = i18n.text(line.substring("i18n:".length())); + } writer.println(line); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java index ffc18f805..65b8a2310 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java @@ -108,7 +108,9 @@ public class LoginRedirector { throws IOException { try { DisplayMessage.setMessage(request, assembleWelcomeMessage()); - response.sendRedirect(getRedirectionUriForLoggedInUser()); + String redirectUrl = getRedirectionUriForLoggedInUser(); + log.debug("Sending redirect to path: " + redirectUrl); + response.sendRedirect(redirectUrl); } catch (IOException e) { log.debug("Problem with re-direction", e); response.sendRedirect(getApplicationHomePageUrl()); @@ -175,21 +177,13 @@ public class LoginRedirector { } } - /** - * The application home page can be overridden by an attribute in the - * ServletContext. Further, it can either be an absolute URL, or it can be - * relative to the application. Weird. - */ private String getApplicationHomePageUrl() { - String contextRedirect = (String) session.getServletContext() - .getAttribute("postLoginRequest"); - if (contextRedirect != null) { - if (contextRedirect.indexOf(":") == -1) { - return request.getContextPath() + contextRedirect; - } else { - return contextRedirect; - } + String contextPath = request.getContextPath(); + if (contextPath.equals("")) { + return "/"; + } + else { + return contextPath; } - return request.getContextPath(); } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/Classes2ClassesRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/Classes2ClassesRetryController.java index d4417e6a3..d51d90452 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/Classes2ClassesRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/Classes2ClassesRetryController.java @@ -43,7 +43,7 @@ public class Classes2ClassesRetryController extends BaseEditController { action = epo.getAction(); } - VClassDao vcDao = ModelAccess.on(getServletContext()).getWebappDaoFactory().getVClassDao(); + VClassDao vcDao = ModelAccess.on(request).getWebappDaoFactory().getVClassDao(); epo.setDataAccessObject(vcDao); Classes2Classes objectForEditing = new Classes2Classes(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java index 5c1535489..82b618665 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java @@ -51,8 +51,7 @@ public class ClassgroupRetryController extends BaseEditController { action = epo.getAction(); } - VClassGroupDao cgDao = ModelAccess.on( - getServletContext()).getWebappDaoFactory().getVClassGroupDao(); + VClassGroupDao cgDao = ModelAccess.on(request).getWebappDaoFactory().getVClassGroupDao(); epo.setDataAccessObject(cgDao); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java index c4b04c08b..70d47a80a 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java @@ -50,11 +50,12 @@ public class DatapropRetryController extends BaseEditController { //create an EditProcessObject for this and put it in the session EditProcessObject epo = super.createEpo(request); + epo.setImplementationClass(DataProperty.class); epo.setBeanClass(DataProperty.class); VitroRequest vreq = new VitroRequest(request); - WebappDaoFactory wadf = ModelAccess.on(getServletContext()).getWebappDaoFactory(); + WebappDaoFactory wadf = ModelAccess.on(vreq).getWebappDaoFactory(); DatatypeDao dDao = wadf.getDatatypeDao(); DataPropertyDao dpDao = wadf.getDataPropertyDao(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java index 941c6e7f6..9f4d27092 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java @@ -106,7 +106,7 @@ public class FauxPropertyRetryController extends BaseEditController { this.epo = epo; - this.fpDao = ModelAccess.on(ctx).getWebappDaoFactory() + this.fpDao = ModelAccess.on(req).getWebappDaoFactory() .getFauxPropertyDao(); } @@ -114,6 +114,8 @@ public class FauxPropertyRetryController extends BaseEditController { void populate() { epo.setDataAccessObject(fpDao); epo.setAction(determineAction()); + epo.setImplementationClass(FauxProperty.class); + epo.setBeanClass(FauxProperty.class); if (epo.getUseRecycledBean()) { beanForEditing = (FauxProperty) epo.getNewBean(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/NamespacePrefixOperationController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/NamespacePrefixOperationController.java index 356569c54..2223c69e6 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/NamespacePrefixOperationController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/NamespacePrefixOperationController.java @@ -71,7 +71,7 @@ public class NamespacePrefixOperationController extends BaseEditController { if (request.getParameter("_cancel") == null) { - OntModel ontModel = ModelAccess.on(getServletContext()).getOntModel(); + OntModel ontModel = ModelAccess.on(req).getOntModel(); String namespaceStr = request.getParameter("namespace"); String prefixStr = request.getParameter("prefix"); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java index d84b15db5..3ae22e5c1 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java @@ -51,7 +51,7 @@ public class PropertyGroupRetryController extends BaseEditController { } PropertyGroupDao pgDao = ModelAccess.on( - getServletContext()).getWebappDaoFactory().getPropertyGroupDao(); + req).getWebappDaoFactory().getPropertyGroupDao(); epo.setDataAccessObject(pgDao); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java index 5e35fb369..909ebeff9 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java @@ -53,6 +53,7 @@ public class PropertyRetryController extends BaseEditController { /*for testing*/ ObjectProperty testMask = new ObjectProperty(); + epo.setImplementationClass(ObjectProperty.class); epo.setBeanClass(ObjectProperty.class); epo.setBeanMask(testMask); @@ -64,7 +65,7 @@ public class PropertyRetryController extends BaseEditController { action = epo.getAction(); } - ObjectPropertyDao propDao = ModelAccess.on(getServletContext()).getWebappDaoFactory().getObjectPropertyDao(); + ObjectPropertyDao propDao = ModelAccess.on(req).getWebappDaoFactory().getObjectPropertyDao(); epo.setDataAccessObject(propDao); OntologyDao ontDao = request.getUnfilteredWebappDaoFactory().getOntologyDao(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/RestrictionOperationController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/RestrictionOperationController.java index f704b5b39..26956be74 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/RestrictionOperationController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/RestrictionOperationController.java @@ -48,7 +48,7 @@ public class RestrictionOperationController extends BaseEditController { String defaultLandingPage = getDefaultLandingPage(request); try { - OntModel ontModel = ModelAccess.on(getServletContext()) + OntModel ontModel = ModelAccess.on(req) .getOntModel(TBOX_ASSERTIONS); HashMap epoHash = null; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java index 340a846e4..a9b018670 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java @@ -48,7 +48,7 @@ public class VclassEditController extends BaseEditController { EditProcessObject epo = super.createEpo(request, FORCE_NEW); request.setAttribute("epoKey", epo.getKey()); - VClassDao vcwDao = ModelAccess.on(getServletContext()).getWebappDaoFactory(ASSERTIONS_ONLY).getVClassDao(); + VClassDao vcwDao = ModelAccess.on(req).getWebappDaoFactory(ASSERTIONS_ONLY).getVClassDao(); VClass vcl = (VClass)vcwDao.getVClassByURI(request.getParameter("uri")); if (vcl == null) { @@ -152,8 +152,8 @@ public class VclassEditController extends BaseEditController { request.setAttribute("formSelect",formSelect); // if supported, we want to show only the asserted superclasses and subclasses. - VClassDao vcDao = ModelAccess.on(getServletContext()).getWebappDaoFactory(ASSERTIONS_ONLY).getVClassDao(); - VClassDao displayVcDao = ModelAccess.on(getServletContext()).getWebappDaoFactory().getVClassDao(); + VClassDao vcDao = ModelAccess.on(req).getWebappDaoFactory(ASSERTIONS_ONLY).getVClassDao(); + VClassDao displayVcDao = ModelAccess.on(req).getWebappDaoFactory().getVClassDao(); List superVClasses = getVClassesForURIList( vcDao.getSuperClassURIs(vcl.getURI(),false), displayVcDao); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java index ee00bc64e..326b4fb1d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java @@ -66,11 +66,10 @@ public class VclassRetryController extends BaseEditController { action = epo.getAction(); } - WebappDaoFactory wadf = ModelAccess.on(getServletContext()).getWebappDaoFactory(); + WebappDaoFactory wadf = ModelAccess.on(req).getWebappDaoFactory(); VClassDao vcwDao = wadf.getVClassDao(); epo.setDataAccessObject(vcwDao); - VClassGroupDao cgDao = wadf.getVClassGroupDao(); OntologyDao oDao = wadf.getOntologyDao(); VClass vclassForEditing = null; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/NamespacesListingController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/NamespacesListingController.java index 05acd18ad..79bc5f071 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/NamespacesListingController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/NamespacesListingController.java @@ -37,7 +37,7 @@ public class NamespacesListingController extends BaseEditController { VitroRequest vrequest = new VitroRequest(request); - OntModel ontModel = ModelAccess.on(getServletContext()).getOntModel(); + OntModel ontModel = ModelAccess.on(vrequest).getOntModel(); ArrayList results = new ArrayList(); request.setAttribute("results",results); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/RestrictionsListingController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/RestrictionsListingController.java index 02b176969..746185621 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/RestrictionsListingController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/listing/jena/RestrictionsListingController.java @@ -54,7 +54,7 @@ public class RestrictionsListingController extends BaseEditController { epo = super.createEpo(request); - OntModel ontModel = ModelAccess.on(getServletContext()).getOntModel(); + OntModel ontModel = ModelAccess.on(vrequest).getOntModel(); ObjectPropertyDao opDao = vrequest.getUnfilteredWebappDaoFactory().getObjectPropertyDao(); VClassDao vcDao = vrequest.getUnfilteredWebappDaoFactory().getVClassDao(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BrowseController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BrowseController.java index 0ee20f2fe..fcf655e36 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BrowseController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BrowseController.java @@ -3,6 +3,8 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,6 +44,8 @@ public class BrowseController extends FreemarkerHttpServlet { List groups = null; VClassGroupsForRequest vcgc = VClassGroupCache.getVClassGroups(vreq); groups =vcgc.getGroups(); + Collections.sort(groups, publicNameComparator); +// sortGroupListByPublicName(groups); List vcgroups = new ArrayList(groups.size()); for (VClassGroup group : groups) { vcgroups.add(new VClassGroupTemplateModel(group)); @@ -50,4 +54,35 @@ public class BrowseController extends FreemarkerHttpServlet { return new TemplateResponseValues(templateName, body); } + public Comparator publicNameComparator = new Comparator() { + + public int compare(VClassGroup s1, VClassGroup s2) { + String groupName1 = s1.getPublicName().toUpperCase(); + String groupName2 = s2.getPublicName().toUpperCase(); + + //ascending order + return groupName1.compareTo(groupName2); + + //descending order + //return groupName2.compareTo(groupName1); + }}; + +// +// public void sortGroupListByPublicName(List groupList) { +// groupList.sort(new Comparator() { +// public int compare(VClassGroup first, VClassGroup second) { +// if (first != null) { +// if (second != null) { +// return (first.getDisplayRank() - second.getDisplayRank()); +// } else { +// log.error("error--2nd VClassGroup is null in VClassGroupDao.getGroupList().compare()"); +// } +// } else { +// log.error("error--1st VClassGroup is null in VClassGroupDao.getGroupList().compare()"); +// } +// return 0; +// } +// }); +// } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeleteIndividualController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeleteIndividualController.java index c6939fb77..f107531dc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeleteIndividualController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeleteIndividualController.java @@ -40,161 +40,160 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; @WebServlet(name="DeleteIndividualController",urlPatterns="/deleteIndividualController") public class DeleteIndividualController extends FreemarkerHttpServlet{ - private static final Log log = LogFactory.getLog(DeleteIndividualController.class); + private static final Log log = LogFactory.getLog(DeleteIndividualController.class); private static final boolean BEGIN = true; private static final boolean END = !BEGIN; - private static String TYPE_QUERY_START = "" + private static String TYPE_QUERY = "" + "PREFIX vitro: " + "SELECT ?type " + "WHERE" - + "{ <"; - private static String TYPE_QUERY_END = "> vitro:mostSpecificType ?type ." + + "{ ?individualURI vitro:mostSpecificType ?type ." + "}"; - private static String queryForDeleteQuery = + private static String queryForDeleteQuery = "PREFIX display: <" + DisplayVocabulary.DISPLAY_NS +"> \n" + "SELECT ?deleteQueryText WHERE { ?associatedURI display:hasDeleteQuery ?deleteQueryText }"; - private static final String DEFAULT_DELETE_QUERY_TEXT = "DESCRIBE ?individualURI"; + private static final String DEFAULT_DELETE_QUERY_TEXT = "DESCRIBE ?individualURI"; + + @Override + protected AuthorizationRequest requiredActions(VitroRequest vreq) { + return SimplePermission.DO_FRONT_END_EDITING.ACTION; + } - @Override - protected AuthorizationRequest requiredActions(VitroRequest vreq) { - return SimplePermission.DO_FRONT_END_EDITING.ACTION; - } + protected ResponseValues processRequest(VitroRequest vreq) { + String errorMessage = handleErrors(vreq); + if (!errorMessage.isEmpty()) { + return prepareErrorMessage(errorMessage); + } + String individualUri = vreq.getParameter("individualUri"); + String type = getObjectMostSpecificType(individualUri, vreq); + Model displayModel = vreq.getDisplayModel(); + + String delteQueryText = getDeleteQueryForType(type, displayModel); + byte[] toRemove = getIndividualsToDelete(individualUri, delteQueryText, vreq); + if (toRemove.length > 0) { + deleteIndividuals(toRemove, vreq); + } + String redirectUrl = getRedirectUrl(vreq); + + return new RedirectResponseValues(redirectUrl, HttpServletResponse.SC_SEE_OTHER); + } - protected ResponseValues processRequest(VitroRequest vreq) { - String errorMessage = handleErrors(vreq); - if (!errorMessage.isEmpty()) { - return prepareErrorMessage(errorMessage); - } - String individualUri = vreq.getParameter("individualUri"); - String type = getObjectMostSpecificType(individualUri, vreq); - Model displayModel = vreq.getDisplayModel(); - - String delteQueryText = getDeleteQueryForType(type, displayModel); - byte[] toRemove = getIndividualsToDelete(individualUri, delteQueryText, vreq); - if (toRemove.length > 0) { - deleteIndividuals(toRemove,vreq); - } - String redirectUrl = getRedirectUrl(vreq); - - return new RedirectResponseValues(redirectUrl, HttpServletResponse.SC_SEE_OTHER); - } - - private String getRedirectUrl(VitroRequest vreq) { - String redirectUrl = vreq.getParameter("redirectUrl"); - if (redirectUrl != null) { - return redirectUrl; - } - return "/"; - } + private String getRedirectUrl(VitroRequest vreq) { + String redirectUrl = vreq.getParameter("redirectUrl"); + if (redirectUrl != null) { + return redirectUrl; + } + return "/"; + } - private TemplateResponseValues prepareErrorMessage(String errorMessage) { - HashMap map = new HashMap(); - map.put("errorMessage", errorMessage); - return new TemplateResponseValues("error-message.ftl", map); - } + private TemplateResponseValues prepareErrorMessage(String errorMessage) { + HashMap map = new HashMap(); + map.put("errorMessage", errorMessage); + return new TemplateResponseValues("error-message.ftl", map); + } - private String handleErrors(VitroRequest vreq) { - String uri = vreq.getParameter("individualUri"); - if ( uri == null) { - return "Individual uri is null. No object to delete."; - } - if (uri.contains(">")) { - return "Individual uri shouldn't contain >"; - } - return ""; - } - - private static String getDeleteQueryForType(String typeURI,Model displayModel) { - - String deleteQueryText = DEFAULT_DELETE_QUERY_TEXT; - - Query queryForTypeSpecificDeleteQuery = QueryFactory.create(queryForDeleteQuery); + private String handleErrors(VitroRequest vreq) { + String uri = vreq.getParameter("individualUri"); + if (uri == null) { + return "Individual uri is null. No object to delete."; + } + if (uri.contains(">")) { + return "Individual uri shouldn't contain >"; + } + return ""; + } + private static String getDeleteQueryForType(String typeURI, Model displayModel) { + String deleteQueryText = DEFAULT_DELETE_QUERY_TEXT; + Query queryForTypeSpecificDeleteQuery = QueryFactory.create(queryForDeleteQuery); QuerySolutionMap initialBindings = new QuerySolutionMap(); - initialBindings.add("associatedURI", ResourceFactory.createResource( typeURI )); - + initialBindings.add("associatedURI", ResourceFactory.createResource(typeURI)); displayModel.enterCriticalSection(Lock.READ); - try{ - QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery,displayModel,initialBindings ); - try{ - ResultSet results = qexec.execSelect(); - while (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - deleteQueryText = solution.get("deleteQueryText").toString(); - } - }finally{ qexec.close(); } - }finally{ displayModel.leaveCriticalSection(); } - + try { + QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery, displayModel, initialBindings); + try { + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + deleteQueryText = solution.get("deleteQueryText").toString(); + } + } finally { + qexec.close(); + } + } finally { + displayModel.leaveCriticalSection(); + } + if (!deleteQueryText.equals(DEFAULT_DELETE_QUERY_TEXT)) { - log.debug("For " + typeURI + " found delete query \n" + deleteQueryText); + log.debug("For " + typeURI + " found delete query \n" + deleteQueryText); } else { - log.debug("For " + typeURI + " delete query not found. Using defalut query \n" + deleteQueryText); + log.debug("For " + typeURI + " delete query not found. Using defalut query \n" + deleteQueryText); } return deleteQueryText; - } + } private String getObjectMostSpecificType(String individualURI, VitroRequest vreq) { String type = ""; try { - ResultSet results = QueryUtils.getLanguageNeutralQueryResults(makeTypeQuery(individualURI), vreq); - while (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - type = solution.get("type").toString(); - log.debug(type); - } + Query typeQuery = QueryFactory.create(TYPE_QUERY); + QuerySolutionMap bindings = new QuerySolutionMap(); + bindings.add("individualURI", ResourceFactory.createResource(individualURI)); + Model ontModel = vreq.getJenaOntModel(); + QueryExecution qexec = QueryExecutionFactory.create(typeQuery, ontModel, bindings); + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + type = solution.get("type").toString(); + log.debug(type); + } } catch (Exception e) { - log.error("Failed to get type for individual URI " + individualURI); - log.error(e, e); + log.error("Failed to get type for individual URI " + individualURI); + log.error(e, e); } - return type; + return type; } - private byte[] getIndividualsToDelete(String targetIndividual, String deleteQuery,VitroRequest vreq) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - Query queryForTypeSpecificDeleteQuery = QueryFactory.create(deleteQuery); - QuerySolutionMap initialBindings = new QuerySolutionMap(); - initialBindings.add("individualURI", ResourceFactory.createResource( targetIndividual )); - Model ontModel = vreq.getJenaOntModel(); - QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery,ontModel,initialBindings ); + private byte[] getIndividualsToDelete(String targetIndividual, String deleteQuery, VitroRequest vreq) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + Query queryForTypeSpecificDeleteQuery = QueryFactory.create(deleteQuery); + QuerySolutionMap bindings = new QuerySolutionMap(); + bindings.add("individualURI", ResourceFactory.createResource(targetIndividual)); + Model ontModel = vreq.getJenaOntModel(); + QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery, ontModel, bindings); Model results = qexec.execDescribe(); - results.write(out,"N3"); - + results.write(out, "N3"); + } catch (Exception e) { - log.error("Query raised an error \n" + deleteQuery); - log.error(e, e); + log.error("Query raised an error \n" + deleteQuery); + log.error(e, e); } return out.toByteArray(); } - private String makeTypeQuery(String objectURI) { - return TYPE_QUERY_START + objectURI + TYPE_QUERY_END; - } - - private void deleteIndividuals(byte[] toRemove, VitroRequest vreq) { - String removingString = new String(toRemove, StandardCharsets.UTF_8); - RDFService rdfService = vreq.getRDFService(); + private void deleteIndividuals(byte[] toRemove, VitroRequest vreq) { + String removingString = new String(toRemove, StandardCharsets.UTF_8); + RDFService rdfService = vreq.getRDFService(); ChangeSet cs = makeChangeSet(rdfService); - InputStream in = new ByteArrayInputStream(toRemove); + InputStream in = new ByteArrayInputStream(toRemove); cs.addRemoval(in, RDFServiceUtils.getSerializationFormatFromJenaString("N3"), ModelNames.ABOX_ASSERTIONS); try { - rdfService.changeSetUpdate(cs); + rdfService.changeSetUpdate(cs); } catch (RDFServiceException e) { - log.error("Got error while removing\n" + removingString); - throw new RuntimeException(e); + log.error("Got error while removing\n" + removingString); + throw new RuntimeException(e); } - } + } private ChangeSet makeChangeSet(RDFService rdfService) { ChangeSet cs = rdfService.manufactureChangeSet(); cs.addPreChangeEvent(new BulkUpdateEvent(null, BEGIN)); cs.addPostChangeEvent(new BulkUpdateEvent(null, END)); return cs; -} - + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeletePropertyController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeletePropertyController.java index c62a2c87a..18fb4f39f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeletePropertyController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/DeletePropertyController.java @@ -10,6 +10,9 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.ResourceFactory; + import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest; import edu.cornell.mannlib.vitro.webapp.beans.DataProperty; @@ -25,6 +28,8 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationUti import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.N3EditUtils; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.web.URLEncoder; + + /* * Custom deletion controller to which deletion requests from default property form are sent. May be replaced * later with additional features in process rdf form controller or alternative location. @@ -61,14 +66,14 @@ public class DeletePropertyController extends FreemarkerHttpServlet { private String getRedirectUrl(VitroRequest vreq) { - // TODO Auto-generated method stub + // TODO Auto-generated method stub String subjectUri = EditConfigurationUtils.getSubjectUri(vreq); String predicateUri = EditConfigurationUtils.getPredicateUri(vreq); - int hashIndex = predicateUri.lastIndexOf("#"); - String localName = predicateUri.substring(hashIndex + 1); + Property prop = ResourceFactory.createProperty(predicateUri); + String localName = prop.getLocalName(); String redirectUrl = "/entity?uri=" + URLEncoder.encode(subjectUri); - return redirectUrl + "#" + URLEncoder.encode(localName); - } + return redirectUrl + "#" + URLEncoder.encode(localName); + } private String handleErrors(VitroRequest vreq) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java index bc7766e83..50f54e2e0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java @@ -8,6 +8,7 @@ import static javax.mail.Message.RecipientType.TO; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.Charset; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -17,7 +18,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import edu.cornell.mannlib.vitro.webapp.dao.jena.MenuDaoJena; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -93,6 +93,8 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { throws IOException, ServletException { super.doGet(request,response); +//UQAM-Optimization set for UTF-8 + response.setCharacterEncoding("UTF-8"); VitroRequest vreq = new VitroRequest(request); ResponseValues responseValues = null; @@ -103,8 +105,8 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { if (!isAuthorizedToDisplayPage(request, response, requiredActions(vreq))) { return; } - responseValues = processRequest(vreq); + doResponse(vreq, response, responseValues); } catch (Throwable e) { @@ -256,7 +258,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { protected void doTemplate(VitroRequest vreq, HttpServletResponse response, ResponseValues values) throws TemplateProcessingException { - Map templateDataModel = new HashMap(); + Map templateDataModel = new HashMap(); templateDataModel.putAll(getPageTemplateValues(vreq)); // Add the values that we got from the subcontroller processRequest() method, and merge to the template. @@ -276,8 +278,15 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { // is specified in the main page template. bodyString = ""; } + templateDataModel.put("body", bodyString); + String lang = vreq.getLocale().getLanguage(); + if (!vreq.getLocale().getCountry().isEmpty()) { + lang += "-" + vreq.getLocale().getCountry(); + } + templateDataModel.put("country", lang); + // Tell the template and any directives it uses that we're processing a page template. templateDataModel.put("templateType", PAGE_TEMPLATE_TYPE); @@ -462,7 +471,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { protected MainMenu getDisplayModelMenu(VitroRequest vreq){ String url = vreq.getRequestURI().substring(vreq.getContextPath().length()); - return vreq.getWebappDaoFactory().getMenuDao().getMainMenu(vreq, url); + return vreq.getWebappDaoFactory().getMenuDao().getMainMenu(url); } // NIHVIVO-3307: we need this here instead of FreemarkerConfiguration.java so that updates to diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java index 85d648f6b..c5439d4a2 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java @@ -42,7 +42,7 @@ public class TemplateProcessingHelper { try { Environment env = template.createProcessingEnvironment(map, writer); - + // Define a setup template to be included by every page template String templateType = (String) map.get("templateType"); if (FreemarkerHttpServlet.PAGE_TEMPLATE_TYPE.equals(templateType)) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java index de4057d6e..bb9d7428b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java @@ -37,7 +37,7 @@ public class UrlBuilder { LOGIN("/login"), LOGOUT("/logout"), OBJECT_PROPERTY_EDIT("/propertyEdit"), - CUSTOMSEARCH("/customsearch"), + EXTENDED_SEARCH("/extendedsearch"), SEARCH("/search"), SITE_ADMIN("/siteAdmin"), TERMS_OF_USE("/termsOfUse"), diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java index 1425b10f7..f68db8d56 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java @@ -11,28 +11,29 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Literal; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils; import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; +import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils; /*Servlet to view all labels in various languages for individual*/ @@ -47,12 +48,13 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{ String subjectUri = vreq.getParameter("subjectUri"); body.put("subjectUri", subjectUri); try { - //Get all language codes/labels in the system, and this list is sorted by language name - List> locales = this.getLocales(vreq); + //the labels already added by the user + ArrayList existingLabels = this.getExistingLabels(subjectUri, vreq); + //Get all language codes/labels used in the list of existing labels + List> locales = this.getLocales(vreq, existingLabels); //Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales); - //the labels already added by the user - ArrayList existingLabels = this.getExistingLabels(subjectUri, vreq); + //existing labels keyed by language name and each of the list of labels is sorted by language name HashMap> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, vreq, subjectUri); //Get available locales for the drop down for adding a new label, also sorted by language name @@ -137,20 +139,26 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{ doGet(request, response); } - //get locales - public List> getLocales(VitroRequest vreq) { - List selectables = SelectedLocale.getSelectableLocales(vreq); - if (selectables.isEmpty()) { + //get locales present in list of literals + public List> getLocales(VitroRequest vreq, + List existingLiterals) { + Set locales = new HashSet(); + for(Literal literal : existingLiterals) { + String language = literal.getLanguage(); + if(!StringUtils.isEmpty(language)) { + locales.add(LanguageFilteringUtils.languageToLocale(language)); + } + } + if (locales.isEmpty()) { return Collections.emptyList(); } List> list = new ArrayList>(); Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); - for (Locale locale : selectables) { + for (Locale locale : locales) { try { list.add(buildLocaleMap(locale, currentLocale)); } catch (FileNotFoundException e) { - log.warn("Can't show the Locale selector for '" + locale - + "': " + e); + log.warn("Can't show locale '" + locale + "': " + e); } } @@ -188,8 +196,8 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{ ArrayList labels = new ArrayList(); try { - //We want to get the labels for all the languages, not just the display language - ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); + // Show only labels with current language filtering + ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); while (results.hasNext()) { QuerySolution soln = results.nextSolution(); Literal nodeLiteral = soln.get("label").asLiteral(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java index c89119f5a..0f7154c88 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java @@ -6,14 +6,13 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; -import org.apache.jena.rdf.model.RDFNode; -import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModelBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; @@ -36,6 +35,7 @@ import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.ExecuteDataRetrieval; import edu.cornell.mannlib.vitro.webapp.web.beanswrappers.ReadOnlyBeansWrapper; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModelBuilder; import edu.ucsf.vitro.opensocial.OpenSocialManager; import freemarker.ext.beans.BeansWrapper; import freemarker.template.TemplateModel; @@ -123,8 +123,10 @@ class IndividualResponseBuilder { * into the data model: no real data can be modified. */ // body.put("individual", wrap(itm, BeansWrapper.EXPOSE_SAFE)); - body.put("labelCount", getLabelCount(itm.getUri(), vreq)); - body.put("languageCount", getLanguagesRepresentedCount(itm.getUri(), vreq)); + LabelAndLanguageCount labelAndLanguageCount = getLabelAndLanguageCount( + itm.getUri(), vreq); + body.put("labelCount", labelAndLanguageCount.getLabelCount()); + body.put("languageCount", labelAndLanguageCount.getLanguageCount()); //We also need to know the number of available locales body.put("localesCount", SelectedLocale.getSelectableLocales(vreq).size()); body.put("profileType", getProfileType(itm.getUri(), vreq)); @@ -282,61 +284,103 @@ class IndividualResponseBuilder { return map; } - private static String LABEL_COUNT_QUERY = "" - + "PREFIX rdfs: \n" - + "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n" - + " ?subject rdfs:label ?label \n" - + " FILTER isLiteral(?label) \n" - + "}" ; - - private static String DISTINCT_LANGUAGE_QUERY = "" + private static String LABEL_QUERY = "" + "PREFIX rdfs: \n" - + "SELECT ( str(COUNT(DISTINCT lang(?label))) AS ?languageCount ) WHERE { \n" + + "SELECT ?label WHERE { \n" + " ?subject rdfs:label ?label \n" + " FILTER isLiteral(?label) \n" + "}" ; + +// Queries that were previously used for counts via RDFService that didn't +// filter results by language. With language filtering, aggregate +// functions like COUNT() cannot be used. + +// private static String LABEL_COUNT_QUERY = "" +// + "PREFIX rdfs: \n" +// + "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n" +// + " ?subject rdfs:label ?label \n" +// + " FILTER isLiteral(?label) \n" +// + "}" ; - private static Integer getLabelCount(String subjectUri, VitroRequest vreq) { - String queryStr = QueryUtils.subUriForQueryVar(LABEL_COUNT_QUERY, "subject", subjectUri); +// private static String DISTINCT_LANGUAGE_QUERY = "" +// + "PREFIX rdfs: \n" +// + "SELECT ( str(COUNT(DISTINCT lang(?label))) AS ?languageCount ) WHERE { \n" +// + " ?subject rdfs:label ?label \n" +// + " FILTER isLiteral(?label) \n" +// + "}" ; + + private static LabelAndLanguageCount getLabelAndLanguageCount( + String subjectUri, VitroRequest vreq) { + // 1.12.0 Now filtering to only the labels for the current locale so as + // to be consistent with other editing forms. Because the language + // filter can only act on a result set containing actual literals, + // we can't do the counting with a COUNT() in the query itself. So + // we will now use the LABEL_QUERY instead of LABEL_COUNT_QUERY and + // count the rows and the number of distinct languages represented. + Set distinctLanguages = new HashSet(); + String queryStr = QueryUtils.subUriForQueryVar(LABEL_QUERY, "subject", subjectUri); log.debug("queryStr = " + queryStr); - int theCount = 0; - try { - //ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); - //Get query results across all languages in order for template to show manage labels link correctly - ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); - if (results.hasNext()) { - QuerySolution soln = results.nextSolution(); - RDFNode labelCount = soln.get("labelCount"); - if (labelCount != null && labelCount.isLiteral()) { - theCount = labelCount.asLiteral().getInt(); + int labelCount = 0; + try { + ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); + while(results.hasNext()) { + QuerySolution qsoln = results.next(); + labelCount++; + String lang = qsoln.getLiteral("label").getLanguage(); + if(lang == null) { + lang = ""; } + distinctLanguages.add(lang); } } catch (Exception e) { log.error(e, e); } - return theCount; + return new LabelAndLanguageCount(labelCount, distinctLanguages.size()); + } + + private static class LabelAndLanguageCount { + + private Integer labelCount; + private Integer languageCount; + + public LabelAndLanguageCount(Integer labelCount, Integer languageCount) { + this.labelCount = labelCount; + this.languageCount = languageCount; + } + + public Integer getLabelCount() { + return this.labelCount; + } + + public Integer getLanguageCount() { + return this.languageCount; + } + } //what is the number of languages represented across the labels - private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) { - String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri); - log.debug("queryStr = " + queryStr); - int theCount = 0; - try { - - ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); - if (results.hasNext()) { - QuerySolution soln = results.nextSolution(); - RDFNode languageCount = soln.get("languageCount"); - if (languageCount != null && languageCount.isLiteral()) { - theCount = languageCount.asLiteral().getInt(); - } - } - } catch (Exception e) { - log.error(e, e); - } - return theCount; - } + // This version not compatible with language-filtering RDF services +// private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) { +// String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri); +// log.debug("queryStr = " + queryStr); +// int theCount = 0; +// try { +// +// ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); +// if (results.hasNext()) { +// QuerySolution soln = results.nextSolution(); +// RDFNode languageCount = soln.get("languageCount"); +// if (languageCount != null && languageCount.isLiteral()) { +// theCount = languageCount.asLiteral().getInt(); +// log.info("Language count is " + theCount); +// } +// } +// } catch (Exception e) { +// log.error(e, e); +// } +// log.info("Returning language count " + theCount); +// return theCount; +// } private static String PROFILE_TYPE_QUERY = "" + "PREFIX display: \n" diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/json/GetRandomSearchIndividualsByVClass.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/json/GetRandomSearchIndividualsByVClass.java index 8f3aaa311..325683d54 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/json/GetRandomSearchIndividualsByVClass.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/json/GetRandomSearchIndividualsByVClass.java @@ -4,6 +4,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.json; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -15,6 +16,7 @@ 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.i18n.selection.SelectedLocale; import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService; import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewService.ShortViewContext; import edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewServiceSetup; @@ -73,7 +75,11 @@ public class GetRandomSearchIndividualsByVClass extends GetSearchIndividualsByVC modelMap.put("individual", IndividualTemplateModelBuilder.build(individual, vreq)); modelMap.put("vclass", vclassName); - + String langCtx = vreq.getLocale().getLanguage(); //UQAM-Linguistic-Management build the linguistic context + if (!vreq.getLocale().getCountry().isEmpty()) { + langCtx += "-" + vreq.getLocale().getCountry(); + } + modelMap.put("langCtx", langCtx); // UQAM-Linguistic-Management add the linguistic context to map ShortViewService svs = ShortViewServiceSetup.getService(ctx); return svs.renderShortView(individual, ShortViewContext.BROWSE, modelMap, vreq); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactory.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactory.java index 622367e23..7ebe8a209 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactory.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactory.java @@ -5,6 +5,8 @@ package edu.cornell.mannlib.vitro.webapp.dao; import java.util.List; import java.util.Set; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; + public interface WebappDaoFactory { /** @@ -132,4 +134,6 @@ public interface WebappDaoFactory { public MenuDao getMenuDao(); + public I18nBundle getI18nBundle(); + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryConfig.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryConfig.java index 3d3dc4afd..9ab5563f5 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryConfig.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryConfig.java @@ -5,21 +5,25 @@ package edu.cornell.mannlib.vitro.webapp.dao; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import edu.cornell.mannlib.vitro.webapp.dao.PropertyDao.FullPropertyKey; +import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils; public class WebappDaoFactoryConfig { private List preferredLanguages; + private List preferredLocales; private String defaultNamespace; private Set nonUserNamespaces; private boolean isUnderlyingStoreReasoned = false; public Map customListViewConfigFileMap; public WebappDaoFactoryConfig() { - preferredLanguages = Arrays.asList("en-US", "en", "EN"); + preferredLanguages = Arrays.asList("en-US", "en", "EN"); + preferredLocales = LanguageFilteringUtils.languagesToLocales(preferredLanguages); defaultNamespace = "http://vitro.mannlib.cornell.edu/ns/default#"; nonUserNamespaces = new HashSet(); nonUserNamespaces.add(VitroVocabulary.vitroURI); @@ -33,6 +37,14 @@ public class WebappDaoFactoryConfig { this.preferredLanguages = pl; } + public List getPreferredLocales() { + return this.preferredLocales; + } + + public void setPreferredLocales(List pl) { + this.preferredLocales = pl; + } + public String getDefaultNamespace() { return defaultNamespace; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyDaoFiltering.java index 85d21a14f..9028f3c93 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyDaoFiltering.java @@ -17,7 +17,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyDao; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; -class DataPropertyDaoFiltering extends BaseFiltering implements DataPropertyDao{ +public class DataPropertyDaoFiltering extends BaseFiltering implements DataPropertyDao{ final DataPropertyDao innerDataPropertyDao; final VitroFilters filters; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyStatementDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyStatementDaoFiltering.java index 5bcc78830..961763297 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyStatementDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/DataPropertyStatementDaoFiltering.java @@ -18,7 +18,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyStatementDao; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; -class DataPropertyStatementDaoFiltering extends BaseFiltering implements DataPropertyStatementDao{ +public class DataPropertyStatementDaoFiltering extends BaseFiltering implements DataPropertyStatementDao{ final DataPropertyStatementDao innerDataPropertyStatementDao; final VitroFilters filters; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/FauxPropertyDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/FauxPropertyDaoFiltering.java index bc79e1443..796bbc8df 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/FauxPropertyDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/FauxPropertyDaoFiltering.java @@ -26,47 +26,34 @@ public class FauxPropertyDaoFiltering extends BaseFiltering implements FauxPrope @Override public List getFauxPropertiesForBaseUri(String uri) { - // TODO Auto-generated method stub - throw new RuntimeException( - "FauxPropertyDao.getFauxPropertiesForBaseUri() not implemented."); + return innerFauxPropertyDao.getFauxPropertiesForBaseUri(uri); } @Override public FauxProperty getFauxPropertyFromContextUri(String contextUri) { - // TODO Auto-generated method stub - throw new RuntimeException( - "FauxPropertyDaoFiltering.getFauxPropertyFromConfigContextUri() not implemented."); - + return innerFauxPropertyDao.getFauxPropertyFromContextUri(contextUri); } @Override public FauxProperty getFauxPropertyByUris(String domainUri, String baseUri, String rangeUri) { - // TODO Auto-generated method stub - throw new RuntimeException( - "FauxPropertyDaoFiltering.getFauxPropertyByUris() not implemented."); - + return innerFauxPropertyDao.getFauxPropertyByUris(domainUri, baseUri, + rangeUri); } @Override public void updateFauxProperty(FauxProperty fp) { - // TODO Auto-generated method stub - throw new RuntimeException("FauxPropertyDaoFiltering.updateFauxProperty() not implemented."); - + innerFauxPropertyDao.updateFauxProperty(fp); } @Override public void deleteFauxProperty(FauxProperty fp) { - // TODO Auto-generated method stub - throw new RuntimeException("FauxPropertyDao.deleteFauxProperty() not implemented."); - + innerFauxPropertyDao.deleteFauxProperty(fp); } @Override public void insertFauxProperty(FauxProperty fp) { - // TODO Auto-generated method stub - throw new RuntimeException("FauxPropertyDao.insertFauxProperty() not implemented."); - + innerFauxPropertyDao.insertFauxProperty(fp); } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java index 74a275f8b..e9121cd7f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java @@ -20,7 +20,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; -class IndividualDaoFiltering extends BaseFiltering implements IndividualDao{ +public class IndividualDaoFiltering extends BaseFiltering implements IndividualDao{ IndividualDao innerIndividualDao; VitroFilters filters; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyDaoFiltering.java index c85ca36a5..30cd70521 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyDaoFiltering.java @@ -17,7 +17,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyDao; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; -class ObjectPropertyDaoFiltering extends BaseFiltering implements ObjectPropertyDao{ +public class ObjectPropertyDaoFiltering extends BaseFiltering implements ObjectPropertyDao{ final ObjectPropertyDao innerObjectPropertyDao; final VitroFilters filters; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyStatementDaoFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyStatementDaoFiltering.java index 3aa4ea3ab..8ccc636cc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyStatementDaoFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/ObjectPropertyStatementDaoFiltering.java @@ -16,7 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatementImpl; import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; -class ObjectPropertyStatementDaoFiltering extends BaseFiltering implements ObjectPropertyStatementDao{ +public class ObjectPropertyStatementDaoFiltering extends BaseFiltering implements ObjectPropertyStatementDao{ final ObjectPropertyStatementDao innerObjectPropertyStatementDao; final VitroFilters filters; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/WebappDaoFactoryFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/WebappDaoFactoryFiltering.java index 84e2823d7..fdb386a12 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/WebappDaoFactoryFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/WebappDaoFactoryFiltering.java @@ -24,6 +24,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.VClassDao; import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; /** * This wraps a WebappDaoFactory and applies filtering. @@ -67,7 +68,7 @@ public class WebappDaoFactoryFiltering implements WebappDaoFactory { transient private PropertyGroupDao filteringPropertyGroupDao=null; transient private PropertyInstanceDao filteringPropertyInstanceDao=null; - public WebappDaoFactoryFiltering( WebappDaoFactory innerDao, VitroFilters filters){ + public WebappDaoFactoryFiltering(WebappDaoFactory innerDao, VitroFilters filters){ if( innerDao == null ) throw new Error("innerWebappDaoFactory must be non-null"); this.filters = filters; @@ -276,4 +277,9 @@ public class WebappDaoFactoryFiltering implements WebappDaoFactory { public void close() { innerWebappDaoFactory.close(); } + + @Override + public I18nBundle getI18nBundle() { + return innerWebappDaoFactory.getI18nBundle(); + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ApplicationDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ApplicationDaoJena.java index 884c5f964..9e1898020 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ApplicationDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ApplicationDaoJena.java @@ -83,22 +83,21 @@ public class ApplicationDaoJena extends JenaBaseDao implements ApplicationDao { } ontModel.enterCriticalSection(Lock.WRITE); try { - appInd.setLabel(application.getApplicationName(), null); - updatePropertyStringValue( - appInd, APPLICATION_ABOUTTEXT, application.getAboutText(), - ontModel); - updatePropertyStringValue( + updateRDFSLabel(appInd, application.getApplicationName()); + updatePlainLiteralValue( + appInd, APPLICATION_ABOUTTEXT, application.getAboutText()); + updatePlainLiteralValue( appInd, APPLICATION_ACKNOWLEGETEXT, - application.getAcknowledgeText(), ontModel); + application.getAcknowledgeText()); updatePropertyStringValue( appInd, APPLICATION_CONTACTMAIL, application.getContactMail(), ontModel); updatePropertyStringValue( appInd, APPLICATION_CORRECTIONMAIL, application.getCorrectionMail(), ontModel); - updatePropertyStringValue( + updatePlainLiteralValue( appInd, APPLICATION_COPYRIGHTANCHOR, - application.getCopyrightAnchor(), ontModel); + application.getCopyrightAnchor()); updatePropertyStringValue( appInd, APPLICATION_COPYRIGHTURL, application.getCopyrightURL(), ontModel); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java index a17ef6ec1..5c836281d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java @@ -516,9 +516,9 @@ public class DataPropertyDaoJena extends PropertyDaoJena implements if (dtp.getFunctional()) { ontModel.add(jDataprop,RDF.type,OWL.FunctionalProperty); } - addPropertyStringValue(jDataprop, EXAMPLE, dtp.getExample(), ontModel); - addPropertyStringValue(jDataprop, DESCRIPTION_ANNOT, dtp.getDescription(), ontModel); - addPropertyStringValue(jDataprop, PUBLIC_DESCRIPTION_ANNOT, dtp.getPublicDescription(), ontModel); + updatePlainLiteralValue(jDataprop, EXAMPLE, dtp.getExample()); + updatePlainLiteralValue(jDataprop, DESCRIPTION_ANNOT, dtp.getDescription()); + updatePlainLiteralValue(jDataprop, PUBLIC_DESCRIPTION_ANNOT, dtp.getPublicDescription()); addPropertyStringValue(jDataprop, EDITING, dtp.getEditing(), ontModel); addPropertyNonNegativeIntValue(jDataprop, DISPLAY_RANK_ANNOT, dtp.getDisplayTier(), ontModel); addPropertyNonNegativeIntValue(jDataprop, DISPLAY_LIMIT, dtp.getDisplayLimit(), ontModel); @@ -587,9 +587,9 @@ public class DataPropertyDaoJena extends PropertyDaoJena implements } } - updatePropertyStringValue(jDataprop, EXAMPLE, dtp.getExample(), ontModel); - updatePropertyStringValue(jDataprop, DESCRIPTION_ANNOT, dtp.getDescription(), ontModel); - updatePropertyStringValue(jDataprop, PUBLIC_DESCRIPTION_ANNOT, dtp.getPublicDescription(), ontModel); + updatePlainLiteralValue(jDataprop, EXAMPLE, dtp.getExample()); + updatePlainLiteralValue(jDataprop, DESCRIPTION_ANNOT, dtp.getDescription()); + updatePlainLiteralValue(jDataprop, PUBLIC_DESCRIPTION_ANNOT, dtp.getPublicDescription()); updatePropertyStringValue(jDataprop, EDITING, dtp.getEditing(), ontModel); updatePropertyNonNegativeIntValue(jDataprop, DISPLAY_RANK_ANNOT, dtp.getDisplayTier(), ontModel); updatePropertyNonNegativeIntValue(jDataprop, DISPLAY_LIMIT, dtp.getDisplayLimit(), ontModel); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/FauxPropertyDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/FauxPropertyDaoJena.java index 744881f7f..a454e1d1e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/FauxPropertyDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/FauxPropertyDaoJena.java @@ -230,10 +230,9 @@ public class FauxPropertyDaoJena extends JenaBaseDao implements FauxPropertyDao OBJECT_PROPERTY_DISPLAY_CONFIG); addPropertyResourceURINotEmpty(config, PROPERTY_GROUP, fp.getGroupURI()); - addPropertyStringValue(config, DISPLAY_NAME, fp.getDisplayName(), - displayModel); - addPropertyStringValue(config, PUBLIC_DESCRIPTION_ANNOT, - fp.getPublicDescription(), displayModel); + updatePlainLiteralValue(config, DISPLAY_NAME, fp.getDisplayName()); + updatePlainLiteralValue(config, PUBLIC_DESCRIPTION_ANNOT, + fp.getPublicDescription()); addPropertyIntValue(config, DISPLAY_RANK_ANNOT, fp.getDisplayTier(), displayModel); addPropertyIntValue(config, DISPLAY_LIMIT, fp.getDisplayLimit(), @@ -328,10 +327,10 @@ public class FauxPropertyDaoJena extends JenaBaseDao implements FauxPropertyDao .getConfigUri()); updatePropertyResourceURIValue(config, PROPERTY_GROUP, fp.getGroupURI()); - updatePropertyStringValue(config, DISPLAY_NAME, - fp.getDisplayName(), displayModel); - updatePropertyStringValue(config, PUBLIC_DESCRIPTION_ANNOT, - fp.getPublicDescription(), displayModel); + updatePlainLiteralValue(config, DISPLAY_NAME, + fp.getDisplayName()); + updatePlainLiteralValue(config, PUBLIC_DESCRIPTION_ANNOT, + fp.getPublicDescription()); updatePropertyIntValue(config, DISPLAY_RANK_ANNOT, fp.getDisplayTier(), displayModel); updatePropertyIntValue(config, DISPLAY_LIMIT, fp.getDisplayLimit(), diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java index f96349bcf..9b0458109 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java @@ -5,8 +5,8 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; @@ -14,14 +14,14 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.jena.iri.IRI; -import org.apache.jena.iri.IRIFactory; - import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; +import org.apache.jena.iri.IRI; +import org.apache.jena.iri.IRIFactory; import org.apache.jena.ontology.DatatypeProperty; import org.apache.jena.ontology.ObjectProperty; import org.apache.jena.ontology.OntClass; @@ -39,6 +39,7 @@ import org.apache.jena.rdf.model.NodeIterator; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.rdf.model.Statement; import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.shared.Lock; @@ -752,22 +753,111 @@ public class JenaBaseDao extends JenaBaseDaoCon { } } } + + /** + * Add to an OntResource an rdfs:label value with lexical form and default + * language tag. Remove any other existing values in default language. + * If lexicalForm parameter is null, remove all plain literal values of + * Property in default language. + * @param ontRes may not be null + * @param lexicalForm may be null. If null, existing values will be deleted + * but none will be added. + */ + protected void updateRDFSLabel(OntResource ontRes, String lexicalForm) { + updatePlainLiteralValue(ontRes, RDFS.label, lexicalForm); + } + + /** + * Add to an OntResource an rdfs:label value with lexical form and optional + * language tag. Remove any other existing plain literal values that match + * specified language or lack language tags if no language is supplied. + * If lexicalForm parameter is null, remove all plain literal labels in + * specified language, or all existing language-less labels + * if no language is specified. + * @param ontRes may not be null + * @param lexicalForm may be null. If null, existing values will be deleted + * but none will be added. + * @param lang may be null. If null, method acts on language-less plain + * literal labels and ignores those with language tags. + */ + protected void updateRDFSLabel(OntResource ontRes, String lexicalForm, String lang) { + updatePlainLiteralValue(ontRes, RDFS.label, lexicalForm, lang); + } /** - * convenience method for updating the RDFS label + * Add to an OntResource a Property value with lexical form and default + * language tag. Remove any other existing values in default language. + * If lexicalForm parameter is null, remove all plain literal values of + * Property in default language. + * @param ontRes may not be null + * @param lexicalForm may be null. If null, existing values will be deleted + * but none will be added. */ - protected void updateRDFSLabel(OntResource ontRes, String label) { - - if (label != null && label.length() > 0) { - - String existingValue = ontRes.getLabel(getDefaultLanguage()); - - if (existingValue == null || !existingValue.equals(label)) { - ontRes.setLabel(label, getDefaultLanguage()); - } - } else { - ontRes.removeAll(RDFS.label); - } + protected void updatePlainLiteralValue(OntResource ontRes, Property property, + String lexicalForm) { + updatePlainLiteralValue(ontRes, property, lexicalForm, getDefaultLanguage()); + } + + /** + * Add to an OntResource a Property value with lexical form and optional + * language tag. Remove any other existing plain literal values that match + * specified language or lack language tags if no language is supplied. + * If lexicalForm parameter is null, remove all plain literal values of + * Property in specified language, or all existing language-less literals + * if no language is specified. + * @param ontRes may not be null + * @param lexicalForm may be null. If null, existing values will be deleted + * but none will be added. + * @param lang may be null. If null, method acts on language-less + * plain literal values and ignores those with language tags. + */ + protected void updatePlainLiteralValue(OntResource ontRes, Property property, + String lexicalForm, String lang) { + if(ontRes == null) { + throw new IllegalArgumentException("ontRes may not be null."); + } + boolean addNew = true; + List toRemove = new ArrayList(); + StmtIterator existingStmts = ontRes.listProperties(property); + while(existingStmts.hasNext()) { + Statement stmt = existingStmts.next(); + if(stmt.getObject().isLiteral()) { + Literal lit = stmt.getObject().asLiteral(); + if( (lang == null && isLanguageLessPlainLiteral(lit)) + || (lang != null && lang.equals(lit.getLanguage())) ) { + if(!lit.getLexicalForm().equals(lexicalForm)) { + toRemove.add(stmt); + } else { + // New literal already exists in the model. + // Do not add it again. + addNew = false; + } + } + } + } + if(!toRemove.isEmpty()) { + ontRes.getModel().remove(toRemove); + } + if (addNew && (lexicalForm != null)) { + if(!StringUtils.isEmpty(lang)) { + ontRes.addProperty(property, ResourceFactory.createLangLiteral( + lexicalForm, lang)); + } else { + ontRes.addProperty(property, ResourceFactory.createPlainLiteral( + lexicalForm)); + } + } + } + + private boolean isLanguageLessPlainLiteral(Literal lit) { + // In RDF 1.1 all the language-less literals get datatype xsd:string. + // The null datatype check is here just in case this gets run on an older + // version of Jena. rdf:PlainLiteral is also a datatype, but doesn't + // (yet) seem to be used by Jena. + return StringUtils.isEmpty(lit.getLanguage()) + && ((lit.getDatatype() == null) + || XSDDatatype.XSDstring.equals(lit.getDatatype()) || + (RDF.getURI() + "PlainLiteral").equals(lit.getDatatypeURI())); } private Literal getLabel(String lang, ListlabelList) { @@ -780,6 +870,13 @@ public class JenaBaseDao extends JenaBaseDaoCon { } if ((lang != null) && (lang.equals(labelLanguage))) { return labelLit; + } else + /* + * UQAM-Linguistic-Management + * Check for country-part of lang (ex: 'en' for default consideration of labelLanguage in english but not encoded by 'en-US' most case of labels in vivo.owl) + */ + if ((lang != null) && (Arrays.asList(lang.split("-")).get(0).equals(labelLanguage))) { + return labelLit; } } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJena.java index 73da8f76b..30dd022c9 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJena.java @@ -72,6 +72,7 @@ public class MenuDaoJena extends JenaBaseDao implements MenuDao { return getMenu( getOntModelSelector().getDisplayModel(), url ); } + @Override public MainMenu getMainMenu( ServletRequest req, String url ) { OntModel displayModel = LanguageFilteringUtils.wrapOntModelInALanguageFilter(getOntModelSelector().getDisplayModel(), req ); return getMenu(displayModel, url) ; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java index ea4d5f041..9ae00d59e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java @@ -714,14 +714,14 @@ public class ObjectPropertyDaoJena extends PropertyDaoJena implements ObjectProp } } - updatePropertyStringValue(p,EXAMPLE_ANNOT,prop.getExample(),getOntModel()); - updatePropertyStringValue(p,DESCRIPTION_ANNOT,prop.getDescription(),getOntModel()); - updatePropertyStringValue(p,PUBLIC_DESCRIPTION_ANNOT,prop.getPublicDescription(),getOntModel()); + updatePlainLiteralValue(p, EXAMPLE_ANNOT, prop.getExample()); + updatePlainLiteralValue(p, DESCRIPTION_ANNOT, prop.getDescription()); + updatePlainLiteralValue(p, PUBLIC_DESCRIPTION_ANNOT, prop.getPublicDescription()); updatePropertyNonNegativeIntegerValue(p,DISPLAY_LIMIT,prop.getDomainDisplayLimitInteger(),getOntModel()); updatePropertyStringValue(p,PROPERTY_ENTITYSORTDIRECTION,prop.getDomainEntitySortDirection(),getOntModel()); if (inv != null) { - updatePropertyStringValue(inv,EXAMPLE_ANNOT,prop.getExample(),getOntModel()); - updatePropertyStringValue(inv,DESCRIPTION_ANNOT,prop.getDescription(),getOntModel()); + updatePlainLiteralValue(inv, EXAMPLE_ANNOT, prop.getExample()); + updatePlainLiteralValue(inv, DESCRIPTION_ANNOT, prop.getDescription()); updatePropertyNonNegativeIntegerValue(inv,DISPLAY_LIMIT,prop.getRangeDisplayLimitInteger(),getOntModel()); updatePropertyStringValue(inv,PROPERTY_ENTITYSORTDIRECTION,prop.getRangeEntitySortDirection(),getOntModel()); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java index 9b6642c4d..f6c3b4dec 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java @@ -289,10 +289,15 @@ public class RDFServiceGraph implements GraphWithPerform { literalBuff.append("\""); pyString(literalBuff, node.getLiteralLexicalForm()); literalBuff.append("\""); - if (node.getLiteralDatatypeURI() != null) { - literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); - } else if (!StringUtils.isEmpty(node.getLiteralLanguage())) { + /* + * UQAM-Bug-Correction + * reversing the condition tests. + * It is important to prioritize the language typology test in order to exploit the linguistic context in testing the type of data + */ + if (!StringUtils.isEmpty(node.getLiteralLanguage())) { literalBuff.append("@").append(node.getLiteralLanguage()); + } else if (node.getLiteralDatatypeURI() != null) { + literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); } return literalBuff.toString(); } else { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/SparqlGraph.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/SparqlGraph.java index 8b2ed8668..f0e55e239 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/SparqlGraph.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/SparqlGraph.java @@ -232,10 +232,10 @@ public class SparqlGraph implements GraphWithPerform { literalBuff.append("\""); pyString(literalBuff, node.getLiteralLexicalForm()); literalBuff.append("\""); - if (node.getLiteralDatatypeURI() != null) { - literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); - } else if (!StringUtils.isEmpty(node.getLiteralLanguage())) { + if (!StringUtils.isEmpty(node.getLiteralLanguage())) { literalBuff.append("@").append(node.getLiteralLanguage()); + } else if (node.getLiteralDatatypeURI() != null) { + literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); } return literalBuff.toString(); } else { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoJena.java index 8d79155b1..0c3183f50 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoJena.java @@ -2,6 +2,8 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import static java.lang.String.format; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -60,15 +62,18 @@ import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao; import edu.cornell.mannlib.vitro.webapp.dao.VClassDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import edu.cornell.mannlib.vitro.webapp.web.URLEncoder; public class VClassDaoJena extends JenaBaseDao implements VClassDao { protected static final Log log = LogFactory.getLog(VClassDaoJena.class); + private final I18nBundle i18n; private boolean isUnderlyingStoreReasoned = false; public VClassDaoJena(WebappDaoFactoryJena wadf, boolean isUnderlyingStoreReasoned) { super(wadf); + this.i18n = wadf.getI18nBundle(); this.isUnderlyingStoreReasoned = isUnderlyingStoreReasoned; } @@ -91,17 +96,19 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { Restriction rest = cls.asRestriction(); OntProperty onProperty = rest.getOnProperty(); StringBuilder labelStr = new StringBuilder(); - labelStr.append("restriction on ").append(getLabelOrId(onProperty)).append(": "); + labelStr.append(format("%s ", i18n.text("restriction_on"))) + .append(getLabelOrId(onProperty)) + .append(": "); if (rest.isAllValuesFromRestriction() || rest.isSomeValuesFromRestriction()) { Resource fillerRes = null; if (rest.isAllValuesFromRestriction()) { AllValuesFromRestriction avfRest = rest.asAllValuesFromRestriction(); fillerRes = avfRest.getAllValuesFrom(); - labelStr.append("all values from "); + labelStr.append(format("%s ", i18n.text("all_values_from"))); } else { SomeValuesFromRestriction svfRest = rest.asSomeValuesFromRestriction(); fillerRes = svfRest.getSomeValuesFrom(); - labelStr.append("some values from "); + labelStr.append(format("%s ", i18n.text("some_values_from"))); } if (fillerRes.canAs(OntClass.class)) { OntClass avf = fillerRes.as(OntClass.class); @@ -115,7 +122,7 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { } } else if (rest.isHasValueRestriction()) { HasValueRestriction hvRest = rest.asHasValueRestriction(); - labelStr.append("has value "); + labelStr.append(format("%s ", i18n.text("has_value"))); RDFNode fillerNode = hvRest.getHasValue(); try { if (fillerNode.isResource()) { @@ -128,22 +135,22 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { } } else if (rest.isMinCardinalityRestriction()) { MinCardinalityRestriction mcRest = rest.asMinCardinalityRestriction(); - labelStr.append("minimum cardinality "); + labelStr.append(format("%s ", i18n.text("minimum_cardinality"))); labelStr.append(mcRest.getMinCardinality()); } else if (rest.isMaxCardinalityRestriction()) { MaxCardinalityRestriction mcRest = rest.asMaxCardinalityRestriction(); - labelStr.append("maximum cardinality "); + labelStr.append(format("%s ", i18n.text("maximum_cardinality"))); labelStr.append(mcRest.getMaxCardinality()); } else if (rest.isCardinalityRestriction()) { CardinalityRestriction cRest = rest.asCardinalityRestriction(); - labelStr.append("cardinality "); + labelStr.append(format("%s ", i18n.text("cardinality"))); labelStr.append(cRest.getCardinality()); } return labelStr.toString(); } else if (isBooleanClassExpression(cls)) { StringBuilder labelStr = new StringBuilder("("); if (cls.isComplementClass()) { - labelStr.append("not "); + labelStr.append(format("%s ", i18n.text("not"))); ComplementClass ccls = cls.as(ComplementClass.class); labelStr.append(getLabelForClass(ccls.getOperand(), withPrefix, forPickList)); } else if (cls.isIntersectionClass()) { @@ -153,7 +160,7 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { OntClass operand = operandIt.next(); labelStr.append(getLabelForClass(operand, withPrefix, forPickList)); if (operandIt.hasNext()) { - labelStr.append(" and "); + labelStr.append(format(" %s ", i18n.text("and"))); } } } else if (cls.isUnionClass()) { @@ -163,7 +170,7 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { OntClass operand = operandIt.next(); labelStr.append(getLabelForClass(operand, withPrefix, forPickList)); if (operandIt.hasNext()) { - labelStr.append(" or "); + labelStr.append(format(" %s ", i18n.text("or"))); } } } @@ -952,9 +959,9 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { } catch (Exception e) { log.error("error linking class "+cls.getURI()+" to class group"); } - addPropertyStringValue(ontCls,SHORTDEF,cls.getShortDef(),ontModel); - addPropertyStringValue(ontCls,EXAMPLE_ANNOT,cls.getExample(),ontModel); - addPropertyStringValue(ontCls,DESCRIPTION_ANNOT,cls.getDescription(),ontModel); + updatePlainLiteralValue(ontCls, SHORTDEF, cls.getShortDef()); + updatePlainLiteralValue(ontCls, EXAMPLE_ANNOT, cls.getExample()); + updatePlainLiteralValue(ontCls, DESCRIPTION_ANNOT, cls.getDescription()); addPropertyIntValue(ontCls,DISPLAY_LIMIT,cls.getDisplayLimit(),ontModel); addPropertyIntValue(ontCls,DISPLAY_RANK_ANNOT,cls.getDisplayRank(),ontModel); @@ -1011,9 +1018,9 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao { if (ontCls != null) { updateRDFSLabel(ontCls, cls.getName()); updatePropertyResourceURIValue(ontCls,IN_CLASSGROUP,cls.getGroupURI(),ontModel); - updatePropertyStringValue(ontCls,SHORTDEF,cls.getShortDef(),ontModel); - updatePropertyStringValue(ontCls,EXAMPLE_ANNOT,cls.getExample(),ontModel); - updatePropertyStringValue(ontCls,DESCRIPTION_ANNOT,cls.getDescription(),ontModel); + updatePlainLiteralValue(ontCls, SHORTDEF, cls.getShortDef()); + updatePlainLiteralValue(ontCls, EXAMPLE_ANNOT, cls.getExample()); + updatePlainLiteralValue(ontCls, DESCRIPTION_ANNOT, cls.getDescription()); updatePropertyNonNegativeIntValue(ontCls,DISPLAY_LIMIT,cls.getDisplayLimit(),ontModel); updatePropertyNonNegativeIntValue(ontCls,DISPLAY_RANK_ANNOT,cls.getDisplayRank(),ontModel); updatePropertyFloatValue(ontCls, SEARCH_BOOST_ANNOT, cls.getSearchBoost(), ontModel); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupDaoJena.java index 3ba868de8..5a655660b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupDaoJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupDaoJena.java @@ -276,7 +276,7 @@ public class VClassGroupDaoJena extends JenaBaseDao implements VClassGroupDao { try { Individual groupInd = ontModel.getIndividual(vcg.getURI()); try { - groupInd.setLabel(vcg.getPublicName(), getDefaultLanguage()); + updateRDFSLabel(groupInd, vcg.getPublicName(), getDefaultLanguage()); } catch (Exception e) { log.error("error updating name for "+groupInd.getURI()); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java index 26aabf0be..813cf27ad 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java @@ -50,6 +50,8 @@ import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactoryConfig; +import edu.cornell.mannlib.vitro.webapp.i18n.I18n; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel; @@ -611,5 +613,10 @@ public class WebappDaoFactoryJena implements WebappDaoFactory { } } + @Override + public I18nBundle getI18nBundle() { + // return context based bundle for preferred locales + return I18n.bundle(config.getPreferredLocales()); + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/EditLiteral.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/EditLiteral.java index 95f2848b7..8ff13bfc0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/EditLiteral.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/EditLiteral.java @@ -132,6 +132,11 @@ public class EditLiteral implements Literal { throw new UnsupportedOperationException(); } + @Override + public boolean isStmtResource() { + throw new UnsupportedOperationException(); + } + public Literal inModel(Model model) { throw new UnsupportedOperationException(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/listener/impl/IndividualDataPropertyStatementProcessor.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/listener/impl/IndividualDataPropertyStatementProcessor.java index f2b6cad4f..b2758c7fd 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/listener/impl/IndividualDataPropertyStatementProcessor.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/listener/impl/IndividualDataPropertyStatementProcessor.java @@ -4,7 +4,6 @@ package edu.cornell.mannlib.vitro.webapp.edit.listener.impl; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import org.apache.commons.logging.Log; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwo.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwo.java index c5eb8280f..1e942aa8e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwo.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwo.java @@ -24,15 +24,25 @@ import edu.cornell.mannlib.vitro.webapp.beans.Datatype; import edu.cornell.mannlib.vitro.webapp.dao.jena.DatatypeDaoJena; import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactoryJena; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; - +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; public class BasicValidationVTwo { + public final static String REQUIRED_FIELD_EMPTY_MSG = "required_field_empty_msg"; + + public final static String DATA_NOT_PAST_MSG = "data_not_past_msg"; + public final static String INVALID_DATE_FORM_MSG = "invalid_date_form_msg"; + public final static String FILE_MUST_BE_ENTERED_MSG = "file_must_be_entered_msg"; + public final static String INVALID_URL_MSG = "invalid_url_msg"; + + private I18nBundle i18n; + Map> varsToValidations; EditConfigurationVTwo editConfig; - public BasicValidationVTwo(EditConfigurationVTwo editConfig, MultiValueEditSubmission editSub){ + public BasicValidationVTwo(EditConfigurationVTwo editConfig, I18nBundle i18n){ this.editConfig = editConfig; + this.i18n = i18n; Map> validatorsForFields = new HashMap>(); for(String fieldName: editConfig.getFields().keySet()){ FieldVTwo field = editConfig.getField(fieldName); @@ -42,8 +52,9 @@ public class BasicValidationVTwo { checkValidations(); } - public BasicValidationVTwo(Map> varsToValidations){ + public BasicValidationVTwo(Map> varsToValidations, I18nBundle i18n){ this.varsToValidations = varsToValidations; + this.i18n = i18n; checkValidations(); } @@ -94,7 +105,7 @@ public class BasicValidationVTwo { //If no literals and this field was required, this is an error message //and can return if((literals == null || literals.size() == 0) && isRequiredField) { - errors.put(name, REQUIRED_FIELD_EMPTY_MSG); + errors.put(name, i18n.text(REQUIRED_FIELD_EMPTY_MSG)); break; } //Loop through literals if literals exist @@ -113,7 +124,7 @@ public class BasicValidationVTwo { // incorrectly generate errors. if (isEmpty(value)) { if (isRequiredField) { - errors.put(name, REQUIRED_FIELD_EMPTY_MSG); + errors.put(name, i18n.text(REQUIRED_FIELD_EMPTY_MSG)); } break; } @@ -154,11 +165,11 @@ public class BasicValidationVTwo { private String validate(String validationType, List fileItems) { if( "nonempty".equalsIgnoreCase(validationType)){ if( fileItems == null || fileItems.size() == 0 ){ - return "a file must be entered for this field."; + return i18n.text(FILE_MUST_BE_ENTERED_MSG); }else{ FileItem fileItem = fileItems.get(0); if( fileItem == null || fileItem.getName() == null || fileItem.getName().length() < 1 || fileItem.getSize() < 0){ - return "a file must be entered for this field."; + return i18n.text(FILE_MUST_BE_ENTERED_MSG); } } } @@ -174,14 +185,14 @@ public class BasicValidationVTwo { // This case may be needed for validation of other field types. if( "nonempty".equalsIgnoreCase(validationType)){ if( isEmpty(value) ) - return REQUIRED_FIELD_EMPTY_MSG; + return i18n.text(REQUIRED_FIELD_EMPTY_MSG); } // Format validation else if("isDate".equalsIgnoreCase(validationType)){ if( isDate( value)) return SUCCESS; else - return "must be in valid date format mm/dd/yyyy."; + return i18n.text(INVALID_DATE_FORM_MSG); } else if( validationType.indexOf("datatype:") == 0 ) { String datatypeURI = validationType.substring(9); @@ -194,7 +205,7 @@ public class BasicValidationVTwo { } else if ("httpUrl".equalsIgnoreCase(validationType)){ //check if it has http or https, we could do more but for now this is all. if(! value.startsWith("http://") && ! value.startsWith("https://") ){ - return "This URL must start with http:// or https://"; + return i18n.text(INVALID_URL_MSG); }else{ return SUCCESS; } @@ -216,7 +227,7 @@ public class BasicValidationVTwo { dayParamStr = value.substring(monthDash + 1, value.length()); inputC.set(Integer.parseInt(yearParamStr), Integer.parseInt(monthParamStr) - 1, Integer.parseInt(dayParamStr)); if(inputC.before(c)) { - return this.DATE_NOT_PAST_MSG; + return i18n.text(DATA_NOT_PAST_MSG); //Returning null makes the error message "field is empty" display instead //return null; } else { @@ -278,14 +289,9 @@ public class BasicValidationVTwo { return (value == null || value.trim().length() == 0); } - - - private static Pattern urlRX = Pattern.compile("(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?"); - /** we use null to indicate success */ public final static String SUCCESS = null; - public final static String REQUIRED_FIELD_EMPTY_MSG = "This field must not be empty."; - public final static String DATE_NOT_PAST_MSG = "Please enter a future target date for publication (past dates are invalid)."; + //public final static String MIN_FIELDS_NOT_POPULATED = "Please enter values for at least "; //public final static String FORM_ERROR_FIELD_ID = "formannotationerrors"; /** regex for strings like "12/31/2004" */ diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java index da3f9be3e..63164c0dd 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java @@ -28,6 +28,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption; import freemarker.template.Configuration; public class EditConfigurationUtils { @@ -66,17 +67,19 @@ public class EditConfigurationUtils { } public static VClass getRangeVClass(VitroRequest vreq) { - // This needs a WebappDaoFactory with no filtering/RDFService - // funny business because it needs to be able to retrieve anonymous union - // classes by their "pseudo-bnode URIs". - // Someday we'll need to figure out a different way of doing this. WebappDaoFactory ctxDaoFact = ModelAccess.on( vreq.getSession().getServletContext()).getWebappDaoFactory(); return ctxDaoFact.getVClassDao().getVClassByURI(getRangeUri(vreq)); } + public static VClass getLangAwardRangeVClass(VitroRequest vreq) { + // UQAM-Linguistic-Management + WebappDaoFactory vreqDaoFact = ModelAccess.on(vreq).getWebappDaoFactory( + LanguageOption.LANGUAGE_AWARE); + return vreqDaoFact.getVClassDao().getVClassByURI(getRangeUri(vreq)); + } + //get individual - public static Individual getSubjectIndividual(VitroRequest vreq) { Individual subject = null; String subjectUri = getSubjectUri(vreq); @@ -126,12 +129,24 @@ public class EditConfigurationUtils { public static ObjectProperty getObjectPropertyForPredicate(VitroRequest vreq, String predicateUri, String domainUri, String rangeUri) { - WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + // WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + // UQAM-Linguistic-Management Use linguistic context + WebappDaoFactory wdf = ModelAccess.on(vreq).getWebappDaoFactory(LanguageOption.LANGUAGE_AWARE); ObjectProperty objectProp = wdf.getObjectPropertyDao().getObjectPropertyByURIs( predicateUri, domainUri, rangeUri); return objectProp; } + // UQAM Use linguistic context + public static ObjectProperty getObjectPropertyForPredicateLangAware(VitroRequest vreq, + String predicateUri, String domainUri, String rangeUri) { + // WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + // UQAM Use linguistic context + WebappDaoFactory wdf = ModelAccess.on(vreq).getWebappDaoFactory(LanguageOption.LANGUAGE_AWARE); + ObjectProperty objectProp = wdf.getObjectPropertyDao().getObjectPropertyByURIs( + predicateUri, domainUri, rangeUri); + return objectProp; + } public static DataProperty getDataPropertyForPredicate(VitroRequest vreq, String predicateUri) { WebappDaoFactory wdf = vreq.getWebappDaoFactory(); //TODO: Check reason for employing unfiltered webapp dao factory and note if using a different version @@ -209,6 +224,7 @@ public class EditConfigurationUtils { return (op != null && dp == null); } + private static boolean isVitroLabel(String predicateUri) { return predicateUri.equals(VitroVocabulary.LABEL); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditN3GeneratorVTwo.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditN3GeneratorVTwo.java index 015652445..b81ce06d5 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditN3GeneratorVTwo.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditN3GeneratorVTwo.java @@ -366,6 +366,8 @@ public class EditN3GeneratorVTwo { { sbuff.append("@") ; sbuff.append(lang) ; + // added by UQAM to exit at this point without adding datatype + return sbuff.toString() ; } // Format the datatype diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/MultiValueEditSubmission.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/MultiValueEditSubmission.java index 8f61f4870..306ebdce7 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/MultiValueEditSubmission.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/MultiValueEditSubmission.java @@ -10,23 +10,23 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.fileupload.FileItem; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.XSD; +import org.apache.jena.vocabulary.RDF; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18n; public class MultiValueEditSubmission { @@ -34,21 +34,31 @@ public class MultiValueEditSubmission { private Map> literalsFromForm ; private Map> urisFromForm ; - private Map validationErrors; private BasicValidationVTwo basicValidation; - - private Map> filesFromForm; - + private static Model literalCreationModel; - private String entityToReturnTo; + private VitroRequest _vreq; + + private static final String TIME_URI = XSD.time.getURI(); + static{ literalCreationModel = ModelFactory.createDefaultModel(); } - - public MultiValueEditSubmission(Map queryParameters, EditConfigurationVTwo editConfig){ + /* + * UQAM + * replace + * public MultiValueEditSubmission(Map queryParameters, EditConfigurationVTwo editConfig) + * by this new signature + * This affect PostEditCleanupController and ProcessRdfFormController classes. + * This replacement is justified by the fact that we need a linguistic context in this class. + */ + public MultiValueEditSubmission(VitroRequest vreq, EditConfigurationVTwo editConfig){ + // UQAM add this both lines + _vreq = vreq; + Map queryParameters = vreq.getParameterMap(); if( editConfig == null ) throw new Error("EditSubmission needs an EditConfiguration"); this.editKey = editConfig.getEditKey(); @@ -92,11 +102,12 @@ public class MultiValueEditSubmission { processEditElementFields(editConfig,queryParameters); //Incorporating basic validation //Validate URIS - this.basicValidation = new BasicValidationVTwo(editConfig, this); + this.basicValidation = new BasicValidationVTwo(editConfig, I18n.bundle(vreq)); Map errors = basicValidation.validateUris( urisFromForm ); //Validate literals and add errors to the list of existing errors errors.putAll(basicValidation.validateLiterals( literalsFromForm )); - if( errors != null ) { + // UQAM Add empty contition + if( errors != null && !errors.isEmpty()) { validationErrors.putAll( errors); } @@ -141,9 +152,8 @@ public class MultiValueEditSubmission { } } } - /* maybe this could be static */ - public Literal createLiteral(String value, String datatypeUri, String lang) { + public Literal createLiteral_ORIG(String value, String datatypeUri, String lang) { if( datatypeUri != null ){ if( "http://www.w3.org/2001/XMLSchema:anyURI".equals(datatypeUri) ){ try { @@ -159,12 +169,27 @@ public class MultiValueEditSubmission { return ResourceFactory.createPlainLiteral(value); } - private static final String DATE_TIME_URI = XSD.dateTime.getURI(); - private static final String DATE_URI = XSD.date.getURI(); - private static final String TIME_URI = XSD.time.getURI(); - - private static DateTimeFormatter dformater = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:00"); - private static DateTimeFormatter dateFormater = DateTimeFormat.forPattern("yyyy-MM-dd"); + /* maybe this could be static */ + public Literal createLiteral(String value, String datatypeUri, String lang) { + if( datatypeUri != null && !datatypeUri.isEmpty() ){ + // UQAM Original code contained tow-dots ':' in place of '#' +// if( "http://www.w3.org/2001/XMLSchema:anyURI".equals(datatypeUri) ){ + if( XSD.anyURI.getURI().equals(datatypeUri) ){ +// try { +// return literalCreationModel.createTypedLiteral( URLEncoder.encode(value, "UTF8"), datatypeUri); + return literalCreationModel.createTypedLiteral( value, datatypeUri); +// } catch (UnsupportedEncodingException e) { +// log.error(e, e); +// } + } else if ( XSD.xstring.getURI().equals(datatypeUri) || RDF.dtLangString.getURI().equals(datatypeUri) ){ + if( lang != null && lang.length() > 0 ) return ResourceFactory.createLangLiteral(value, lang); + } + return literalCreationModel.createTypedLiteral(value, datatypeUri); + // UQAM take into account the linguistic context + } else if( lang != null && lang.length() > 0 ) + return ResourceFactory.createLangLiteral(value, lang); + return ResourceFactory.createPlainLiteral(value); + } public Map getValidationErrors(){ return validationErrors; @@ -264,12 +289,46 @@ public class MultiValueEditSubmission { for(String value:valueList) { value = N3EditUtils.stripInvalidXMLChars(value); //Add to array of literals corresponding to this variable + /* UQAM OLD if (!StringUtils.isEmpty(value)) { literalsArray.add(createLiteral( value, field.getRangeDatatypeUri(), field.getRangeLang())); } + */ + /* + * UQAM Replaced by this to take the linguistic context into consideration. + */ + if (!StringUtils.isEmpty(value)) { + String rangeLang = field.getRangeLang(); //UQAM Default value + try { + if (_vreq != null ) { + // only if the request comes from the rdfsLabelGenerator the language should be used + Boolean getLabelLanguage = false; + if (!StringUtils.isBlank(editConfig.formUrl) && editConfig.formUrl.contains("RDFSLabelGenerator")) { + getLabelLanguage = true; + } + // if the language is set in the given Literal, this language-tag should be used and remain the same + // for example when you edit an label with an langauge-tag (no matter which language is selected globally) + if (!StringUtils.isBlank(editConfig.getLiteralsInScope().get("label").get(0).getLanguage()) && getLabelLanguage) + { + rangeLang = editConfig.getLiteralsInScope().get("label").get(0).getLanguage(); + } else { // if the literal has no langauge-tag, use the language which is globally selected + rangeLang = _vreq.getLocale().getLanguage(); + if (!_vreq.getLocale().getCountry().isEmpty()) { + rangeLang += "-" + _vreq.getLocale().getCountry(); + } + } + } + + } catch (Exception e) { + } + literalsArray.add(createLiteral( + value, + field.getRangeDatatypeUri(), + rangeLang)); + } } literalsFromForm.put(var, literalsArray); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfForm.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfForm.java index 54fd7fb1b..9652d72dc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfForm.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfForm.java @@ -9,15 +9,24 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.ontology.OntModel; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.rdf.model.Statement; import org.apache.jena.shared.Lock; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.XSD; +import org.apache.commons.lang3.StringUtils; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; @@ -41,6 +50,7 @@ public class ProcessRdfForm { private EditN3GeneratorVTwo populator; private Map urisForNewResources = null; +// private VitroRequest _vreq; /** * Construct the ProcessRdfForm object. */ @@ -76,9 +86,9 @@ public class ProcessRdfForm { AdditionsAndRetractions changes; if( configuration.isUpdate() ){ - changes = editExistingStatements(configuration, submission); + changes = editExistingStatements(configuration, submission, vreq); //UQAM vreq for getting linguistic context } else { - changes = createNewStatements(configuration, submission ); + changes = createNewStatements(configuration, submission, vreq ); //UQAM vreq for getting linguistic context } changes = getMinimalChanges(changes); @@ -99,12 +109,15 @@ public class ProcessRdfForm { * any optional N3 is to originally configure the * configuration.setN3Optional() to be empty. * + * UQAM add vreq for linguistic context managing + * * @throws Exception May throw an exception if the required N3 * does not parse. + * */ private AdditionsAndRetractions createNewStatements( EditConfigurationVTwo configuration, - MultiValueEditSubmission submission) throws Exception { + MultiValueEditSubmission submission, VitroRequest vreq) throws Exception { log.debug("in createNewStatements()" ); //getN3Required and getN3Optional will return copies of the @@ -113,10 +126,10 @@ public class ProcessRdfForm { List optionalN3 = configuration.getN3Optional(); /* substitute in the form values and existing values */ - subInValuesToN3( configuration, submission, requiredN3, optionalN3, null , null); + subInValuesToN3( configuration, submission, requiredN3, optionalN3, null , null, vreq); /* parse N3 to RDF Models, No retractions since all of the statements are new. */ - return parseN3ToChange(requiredN3, optionalN3, null, null); + return parseN3ToChange(requiredN3, optionalN3, null, null, vreq, null); } /* for a list of N3 strings, substitute in the subject, predicate and object URIs @@ -140,10 +153,12 @@ public class ProcessRdfForm { * retractions are mutually diff'ed before statements are added to or * removed from the model. The explicit change check can cause problems in * more complex setups, like the automatic form building in DataStaR. + * @param vreq For getting linguistic context + */ protected AdditionsAndRetractions editExistingStatements( EditConfigurationVTwo editConfig, - MultiValueEditSubmission submission) throws Exception { + MultiValueEditSubmission submission, VitroRequest vreq) throws Exception { log.debug("editing an existing resource: " + editConfig.getObject() ); @@ -156,18 +171,18 @@ public class ProcessRdfForm { subInValuesToN3(editConfig, submission, N3RequiredAssert, N3OptionalAssert, - N3RequiredRetract, N3OptionalRetract); + N3RequiredRetract, N3OptionalRetract, vreq); return parseN3ToChange( N3RequiredAssert,N3OptionalAssert, - N3RequiredRetract, N3OptionalRetract); + N3RequiredRetract, N3OptionalRetract, vreq, editConfig); } @SuppressWarnings("unchecked") protected void subInValuesToN3( EditConfigurationVTwo editConfig, MultiValueEditSubmission submission, List requiredAsserts, List optionalAsserts, - List requiredRetracts, List optionalRetracts ) throws InsertException{ + List requiredRetracts, List optionalRetracts, VitroRequest vreq ) throws InsertException{ //need to substitute into the return to URL becase it may need new resource URIs List URLToReturnTo = Arrays.asList(submission.getEntityToReturnTo()); @@ -184,7 +199,41 @@ public class ProcessRdfForm { //Retractions does NOT get values from form. /* ******** Form submission Literals *********** */ - substituteInMultiLiterals( submission.getLiteralsFromForm(), requiredAsserts, optionalAsserts, URLToReturnTo); + /* + * UQAM Set all literals in the linguistic context + */ + Map> literalsFromForm = submission.getLiteralsFromForm(); + Set keys = literalsFromForm.keySet(); + for (String aKey : keys) { + List literalFromForm = literalsFromForm.get(aKey); + List newLiteralFromForm = new ArrayList<>(); + for (Literal aLiteral : literalFromForm) { + if (aLiteral != null) { + String aLiteratDT = aLiteral.getDatatype().getURI(); + Literal newLiteral = null; + String aText = aLiteral.getLexicalForm(); + /* + * do it only if aLiteral are xstring datatype + */ + if (RDF.dtLangString.getURI().equals(aLiteratDT) && !aLiteral.getLanguage().isEmpty()) { + newLiteral = aLiteral; + } + else if (XSD.xstring.getURI().equals(aLiteratDT) || RDF.dtLangString.getURI().equals(aLiteratDT)) { + String lang = vreq.getLocale().getLanguage(); + if (!vreq.getLocale().getCountry().isEmpty()) { + lang += "-" + vreq.getLocale().getCountry(); + } + newLiteral = ResourceFactory.createLangLiteral(aText, lang); + } else { + newLiteral = ResourceFactory.createTypedLiteral(aText, aLiteral.getDatatype()); + } + newLiteralFromForm.add(newLiteral); + } + } + literalsFromForm.replace(aKey, newLiteralFromForm); + } + + substituteInMultiLiterals( literalsFromForm, requiredAsserts, optionalAsserts, URLToReturnTo); logSubstitue( "Added form Literals", requiredAsserts, optionalAsserts, requiredRetracts, optionalRetracts); //Retractions does NOT get values from form. @@ -254,20 +303,106 @@ public class ProcessRdfForm { protected AdditionsAndRetractions parseN3ToChange( List requiredAdds, List optionalAdds, - List requiredDels, List optionalDels) throws Exception{ + List requiredDels, List optionalDels, VitroRequest vreq, EditConfigurationVTwo editConfig) throws Exception{ List adds = parseN3ToRDF(requiredAdds, REQUIRED); adds.addAll( parseN3ToRDF(optionalAdds, OPTIONAL)); - List retracts = new ArrayList(); if( requiredDels != null && optionalDels != null ){ - retracts.addAll( parseN3ToRDF(requiredDels, REQUIRED) ); - retracts.addAll( parseN3ToRDF(optionalDels, OPTIONAL) ); + String lingCxt=null; + //UQAM Taking into account the linguistic context in retract + try { + // only if the request comes from the rdfsLabelGenerator the language should be used + Boolean getLabelLanguage = false; + if (!StringUtils.isBlank(editConfig.formUrl) && editConfig.formUrl.contains("RDFSLabelGenerator")) { + getLabelLanguage = true; + } + // if the language is set in the given Literal, this language-tag should be used and remain the same + // for example when you edit an label with an langauge-tag (no matter which language is selected globally) + if (editConfig != null && !StringUtils.isBlank(editConfig.getLiteralsInScope().get("label").get(0).getLanguage()) && getLabelLanguage) { + lingCxt = editConfig.getLiteralsInScope().get("label").get(0).getLanguage(); + } else { // if the literal has no langauge-tag, use the language which is globally selected + lingCxt = vreq.getLocale().getLanguage(); + if (!vreq.getLocale().getCountry().isEmpty()) { + lingCxt += "-" + vreq.getLocale().getCountry(); + } + } + } catch (Exception e) { + } + retracts.addAll( parseN3ToRDF(requiredDels, REQUIRED, lingCxt) ); + retracts.addAll( parseN3ToRDF(optionalDels, OPTIONAL, lingCxt) ); } return new AdditionsAndRetractions(adds,retracts); } + /** + * Parse the n3Strings to a List of RDF Model objects. + * + * @param n3Strings N3 Strings to parse + * @param parseType if OPTIONAL, then don't throw exceptions on errors + * @param linguisticContext For Literals, Making parse only if the literal linguisticContext are same than linguisticContext parameter //UQAM + * If REQUIRED, then throw exceptions on errors. + * @throws Exception + */ + protected static List parseN3ToRDF( + List n3Strings, N3ParseType parseType, String linguisticContext ) throws Exception { + // Use non-linguistic version of this method if no linguisticContext is provided + if (linguisticContext == null) { + return parseN3ToRDF(n3Strings, parseType); + } + List errorMessages = new ArrayList(); + + List rdfModels = new ArrayList(); + for(String n3 : n3Strings){ + try{ + Model model = ModelFactory.createDefaultModel(); + StringReader reader = new StringReader(n3); + model.read(reader, "", "N3"); + List stmts = model.listStatements().toList(); + for (Iterator iterator = stmts.iterator(); iterator.hasNext();) { + Statement stmt = (Statement) iterator.next(); + Resource subj = stmt.getSubject(); + Property pred = stmt.getPredicate(); + RDFNode obj = stmt.getObject(); + if (obj.isLiteral()) { + Literal lit = obj.asLiteral(); + String lang = lit.getLanguage(); + if (! linguisticContext.equals(lang)) { + //UQAM Remove if linguisticContext != lang of the Literal + model.remove(subj, pred, obj); + } + } + + } + rdfModels.add( model ); + }catch(Throwable t){ + errorMessages.add(t.getMessage() + "\nN3: \n" + n3 + "\n"); + } + } + + StringBuilder errors = new StringBuilder(); + for( String errorMsg : errorMessages){ + errors.append(errorMsg).append('\n'); + } + + if( !errorMessages.isEmpty() ){ + if( REQUIRED.equals(parseType) ){ + throw new Exception("Errors processing required N3. The EditConfiguration should " + + "be setup so that if a submission passes validation, there will not be errors " + + "in the required N3.\n" + errors ); + }else if( OPTIONAL.equals(parseType) ){ + log.debug("Some Optional N3 did not parse, if a optional N3 does not parse it " + + "will be ignored. This allows optional parts of a form submission to " + + "remain unfilled out and then the optional N3 does not get values subsituted in from" + + "the form submission values. It may also be the case that there are unintentional " + + "syntax errors the optional N3." ); + log.debug(errors.toString()); + } + } + + return rdfModels; + } /** * Parse the n3Strings to a List of RDF Model objects. * @@ -479,4 +614,4 @@ public class ProcessRdfForm { private static Log log = LogFactory.getLog(ProcessRdfForm.class); -} +} \ No newline at end of file diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesOptions.java index f87dd8f48..0c7b44ef2 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesOptions.java @@ -7,6 +7,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,7 +41,8 @@ public class ChildVClassesOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception{ + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception{ // now create an empty HashMap to populate and return HashMap optionsMap = new LinkedHashMap(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesWithParent.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesWithParent.java index 135b38474..22f5763a4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesWithParent.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ChildVClassesWithParent.java @@ -1,6 +1,6 @@ /* $This file is distributed under the terms of the license in LICENSE$ */ -package edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields; +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; @@ -15,6 +15,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.dao.VClassDao; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; public class ChildVClassesWithParent implements FieldOptions { @@ -37,20 +38,24 @@ public class ChildVClassesWithParent implements FieldOptions { return this; } - @Override +/* + * UQAM-Linguistic-Management + * This method is polymorphism of getOptions(EditConfigurationVTwo editConfig,String fieldName, WebappDaoFactory wDaoFact) + * for the internationalization of word "other" in the scroling list of personHasAdvisorRelationship.ftl + */ public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception { - + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception { HashMap optionsMap = new LinkedHashMap(); // first test to see whether there's a default "leave blank" value specified with the literal options if ( ! StringUtils.isEmpty( defaultOptionLabel ) ){ optionsMap.put(LEFT_BLANK, defaultOptionLabel); } - - optionsMap.put(classUri, "Other"); - + String other_i18n = i18n.text("other"); + // first character in capital + optionsMap.put(classUri, other_i18n.substring(0, 1).toUpperCase() + other_i18n.substring(1)); VClassDao vclassDao = wDaoFact.getVClassDao(); List subClassList = vclassDao.getAllSubClassURIs(classUri); if (subClassList != null && subClassList.size() > 0) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ConstantFieldOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ConstantFieldOptions.java index 3c68ac14d..9fc7662d5 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ConstantFieldOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/ConstantFieldOptions.java @@ -10,6 +10,7 @@ import java.util.Map; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; public class ConstantFieldOptions implements FieldOptions { @@ -54,7 +55,8 @@ public class ConstantFieldOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception { + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception { // originally not auto-sorted but sorted now, and empty values not removed or replaced HashMap optionsMap = new LinkedHashMap(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/FieldOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/FieldOptions.java index b2c67f549..772b22913 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/FieldOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/FieldOptions.java @@ -6,6 +6,7 @@ import java.util.Map; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; /** * Represents an object that can return a list of options @@ -28,7 +29,8 @@ public interface FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception; + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception; /* * Certain field options may have custom sorting requirements. If no sorting requirements exist, diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaClassGroupOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaClassGroupOptions.java index b0a499005..fa627a12e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaClassGroupOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaClassGroupOptions.java @@ -6,6 +6,7 @@ import java.util.Map; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; public class IndividualsViaClassGroupOptions implements FieldOptions { @@ -27,7 +28,8 @@ public class IndividualsViaClassGroupOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception { + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception { throw new Error("not implemented"); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaObjectPropetyOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaObjectPropetyOptions.java index 6a5d30be5..0cd216ef7 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaObjectPropetyOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaObjectPropetyOptions.java @@ -11,6 +11,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -77,7 +78,8 @@ public class IndividualsViaObjectPropetyOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) { + WebappDaoFactory wDaoFact, + I18nBundle i18n) { HashMap optionsMap = new LinkedHashMap(); int optionsCount = 0; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaSearchQueryOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaSearchQueryOptions.java index ec7167c58..d752bfe28 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaSearchQueryOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaSearchQueryOptions.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -90,7 +91,8 @@ public class IndividualsViaSearchQueryOptions extends IndividualsViaVClassOption public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception { + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception { Map individualMap = new HashMap(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaVClassOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaVClassOptions.java index 80c9cf273..374d571ed 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaVClassOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/IndividualsViaVClassOptions.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -48,7 +49,8 @@ public class IndividualsViaVClassOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact) throws Exception { + WebappDaoFactory wDaoFact, + I18nBundle i18n) throws Exception { Map individualMap = new HashMap(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/RdfTypeOptions.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/RdfTypeOptions.java index 994385673..32c520fd3 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/RdfTypeOptions.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/RdfTypeOptions.java @@ -9,6 +9,7 @@ import java.util.Map; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; public class RdfTypeOptions implements FieldOptions { @@ -29,7 +30,8 @@ public class RdfTypeOptions implements FieldOptions { public Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wdf) { + WebappDaoFactory wdf, + I18nBundle i18n) { Map uriToLabel = new HashMap(); for (String uri : typeURIs) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/SelectListGeneratorVTwo.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/SelectListGeneratorVTwo.java index 3e732ce58..c554cf39e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/SelectListGeneratorVTwo.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/fields/SelectListGeneratorVTwo.java @@ -11,6 +11,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import edu.cornell.mannlib.vitro.webapp.i18n.I18n; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,7 +27,8 @@ public class SelectListGeneratorVTwo { public static Map getOptions( EditConfigurationVTwo editConfig, String fieldName, - WebappDaoFactory wDaoFact){ + WebappDaoFactory wDaoFact, + I18nBundle i18n){ if( editConfig == null ){ @@ -48,13 +51,51 @@ public class SelectListGeneratorVTwo { } try { - return field.getFieldOptions().getOptions(editConfig,fieldName,wDaoFact); + return field.getFieldOptions().getOptions(editConfig,fieldName,wDaoFact,i18n); } catch (Exception e) { log.error("Error runing getFieldOptionis()",e); return Collections.emptyMap(); } } + // UQAM Overcharge method for linguistic contexte processisng + // AWoods: This method appears to never be invoked. + public static Map getOptions( + EditConfigurationVTwo editConfig, + String fieldName, + VitroRequest vreq){ + + + if( editConfig == null ){ + log.error( "fieldToSelectItemList() must be called with a non-null EditConfigurationVTwo "); + return Collections.emptyMap(); + } + if( fieldName == null ){ + log.error( "fieldToSelectItemList() must be called with a non-null fieldName"); + return Collections.emptyMap(); + } + + FieldVTwo field = editConfig.getField(fieldName); + if (field==null) { + log.error("no field \""+fieldName+"\" found from editConfig."); + return Collections.emptyMap(); + } + + if( field.getFieldOptions() == null ){ + return Collections.emptyMap(); + } + + try { + //UQAM need vreq instead of WebappDaoFactory + Map parentClass = Collections.emptyMap(); + FieldOptions fieldOptions = field.getFieldOptions(); + return fieldOptions.getOptions(editConfig,fieldName,vreq.getWebappDaoFactory(), I18n.bundle(vreq)); + } catch (Exception e) { + log.error("Error runing getFieldOptionis()",e); + return Collections.emptyMap(); + } + } + //Methods to sort the options map // from http://forum.java.sun.com/thread.jspa?threadID=639077&messageID=4250708 diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java index f5de55789..55cff7761 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java @@ -16,6 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTw import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.IdModelSelector; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.StandardModelSelector; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption; public abstract class BaseEditConfigurationGenerator implements EditConfigurationGenerator { @@ -63,6 +64,7 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio setupModelSelectorsFromVitroRequest(vreq, editConfig); OntModel queryModel = ModelAccess.on(vreq).getOntModel(); + OntModel languageNeutralModel = vreq.getLanguageNeutralUnionFullModel(); if( editConfig.getSubjectUri() == null) editConfig.setSubjectUri( EditConfigurationUtils.getSubjectUri(vreq)); @@ -78,7 +80,10 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio editConfig.prepareForObjPropUpdate(queryModel); } else if( dataKey != null ) { // edit of a data prop statement //do nothing since the data prop form generator must take care of it - editConfig.prepareForDataPropUpdate(queryModel, vreq.getWebappDaoFactory().getDataPropertyDao()); + // Use language-neutral model to ensure that a data property statement + // is found for any literal hash, even if the UI locale is changed. + editConfig.prepareForDataPropUpdate(languageNeutralModel, + vreq.getWebappDaoFactory().getDataPropertyDao()); } else{ //this might be a create new or a form editConfig.prepareForNonUpdate(queryModel); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultAddMissingIndividualFormGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultAddMissingIndividualFormGenerator.java index 702474da2..5537cecac 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultAddMissingIndividualFormGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultAddMissingIndividualFormGenerator.java @@ -350,7 +350,7 @@ public class DefaultAddMissingIndividualFormGenerator implements EditConfigurati private void prepareForUpdate(VitroRequest vreq, HttpSession session, EditConfigurationVTwo editConfiguration) { //Here, retrieve model from - OntModel model = ModelAccess.on(session.getServletContext()).getOntModel(); + OntModel model = ModelAccess.on(vreq).getOntModel(); //if object property if(EditConfigurationUtils.isObjectProperty(EditConfigurationUtils.getPredicateUri(vreq), vreq)){ Individual objectIndividual = EditConfigurationUtils.getObjectIndividual(vreq); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultDeleteGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultDeleteGenerator.java index c329543fe..bb615c42c 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultDeleteGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultDeleteGenerator.java @@ -28,8 +28,8 @@ public class DefaultDeleteGenerator extends BaseEditConfigurationGenerator imple private Integer dataHash = 0; private DataPropertyStatement dps = null; private String dataLiteral = null; - private String propertyTemplate = "confirmDeletePropertyForm.ftl"; - private String individualTemplate = "confirmDeleteIndividualForm.ftl"; + private String propertyTemplate = "confirmDeletePropertyForm.ftl"; + private String individualTemplate = "confirmDeleteIndividualForm.ftl"; //In this case, simply return the edit configuration currently saved in session diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java index ce471ca98..adb7e8bb8 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java @@ -2,6 +2,8 @@ package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LANGUAGE_NEUTRAL; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.POLICY_NEUTRAL; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY; import java.util.ArrayList; @@ -35,6 +37,8 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.IndividualsVi import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.validators.AntiXssValidation; import edu.cornell.mannlib.vitro.webapp.i18n.I18n; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.PolicyOption; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineException; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; @@ -125,7 +129,10 @@ public class DefaultObjectPropertyFormGenerator implements EditConfigurationGene // Someday we'll need to figure out a different way of doing this. //WebappDaoFactory ctxDaoFact = ModelAccess.on( // vreq.getSession().getServletContext()).getWebappDaoFactory(); - WebappDaoFactory ctxDaoFact = vreq.getLanguageNeutralWebappDaoFactory(); +// WebappDaoFactory ctxDaoFact = vreq.getLanguageNeutralWebappDaoFactory(); + //UQAM Linguistic-Management Manage linguistic context + WebappDaoFactory ctxDaoFact = ModelAccess.on(vreq).getWebappDaoFactory(LanguageOption.LANGUAGE_AWARE, PolicyOption.POLICY_NEUTRAL); + List types = new ArrayList(); Individual subject = EditConfigurationUtils.getSubjectIndividual(vreq); @@ -460,7 +467,7 @@ public class DefaultObjectPropertyFormGenerator implements EditConfigurationGene private void prepareForUpdate(VitroRequest vreq, HttpSession session, EditConfigurationVTwo editConfiguration) { //Here, retrieve model from - OntModel model = ModelAccess.on(session.getServletContext()).getOntModel(); + OntModel model = ModelAccess.on(vreq).getOntModel(); //if object property if(EditConfigurationUtils.isObjectProperty(EditConfigurationUtils.getPredicateUri(vreq), vreq)){ Individual objectIndividual = EditConfigurationUtils.getObjectIndividual(vreq); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java index 15782747b..3f7e912bb 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpSession; @@ -41,6 +42,7 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.FoafNameToRdfsLabelPreprocessor; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.ManageLabelsForIndividualPreprocessor; import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; +import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DataPropertyStatementTemplateModel; /** @@ -202,12 +204,12 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen private void addFormSpecificData(EditConfigurationVTwo config, VitroRequest vreq) { - //Get all language codes/labels in the system, and this list is sorted by language name - List> locales = this.getLocales(vreq); + //the labels already added by the user + ArrayList existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq); + //Get language codes/labels for languages present in the existing labels + List> locales = this.getLocales(vreq, existingLabels); //Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal - HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales); - //the labels already added by the user - ArrayList existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq); + HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales); int numberExistingLabels = existingLabels.size(); //existing labels keyed by language name and each of the list of labels is sorted by language name HashMap> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, config, vreq); @@ -224,6 +226,20 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen config.addFormSpecificData("displayRemoveLink", (numberExistingLabels > 1)); + // get current selected locale + String rangeLang = vreq.getLocale().getLanguage(); + if (!vreq.getLocale().getCountry().isEmpty()) { + rangeLang += "-" + vreq.getLocale().getCountry(); + } + + // check if locale already has an entry (label) + boolean localeEntryExisting = true; + for (HashMap tmp : availableLocalesForAdd) { + if (tmp.get("code").equals(rangeLang)) localeEntryExisting = false; + } + config.addFormSpecificData("localeEntryExisting", localeEntryExisting); + config.addFormSpecificData("currentSelectedLocale", rangeLang); + //How do we edit? Will need to see config.addFormSpecificData("deleteWebpageUrl", "/edit/primitiveDelete"); @@ -359,8 +375,9 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen ArrayList labels = new ArrayList(); try { - //We want to get the labels for all the languages, not just the display language - ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); + // Get results filtered to current locale so as to be consistent + // with other editing forms. + ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); while (results.hasNext()) { QuerySolution soln = results.nextSolution(); Literal nodeLiteral = soln.get("label").asLiteral(); @@ -387,30 +404,32 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen return template; } + //get locales present in list of literals + public List> getLocales(VitroRequest vreq, + List existingLiterals) { + Set locales = new HashSet(); + for(Literal literal : existingLiterals) { + String language = literal.getLanguage(); + if(!StringUtils.isEmpty(language)) { + locales.add(LanguageFilteringUtils.languageToLocale(language)); + } + } + if (locales.isEmpty()) { + return Collections.emptyList(); + } + List> list = new ArrayList>(); + Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); + for (Locale locale : locales) { + try { + list.add(buildLocaleMap(locale, currentLocale)); + } catch (FileNotFoundException e) { + log.warn("Can't show locale '" + locale + "': " + e); + } + } - - //get locales - public List> getLocales(VitroRequest vreq) { - List selectables = SelectedLocale.getSelectableLocales(vreq); - if (selectables.isEmpty()) { - return Collections.emptyList(); - } - List> list = new ArrayList>(); - Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); - for (Locale locale : selectables) { - try { - list.add(buildLocaleMap(locale, currentLocale)); - } catch (FileNotFoundException e) { - log.warn("Can't show the Locale selector for '" + locale - + "': " + e); - } - } - - return list; + return list; } - - public HashMap getFullCodeToLanguageNameMap(List> localesList) { HashMap codeToLanguageMap = new HashMap(); for(Map locale: localesList) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/NewIndividualFormGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/NewIndividualFormGenerator.java index 8c251467b..fff4d6732 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/NewIndividualFormGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/NewIndividualFormGenerator.java @@ -233,7 +233,7 @@ public class NewIndividualFormGenerator implements EditConfigurationGenerator { private void prepareForUpdate(VitroRequest vreq, HttpSession session, EditConfigurationVTwo editConfiguration) { //Here, retrieve model from - OntModel model = ModelAccess.on(session.getServletContext()).getOntModel(); + OntModel model = ModelAccess.on(vreq).getOntModel(); //This form is always doing a non-update editConfiguration.prepareForNonUpdate( model ); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/RDFSLabelGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/RDFSLabelGenerator.java index 3a3e7f371..13e845a86 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/RDFSLabelGenerator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/RDFSLabelGenerator.java @@ -298,7 +298,7 @@ public class RDFSLabelGenerator implements EditConfigurationGenerator { private void prepareForUpdate(VitroRequest vreq, HttpSession session, EditConfigurationVTwo editConfiguration) { //Here, retrieve model from - OntModel model = ModelAccess.on(session.getServletContext()).getOntModel(); + OntModel model = ModelAccess.on(vreq).getOntModel(); if( editConfiguration.isDataPropertyUpdate() ){ editConfiguration.prepareForDataPropUpdate(model, vreq.getWebappDaoFactory().getDataPropertyDao()); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguage.java new file mode 100644 index 000000000..acd94dfae --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguage.java @@ -0,0 +1,92 @@ +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils; + +/** + * A model change preprocessor that discards triples with language tags + * in the retractionsModel that do not match the specified language, unless + * the additionsModel also contains a new value for the same subject and + * predicate in that language, or no values in any language are added for the + * same subject and predicate (such as when an entire resource is deleted) . + */ +public class LimitRemovalsToLanguage implements ModelChangePreprocessor { + + private static final Log log = LogFactory.getLog(LimitRemovalsToLanguage.class); + private String language; + + /** + * @param locale the Java locale object representing the language + * to which edits should be limited. May not be null. + */ + public LimitRemovalsToLanguage(Locale locale) { + if(locale == null) { + throw new IllegalArgumentException("Locale may not be null."); + } + this.language = LanguageFilteringUtils.localeToLanguage(locale); + } + + /** + * @param language string representing the RDF language tag to which + * edits should be limited. May not be null. + */ + public LimitRemovalsToLanguage(String language) { + if(language == null) { + throw new IllegalArgumentException("Language may not be null."); + } + this.language = language; + } + + @Override + public void preprocess(Model retractionsModel, Model additionsModel, + HttpServletRequest request) { + log.debug("limiting changes to " + language); + List eliminatedRetractions = new ArrayList(); + StmtIterator sit = retractionsModel.listStatements(); + while(sit.hasNext()) { + Statement stmt = sit.next(); + if(stmt.getObject().isLiteral()) { + Literal lit = stmt.getObject().asLiteral(); + if(!StringUtils.isEmpty(lit.getLanguage()) + && !lit.getLanguage().equals(language)) { + boolean eliminateRetraction = true; + StmtIterator replacements = additionsModel + .listStatements(stmt.getSubject(), + stmt.getPredicate(), (RDFNode) null); + if(!replacements.hasNext()) { + eliminateRetraction = false; + } else { + while(replacements.hasNext()) { + Statement replacement = replacements.next(); + if(replacement.getObject().isLiteral() + && lit.getLanguage().equals(replacement + .getObject().asLiteral() + .getLanguage())) { + eliminateRetraction = false; + } + } + } + if(eliminateRetraction) { + eliminatedRetractions.add(stmt); + } + } + } + } + retractionsModel.remove(eliminatedRetractions); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java index 20b894878..c33b70279 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java @@ -169,7 +169,7 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet { templateData.put("editConfiguration", etm); templateData.put("editSubmission", submissionTemplateModel); //Corresponding to original note for consistency with selenium tests and 1.1.1 - templateData.put("title", "Edit"); + templateData.put("title", etm.getPageTitle()); templateData.put("submitUrl", getSubmissionUrl(vreq)); templateData.put("cancelUrl", etm.getCancelUrl()); templateData.put("editKey", editConfig.getEditKey()); @@ -376,7 +376,7 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet { String predicateUri = EditConfigurationUtils.getPredicateUri(vreq); String formParam = getFormParam(vreq); //if no form parameter, then predicate uri and subject uri must both be populated - if ((formParam == null || "".equals(formParam)) && !isDeleteForm(vreq)) { + if ((formParam == null || "".equals(formParam)) && !isDeleteForm(vreq)) { if ((predicateUri == null || predicateUri.trim().length() == 0)) { return true; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/PostEditCleanupController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/PostEditCleanupController.java index 64f605103..b1336b50f 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/PostEditCleanupController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/PostEditCleanupController.java @@ -74,9 +74,11 @@ public class PostEditCleanupController extends FreemarkerHttpServlet{ //The submission for getting the entity to return to is not retrieved from the session but needs //to be created - as it is in processRdfForm3.jsp if( entityToReturnTo == null ){ - //this will not work if there entityToReturnTo has a new resource URI, - //in that case entityToReturnTo should not have been passed to this method as null - MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq.getParameterMap(), editConfig); + // this will not work if there entityToReturnTo has a new resource URI, + // in that case entityToReturnTo should not have been passed to this method as null + // UQAM-Linguistic-Management + // MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq.getParameterMap(), editConfig); + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, editConfig); entityToReturnTo = N3EditUtils.processEntityToReturnTo(editConfig, submission, vreq); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java index 790781a95..1c82b034b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java @@ -8,10 +8,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.servlet.annotation.WebServlet; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Property; @@ -37,8 +38,7 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.MultiValueEditSubmis import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.N3EditUtils; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.ProcessRdfForm; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.RdfLiteralHash; - -import javax.servlet.annotation.WebServlet; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.LimitRemovalsToLanguage; /** * This servlet will convert a request to an EditSubmission, @@ -66,7 +66,9 @@ public class ProcessRdfFormController extends FreemarkerHttpServlet{ return handleMissingConfiguration(vreq); //get the EditSubmission - MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq.getParameterMap(), configuration); + // MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq.getParameterMap(), configuration); + // Modified by UQAM-Linguistic-Management + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, configuration); EditSubmissionUtils.putEditSubmissionInSession(vreq.getSession(), submission); //if errors, return error response @@ -101,8 +103,11 @@ public class ProcessRdfFormController extends FreemarkerHttpServlet{ if( configuration.isUseDependentResourceDelete() ) changes = ProcessRdfForm.addDependentDeletes(changes, queryModel); + // prevent form from removing literals in languages other than the one + // associated with the current request + configuration.addModelChangePreprocessor(new LimitRemovalsToLanguage(vreq.getLocale())); N3EditUtils.preprocessModels(changes, configuration, vreq); - + ProcessRdfForm.applyChangesToWriteModel(changes, queryModel, writeModel, N3EditUtils.getEditorUri(vreq) ); //Here we are trying to get the entity to return to URL, diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/ModelSwitcher.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/ModelSwitcher.java index 7c0813275..7e5f49a58 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/ModelSwitcher.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/ModelSwitcher.java @@ -35,6 +35,7 @@ import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService; import edu.cornell.mannlib.vitro.webapp.modelaccess.impl.RequestModelAccessImpl; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; /** @@ -88,8 +89,10 @@ public class ModelSwitcher { // If they asked for the display model, give it to them. if (isParameterPresent(vreq, SWITCH_TO_DISPLAY_MODEL)) { - OntModel mainOntModel = ModelAccess.on(_context).getOntModel(DISPLAY); - OntModel tboxOntModel = ModelAccess.on(_context).getOntModel(DISPLAY_TBOX); + OntModel mainOntModel = LanguageFilteringUtils.wrapOntModelInALanguageFilter( + ModelAccess.on(vreq).getOntModel(DISPLAY), vreq); + OntModel tboxOntModel = LanguageFilteringUtils.wrapOntModelInALanguageFilter( + ModelAccess.on(vreq).getOntModel(DISPLAY_TBOX), vreq); setSpecialWriteModel(vreq, mainOntModel); vreq.setAttribute(VitroRequest.ID_FOR_ABOX_MODEL, VitroModelSource.ModelName.DISPLAY.toString()); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/PageRoutingFilter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/PageRoutingFilter.java index d956483f6..0f739361c 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/PageRoutingFilter.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/filters/PageRoutingFilter.java @@ -63,6 +63,7 @@ public class PageRoutingFilter implements Filter{ // get URL without hostname or servlet context HttpServletResponse response = (HttpServletResponse) arg1; HttpServletRequest req = (HttpServletRequest) arg0; + String path = req.getRequestURI().substring(req.getContextPath().length()); // check for first part of path diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java index c0ab92d25..719b041ab 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java @@ -6,6 +6,7 @@ import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import javax.servlet.ServletContext; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java index 85d6282cf..d4899c19a 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java @@ -304,7 +304,7 @@ public class FreemarkerConfigurationImpl extends Configuration { urls.put("home", UrlBuilder.getHomeUrl()); urls.put("about", UrlBuilder.getUrl(Route.ABOUT)); urls.put("search", UrlBuilder.getUrl(Route.SEARCH)); - urls.put("customsearch", UrlBuilder.getUrl(Route.CUSTOMSEARCH)); + urls.put("extendedsearch", UrlBuilder.getUrl(Route.EXTENDED_SEARCH)); urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE)); urls.put("login", UrlBuilder.getLoginUrl()); urls.put("logout", UrlBuilder.getLogoutUrl()); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18n.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18n.java index 8f07f7301..85f58495d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18n.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18n.java @@ -8,6 +8,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; +import java.util.Objects; import java.util.ResourceBundle; import java.util.SortedSet; import java.util.TreeSet; @@ -20,7 +21,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings; import edu.cornell.mannlib.vitro.webapp.utils.developer.Key; @@ -49,12 +52,27 @@ public class I18n { * This is where the work gets done. Not declared final, so it can be * modified in unit tests. */ - private static I18n instance = new I18n(); + private static I18n instance; + + private final ServletContext ctx; + + protected I18n(ServletContext ctx) { + this.ctx = ctx; + } // ---------------------------------------------------------------------- // Static methods // ---------------------------------------------------------------------- + /** + * This setup method must be called before I18n static methods can be used. + * It is currently called from LocaleSelectionSetup.contextInitialized, which + * should ensure it is called when the context is initialized. + */ + public static void setup(ServletContext ctx) { + I18n.instance = new I18n(ctx); + } + /** * A convenience method to get a bundle and format the text. */ @@ -72,19 +90,26 @@ public class I18n { } /** - * Get a I18nBundle by this name. + * Get a request I18nBundle by this name. */ public static I18nBundle bundle(String bundleName, HttpServletRequest req) { return instance.getBundle(bundleName, req); } /** - * Get the default I18nBundle. + * Get the default request I18nBundle. */ public static I18nBundle bundle(HttpServletRequest req) { return instance.getBundle(DEFAULT_BUNDLE_NAME, req); } + /** + * Get the default context I18nBundle for preferred locales. + */ + public static I18nBundle bundle(List preferredLocales) { + return instance.getBundle(DEFAULT_BUNDLE_NAME, preferredLocales); + } + // ---------------------------------------------------------------------- // The instance // ---------------------------------------------------------------------- @@ -106,25 +131,59 @@ public class I18n { * Declared 'protected' so it can be overridden in unit tests. */ protected I18nBundle getBundle(String bundleName, HttpServletRequest req) { - log.debug("Getting bundle '" + bundleName + "'"); + log.debug("Getting request bundle '" + bundleName + "'"); + checkDevelopmentMode(req); + checkForChangeInThemeDirectory(req); + + Locale locale = req.getLocale(); + + return getBundle(bundleName, locale); + } + + /** + * Get an I18nBundle by this name. The context provides the selectable + * Locale, the application directory, the theme directory and the + * development mode flag. Choosing matching locale from context by + * provided preferred locales. + * + * If the context indicates that the system is in development mode, then the + * cache is cleared on each request. + * + * If the theme directory has changed, the cache is cleared. + * + * Declared 'protected' so it can be overridden in unit tests. + */ + protected I18nBundle getBundle(String bundleName, List preferredLocales) { + log.debug("Getting context bundle '" + bundleName + "'"); + + checkDevelopmentMode(); + checkForChangeInThemeDirectory(ctx); + + Locale locale = SelectedLocale.getPreferredLocale(ctx, preferredLocales); + + return getBundle(bundleName, locale); + } + + /** + * Get an I18nBundle by this name, context, and locale. + */ + private I18nBundle getBundle(String bundleName, Locale locale) { I18nLogger i18nLogger = new I18nLogger(); try { - checkDevelopmentMode(req); - checkForChangeInThemeDirectory(req); - String dir = themeDirectory.get(); - ServletContext ctx = req.getSession().getServletContext(); - ResourceBundle.Control control = new ThemeBasedControl(ctx, dir); ResourceBundle rb = ResourceBundle.getBundle(bundleName, - req.getLocale(), control); + locale, control); + return new I18nBundle(bundleName, rb, i18nLogger); } catch (MissingResourceException e) { log.warn("Didn't find text bundle '" + bundleName + "'"); + return I18nBundle.emptyBundle(bundleName, i18nLogger); } catch (Exception e) { log.error("Failed to create text bundle '" + bundleName + "'", e); + return I18nBundle.emptyBundle(bundleName, i18nLogger); } } @@ -139,6 +198,16 @@ public class I18n { } } + /** + * If we are in development mode, clear the cache. + */ + private void checkDevelopmentMode() { + if (DeveloperSettings.getInstance().getBoolean(Key.I18N_DEFEAT_CACHE)) { + log.debug("In development mode - clearing the cache."); + ResourceBundle.clearCache(); + } + } + /** * If the theme directory has changed from before, clear the cache of all * ResourceBundles. @@ -153,6 +222,30 @@ public class I18n { } } + /** + * If we have a complete model access and the theme directory has changed + * from before, clear the cache of all ResourceBundles. + */ + private void checkForChangeInThemeDirectory(ServletContext ctx) { + WebappDaoFactory wdf = ModelAccess.on(ctx) + .getWebappDaoFactory(); + // Only applicable if context has a complete model access + if (Objects.nonNull(wdf) + && Objects.nonNull(wdf.getApplicationDao()) + && Objects.nonNull(wdf.getApplicationDao().getApplicationBean())) { + String currentDir = wdf + .getApplicationDao() + .getApplicationBean() + .getThemeDir(); + String previousDir = themeDirectory.getAndSet(currentDir); + if (!currentDir.equals(previousDir)) { + log.debug("Theme directory changed from '" + previousDir + "' to '" + + currentDir + "' - clearing the cache."); + ResourceBundle.clearCache(); + } + } + } + /** Only clear the cache one time per request. */ private void clearCacheOnRequest(HttpServletRequest req) { if (req.getAttribute(ATTRIBUTE_CACHE_CLEARED) != null) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18nBundle.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18nBundle.java index 0d57cb8fd..030edaf84 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18nBundle.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/I18nBundle.java @@ -10,6 +10,9 @@ import java.util.ResourceBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings; +import edu.cornell.mannlib.vitro.webapp.utils.developer.Key; + /** * A wrapper for a ResourceBundle that will not throw an exception, no matter * what string you request. @@ -20,7 +23,9 @@ import org.apache.commons.logging.LogFactory; */ public class I18nBundle { private static final Log log = LogFactory.getLog(I18nBundle.class); - + private static final String startSep = "\u25a4"; + private static final String endSep = "\u25a5"; + public static final String intSep = "\u25a6"; private static final String MESSAGE_BUNDLE_NOT_FOUND = "Text bundle ''{0}'' not found."; private static final String MESSAGE_KEY_NOT_FOUND = "Text bundle ''{0}'' has no text for ''{1}''"; @@ -75,13 +80,27 @@ public class I18nBundle { key); log.warn(message); textString = "ERROR: " + message; - } - String result = formatString(textString, parameters); + } + String message = formatString(textString, parameters); if (i18nLogger != null) { - i18nLogger.log(bundleName, key, parameters, textString, result); + i18nLogger.log(bundleName, key, parameters, textString, message); } - return result; + if (isNeedExportInfo()) { + String separatedArgs = ""; + for (int i = 0; i < parameters.length; i++) { + separatedArgs += parameters[i] + intSep; + } + + return startSep + key + intSep + textString + intSep + separatedArgs + message + endSep; + } else { + return message; + } + + } + + private static boolean isNeedExportInfo() { + return DeveloperSettings.getInstance().getBoolean(Key.I18N_ONLINE_TRANSLATION); } private static String formatString(String textString, Object... parameters) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/freemarker/I18nStringTemplateModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/freemarker/I18nStringTemplateModel.java index c7b129349..8d16f6c03 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/freemarker/I18nStringTemplateModel.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/freemarker/I18nStringTemplateModel.java @@ -8,6 +8,9 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; +import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings; +import edu.cornell.mannlib.vitro.webapp.utils.developer.Key; import freemarker.template.TemplateMethodModelEx; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; @@ -65,7 +68,11 @@ public class I18nStringTemplateModel implements TemplateMethodModelEx, .get(i)); } try { - return MessageFormat.format(textString, unwrappedArgs); + if(isOnlineTranslationsEnabled()) { + return getOnlineTranslationsFormattedMessage(textString, unwrappedArgs); + } else { + return MessageFormat.format(textString, unwrappedArgs); + } } catch (Exception e) { String message = "Can't format '" + key + "' from bundle '" + bundleName + "', wrong argument types: " + args @@ -75,5 +82,40 @@ public class I18nStringTemplateModel implements TemplateMethodModelEx, } } } + + /** + * Splits preProcessed string, formats message with arguments, lists arguments before message + * and combines preProcessed string back to be used with online translations. + * Substitutes arguments in message which is a part of preProcessed string + * @param preProcessed String "startSep + key + intSep + textString + intSep + message + endSep" + * @param arguments that should be listed before message and substituted in the message itself + * @return + */ + private String getOnlineTranslationsFormattedMessage(String preProcessed, Object[] args) { + String[] parts = preProcessed.split(I18nBundle.intSep); + final int messageIndex = parts.length -1; + String message = MessageFormat.format(parts[messageIndex], args); + String[] arguments = convertToArrayOfStrings(args); + parts[messageIndex] = ""; + String result = String.join(I18nBundle.intSep, parts) + + String.join(I18nBundle.intSep, arguments) + + I18nBundle.intSep + message ; + return result; + } + + private String[] convertToArrayOfStrings(Object[] args) { + String[] result = new String[args.length]; + for (int i = 0; i < result.length; i++) + if (args[i] != null) { + result[i] = args[i].toString(); + } else { + result[i] = ""; + } + return result; + } + + private static boolean isOnlineTranslationsEnabled() { + return DeveloperSettings.getInstance().getBoolean(Key.I18N_ONLINE_TRANSLATION); + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java index 3fd949e87..d0bff6349 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java @@ -89,8 +89,8 @@ public class LocaleSelectionDataGetter implements DataGetter { Locale currentLocale) throws FileNotFoundException { Map map = new HashMap<>(); map.put("code", locale.toString()); - map.put("label", locale.getDisplayName(currentLocale)); - map.put("imageUrl", LocaleSelectorUtilities.getImageUrl(vreq, locale)); + map.put("label", locale.getDisplayLanguage(locale)); + map.put("country", locale.getDisplayCountry(locale)); map.put("selected", currentLocale.equals(locale)); return map; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionSetup.java index 6530d8aff..596880edd 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionSetup.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.i18n.I18n; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; /** @@ -48,6 +49,10 @@ public class LocaleSelectionSetup implements ServletContextListener { ss = StartupStatus.getBean(ctx); props = ConfigurationProperties.getBean(sce); + // Instantiate I18n instance to afford access to ServletContext + // when requesting a bundle with or without a VitroRequest + I18n.setup(ctx); + readProperties(); if (isForcing() && hasSelectables()) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectorUtilities.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectorUtilities.java deleted file mode 100644 index 111181574..000000000 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectorUtilities.java +++ /dev/null @@ -1,58 +0,0 @@ -/* $This file is distributed under the terms of the license in LICENSE$ */ - -package edu.cornell.mannlib.vitro.webapp.i18n.selection; - -import java.io.FileNotFoundException; -import java.util.Locale; -import java.util.Set; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; - -/** - * Some static methods for the GUI aspects of selecting a Locale. - */ -public class LocaleSelectorUtilities { - private static final Log log = LogFactory - .getLog(LocaleSelectorUtilities.class); - - /** - * Look in the current theme directory to find a selection image for this - * Locale. - * - * Images are expected at a resource path like - * /[themeDir]/i18n/images/select_locale_[locale_code].* - * - * For example, /themes/wilma/i18n/images/select_locale_en.png - * /themes/wilma/i18n/images/select_locale_en.JPEG - * /themes/wilma/i18n/images/select_locale_en.gif - * - * To create a proper URL, prepend the context path. - */ - public static String getImageUrl(VitroRequest vreq, Locale locale) - throws FileNotFoundException { - String filename = "select_locale_" + locale + "."; - - String themeDir = vreq.getAppBean().getThemeDir(); - String imageDirPath = "/" + themeDir + "i18n/images/"; - - ServletContext ctx = vreq.getSession().getServletContext(); - @SuppressWarnings("unchecked") - Set resourcePaths = ctx.getResourcePaths(imageDirPath); - if (resourcePaths != null) { - for (String resourcePath : resourcePaths) { - if (resourcePath.contains(filename)) { - String fullPath = vreq.getContextPath() + resourcePath; - log.debug("Found image for " + locale + " at '" + fullPath - + "'"); - return fullPath; - } - } - } - throw new FileNotFoundException("Can't find an image for " + locale); - } -} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/SelectedLocale.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/SelectedLocale.java index 4605436a4..f044546e0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/SelectedLocale.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/i18n/selection/SelectedLocale.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.Optional; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -61,13 +63,10 @@ public abstract class SelectedLocale { ServletContext ctx = session.getServletContext(); Object ctxInfo = ctx.getAttribute(ATTRIBUTE_NAME); - if (ctxInfo instanceof ContextSelectedLocale) { - Locale forcedLocale = ((ContextSelectedLocale) ctxInfo) - .getForcedLocale(); - if (forcedLocale != null) { - log.debug("Found forced locale in the context: " + forcedLocale); - return forcedLocale; - } + + Optional forcedLocale = getForcedLocale(ctxInfo); + if (forcedLocale.isPresent()) { + return forcedLocale.get(); } Object sessionInfo = session.getAttribute(ATTRIBUTE_NAME); @@ -92,7 +91,62 @@ public abstract class SelectedLocale { } } - return null; + Locale fallbackLocale = getFallbackLocale(); + log.debug("Using fallback locale as default: " + fallbackLocale); + return fallbackLocale; + } + + /** + * Get the overriding Locale to use, which is the first of these to be found: + *
    + *
  • The forced Locale in the servlet context
  • + *
  • The first selectable locale matching a preferred locale
  • + *
  • The first of the preferred locale
  • + *
  • null
  • + *
+ */ + public static Locale getOverridingLocale(ServletContext ctx, List preferredLocales) { + Object ctxInfo = ctx.getAttribute(ATTRIBUTE_NAME); + Optional forcedLocale = getForcedLocale(ctxInfo); + if (forcedLocale.isPresent()) { + return forcedLocale.get(); + } + + if (ctxInfo instanceof ContextSelectedLocale) { + List selectableLocales = ((ContextSelectedLocale) ctxInfo) + .getSelectableLocales(); + + if (Objects.nonNull(selectableLocales) && Objects.nonNull(preferredLocales)) { + for (Locale preferredLocal : preferredLocales) { + for (Locale selectableLocale : selectableLocales) { + if (selectableLocale.equals(preferredLocal)) { + log.debug("Using first matching selectable locale from context: " + + selectableLocale); + return selectableLocale; + } + } + } + } + } + + if (Objects.nonNull(preferredLocales) && !preferredLocales.isEmpty()) { + Locale preferredLocal = preferredLocales.get(0); + log.debug("Using first preferred locale as default: " + + preferredLocal); + return preferredLocal; + } + + Locale fallbackLocale = getFallbackLocale(); + log.debug("Using fallback locale as default: " + fallbackLocale); + return fallbackLocale; + } + + /** + * @return a default locale to use if no other criteria for selecting a + * different one exist. + */ + public static Locale getFallbackLocale() { + return new Locale("en", "US"); } /** @@ -121,6 +175,42 @@ public abstract class SelectedLocale { return Locale.getDefault(); } + /** + * Get the preferred Locale to use, which is the first of these to be found: + *
    + *
  • The forced Locale in the servlet context
  • + *
  • The first selectable locale matching a preferred locale
  • + *
  • The first of the preferred locale
  • + *
  • The default Locale for the JVM
  • + *
+ */ + public static Locale getPreferredLocale(ServletContext ctx, List preferredLocales) { + Locale overridingLocale = getOverridingLocale(ctx, preferredLocales); + + if (overridingLocale != null) { + return overridingLocale; + } + + log.debug("Using default locale: " + Locale.getDefault()); + return Locale.getDefault(); + } + + /** + * Check for forced locale on the context. + */ + private static Optional getForcedLocale(Object ctxInfo) { + if (ctxInfo instanceof ContextSelectedLocale) { + Locale forcedLocale = ((ContextSelectedLocale) ctxInfo) + .getForcedLocale(); + if (forcedLocale != null) { + log.debug("Found forced locale in the context: " + forcedLocale); + return Optional.of(forcedLocale); + } + } + + return Optional.empty(); + } + /** * Store a list of selectable Locales in the servlet context, so we can * easily build the selection panel in the GUI. Clears any forced locale. diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelNames.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelNames.java index 245ca9908..10493a0c3 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelNames.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelNames.java @@ -13,20 +13,27 @@ public class ModelNames { public static final String ABOX_ASSERTIONS = "http://vitro.mannlib.cornell.edu/default/vitro-kb-2"; public static final String ABOX_INFERENCES = "http://vitro.mannlib.cornell.edu/default/vitro-kb-inf"; public static final String ABOX_UNION = "vitro:aboxOntModel"; + public static final String ABOX_ASSERTIONS_FIRSTTIME_BACKUP = ABOX_ASSERTIONS + "FirsttimeBackup"; public static final String TBOX_ASSERTIONS = "http://vitro.mannlib.cornell.edu/default/asserted-tbox"; public static final String TBOX_INFERENCES = "http://vitro.mannlib.cornell.edu/default/inferred-tbox"; public static final String TBOX_UNION = "vitro:tboxOntModel"; + public static final String TBOX_ASSERTIONS_FIRSTTIME_BACKUP = TBOX_ASSERTIONS + "FirsttimeBackup"; public static final String FULL_ASSERTIONS = "vitro:baseOntModel"; public static final String FULL_INFERENCES = "vitro:inferenceOntModel"; public static final String FULL_UNION = "vitro:jenaOntModel"; public static final String APPLICATION_METADATA = "http://vitro.mannlib.cornell.edu/default/vitro-kb-applicationMetadata"; + public static final String APPLICATION_METADATA_FIRSTTIME_BACKUP = APPLICATION_METADATA + "FirsttimeBackup"; public static final String USER_ACCOUNTS = "http://vitro.mannlib.cornell.edu/default/vitro-kb-userAccounts"; + public static final String USER_ACCOUNTS_FIRSTTIME_BACKUP = USER_ACCOUNTS + "FirsttimeBackup"; public static final String DISPLAY = "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata"; + public static final String DISPLAY_FIRSTTIME_BACKUP = DISPLAY + "FirsttimeBackup"; public static final String DISPLAY_TBOX = "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadataTBOX"; + public static final String DISPLAY_TBOX_FIRSTTIME_BACKUP = DISPLAY_TBOX + "FirsttimeBackup"; public static final String DISPLAY_DISPLAY = "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata-displayModel"; + public static final String DISPLAY_DISPLAY_FIRSTTIME_BACKUP = DISPLAY_DISPLAY + "FirsttimeBackup"; /** * A map of the URIS, keyed by their short names, intended only for display @@ -39,17 +46,24 @@ public class ModelNames { map.put("ABOX_ASSERTIONS", ABOX_ASSERTIONS); map.put("ABOX_INFERENCES", ABOX_INFERENCES); map.put("ABOX_UNION", ABOX_UNION); + map.put("ABOX_ASSERTIONS_FIRSTTIME_BACKUP", ABOX_ASSERTIONS_FIRSTTIME_BACKUP); map.put("TBOX_ASSERTIONS", TBOX_ASSERTIONS); map.put("TBOX_INFERENCES", TBOX_INFERENCES); map.put("TBOX_UNION", TBOX_UNION); + map.put("TBOX_ASSERTIONS_FIRSTTIME_BACKUP", TBOX_ASSERTIONS_FIRSTTIME_BACKUP); map.put("FULL_ASSERTIONS", FULL_ASSERTIONS); map.put("FULL_INFERENCES", FULL_INFERENCES); map.put("FULL_UNION", FULL_UNION); map.put("APPLICATION_METADATA", APPLICATION_METADATA); + map.put("APPLICATION_METADATA_FIRSTTIME_BACKUP", APPLICATION_METADATA_FIRSTTIME_BACKUP); map.put("USER_ACCOUNTS", USER_ACCOUNTS); + map.put("USER_ACCOUNTS_FIRSTTIME_BACKUP", USER_ACCOUNTS_FIRSTTIME_BACKUP); map.put("DISPLAY", DISPLAY); + map.put("DISPLAY_FIRSTTIME_BACKUP", DISPLAY_FIRSTTIME_BACKUP); map.put("DISPLAY_TBOX", DISPLAY_TBOX); + map.put("DISPLAY_TBOX_FIRSTTIME_BACKUP", DISPLAY_TBOX_FIRSTTIME_BACKUP); map.put("DISPLAY_DISPLAY", DISPLAY_DISPLAY); + map.put("DISPLAY_DISPLAY_FIRSTTIME_BACKUP", DISPLAY_DISPLAY_FIRSTTIME_BACKUP); return Collections.unmodifiableMap(map); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/tripleSource/ConfigurationTripleSource.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/tripleSource/ConfigurationTripleSource.java index fa1725af5..fe766329b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/tripleSource/ConfigurationTripleSource.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modules/tripleSource/ConfigurationTripleSource.java @@ -6,6 +6,14 @@ import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_DISPLAY; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_TBOX; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.USER_ACCOUNTS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.USER_ACCOUNTS_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_TBOX_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_DISPLAY_FIRSTTIME_BACKUP; + import org.apache.jena.rdf.model.ModelMaker; @@ -18,7 +26,10 @@ public abstract class ConfigurationTripleSource implements TripleSource { * add memory-mapping. */ protected static final String[] CONFIGURATION_MODELS = { DISPLAY, - DISPLAY_TBOX, DISPLAY_DISPLAY, USER_ACCOUNTS }; + DISPLAY_TBOX, DISPLAY_DISPLAY, USER_ACCOUNTS, ABOX_ASSERTIONS_FIRSTTIME_BACKUP, + TBOX_ASSERTIONS_FIRSTTIME_BACKUP, APPLICATION_METADATA_FIRSTTIME_BACKUP, + USER_ACCOUNTS_FIRSTTIME_BACKUP, DISPLAY_FIRSTTIME_BACKUP, + DISPLAY_TBOX_FIRSTTIME_BACKUP, DISPLAY_DISPLAY_FIRSTTIME_BACKUP }; /** * These decorators are added to a Configuration ModelMaker, regardless of diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/RDFService.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/RDFService.java index babac6c52..06a17e4f4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/RDFService.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/RDFService.java @@ -10,6 +10,8 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelChangedListener; import org.apache.jena.rdf.model.RDFNode; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; + /** * Interface for API to write, read, and update Vitro's RDF store, with support * to allow listening, logging and auditing. @@ -263,4 +265,10 @@ public interface RDFService { * multiple invocations do not cause an error. */ public void close(); + /** + * UQAM-Bug-Correction Useful among other things to transport the linguistic context in the service + * @param vitroRequest + */ + public void setVitroRequest(VitroRequest vitroRequest); + public VitroRequest getVitroRequest(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractGraphDecorator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractGraphDecorator.java new file mode 100644 index 000000000..c2f9faa67 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractGraphDecorator.java @@ -0,0 +1,136 @@ +package edu.cornell.mannlib.vitro.webapp.rdfservice.adapters; + +import org.apache.jena.graph.Capabilities; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.GraphEventManager; +import org.apache.jena.graph.GraphStatisticsHandler; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.TransactionHandler; +import org.apache.jena.graph.Triple; +import org.apache.jena.shared.AddDeniedException; +import org.apache.jena.shared.DeleteDeniedException; +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.util.iterator.ExtendedIterator; + +import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString; + +/** + * The base class for a delegating graph decorator. + * + * As implemented, all methods simply delegate to the inner graph. Subclasses + * should override selected methods to provide functionality. + */ +public abstract class AbstractGraphDecorator implements + Graph { + + private final Graph inner; + + protected AbstractGraphDecorator(Graph g) { + if (g == null) { + throw new IllegalArgumentException("g may not be null."); + } + this.inner = g; + } + + @Override + public String toString() { + return ToString.simpleName(this) + "[" + ToString.hashHex(this) + + ", inner=" + ToString.graphToString(inner) + "]"; + } + + @Override + public void add(Triple arg0) throws AddDeniedException { + inner.add(arg0); + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + public void close() { + inner.close(); + } + + @Override + public boolean contains(Triple arg0) { + return inner.contains(arg0); + } + + @Override + public boolean contains(Node arg0, Node arg1, Node arg2) { + return inner.contains(arg0, arg1, arg2); + } + + @Override + public void delete(Triple arg0) throws DeleteDeniedException { + inner.delete(arg0); + } + + @Override + public boolean dependsOn(Graph arg0) { + return inner.dependsOn(arg0); + } + + @Override + public ExtendedIterator find(Triple arg0) { + return inner.find(arg0); + } + + @Override + public ExtendedIterator find(Node arg0, Node arg1, Node arg2) { + return inner.find(arg0, arg1, arg2); + } + + @Override + public Capabilities getCapabilities() { + return inner.getCapabilities(); + } + + @Override + public GraphEventManager getEventManager() { + return inner.getEventManager(); + } + + @Override + public PrefixMapping getPrefixMapping() { + return inner.getPrefixMapping(); + } + + @Override + public GraphStatisticsHandler getStatisticsHandler() { + return inner.getStatisticsHandler(); + } + + @Override + public TransactionHandler getTransactionHandler() { + return inner.getTransactionHandler(); + } + + @Override + public boolean isClosed() { + return inner.isClosed(); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public boolean isIsomorphicWith(Graph arg0) { + return inner.isIsomorphicWith(arg0); + } + + @Override + public void remove(Node arg0, Node arg1, Node arg2) { + inner.remove(arg0, arg1, arg2); + } + + @Override + public int size() { + return inner.size(); + } + +} \ No newline at end of file diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractModelDecorator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractModelDecorator.java index 052d1b8db..6a5fc0f90 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractModelDecorator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractModelDecorator.java @@ -54,7 +54,7 @@ public abstract class AbstractModelDecorator implements Model { protected AbstractModelDecorator(Model m) { if (m == null) { - throw new NullPointerException("m may not be null."); + throw new IllegalArgumentException("m may not be null."); } this.inner = m; } @@ -325,6 +325,11 @@ public abstract class AbstractModelDecorator implements Model { return inner.lock(); } + @Override + public boolean hasNoMappings() { + return inner.hasNoMappings(); + } + @Override public boolean samePrefixMappingAs(PrefixMapping other) { return inner.samePrefixMappingAs(other); @@ -684,6 +689,11 @@ public abstract class AbstractModelDecorator implements Model { return inner.createResource(uri); } + @Override + public Resource createResource(Statement statement) { + return inner.createResource(statement); + } + @Override public Property createProperty(String nameSpace, String localName) { return inner.createProperty(nameSpace, localName); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractOntModelDecorator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractOntModelDecorator.java index 895928987..f9305d8f4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractOntModelDecorator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/AbstractOntModelDecorator.java @@ -364,6 +364,11 @@ public abstract class AbstractOntModelDecorator implements OntModel { return inner.lock(); } + @Override + public boolean hasNoMappings() { + return inner.hasNoMappings(); + } + @Override public boolean samePrefixMappingAs(PrefixMapping other) { return inner.samePrefixMappingAs(other); @@ -729,6 +734,11 @@ public abstract class AbstractOntModelDecorator implements OntModel { return inner.createResource(uri); } + @Override + public Resource createResource(Statement statement) { + return inner.createResource(statement); + } + @Override public Property createProperty(String nameSpace, String localName) { return inner.createProperty(nameSpace, localName); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/AcceptableLanguages.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/AcceptableLanguages.java new file mode 100644 index 000000000..01e96c9cc --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/AcceptableLanguages.java @@ -0,0 +1,36 @@ +package edu.cornell.mannlib.vitro.webapp.rdfservice.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A normalized list of languages/locales acceptable in results + * returned by language-filtering RDFServices, graphs, models, etc. + */ +public class AcceptableLanguages extends ArrayList{ + + private static final long serialVersionUID = 1L; + private static final Log log = LogFactory.getLog(AcceptableLanguages.class); + + /** + * Construct a normalized list of acceptable language strings + * from a set of raw language strings. For any values of form 'aa-BB', + * the base language ('aa') will be also added to the list. + * @param rawLanguageStrs may not be null + */ + public AcceptableLanguages(List rawLanguageStrs) { + log.debug("Raw language strings:" + rawLanguageStrs); + for (String lang : rawLanguageStrs) { + this.add(lang); + String baseLang = lang.split("-")[0]; + if (!lang.equals(baseLang) && !this.contains(baseLang)) { + this.add(baseLang); + } + } + log.debug("Normalized language strings:" + this); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java index 230766d3e..e116caece 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java @@ -9,6 +9,7 @@ import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Model; import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.engine.binding.BindingUtils; public class FilteredResultSet implements ResultSet { @@ -53,7 +54,7 @@ public class FilteredResultSet implements ResultSet { @Override public Binding nextBinding() { - throw new UnsupportedOperationException("Can we ignore this?"); + return BindingUtils.asBinding(nextSolution()); } @Override diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java new file mode 100644 index 000000000..10007b119 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java @@ -0,0 +1,78 @@ +package edu.cornell.mannlib.vitro.webapp.rdfservice.filter; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A class for sorting language strings by acceptability according to a + * supplied list of language preferences. + * Refactored from LanguageFilteringRDFService for reuse by classes performing + * similar functions. + */ +public class LangSort { + + private static final Log log = LogFactory.getLog(LangSort.class); + + protected List langs; + private int inexactMatchPenalty; + private int noLanguage; + private int noMatch; + + /** + * Construct a language string sorter with a supplied list of preferred + * language strings + * @param preferredLanguageStrings list of preferred languages of form + * 'en-US', 'es', 'fr-CA'. May not be null. + */ + public LangSort(List preferredLanguageStrings) { + this.langs = preferredLanguageStrings; + this.inexactMatchPenalty = langs.size(); + // no language is worse than any inexact match (if the preferred list does not include ""). + this.noLanguage = 2 * inexactMatchPenalty; + // no match is worse than no language. + this.noMatch = noLanguage + 1; + } + + protected int compareLangs(String t1lang, String t2lang) { + int index1 = languageIndex(t1lang); + int index2 = languageIndex(t2lang); + if(index1 == index2) { + return t1lang.compareTo(t2lang); + } else { + return languageIndex(t1lang) - languageIndex(t2lang); + } + } + + /** + * Return index of exact match, or index of partial match, or + * language-free, or no match. + */ + private int languageIndex(String lang) { + if (lang == null) { + lang = ""; + } + + int index = langs.indexOf(lang); + if (index >= 0) { + log.debug("languageIndex for '" + lang + "' is " + index); + return index; + } + + if (lang.length() > 2) { + index = langs.indexOf(lang.substring(0, 2)); + if (index >= 0) { + log.debug("languageIndex for '" + lang + "' is " + index + inexactMatchPenalty); + return index + inexactMatchPenalty; + } + } + + if (lang.isEmpty()) { + log.debug("languageIndex for '" + lang + "' is " + noLanguage); + return noLanguage; + } + + return noMatch; + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilterModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilterModel.java new file mode 100644 index 000000000..0b1e5ce2f --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilterModel.java @@ -0,0 +1,104 @@ +package edu.cornell.mannlib.vitro.webapp.rdfservice.filter; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; + +/** + * A filter of literal statements from Models according to language preferences. + */ +public class LanguageFilterModel { + + private static final Log log = LogFactory.getLog(LanguageFilterModel.class); + + /** + * + * @param m the model to filter. May not be null. + * @param langs list of strings of type 'en-US'. May not be null. + * @return model with language-inappropriate literal statements filtered out. + */ + public Model filterModel(Model m, List langs) { + log.debug("filterModel"); + List retractions = new ArrayList(); + StmtIterator stmtIt = m.listStatements(); + while (stmtIt.hasNext()) { + Statement stmt = stmtIt.nextStatement(); + if (stmt.getObject().isLiteral()) { + List candidatesForRemoval = m.listStatements( + stmt.getSubject(), stmt.getPredicate(), (RDFNode) null).toList(); + if (candidatesForRemoval.size() == 1) { + continue; + } + candidatesForRemoval.sort(new StatementSortByLang(langs)); + log.debug("sorted statements: " + showSortedStatements(candidatesForRemoval)); + Iterator candIt = candidatesForRemoval.iterator(); + String langRegister = null; + boolean chuckRemaining = false; + while(candIt.hasNext()) { + Statement s = candIt.next(); + if (!s.getObject().isLiteral()) { + continue; + } else if (chuckRemaining) { + retractions.add(s); + } + String lang = s.getObject().asLiteral().getLanguage(); + if (langRegister == null) { + langRegister = lang; + } else if (!langRegister.equals(lang)) { + chuckRemaining = true; + retractions.add(s); + } + } + } + + } + m.remove(retractions); + return m; + } + + private String showSortedStatements(List candidatesForRemoval) { + List langStrings = new ArrayList(); + for (Statement stmt: candidatesForRemoval) { + if (stmt == null) { + langStrings.add("null stmt"); + } else { + RDFNode node = stmt.getObject(); + if (!node.isLiteral()) { + langStrings.add("not literal"); + } else { + langStrings.add(node.asLiteral().getLanguage()); + } + } + } + return langStrings.toString(); + } + + private class StatementSortByLang extends LangSort implements Comparator { + + public StatementSortByLang(List langs) { + super(langs); + } + + public int compare(Statement s1, Statement s2) { + if (s1 == null || s2 == null) { + return 0; + } else if (!s1.getObject().isLiteral() || !s2.getObject().isLiteral()) { + return 0; + } + + String s1lang = s1.getObject().asLiteral().getLanguage(); + String s2lang = s2.getObject().asLiteral().getLanguage(); + + return compareLangs(s1lang, s2lang); + } + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringGraph.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringGraph.java new file mode 100644 index 000000000..b578fddeb --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringGraph.java @@ -0,0 +1,59 @@ +package edu.cornell.mannlib.vitro.webapp.rdfservice.filter; + +import java.util.List; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.mem.GraphMem; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.util.iterator.ExtendedIterator; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.adapters.AbstractGraphDecorator; + +/** + * A graph decorator that filters find() results according to a list of + * preferred language strings + */ +public class LanguageFilteringGraph extends AbstractGraphDecorator + implements Graph { + + private List langs; + private LanguageFilterModel filterModel = new LanguageFilterModel(); + + /** + * Return a graph wrapped in a decorator that will filter find() results + * according to the supplied list of acceptable languages + * @param g the graph to wrap with language awareness. May not be null. + * @param preferredLanguages a list of preferred language strings. May not + * be null. + */ + protected LanguageFilteringGraph(Graph g, List preferredLanguages) { + super(g); + this.langs = preferredLanguages; + } + + @Override + public ExtendedIterator find(Triple arg0) { + return filter(super.find(arg0)); + + } + + @Override + public ExtendedIterator find(Node arg0, Node arg1, Node arg2) { + return filter(super.find(arg0, arg1, arg2)); + } + + private ExtendedIterator filter(ExtendedIterator triples) { + Graph tmp = new GraphMem(); + while(triples.hasNext()) { + Triple t = triples.next(); + tmp.add(t); + } + Model filteredModel = filterModel.filterModel( + ModelFactory.createModelForGraph(tmp), langs); + return filteredModel.getGraph().find(); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFService.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFService.java index 903711e52..5e59118dd 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFService.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFService.java @@ -7,14 +7,14 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.jena.query.QuerySolution; import org.apache.jena.query.ResultSet; import org.apache.jena.query.ResultSetFactory; @@ -22,11 +22,9 @@ import org.apache.jena.query.ResultSetFormatter; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelChangedListener; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; -import org.apache.jena.rdf.model.Statement; -import org.apache.jena.rdf.model.StmtIterator; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; @@ -39,27 +37,13 @@ public class LanguageFilteringRDFService implements RDFService { private static final Log log = LogFactory.getLog(LanguageFilteringRDFService.class); private RDFService s; private List langs; + private LanguageFilterModel filterModel = new LanguageFilterModel(); public LanguageFilteringRDFService(RDFService service, List langs) { this.s = service; - this.langs = normalizeLangs(langs); + this.langs = new AcceptableLanguages(langs); } - private List normalizeLangs(List langs) { - log.debug("Preferred languages:" + langs); - - List normalizedLangs = new ArrayList(langs); - for (String lang : langs) { - String baseLang = lang.split("-")[0]; - if (!normalizedLangs.contains(baseLang)) { - normalizedLangs.add(baseLang); - } - } - - log.debug("Normalized languages:" + normalizedLangs); - return normalizedLangs; - } - @Override public boolean changeSetUpdate(ChangeSet changeSet) throws RDFServiceException { @@ -83,23 +67,17 @@ public class LanguageFilteringRDFService implements RDFService { public InputStream sparqlConstructQuery(String query, ModelSerializationFormat resultFormat) throws RDFServiceException { - Model m = RDFServiceUtils.parseModel(s.sparqlConstructQuery(query, resultFormat), resultFormat); - InputStream in = outputModel(filterModel(m), resultFormat); + Model m = RDFServiceUtils.parseModel(s.sparqlConstructQuery( + query, resultFormat), resultFormat); + InputStream in = outputModel(filterModel.filterModel( + m, langs), resultFormat); return in; } @Override public void sparqlConstructQuery(String query, Model model) throws RDFServiceException { - if (model.isEmpty()) { - s.sparqlConstructQuery(query, model); - filterModel(model); - } else { - Model constructedModel = ModelFactory.createDefaultModel(); - s.sparqlConstructQuery(query, constructedModel); - filterModel(constructedModel); - model.add(constructedModel); - } + s.sparqlConstructQuery(query, model); } @Override @@ -107,7 +85,7 @@ public class LanguageFilteringRDFService implements RDFService { ModelSerializationFormat resultFormat) throws RDFServiceException { Model m = RDFServiceUtils.parseModel(s.sparqlDescribeQuery(query, resultFormat), resultFormat); - return outputModel(filterModel(m), resultFormat); + return outputModel(filterModel.filterModel(m, langs), resultFormat); } private InputStream outputModel(Model m, ModelSerializationFormat resultFormat) { @@ -116,62 +94,6 @@ public class LanguageFilteringRDFService implements RDFService { return new ByteArrayInputStream(out.toByteArray()); } - private Model filterModel(Model m) { - log.debug("filterModel"); - List retractions = new ArrayList(); - StmtIterator stmtIt = m.listStatements(); - while (stmtIt.hasNext()) { - Statement stmt = stmtIt.nextStatement(); - if (stmt.getObject().isLiteral()) { - List candidatesForRemoval = m.listStatements( - stmt.getSubject(), stmt.getPredicate(), (RDFNode) null).toList(); - if (candidatesForRemoval.size() == 1) { - continue; - } - candidatesForRemoval.sort(new StatementSortByLang()); - log.debug("sorted statements: " + showSortedStatements(candidatesForRemoval)); - Iterator candIt = candidatesForRemoval.iterator(); - String langRegister = null; - boolean chuckRemaining = false; - while(candIt.hasNext()) { - Statement s = candIt.next(); - if (!s.getObject().isLiteral()) { - continue; - } else if (chuckRemaining) { - retractions.add(s); - } - String lang = s.getObject().asLiteral().getLanguage(); - if (langRegister == null) { - langRegister = lang; - } else if (!langRegister.equals(lang)) { - chuckRemaining = true; - retractions.add(s); - } - } - } - - } - m.remove(retractions); - return m; - } - - private String showSortedStatements(List candidatesForRemoval) { - List langStrings = new ArrayList(); - for (Statement stmt: candidatesForRemoval) { - if (stmt == null) { - langStrings.add("null stmt"); - } else { - RDFNode node = stmt.getObject(); - if (!node.isLiteral()) { - langStrings.add("not literal"); - } else { - langStrings.add(node.asLiteral().getLanguage()); - } - } - } - return langStrings.toString(); - } - @Override public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat) throws RDFServiceException { @@ -180,7 +102,16 @@ public class LanguageFilteringRDFService implements RDFService { s.sparqlSelectQuery(query, RDFService.ResultFormat.JSON)); List solnList = getSolutionList(resultSet); List vars = resultSet.getResultVars(); + + // This block loops all of the Query variables; + // for each QuerySolution, creates a map of the values of the other variables than the current + // 'variable' --> a list of RowIndexedLiterals. + // In this way, all of the QuerySolutions with equal values of their other variables are grouped. + // This map is used subsequently to filter Literals based on lang for (String var : vars) { + Map, List> nonVarToRowIndexedLiterals = new HashMap<>(); + + // First pass of solnList to populate map for (int i = 0; i < solnList.size(); i++) { QuerySolution s = solnList.get(i); if (s == null) { @@ -190,23 +121,32 @@ public class LanguageFilteringRDFService implements RDFService { if (node == null || !node.isLiteral()) { continue; } - List candidatesForRemoval = - new ArrayList(); - candidatesForRemoval.add(new RowIndexedLiteral(node.asLiteral(), i)); - for (int j = i + 1; j < solnList.size(); j++) { - QuerySolution t = solnList.get(j); - if (t == null) { - continue; - } - if (matchesExceptForVar(s, t, var, vars)) { - candidatesForRemoval.add( - new RowIndexedLiteral(t.getLiteral(var), j)); + + // Create entry representing values other than current 'var' for this QuerySolution + List nonVarList = new ArrayList(vars.size() - 1); + for (String v : vars) { + if (!v.equals(var)) { + nonVarList.add(s.get(v)); } } + + List rowIndexedLiterals = nonVarToRowIndexedLiterals.get(nonVarList); + if (rowIndexedLiterals == null) { + rowIndexedLiterals = new ArrayList(); + } + rowIndexedLiterals.add(new RowIndexedLiteral(node.asLiteral(), i)); + + // Add RowIndexedLiterals to the map + nonVarToRowIndexedLiterals.put(nonVarList, rowIndexedLiterals); + } + + // Second pass of solnList (via the map) to evaluate candidatesForRemoval + for (List key : nonVarToRowIndexedLiterals.keySet()) { + List candidatesForRemoval = nonVarToRowIndexedLiterals.get(key); if (candidatesForRemoval.size() == 1) { continue; } - candidatesForRemoval.sort(new RowIndexedLiteralSortByLang()); + candidatesForRemoval.sort(new RowIndexedLiteralSortByLang(langs)); log.debug("sorted RowIndexedLiterals: " + showSortedRILs(candidatesForRemoval)); Iterator candIt = candidatesForRemoval.iterator(); String langRegister = null; @@ -254,10 +194,9 @@ public class LanguageFilteringRDFService implements RDFService { @Override public void sparqlSelectQuery(String query, ResultSetConsumer consumer) throws RDFServiceException { log.debug("sparqlSelectQuery: " + query.replaceAll("\\s+", " ")); - s.sparqlSelectQuery(query, new ResultSetConsumer.Chaining(consumer) { List vars; - List solnList = new ArrayList(); + List solnList = new ArrayList<>(); @Override protected void processQuerySolution(QuerySolution qs) { @@ -273,7 +212,15 @@ public class LanguageFilteringRDFService implements RDFService { protected void endProcessing() { chainStartProcessing(); + // This block loops all of the Query variables; + // for each QuerySolution, creates a map of the values of the other variables than the current + // 'variable' --> a list of RowIndexedLiterals. + // In this way, all of the QuerySolutions with equal values of their other variables are grouped. + // This map is used subsequently to filter Literals based on lang for (String var : vars) { + Map, List> nonVarToRowIndexedLiterals = new HashMap<>(); + + // First pass of solnList to populate map for (int i = 0; i < solnList.size(); i++) { QuerySolution s = solnList.get(i); if (s == null) { @@ -283,23 +230,32 @@ public class LanguageFilteringRDFService implements RDFService { if (node == null || !node.isLiteral()) { continue; } - List candidatesForRemoval = - new ArrayList(); - candidatesForRemoval.add(new RowIndexedLiteral(node.asLiteral(), i)); - for (int j = i + 1; j < solnList.size(); j++) { - QuerySolution t = solnList.get(j); - if (t == null) { - continue; - } - if (matchesExceptForVar(s, t, var, vars)) { - candidatesForRemoval.add( - new RowIndexedLiteral(t.getLiteral(var), j)); + + // Create entry representing values other than current 'var' for this QuerySolution + List nonVarList = new ArrayList(vars.size() - 1); + for (String v : vars) { + if (!v.equals(var)) { + nonVarList.add(s.get(v)); } } + + List rowIndexedLiterals = nonVarToRowIndexedLiterals.get(nonVarList); + if (rowIndexedLiterals == null) { + rowIndexedLiterals = new ArrayList(); + } + rowIndexedLiterals.add(new RowIndexedLiteral(node.asLiteral(), i)); + + // Add RowIndexedLiterals to the map + nonVarToRowIndexedLiterals.put(nonVarList, rowIndexedLiterals); + } + + // Second pass of solnList (via the map) to evaluate candidatesForRemoval + for (List key : nonVarToRowIndexedLiterals.keySet()) { + List candidatesForRemoval = nonVarToRowIndexedLiterals.get(key); if (candidatesForRemoval.size() == 1) { continue; } - candidatesForRemoval.sort(new RowIndexedLiteralSortByLang()); + candidatesForRemoval.sort(new RowIndexedLiteralSortByLang(langs)); log.debug("sorted RowIndexedLiterals: " + showSortedRILs(candidatesForRemoval)); Iterator candIt = candidatesForRemoval.iterator(); String langRegister = null; @@ -357,34 +313,6 @@ public class LanguageFilteringRDFService implements RDFService { } - private boolean matchesExceptForVar(QuerySolution a, QuerySolution b, - String varName, List varList) { - if (varName == null) { - throw new RuntimeException("expected non-null variable nane"); - } - for (String var : varList) { - RDFNode nodea = a.get(var); - RDFNode nodeb = b.get(var); - if (var.equals(varName)) { - if (nodea == null || !nodea.isLiteral() || nodeb == null || !nodeb.isLiteral()) { - return false; - } - } else { - if (nodea == null && nodeb == null) { - continue; - } else if (nodea == null && nodeb != null) { - return false; - } else if (nodeb == null && nodea != null) { - return false; - } - if (!a.get(var).equals(b.get(var))) { - return false; - } - } - } - return true; - } - private List getSolutionList(ResultSet resultSet) { List solnList = new ArrayList(); while (resultSet.hasNext()) { @@ -489,52 +417,12 @@ public class LanguageFilteringRDFService implements RDFService { s.close(); } - private class LangSort { - // any inexact match is worse than any exact match - private int inexactMatchPenalty = langs.size(); - // no language is worse than any inexact match (if the preferred list does not include ""). - private int noLanguage = 2 * inexactMatchPenalty; - // no match is worse than no language. - private int noMatch = noLanguage + 1; - - protected int compareLangs(String t1lang, String t2lang) { - return languageIndex(t1lang) - languageIndex(t2lang); - } - - /** - * Return index of exact match, or index of partial match, or - * language-free, or no match. - */ - private int languageIndex(String lang) { - if (lang == null) { - lang = ""; - } - - int index = langs.indexOf(lang); - if (index >= 0) { - log.debug("languageIndex for '" + lang + "' is " + index); - return index; - } - - if (lang.length() > 2) { - index = langs.indexOf(lang.substring(0, 2)); - if (index >= 0) { - log.debug("languageIndex for '" + lang + "' is " + index + inexactMatchPenalty); - return index + inexactMatchPenalty; - } - } - - if (lang.isEmpty()) { - log.debug("languageIndex for '" + lang + "' is " + noLanguage); - return noLanguage; - } - - return noMatch; - } - } - private class RowIndexedLiteralSortByLang extends LangSort implements Comparator { + public RowIndexedLiteralSortByLang(List langs) { + super(langs); + } + public int compare(RowIndexedLiteral rilit1, RowIndexedLiteral rilit2) { if (rilit1 == null || rilit2 == null) { return 0; @@ -547,21 +435,20 @@ public class LanguageFilteringRDFService implements RDFService { } } - private class StatementSortByLang extends LangSort implements Comparator { + /* + * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; - public int compare(Statement s1, Statement s2) { - if (s1 == null || s2 == null) { - return 0; - } else if (!s1.getObject().isLiteral() || !s2.getObject().isLiteral()) { - return 0; - } + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } - String s1lang = s1.getObject().asLiteral().getLanguage(); - String s2lang = s2.getObject().asLiteral().getLanguage(); - - return compareLangs(s1lang, s2lang); - } - } + public VitroRequest getVitroRequest() { + return vitroRequest; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringUtils.java index 5a0336462..720bcc148 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringUtils.java @@ -4,26 +4,62 @@ package edu.cornell.mannlib.vitro.webapp.rdfservice.filter; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import javax.servlet.ServletRequest; +import org.apache.commons.lang3.LocaleUtils; import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.ModelFactory; -import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceGraph; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel; - /** * Some methods that will come in handy when dealing with Language Filtering */ public class LanguageFilteringUtils { + private static final String UNDERSCORE = "_"; + private static final String HYPHEN = "-"; + private static final String DEFAULT_LANG_STRING = "en"; + + /** + * Take a Locale object, such as we might get from a + * request, and convert to a language string used in RDF. + * + * While converting, change all underscores (as in Locale names) to hyphens + * (as in RDF language specifiers). + */ + public static String localeToLanguage(Locale locale) { + return locale.toString().replace(UNDERSCORE, HYPHEN); + } + + /** + * Take a language string and convert to a Locale. + * + * While converting, change all hyphens (as in RDF language specifiers) to + * underscores (as in Locale names). Ensure language string is lowercase + * and country abbreviation is uppercase. + */ + public static Locale languageToLocale(String langStr) { + String[] langParts = langStr.split(HYPHEN); + if (langParts.length > 2) { + langStr = String.join(UNDERSCORE, langParts[0].toLowerCase(), + langParts[1].toUpperCase(), langParts[2]); + } else if (langParts.length > 1) { + langStr = String.join(UNDERSCORE, langParts[0].toLowerCase(), + langParts[1].toUpperCase()); + } else { + langStr = langParts[0].toLowerCase(); + } + return LocaleUtils.toLocale(langStr); + } + /** * Take an Enumeration of Locale objects, such as we might get from a - * request, and convert to a List of langauage strings, such as are needed + * request, and convert to a List of language strings, such as are needed * by the LanguageFilteringRDFService. * * While converting, change all underscores (as in Locale names) to hyphens @@ -33,27 +69,43 @@ public class LanguageFilteringUtils { List langs = new ArrayList<>(); while (locales.hasMoreElements()) { Locale locale = (Locale) locales.nextElement(); - langs.add(locale.toString().replace("_", "-")); + langs.add(locale.toString().replace(UNDERSCORE, HYPHEN)); } if (langs.isEmpty()) { - langs.add("en"); + langs.add(DEFAULT_LANG_STRING); } - return langs; + return langs; } /** - * Add a Language Filtering layer to an OntModel by treating it as an RDFService. + * Take a List of language strings and convert to a List of Locale. + * + * While converting, change all hyphens (as in RDF language specifiers) to + * under scores (as in Locale names). Ensure language string is lowercase + * and country abbreviation is uppercase. + */ + public static List languagesToLocales(List langs) { + Set locales = new HashSet<>(); + langs.forEach(langStr -> { + locales.add(languageToLocale(langStr)); + }); + if (locales.isEmpty()) { + locales.add(LocaleUtils.toLocale(DEFAULT_LANG_STRING)); + } + + return new ArrayList<>(locales); + } + + /** + * Add a Language Filtering layer to an OntModel */ public static OntModel wrapOntModelInALanguageFilter(OntModel rawModel, ServletRequest req) { - /** This is some nasty layering. Could we do this more easily? */ - List languages = localesToLanguages(req.getLocales()); + List languages = new AcceptableLanguages(localesToLanguages(req.getLocales())); return ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, - RDFServiceGraph.createRDFServiceModel( - new RDFServiceGraph( - new LanguageFilteringRDFService( - new RDFServiceModel(rawModel), languages)))); + ModelFactory.createModelForGraph(new LanguageFilteringGraph( + rawModel.getGraph(), languages))); } private LanguageFilteringUtils() { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceFactorySingle.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceFactorySingle.java index f44d2b59f..44bd2aabb 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceFactorySingle.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceFactorySingle.java @@ -9,6 +9,7 @@ import java.util.List; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelChangedListener; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; @@ -218,7 +219,20 @@ public class RDFServiceFactorySingle implements RDFServiceFactory { return ToString.simpleName(this) + "[" + ToString.hashHex(this) + ", inner=" + s + "]"; } + /* + * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } + + public VitroRequest getVitroRequest() { + return vitroRequest; + } } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java index 217149283..6ff961382 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java @@ -32,6 +32,7 @@ import org.apache.jena.riot.out.NodeFormatter; import org.apache.jena.riot.out.NodeFormatterTTL; import org.apache.jena.vocabulary.RDF; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange; @@ -141,8 +142,8 @@ public abstract class RDFServiceImpl implements RDFService { } protected void notifyListeners(ModelChange modelChange) throws IOException { + modelChange.getSerializedModel().reset(); for (ChangeListener listener : registeredListeners) { - modelChange.getSerializedModel().reset(); listener.notifyModelChange(modelChange); } log.debug(registeredJenaListeners.size() + " registered Jena listeners"); @@ -254,11 +255,12 @@ public abstract class RDFServiceImpl implements RDFService { literalBuff.append("\""); pyString(literalBuff, node.getLiteralLexicalForm()); literalBuff.append("\""); - if (node.getLiteralDatatypeURI() != null) { - literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); - } else if (node.getLiteralLanguage() != null && node.getLiteralLanguage().length() > 0) { + if (node.getLiteralLanguage() != null && node.getLiteralLanguage().length() > 0) { literalBuff.append("@").append(node.getLiteralLanguage()); + } else if (node.getLiteralDatatypeURI() != null) { + literalBuff.append("^^<").append(node.getLiteralDatatypeURI()).append(">"); } + return literalBuff.toString(); } else { return varName; @@ -453,4 +455,19 @@ public abstract class RDFServiceImpl implements RDFService { } } } + /* + * UQAM Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; + + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } + + public VitroRequest getVitroRequest() { + return vitroRequest; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java index dadccfed4..09a98a647 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java @@ -42,6 +42,7 @@ import org.apache.jena.sdb.SDB; import org.apache.jena.shared.Lock; import org.apache.jena.sparql.core.Quad; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.jena.DatasetWrapper; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; import edu.cornell.mannlib.vitro.webapp.dao.jena.SparqlGraph; @@ -200,8 +201,8 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic log.debug("blank node model size " + blankNodeModel.size()); if (blankNodeModel.size() == 1) { - log.warn("Deleting single triple with blank node: " + blankNodeModel); - log.warn("This likely indicates a problem; excessive data may be deleted."); + log.debug("Deleting single triple with blank node: " + blankNodeModel); + log.debug("This could result in the deletion of multiple triples if multiple blank nodes match the same triple pattern."); } Query rootFinderQuery = QueryFactory.create(BNODE_ROOT_QUERY); @@ -704,4 +705,19 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic protected QueryExecution createQueryExecution(String queryString, Query q, Dataset d) { return QueryExecutionFactory.create(q, d); } + /* + * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; + + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } + + public VitroRequest getVitroRequest() { + return vitroRequest; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/model/RDFServiceModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/model/RDFServiceModel.java index 496928fbc..4b910b568 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/model/RDFServiceModel.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/model/RDFServiceModel.java @@ -13,6 +13,7 @@ import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.rdf.model.Model; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.jena.DatasetWrapper; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange; @@ -119,4 +120,18 @@ public class RDFServiceModel extends RDFServiceJena implements RDFService { return true; } + /* + * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; + + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } + + public VitroRequest getVitroRequest() { + return vitroRequest; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java index 6b5e4557a..adcb69826 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java @@ -13,6 +13,8 @@ import java.nio.file.Paths; import java.util.List; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.RDFNode; + import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -74,11 +76,19 @@ public class RDFServiceTDB extends RDFServiceJena { } notifyListenersOfPreChangeEvents(changeSet); - dataset.begin(ReadWrite.WRITE); - try { - applyChangeSetToModel(changeSet, dataset); - dataset.commit(); - } finally { + dataset.begin(ReadWrite.WRITE); + try { + boolean committed = false; + try { + applyChangeSetToModel(changeSet, dataset); + dataset.commit(); + committed = true; + } finally { + if(!committed) { + dataset.abort(); + } + } + } finally { dataset.end(); } @@ -93,6 +103,10 @@ public class RDFServiceTDB extends RDFServiceJena { } } + @Override + public boolean preferPreciseOptionals() { + return true; + } @Override public void close() { @@ -232,6 +246,28 @@ public class RDFServiceTDB extends RDFServiceJena { return isEquivalentGraph(graphURI, inStream, ModelSerializationFormat.NTRIPLE); } + @Override + public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) + throws RDFServiceException { + dataset.begin(ReadWrite.READ); + try { + return super.countTriples(subject, predicate, object); + } finally { + dataset.end(); + } + } + + @Override + public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, + long limit, long offset) throws RDFServiceException { + dataset.begin(ReadWrite.READ); + try { + return super.getTriples(subject, predicate, object, limit, offset); + } finally { + dataset.end(); + } + } + /** * Convert all of the references to integer compatible type to "integer" in the serialized graph. * diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/logging/LoggingRDFService.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/logging/LoggingRDFService.java index 5a0189f41..abae2aeb9 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/logging/LoggingRDFService.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/logging/LoggingRDFService.java @@ -9,6 +9,7 @@ import java.util.List; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelChangedListener; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener; import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; @@ -207,4 +208,19 @@ public class LoggingRDFService implements RDFService { public String toString() { return "LoggingRDFService[inner=" + innerService + "]"; } + /* + * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service + * (non-Javadoc) + * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest) + */ + private VitroRequest vitroRequest; + + public void setVitroRequest(VitroRequest vitroRequest) { + this.vitroRequest = vitroRequest; + } + + public VitroRequest getVitroRequest() { + return vitroRequest; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/CustomSearchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java similarity index 96% rename from api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/CustomSearchController.java rename to api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java index f58418f86..9bc308db0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/CustomSearchController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java @@ -58,11 +58,11 @@ import edu.ucsf.vitro.opensocial.OpenSocialManager; * Paged search controller that uses the search engine */ -@WebServlet(name = "CustomSearchController", urlPatterns = {"/customsearch","/customsearch.jsp","/customfedsearch","/customsearchcontroller"} ) -public class CustomSearchController extends FreemarkerHttpServlet { +@WebServlet(name = "ExtendedSearchController", urlPatterns = {"/extendedsearch","/extendedsearch.jsp","/extendedfedsearch","/extendedsearchcontroller"} ) +public class ExtendedSearchController extends FreemarkerHttpServlet { private static final long serialVersionUID = 1L; - private static final Log log = LogFactory.getLog(CustomSearchController.class); + private static final Log log = LogFactory.getLog(ExtendedSearchController.class); protected static final int DEFAULT_HITS_PER_PAGE = 25; protected static final int DEFAULT_MAX_HIT_COUNT = 1000; @@ -74,6 +74,7 @@ public class CustomSearchController extends FreemarkerHttpServlet { private static final String PARAM_CLASSGROUP = "classgroup"; private static final String PARAM_RDFTYPE = "type"; private static final String PARAM_QUERY_TEXT = "querytext"; + private static final String EXTENDEDSEARCH = "/extendedsearch"; protected static final Map> templateTable; @@ -552,9 +553,9 @@ public class CustomSearchController extends FreemarkerHttpServlet { } public static class VClassGroupSearchLink extends LinkTemplateModel { - long count = 0; + long count = 0; VClassGroupSearchLink(String querytext, VClassGroup classgroup, long count) { - super(classgroup.getPublicName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, classgroup.getURI()); + super(classgroup.getPublicName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, classgroup.getURI()); this.count = count; } @@ -564,7 +565,7 @@ public class CustomSearchController extends FreemarkerHttpServlet { public static class VClassSearchLink extends LinkTemplateModel { long count = 0; VClassSearchLink(String querytext, VClass type, long count) { - super(type.getName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI()); + super(type.getName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI()); this.count = count; } @@ -738,15 +739,15 @@ public class CustomSearchController extends FreemarkerHttpServlet { HashMap resultsToTemplates = new HashMap(); // set up HTML format - resultsToTemplates.put(Result.PAGED, "search-pagedResults.ftl"); - resultsToTemplates.put(Result.ERROR, "search-error.ftl"); + resultsToTemplates.put(Result.PAGED, "extendedsearch-pagedResults.ftl"); + resultsToTemplates.put(Result.ERROR, "extendedsearch-error.ftl"); // resultsToTemplates.put(Result.BAD_QUERY, "search-badQuery.ftl"); table.put(Format.HTML, Collections.unmodifiableMap(resultsToTemplates)); // set up XML format resultsToTemplates = new HashMap(); - resultsToTemplates.put(Result.PAGED, "search-xmlResults.ftl"); - resultsToTemplates.put(Result.ERROR, "search-xmlError.ftl"); + resultsToTemplates.put(Result.PAGED, "extendedsearch-xmlResults.ftl"); + resultsToTemplates.put(Result.ERROR, "extendedsearch-xmlError.ftl"); // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); table.put(Format.XML, Collections.unmodifiableMap(resultsToTemplates)); @@ -754,8 +755,8 @@ public class CustomSearchController extends FreemarkerHttpServlet { // set up CSV format resultsToTemplates = new HashMap(); - resultsToTemplates.put(Result.PAGED, "search-csvResults.ftl"); - resultsToTemplates.put(Result.ERROR, "search-csvError.ftl"); + resultsToTemplates.put(Result.PAGED, "extendedsearch-csvResults.ftl"); + resultsToTemplates.put(Result.ERROR, "extendedsearch-csvError.ftl"); // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); table.put(Format.CSV, Collections.unmodifiableMap(resultsToTemplates)); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java index 6596878b2..578c6a4a6 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java @@ -10,7 +10,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectProperties.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectProperties.java index d8b2233fc..6462cd3fc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectProperties.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectProperties.java @@ -59,16 +59,16 @@ public class AdditionalURIsForObjectProperties implements IndexingUriFinder, Con public void endIndexing() { /* nothing to do */ } protected List doObjectPropertyStmt(Statement stmt) { - // Only need to consider the object since the subject - // will already be updated in search index as part of - // SearchReindexingListener. - // Also, context nodes are not handled here. They are // taken care of in AdditionalURIsForContextNodex. - if( stmt.getObject().isURIResource() ) - return Collections.singletonList( stmt.getObject().as(Resource.class).getURI() ); - else - return Collections.emptyList(); + List uris = new ArrayList(); + if( stmt.getObject().isURIResource() ) { + uris.add(stmt.getObject().as(Resource.class).getURI() ); + } + if( stmt.getSubject().isURIResource() ) { + uris.add(stmt.getSubject().as(Resource.class).getURI() ); + } + return uris; } protected List doDataPropertyStmt(Statement stmt) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java index 383177cd7..770aab181 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/services/shortview/FakeApplicationOntologyService.java @@ -445,14 +445,26 @@ public class FakeApplicationOntologyService { * "display model". The query finds a preferred title for the individual. */ private static class FakeVivoPeopleDataGetter extends SparqlQueryDataGetter { - private static String QUERY_STRING = "" + // private static String QUERY_STRING = "" + // + "PREFIX obo: \n" + // + "PREFIX vcard: \n" + // + "SELECT ?pt \n" + "WHERE { \n" + // + " ?uri obo:ARG_2000028 ?vIndividual . \n" + // + " ?vIndividual vcard:hasTitle ?vTitle . \n" + // + " ?vTitle vcard:title ?pt . \n" + "} LIMIT 1"; + + /* + * UQAM-Optimization New query including Linguistic context + */ + private static String QUERY_STRING_LANG = "" + "PREFIX obo: \n" + "PREFIX vcard: \n" + "SELECT ?pt \n" + "WHERE { \n" + " ?uri obo:ARG_2000028 ?vIndividual . \n" + " ?vIndividual vcard:hasTitle ?vTitle . \n" - + " ?vTitle vcard:title ?pt . \n" + "} LIMIT 1"; - + + " ?vTitle vcard:title ?pt . \n" + + " FILTER (lang(?pt) = '?langCtx' ) \n" + + " } LIMIT 1"; private static final String FAKE_VIVO_PEOPLE_DATA_GETTER_URI = "http://FakeVivoPeopleDataGetter"; private static OntModel fakeDisplayModel = initializeFakeDisplayModel(); @@ -467,7 +479,7 @@ public class FakeApplicationOntologyService { Property saveToVarProperty = m .getProperty(DisplayVocabulary.SAVE_TO_VAR); - m.add(dataGetter, queryProperty, QUERY_STRING); + m.add(dataGetter, queryProperty, QUERY_STRING_LANG); //UQAM-Optimization Using query with linguistic context m.add(dataGetter, saveToVarProperty, "extra"); return m; } @@ -475,18 +487,24 @@ public class FakeApplicationOntologyService { private String individualUri; private VitroRequest vreq; private ServletContext ctx; + private String langCtx = "en-US"; public FakeVivoPeopleDataGetter(VitroRequest vreq, String individualUri) { - super(vreq, fakeDisplayModel, "http://FakeVivoPeopleDataGetter"); + super(vreq, initializeFakeDisplayModel(), "http://FakeVivoPeopleDataGetter"); this.individualUri = individualUri; this.vreq = vreq; this.ctx = vreq.getSession().getServletContext(); + this.langCtx = vreq.getLocale().getLanguage(); // UQAM-Optimization add the linguistic context + if (!vreq.getLocale().getCountry().isEmpty()) { + this.langCtx += "-" + vreq.getLocale().getCountry(); + } } @Override public Map getData(Map pageData) { Map parms = new HashMap<>(); parms.put("uri", individualUri); + parms.put("langCtx", langCtx); //UQAM-Optimization add the linguistic context return super.getData(parms); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ConfigurationModelsSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ConfigurationModelsSetup.java index 1c0ea33ce..6c6429327 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ConfigurationModelsSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ConfigurationModelsSetup.java @@ -12,15 +12,30 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.apache.jena.ontology.OntModel; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; +import edu.cornell.mannlib.vitro.webapp.rdfservice.adapters.VitroModelFactory; /** * Set up the models that use the CONFIGURATION RDFService. They are all mapped * to memory-based models. */ public class ConfigurationModelsSetup implements ServletContextListener { + private static final Log log = LogFactory.getLog(ConfigurationModelsSetup.class); + @Override public void contextInitialized(ServletContextEvent sce) { ServletContext ctx = sce.getServletContext(); @@ -31,33 +46,223 @@ public class ConfigurationModelsSetup implements ServletContextListener { setupModel(ctx, DISPLAY_TBOX, "displayTbox"); setupModel(ctx, DISPLAY_DISPLAY, "displayDisplay"); setupModel(ctx, USER_ACCOUNTS, "auth"); - ss.info(this, - "Set up the display models and the user accounts model."); + ss.info(this, "Set up the display models and the user accounts model."); } catch (Exception e) { ss.fatal(this, e.getMessage(), e.getCause()); } } - private void setupModel(ServletContext ctx, String modelUri, - String modelPath) { + private void setupModel(ServletContext ctx, String modelUri, String modelPath) { try { OntModel ontModel = ModelAccess.on(ctx).getOntModel(modelUri); - loadFirstTimeFiles(ctx, modelPath, ontModel); + if (ontModel.isEmpty()) { + loadFirstTimeFiles(ctx, modelPath, ontModel); + // backup firsttime files + OntModel baseModelFirsttime = ModelAccess.on(ctx).getOntModel(modelUri + "FirsttimeBackup"); + baseModelFirsttime.add(ontModel); + } else { + // Check if the firsttime files have changed since the firsttime startup, + // if so, then apply the changes but not overwrite the whole user model + applyFirstTimeChanges(ctx, modelPath, modelUri, ontModel); + } + loadEveryTimeFiles(ctx, modelPath, ontModel); } catch (Exception e) { - throw new RuntimeException("Failed to create the '" + modelPath - + "' model (" + modelUri + ").", e); + throw new RuntimeException("Failed to create the '" + modelPath + "' model (" + modelUri + ").", e); } } - private void loadFirstTimeFiles(ServletContext ctx, String modelPath, - OntModel baseModel) { - RDFFilesLoader.loadFirstTimeFiles(modelPath, baseModel, baseModel.isEmpty()); + private void loadFirstTimeFiles(ServletContext ctx, String modelPath, OntModel baseModel) { + RDFFilesLoader.loadFirstTimeFiles(ctx, modelPath, baseModel, baseModel.isEmpty()); } - private void loadEveryTimeFiles(ServletContext ctx, String modelPath, - OntModel memoryModel) { - RDFFilesLoader.loadEveryTimeFiles(modelPath, memoryModel); + private void loadEveryTimeFiles(ServletContext ctx, String modelPath, OntModel memoryModel) { + RDFFilesLoader.loadEveryTimeFiles(ctx, modelPath, memoryModel); + } + + /* + * Check if the firsttime files have changed since the firsttime startup, if so, + * then apply the changes but not overwrite the whole user model + */ + private void applyFirstTimeChanges(ServletContext ctx, String modelPath, String modelUri, OntModel userModel) { + + log.info("Reload firsttime files on start-up if changed: '" + modelPath +"', URI: '" + modelUri + "'"); + boolean updatedFiles = false; + + // get configuration models from the firsttime start up (backup state) + OntModel baseModelFirsttimeBackup = ModelAccess.on(ctx).getOntModel(modelUri + "FirsttimeBackup"); + + // compare firsttime files with configuration models + log.debug("compare firsttime files with configuration models (backup from first start) for " + modelPath); + + OntModel baseModelFirsttime = VitroModelFactory.createOntologyModel(); + RDFFilesLoader.loadFirstTimeFiles(ctx, modelPath, baseModelFirsttime, true); + + if (baseModelFirsttime.isIsomorphicWith(baseModelFirsttimeBackup)) { + log.debug("They are the same, so do nothing: '" + modelPath + "'"); + } else { + log.debug("They differ:" + modelPath + ", compare values in configuration models with user's triplestore"); + + updatedFiles = applyChanges(baseModelFirsttimeBackup, baseModelFirsttime, userModel, modelPath); + if (updatedFiles) + log.info("The model was updated, " + modelPath); + } + } + + /* + * This method is designed to compare configuration models (baseModel) with firsttime files (newModel): + * if they are the same, stop + * else, if they differ, compare values in configuration models (baseModel) with user's triplestore + * if they are the same, update user's triplestore with value in new firsttime files + * else, if they differ, leave user's triplestore statement alone + * finally, overwrite the configuration models with content of the updated firstime files + * + * @param baseModel The backup firsttime model (from the first startup) + * @param newModel The current state of the firsttime files in the directory + * @param userModel The current state of the user model + * @param modelIdString Just an string for the output for better debugging + * (display, displayTbox, displayDisplay, auth) + */ + private boolean applyChanges(Model baseModel, Model newModel, Model userModel, String modelIdString) { + boolean updatedFiles = false; + StringWriter out = new StringWriter(); + StringWriter out2 = new StringWriter(); + Model difOldNew = baseModel.difference(newModel); + Model difNewOld = newModel.difference(baseModel); + + // remove special cases for display, problem with blank nodes + if (modelIdString.equals("display")) { + + removeBlankTriples(difOldNew); + removeBlankTriples(difNewOld); + } + + if (difOldNew.isEmpty() && difNewOld.isEmpty()) { + // if there is no difference, nothing needs to be done + log.debug("For the " + modelIdString + " model, there is no difference in both directions. So do nothing."); + } else { + // if there is a difference, we need to remove the triples in difOldNew and + // add the triples in difNewOld to the back up firsttime model + + if (!difOldNew.isEmpty()) { + difOldNew.write(out, "TTL"); + log.debug("Difference for " + modelIdString + " (old -> new), these triples should be removed: " + out); + + // Check if the UI-changes Overlap with the changes made in the fristtime-files + checkUiChangesOverlapWithFileChanges(baseModel, userModel, difOldNew); + + // before we remove the triples, we need to compare values in back up firsttime with user's triplestore + // if the triples which should be removed are still in user´s triplestore, remove them + if (userModel.containsAny(difOldNew)) { + log.debug("Some of these triples are in the user triples store, so they will be removed now"); + userModel.remove(difOldNew); + updatedFiles = true; + } + + // remove the triples from the backup firsttime model for the next check + baseModel.remove(difOldNew); + } + if (!difNewOld.isEmpty()) { + difNewOld.write(out2, "TTL"); + log.debug("Difference for " + modelIdString + " (new -> old), these triples should be added: " + out2); + + // Check if the UI-changes Overlap with the changes made in the fristtime-files + checkUiChangesOverlapWithFileChanges(baseModel, userModel, difNewOld); + + // before we add the triples, we need to compare values in back up firsttime with user's triplestore + // if the triples which should be added are not already in user´s triplestore, add them + if (!userModel.containsAll(difNewOld)) { + log.debug("Some of these triples are not in the user triples store, so they will be added now"); + // but only the triples that are no already there + Model tmp = difNewOld.difference(userModel); + userModel.add(tmp); + updatedFiles = true; + } + + // add the triples from the back up firsttime model for the next check + baseModel.add(difNewOld); + } + } + return updatedFiles; + } + + /** + * Check if the UI-changes Overlap with the changes made in the fristtime-files, if they overlap these changes are not applied to the user-model (UI) + * + * @param baseModel firsttime backup model + * @param userModel current state in the system (user/UI-model) + * @param changesModel the changes between firsttime-files and firttime-backup + */ + private void checkUiChangesOverlapWithFileChanges(Model baseModel, Model userModel, Model changesModel) { + log.debug("Beginn check if subtractions from Backup-firsttime model to current state of firsttime-files were changed in user-model (via UI)"); + Model changesUserModel = userModel.difference(baseModel); + List changedInUIandFileStatements = new ArrayList(); + + if(!changesUserModel.isEmpty()) + { + removeBlankTriples(changesUserModel); + + StringWriter out3 = new StringWriter(); + changesUserModel.write(out3, "TTL"); + log.debug("There were changes in the user-model via UI which have also changed in the firsttime files, the following triples will not be updated"); + + // iterate all statements and check if the ones which should be removed were not changed via the UI + StmtIterator iter = changesUserModel.listStatements(); + while (iter.hasNext()) { + Statement stmt = iter.nextStatement(); // get next statement + Resource subject = stmt.getSubject(); // get the subject + Property predicate = stmt.getPredicate(); // get the predicate + RDFNode object = stmt.getObject(); // get the object + + StmtIterator iter2 = changesModel.listStatements(); + + while (iter2.hasNext()) { + Statement stmt2 = iter2.nextStatement(); // get next statement + Resource subject2 = stmt2.getSubject(); // get the subject + Property predicate2 = stmt2.getPredicate(); // get the predicate + RDFNode object2 = stmt2.getObject(); // get the object + + // if subject and predicate are equal but the object differs and the language tag is the same, do not update these triples + // this case indicates an change in the UI, which should not be overwriten from the firsttime files + if(subject.equals(subject2) && predicate.equals(predicate2) && !object.equals(object2) ) { + // if object is an literal, check the language tag + if (object.isLiteral() && object2.isLiteral()) { + // if the langauge tag is the same, remove this triple from the update list + if(object.asLiteral().getLanguage().equals(object2.asLiteral().getLanguage())) { + log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2); + changedInUIandFileStatements.add(stmt2); + } + } else { + log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2); + changedInUIandFileStatements.add(stmt2); + } + } + } + } + // remove triples which were changed in the user model (UI) from the list + changesModel.remove(changedInUIandFileStatements); + } else { + log.debug("There were no changes in the user-model via UI compared to the backup-firsttime-model"); + } + } + + /** + * Remove all triples where subject or object is blank (Anon) + */ + private void removeBlankTriples(Model model) { + StmtIterator iter = model.listStatements(); + List removeStatement = new ArrayList(); + while (iter.hasNext()) { + Statement stmt = iter.nextStatement(); // get next statement + Resource subject = stmt.getSubject(); // get the subject + RDFNode object = stmt.getObject(); // get the object + + if(subject.isAnon() || object.isAnon()) + { + removeStatement.add(stmt); + } + } + model.remove(removeStatement); } @Override diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java index 84e2d6809..2024e59e8 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java @@ -5,9 +5,13 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS_FIRSTTIME_BACKUP; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA_FIRSTTIME_BACKUP; import java.util.ArrayList; import java.util.List; +import java.io.StringWriter; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -17,12 +21,16 @@ import org.apache.commons.logging.LogFactory; import org.apache.jena.ontology.OntModel; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.ResIterator; import org.apache.jena.rdf.model.Resource; import org.apache.jena.shared.Lock; import org.apache.jena.util.ResourceUtils; import org.apache.jena.util.iterator.ClosableIterator; import org.apache.jena.vocabulary.RDF; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; +import org.apache.jena.rdf.model.RDFNode; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; @@ -60,22 +68,39 @@ public class ContentModelSetup extends JenaDataSourceSetupBase Model applicationMetadataModel = models.getOntModel(APPLICATION_METADATA); if (applicationMetadataModel.isEmpty()) { firstTimeStartup = true; - initializeApplicationMetadata(ctx, applicationMetadataModel); + initializeApplicationMetadata(ctx, applicationMetadataModel); + + // backup copy from firsttime files + OntModel applicationMetadataModelFirsttime = models.getOntModel(APPLICATION_METADATA_FIRSTTIME_BACKUP); + applicationMetadataModelFirsttime.add(applicationMetadataModel); + } else { + // check if some of the firsttime files have changed since the first start up and + // if they changed, apply these changes to the user models + applyFirstTimeChanges(ctx); + checkForNamespaceMismatch( applicationMetadataModel, ctx ); } OntModel baseABoxModel = models.getOntModel(ABOX_ASSERTIONS); if (firstTimeStartup) { - RDFFilesLoader.loadFirstTimeFiles("abox", baseABoxModel, true); + RDFFilesLoader.loadFirstTimeFiles(ctx, "abox", baseABoxModel, true); + + // backup copy from firsttime files + OntModel baseABoxModelFirsttime = models.getOntModel(ABOX_ASSERTIONS_FIRSTTIME_BACKUP); + baseABoxModelFirsttime.add(baseABoxModel); } - RDFFilesLoader.loadEveryTimeFiles("abox", baseABoxModel); + RDFFilesLoader.loadEveryTimeFiles(ctx, "abox", baseABoxModel); OntModel baseTBoxModel = models.getOntModel(TBOX_ASSERTIONS); if (firstTimeStartup) { - RDFFilesLoader.loadFirstTimeFiles("tbox", baseTBoxModel, true); + RDFFilesLoader.loadFirstTimeFiles(ctx, "tbox", baseTBoxModel, true); + + // backup copy from firsttime files + OntModel baseTBoxModelFirsttime = models.getOntModel(TBOX_ASSERTIONS_FIRSTTIME_BACKUP); + baseTBoxModelFirsttime.add(baseTBoxModel); } - RDFFilesLoader.loadEveryTimeFiles("tbox", baseTBoxModel); + RDFFilesLoader.loadEveryTimeFiles(ctx, "tbox", baseTBoxModel); } private long secondsSince(long startTime) { @@ -94,7 +119,7 @@ public class ContentModelSetup extends JenaDataSourceSetupBase private void initializeApplicationMetadata(ServletContext ctx, Model applicationMetadataModel) { OntModel temporaryAMModel = VitroModelFactory.createOntologyModel(); - RDFFilesLoader.loadFirstTimeFiles("applicationMetadata", temporaryAMModel, true); + RDFFilesLoader.loadFirstTimeFiles(ctx, "applicationMetadata", temporaryAMModel, true); setPortalUriOnFirstTime(temporaryAMModel, ctx); applicationMetadataModel.add(temporaryAMModel); } @@ -191,6 +216,188 @@ public class ContentModelSetup extends JenaDataSourceSetupBase } } + /* + * Check if the firsttime files have changed since the firsttime startup for all ContentModels, + * if so, then apply the changes but not overwrite the whole user model + */ + private void applyFirstTimeChanges(ServletContext ctx) { + + applyFirstTimeChanges(ctx, "applicationMetadata", APPLICATION_METADATA_FIRSTTIME_BACKUP, APPLICATION_METADATA); + + applyFirstTimeChanges(ctx, "abox", ABOX_ASSERTIONS_FIRSTTIME_BACKUP, ABOX_ASSERTIONS); + + applyFirstTimeChanges(ctx, "tbox", TBOX_ASSERTIONS_FIRSTTIME_BACKUP, TBOX_ASSERTIONS); + } + + + /* + * Check if the firsttime files have changed since the firsttime startup for one ContentModel, + * if so, then apply the changes but not overwrite the whole user model + */ + private void applyFirstTimeChanges(ServletContext ctx, String modelPath, String firsttimeBackupModelUri, String userModelUri) { + log.info("Reload firsttime files on start-up if changed: '" + modelPath +"', URI: '" +userModelUri+ "'"); + ContextModelAccess models = ModelAccess.on(ctx); + OntModel firsttimeBackupModel = models.getOntModel(firsttimeBackupModelUri); + + // compare firsttime files with configuration models + log.debug("compare firsttime files with configuration models (backup from first start) for " + modelPath); + OntModel firsttimeFilesModel = VitroModelFactory.createOntologyModel(); + RDFFilesLoader.loadFirstTimeFiles(ctx, modelPath, firsttimeFilesModel, true); + + // special initialization for application metadata model + if (firsttimeBackupModelUri.equals(APPLICATION_METADATA_FIRSTTIME_BACKUP)) { + setPortalUriOnFirstTime(firsttimeFilesModel, ctx); + } + + if ( firsttimeBackupModel.isIsomorphicWith(firsttimeFilesModel) ) { + log.debug("They are the same, so do nothing: '" + modelPath + "'"); + } else { + log.debug("They differ: '" + modelPath + "', compare values in configuration models with user's triplestore"); + OntModel userModel = models.getOntModel(userModelUri); + + // double check the statements (blank notes, etc.) and apply the changes + boolean updatedFiles = applyChanges(firsttimeBackupModel, firsttimeFilesModel, userModel, modelPath); + if (updatedFiles) log.info("The model was updated, " + modelPath); + } + } + + /* + * This method is designed to compare configuration models (baseModel) with firsttime files (newModel): + * if they are the same, stopFirstTime + * else, if they differ, compare values in configuration models (baseModel) with user's triplestore + * if they are the same, update user's triplestore with value in new firsttime files + * else, if they differ, leave user's triplestore statement alone + * finally, overwrite the configuration models with content of the updated firstime files + * + * @param baseModel The backup firsttime model (from the first startup) + * @param newModel The current state of the firsttime files in the directory + * @param userModel The current state of the user model + * @param modelIdString Just an string for the output for better debugging (tbox, abox, applicationMetadata) + */ + private boolean applyChanges(Model baseModel, Model newModel, Model userModel, String modelIdString) { + boolean updatedFiles = false; + StringWriter out = new StringWriter(); + StringWriter out2 = new StringWriter(); + Model difOldNew = baseModel.difference(newModel); + Model difNewOld = newModel.difference(baseModel); + + // special case for "rootTab" triple, do not need an update (is it still used in general? if not remove this case) + if(modelIdString.equals("applicationMetadata")) { + + Property p = userModel.createProperty("http://vitro.mannlib.cornell.edu/ns/vitro/0.7#", "rootTab"); + difOldNew.removeAll(null, p, null); + difNewOld.removeAll(null, p, null); + } + + if (difOldNew.isEmpty() && difNewOld.isEmpty()) { + // if there is no difference, nothing needs to be done + log.debug("For the " + modelIdString + " model, there is no difference in both directions. So do nothing."); + } else { + // if there is a difference, we need to remove the triples in difOldNew and + // add the triples in difNewOld to the back up firsttime model + + if (!difOldNew.isEmpty()) { + difOldNew.write(out, "TTL"); + log.debug("Difference for " + modelIdString + " (old -> new), these triples should be removed: " + out); + + // Check if the UI-changes Overlap with the changes made in the fristtime-files + checkUiChangesOverlapWithFileChanges(baseModel, userModel, difOldNew); + + // before we remove the triples, we need to compare values in back up firsttime with user's triplestore + // if the triples which should be removed are still in user´s triplestore, remove them + if (userModel.containsAny(difOldNew)) { + log.debug("Some of these triples are in the user triples store, so they will be removed now"); + userModel.remove(difOldNew); + updatedFiles = true; + } + + // remove the triples from the back up firsttime model for the next check + baseModel.remove(difOldNew); + + } + if (!difNewOld.isEmpty()) { + difNewOld.write(out2, "TTL"); + log.debug("Difference for " + modelIdString + " (new -> old), these triples should be added: " + out2); + + // Check if the UI-changes Overlap with the changes made in the fristtime-files + checkUiChangesOverlapWithFileChanges(baseModel, userModel, difNewOld); + + // before we add the triples, we need to compare values in back up firsttime with user's triplestore + // if the triples which should be added are not already in user´s triplestore, add them + if (!userModel.containsAll(difNewOld)) { + log.debug("Some of these triples are not in the user triples store, so they will be added now"); + // but only the triples that are no already there + Model tmp = difNewOld.difference(userModel); + userModel.add(tmp); + updatedFiles = true; + } + + // add the triples from the back up firsttime model for the next check + baseModel.add(difNewOld); + } + } + return updatedFiles; + } + + /** + * Check if the UI-changes Overlap with the changes made in the fristtime-files, if they overlap these changes are not applied to the user-model (UI) + * + * @param baseModel firsttime backup model + * @param userModel current state in the system (user/UI-model) + * @param changesModel the changes between firsttime-files and firttime-backup + */ + private void checkUiChangesOverlapWithFileChanges(Model baseModel, Model userModel, Model changesModel) { + log.debug("Beginn check if subtractions from Backup-firsttime model to current state of firsttime-files were changed in user-model (via UI)"); + Model changesUserModel = userModel.difference(baseModel); + List changedInUIandFileStatements = new ArrayList(); + + if(!changesUserModel.isEmpty()) + { + + StringWriter out3 = new StringWriter(); + changesUserModel.write(out3, "TTL"); + log.debug("There were changes in the user-model via UI which have also changed in the firsttime files, the following triples will not be updated"); + + // iterate all statements and check if the ones which should be removed were not changed via the UI + StmtIterator iter = changesUserModel.listStatements(); + while (iter.hasNext()) { + Statement stmt = iter.nextStatement(); // get next statement + Resource subject = stmt.getSubject(); // get the subject + Property predicate = stmt.getPredicate(); // get the predicate + RDFNode object = stmt.getObject(); // get the object + + StmtIterator iter2 = changesModel.listStatements(); + + while (iter2.hasNext()) { + Statement stmt2 = iter2.nextStatement(); // get next statement + Resource subject2 = stmt2.getSubject(); // get the subject + Property predicate2 = stmt2.getPredicate(); // get the predicate + RDFNode object2 = stmt2.getObject(); // get the object + + // if subject and predicate are equal but the object differs and the language tag is the same, do not update these triples + // this case indicates an change in the UI, which should not be overwriten from the firsttime files + if(subject.equals(subject2) && predicate.equals(predicate2) && !object.equals(object2) ) { + // if object is an literal, check the language tag + if (object.isLiteral() && object2.isLiteral()) { + // if the langauge tag is the same, remove this triple from the update list + if(object.asLiteral().getLanguage().equals(object2.asLiteral().getLanguage())) { + log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2); + changedInUIandFileStatements.add(stmt2); + } + } else { + log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2); + changedInUIandFileStatements.add(stmt2); + } + } + } + } + // remove triples which were changed in the user model (UI) from the list + changesModel.remove(changedInUIandFileStatements); + } else { + log.debug("There were no changes in the user-model via UI compared to the backup-firsttime-model"); + } + } + /* ===================================================================== */ @Override diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java index e49f0e3df..80656403b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONTENT; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.RDFFilesLoader.getEnabledLocales; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -46,6 +47,7 @@ public class FileGraphSetup implements ServletContextListener { private static final Log log = LogFactory.getLog(FileGraphSetup.class); private static final String RDF = "rdf"; + private static final String I18N = "i18n"; private static final String ABOX = "abox"; private static final String TBOX = "tbox"; private static final String FILEGRAPH = "filegraph"; @@ -76,6 +78,12 @@ public class FileGraphSetup implements ServletContextListener { // ABox files Set paths = getFilegraphPaths(ctx, RDF, ABOX, FILEGRAPH); + // Load ABox files from enabled languages + Set enabledLocales = getEnabledLocales(ctx); + for (String locale : enabledLocales) { + paths.addAll(getFilegraphPaths(ctx, RDF, I18N, locale, ABOX, FILEGRAPH)); + } + cleanupDB(dataset, pathsToURIs(paths, ABOX), ABOX); // Just update the ABox filegraphs in the DB; don't attach them to a base model. @@ -84,6 +92,11 @@ public class FileGraphSetup implements ServletContextListener { // TBox files paths = getFilegraphPaths(ctx, RDF, TBOX, FILEGRAPH); + // Load TBox files from enabled languages + for (String locale : enabledLocales) { + paths.addAll(getFilegraphPaths(ctx, RDF, I18N, locale, TBOX, FILEGRAPH)); + } + cleanupDB(dataset, pathsToURIs(paths, TBOX),TBOX); OntModel tboxBaseModel = ModelAccess.on(ctx).getOntModel(ModelNames.TBOX_ASSERTIONS); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java index 35fdaf54d..acaa93fb6 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java @@ -2,7 +2,6 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup; -import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -10,9 +9,13 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.TreeSet; +import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -23,6 +26,8 @@ import org.apache.jena.rdf.model.ModelFactory; import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; +import javax.servlet.ServletContext; + /** * Help to load RDF files on first time and on every startup. */ @@ -31,11 +36,12 @@ public class RDFFilesLoader { private static final String DEFAULT_RDF_FORMAT = "RDF/XML"; private static final String RDF = "rdf"; + private static final String I18N = "i18n"; private static final String FIRST_TIME = "firsttime"; private static final String EVERY_TIME = "everytime"; /** - * Path filter that ignores sub-directories, hidden files, and markdown + * Path filter that ignores sub-directories, hidden files and markdown * files. */ private static final DirectoryStream.Filter RDF_FILE_FILTER = new DirectoryStream.Filter() { @@ -64,11 +70,20 @@ public class RDFFilesLoader { * * The files from the directory are added to the model. */ - public static void loadFirstTimeFiles(String modelPath, Model model, + public static void loadFirstTimeFiles(ServletContext ctx, String modelPath, Model model, boolean firstTime) { if (firstTime) { String home = locateHomeDirectory(); + + // Load common files Set paths = getPaths(home, RDF, modelPath, FIRST_TIME); + + // Load enabled languages + Set enabledLocales = getEnabledLocales(ctx); + for (String locale : enabledLocales) { + paths.addAll(getPaths(home, RDF, I18N, locale, modelPath, FIRST_TIME)); + } + for (Path p : paths) { log.info("Loading " + relativePath(p, home)); readOntologyFileIntoModel(p, model); @@ -87,11 +102,20 @@ public class RDFFilesLoader { * * The files from the directory become a sub-model of the model. */ - public static void loadEveryTimeFiles(String modelPath, OntModel model) { + public static void loadEveryTimeFiles(ServletContext ctx, String modelPath, OntModel model) { OntModel everytimeModel = ModelFactory .createOntologyModel(OntModelSpec.OWL_MEM); String home = locateHomeDirectory(); + + // Load common files Set paths = getPaths(home, RDF, modelPath, EVERY_TIME); + + // Load enabled languages + Set enabledLocales = getEnabledLocales(ctx); + for (String locale : enabledLocales) { + paths.addAll(getPaths(home, RDF, I18N, locale, modelPath, EVERY_TIME)); + } + for (Path p : paths) { log.info("Loading " + relativePath(p, home)); readOntologyFileIntoModel(p, everytimeModel); @@ -99,6 +123,23 @@ public class RDFFilesLoader { model.addSubModel(everytimeModel); } + public static Set getEnabledLocales(ServletContext ctx) { + Set enabledLocales = new HashSet<>(); + + // Which locales are enabled in runtime.properties? + List locales = SelectedLocale.getSelectableLocales(ctx); + for (Locale locale : locales) { + enabledLocales.add(locale.toLanguageTag().replace('-', '_')); + } + + // If no languages were enabled in runtime.properties, add a fallback as the default + if (enabledLocales.isEmpty()) { + enabledLocales.add(SelectedLocale.getFallbackLocale().toString()); + } + + return enabledLocales; + } + private static Path relativePath(Path p, String home) { try { return Paths.get(home).relativize(p); @@ -107,36 +148,9 @@ public class RDFFilesLoader { } } - /** - * Create a model from all the RDF files in the specified directory. - */ - public static OntModel getModelFromDir(File dir) { - OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - if (dir == null) { - log.warn("Must pass a File to getModelFromDir()"); - return model; - } - if (!dir.isDirectory()) { - log.warn("Directory must be a File object for a directory"); - return model; - } - if (!dir.canRead()) { - log.warn("getModelFromDir(): Directory " - + " must be readable, check permissions on " - + dir.getAbsolutePath()); - return model; - } - - Set paths = getPaths(dir.getPath()); - for (Path p : paths) { - readOntologyFileIntoModel(p, model); - } - return model; - } - /** * Find the paths to RDF files in this directory. Sub-directories, hidden - * files, and markdown files are ignored. + * files, markdown, and non-enabled language files are ignored. */ private static Set getPaths(String parentDir, String... strings) { Path dir = Paths.get(parentDir, strings); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java index 9cc824748..1adb6c694 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java @@ -6,7 +6,6 @@ import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATIO import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.servlet.ServletContext; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java index 00fdcf1b0..1bce3fbad 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java @@ -534,8 +534,12 @@ public class UpdateKnowledgeBase { try { if (f.getName().endsWith(".md")) { // Markdown files are documentation - skip. - } else if (f.getName().endsWith(".n3")) { + // UQAM-Optimization accept lower and upper case in fn extension + } else if (f.getName().toLowerCase().endsWith(".n3")) { om.read(fis, null, "N3"); + // UQAM-Optimization Accept Turtle (Must for us) + } else if (f.getName().toLowerCase().endsWith(".ttl")) { + om.read(fis, null, "TURTLE"); } else { om.read(fis, null, "RDF/XML"); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/BasicShortTermCombinedTripleSource.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/BasicShortTermCombinedTripleSource.java index 5d43d04b7..93bc5f895 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/BasicShortTermCombinedTripleSource.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/BasicShortTermCombinedTripleSource.java @@ -98,9 +98,11 @@ public class BasicShortTermCombinedTripleSource implements @Override public WebappDaoFactoryConfig getWebappDaoFactoryConfig() { List langs = getPreferredLanguages(); + List locales = Collections.list(getPreferredLocales()); WebappDaoFactoryConfig config = new WebappDaoFactoryConfig(); config.setDefaultNamespace(props.getProperty("Vitro.defaultNamespace")); config.setPreferredLanguages(langs); + config.setPreferredLocales(locales); config.setUnderlyingStoreReasoned(isStoreReasoned()); config.setCustomListViewConfigFileMap(getCustomListViewConfigFileMap()); return config; @@ -111,7 +113,6 @@ public class BasicShortTermCombinedTripleSource implements return LanguageFilteringUtils.localesToLanguages(getPreferredLocales()); } - @SuppressWarnings("unchecked") private Enumeration getPreferredLocales() { return req.getLocales(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetter.java index 7c1da263e..e5c057778 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetter.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetter.java @@ -165,7 +165,16 @@ public class SparqlQueryDataGetter extends DataGetterBase implements DataGetter{ private String bindParameters(String text, Map merged) { String bound = text; for (String key : merged.keySet()) { - bound = bound.replaceAll("([?$]" + key + ")([^a-zA-Z0-9_\\-])", "<" + merged.get(key) + ">$2"); + String value = merged.get(key); + if (value.startsWith("http://") || value.startsWith("https://")) { + /* + * UQAM-Optimization if the "value" looks like an URI then wrap the value with the characters '<' '>' + * + */ + bound = bound.replaceAll("([?$]" + key + ")([^a-zA-Z0-9_\\-])", "<" + value + ">$2"); + } else { + bound = bound.replaceAll("([?$]" + key + ")([^a-zA-Z0-9_\\-])", value + "$2"); + } } if (log.isDebugEnabled()) { log.debug("parameters: " + merged); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java index 4414c1b8d..1037330e5 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java @@ -43,7 +43,10 @@ public enum Key { * Load language property files every time they are requested. */ I18N_DEFEAT_CACHE("developer.i18n.defeatCache", true), - + /** + * Enable online translations. + */ + I18N_ONLINE_TRANSLATION("developer.i18n.onlineTranslation", true), /** * Enable the I18nLogger to log each string request. */ diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java index d0ff197f7..2d3844933 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java @@ -21,6 +21,10 @@ import org.apache.commons.logging.LogFactory; * check their current status. */ public class VitroBackgroundThread extends Thread { + // UQAM-Bug-Correction add start + public synchronized void start() { + super.start(); + } private static final Log log = LogFactory.getLog(VitroBackgroundThread.class); private static final ConcurrentLinkedQueue> allThreads = new ConcurrentLinkedQueue>(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/customlistview/CustomListViewConfigFile.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/customlistview/CustomListViewConfigFile.java index 081690415..89dc8c1bb 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/customlistview/CustomListViewConfigFile.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/customlistview/CustomListViewConfigFile.java @@ -20,8 +20,6 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean; -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Document; diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java index 05a9c676f..577229162 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java @@ -6,7 +6,6 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.Collator; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -39,6 +38,8 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.SelectListGeneratorVTwo; import edu.cornell.mannlib.vitro.webapp.i18n.I18n; import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption; import edu.cornell.mannlib.vitro.webapp.web.beanswrappers.ReadOnlyBeansWrapper; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.ObjectPropertyStatementTemplateModel; @@ -98,7 +99,9 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { private void populateDropdowns() throws Exception { //For each field with an optionType defined, create the options - WebappDaoFactory wdf = vreq.getWebappDaoFactory(); +// WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + // UQAM-Optimization Manage Linguistic context + WebappDaoFactory wdf = ModelAccess.on(vreq).getWebappDaoFactory(LanguageOption.LANGUAGE_AWARE); for(String fieldName: editConfig.getFields().keySet()){ FieldVTwo field = editConfig.getField(fieldName); //TODO: Check if we even need empty options if field options do not exist @@ -106,7 +109,8 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { //empty options field.setOptions(new ConstantFieldOptions()); } - Map optionsMap = SelectListGeneratorVTwo.getOptions(editConfig, fieldName, wdf); + //UQAM-Optimization changing signature for including internationalization in scroll-down menu + Map optionsMap = SelectListGeneratorVTwo.getOptions(editConfig, fieldName, wdf, I18n.bundle(vreq)); optionsMap = SelectListGeneratorVTwo.getSortedMap(optionsMap, field.getFieldOptions().getCustomComparator(), vreq); if(pageData.containsKey(fieldName)) { log.error("Check the edit configuration setup as pageData already contains " + fieldName + " and this will be overwritten now with empty collection"); @@ -129,6 +133,11 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { return selectedValue; } + public String getPageTitle() { + String pageTitle = i18n.text("edit_page_title"); + return pageTitle != null ? pageTitle : "Edit"; + } + private void setFormTitle() { String predicateUri = editConfig.getPredicateUri(); if(predicateUri != null) { @@ -184,7 +193,7 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { Individual objectIndividual = EditConfigurationUtils.getObjectIndividual(vreq); ObjectProperty prop = EditConfigurationUtils.getObjectProperty(vreq); Individual subject = EditConfigurationUtils.getSubjectIndividual(vreq); - VClass rangeClass = EditConfigurationUtils.getRangeVClass(vreq); + VClass rangeClass = EditConfigurationUtils.getLangAwardRangeVClass(vreq); if(objectIndividual != null) { propertyTitle = prop.getDomainPublic(); } else { @@ -529,14 +538,17 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { //TODO:Check where this logic should actually go, copied from input element formatting tag //Updating to enable multiple vclasses applicable to subject to be analyzed to understand possible range of types public Map getOfferTypesCreateNew() { - WebappDaoFactory wdf = vreq.getWebappDaoFactory(); +// WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + // UQAM-Optimization Manage Linguistic context + WebappDaoFactory wdf = ModelAccess.on(vreq).getWebappDaoFactory(LanguageOption.LANGUAGE_AWARE); ObjectProperty op = wdf.getObjectPropertyDao().getObjectPropertyByURI(editConfig.getPredicateUri()); Individual sub = wdf.getIndividualDao().getIndividualByURI(editConfig.getSubjectUri()); - VClass rangeClass = EditConfigurationUtils.getRangeVClass(vreq); + // UQAM-Optimization Manage Linguistic context + VClass rangeClass = EditConfigurationUtils.getLangAwardRangeVClass(vreq); List vclasses = null; List subjectVClasses = sub.getVClasses(); @@ -705,9 +717,9 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { public String getDeleteProcessingUrl() { return vreq.getContextPath() + "/deletePropertyController"; } - + public String getDeleteIndividualProcessingUrl() { - return vreq.getContextPath() + "/deleteIndividualController"; + return vreq.getContextPath() + "/deleteIndividualController"; } //TODO: Check if this logic is correct and delete prohibited does not expect a specific value diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java index efaa8396f..6ebd52ebc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java @@ -39,7 +39,7 @@ import edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel; public abstract class BaseIndividualTemplateModel extends BaseTemplateModel { private static final Log log = LogFactory.getLog(BaseIndividualTemplateModel.class); - private static final String EDIT_PATH = "editRequestDispatch"; + private static final String EDIT_PATH = "editRequestDispatch"; protected final Individual individual; protected final LoginStatusBean loginStatusBean; diff --git a/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java b/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java index 6e99778d0..e4427d82e 100644 --- a/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java +++ b/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java @@ -1,15 +1,24 @@ package org.vivoweb.linkeddatafragments.servlet; -import com.fasterxml.jackson.databind.JsonNode; -import edu.cornell.mannlib.vitro.webapp.beans.Ontology; -import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; -import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.apache.commons.io.IOUtils; -import org.apache.jena.riot.Lang; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.riot.Lang; import org.linkeddatafragments.config.ConfigReader; import org.linkeddatafragments.datasource.DataSourceFactory; import org.linkeddatafragments.datasource.DataSourceTypesRegistry; @@ -22,26 +31,19 @@ import org.linkeddatafragments.fragments.ILinkedDataFragment; import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest; import org.linkeddatafragments.util.MIMEParse; import org.linkeddatafragments.views.ILinkedDataFragmentWriter; -import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl; -import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory; import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceBasedRequestProcessorForTPFs; import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSourceType; +import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl; +import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; +import com.fasterxml.jackson.databind.JsonNode; + +import edu.cornell.mannlib.vitro.webapp.beans.Ontology; +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; +import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; /** * Servlet that responds with a Linked Data Fragment. @@ -52,29 +54,13 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet { private final static long serialVersionUID = 1L; private static final String PROPERTY_TPF_ACTIVE_FLAG = "tpf.activeFlag"; + private static final Log log = LogFactory.getLog(VitroLinkedDataFragmentServlet.class); private ConfigReader config; private final HashMap dataSources = new HashMap<>(); - private final Collection mimeTypes = new ArrayList<>(); private ConfigurationProperties configProps; private String tpfActiveFlag; - private File getConfigFile(ServletConfig config) throws IOException { - String path = config.getServletContext().getRealPath("/"); - if (path == null) { - // this can happen when running standalone - path = System.getProperty("user.dir"); - } - File cfg = new File(path, "config-example.json"); - if (!cfg.exists()) { - throw new IOException("Configuration file " + cfg + " not found."); - } - if (!cfg.isFile()) { - throw new IOException("Configuration file " + cfg + " is not a file."); - } - return cfg; - } - @Override public void init(ServletConfig servletConfig) throws ServletException { try { @@ -82,11 +68,11 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet { configProps = ConfigurationProperties.getBean(ctx); if (!configurationPresent()) { - throw new ServletException("TPF is currently disabled. To enable, add 'tpfActive.flag=true' to the runtime.properties."); - } else { - if (!tpfActiveFlag.equalsIgnoreCase("true")) { - throw new ServletException("TPF is currently disabled. To enable, set 'tpfActive.flag=true' in runtime.properties."); - } + throw new ServletException("TPF is currently disabled. To enable, add '" + + PROPERTY_TPF_ACTIVE_FLAG + " = true' to runtime.properties."); + } else if (!tpfActiveFlag.equalsIgnoreCase("true")) { + throw new ServletException("TPF is currently disabled. To enable, set '" + + PROPERTY_TPF_ACTIVE_FLAG + " = true' in runtime.properties."); } RDFService rdfService = ModelAccess.on(ctx).getRDFService(); @@ -215,18 +201,18 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet { writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest); } catch (DataSourceNotFoundException ex) { + log.error(ex, ex); try { response.setStatus(404); writer.writeNotFound(response.getOutputStream(), request); } catch (Exception ex1) { + log.error(ex1, ex1); throw new ServletException(ex1); } - } catch (Exception e) { - response.setStatus(500); - writer.writeError(response.getOutputStream(), e); - } + } } catch (Exception e) { + log.error(e, e); throw new ServletException(e); } finally { diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/testing/AbstractTestClass.java b/api/src/test/java/edu/cornell/mannlib/vitro/testing/AbstractTestClass.java index fa5e4fe01..77d669415 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/testing/AbstractTestClass.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/testing/AbstractTestClass.java @@ -56,6 +56,7 @@ import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import stubs.edu.cornell.mannlib.vitro.webapp.i18n.I18nStub; /** * A collection of useful routines to help when testing. @@ -85,8 +86,21 @@ public abstract class AbstractTestClass { * {@link Level#INFO}. */ @Before + public void setUp() { + initializeLogging(); + useI18nStubBundles(); + } + @After - public void initializeLogging() { + public void tearDown() { + initializeLogging(); + } + + protected void useI18nStubBundles() { + I18nStub.setup(); + } + + private void initializeLogging() { LogManager.resetConfiguration(); Logger.getRootLogger().addAppender(new ConsoleAppender(patternLayout)); Logger.getRootLogger().setLevel(Level.INFO); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicyTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicyTest.java index 68b0ab4cd..17ef2333a 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicyTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicyTest.java @@ -67,7 +67,7 @@ public class SelfEditingPolicyTest extends AbstractTestClass { private OntModel ontModel; @Before - public void setUp() throws Exception { + public void setUp() { ctx = new ServletContextStub(); PropertyRestrictionBeanStub diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicy_2_Test.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicy_2_Test.java index 044318139..ce7749868 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicy_2_Test.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/auth/policy/SelfEditingPolicy_2_Test.java @@ -75,7 +75,7 @@ public class SelfEditingPolicy_2_Test extends AbstractTestClass { @Before - public void setUp() throws Exception { + public void setUp() { ServletContextStub ctx = new ServletContextStub(); PropertyRestrictionBeanStub.getInstance(new String[] { ADMIN_NS }); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiTest.java index 8fde7a742..aeed2e551 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiTest.java @@ -73,7 +73,6 @@ public class SparqlUpdateApiTest extends AbstractTestClass { try { if(ds.supportsTransactions()) { ds.begin(ReadWrite.WRITE); - System.out.println("yep"); } UpdateAction.execute(UpdateFactory.create(updateStr1), graphStore); } finally { diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java index f15e05567..c849fa411 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java @@ -48,7 +48,6 @@ import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList; import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; -import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator; import edu.cornell.mannlib.vitro.webapp.controller.authenticate.AuthenticatorStub; import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State; diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJenaTest.java index 8548ea76d..6e436c8f8 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJenaTest.java @@ -11,6 +11,7 @@ import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.OWL; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; @@ -65,9 +66,9 @@ public class DataPropertyDaoJenaTest extends AbstractTestClass { property1.setPropertyValue(RDFS.domain, subModel.createResource("http://thisIsTheDomainClassURI")); property1.setPropertyValue(RDFS.range, subModel.createResource("http://thisIsTheRangeClassURI")); property1.addProperty(RDF.type, OWL.FunctionalProperty); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), subModel.createTypedLiteral("this is the example")); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), subModel.createTypedLiteral("this is the description")); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PUBLIC_DESCRIPTION_ANNOT), subModel.createTypedLiteral("this is the public description")); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), ResourceFactory.createLangLiteral("this is the example", lang)); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), ResourceFactory.createLangLiteral("this is the description", lang)); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PUBLIC_DESCRIPTION_ANNOT), ResourceFactory.createLangLiteral("this is the public description", lang)); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DISPLAY_RANK_ANNOT), subModel.createTypedLiteral(21)); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DISPLAY_LIMIT), subModel.createTypedLiteral(5)); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.HIDDEN_FROM_DISPLAY_BELOW_ROLE_LEVEL_ANNOT), subModel.createResource("http://vitro.mannlib.cornell.edu/ns/vitro/role#curator")); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DependentResourceDeleteJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DependentResourceDeleteJenaTest.java index 2428b56de..24b98809d 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DependentResourceDeleteJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/DependentResourceDeleteJenaTest.java @@ -5,6 +5,7 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; import java.io.StringReader; import java.util.List; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Assert; import org.apache.jena.rdf.model.Model; @@ -16,7 +17,7 @@ import org.apache.jena.vocabulary.XSD; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; -public class DependentResourceDeleteJenaTest { +public class DependentResourceDeleteJenaTest extends AbstractTestClass { String isDependentRelation = " <"+VitroVocabulary.PROPERTY_STUBOBJECTPROPERTYANNOT+"> \"true\"^^xsd:boolean .\n" ; diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoTest.java index 01058f799..fe3938974 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoTest.java @@ -2,26 +2,29 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import static org.junit.Assert.assertEquals; + import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.StringReader; -import org.junit.Assert; - -import org.junit.Test; - import org.apache.jena.ontology.OntClass; import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.ontology.OntProperty; +import org.apache.jena.ontology.OntResource; import org.apache.jena.ontology.Restriction; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.OWL; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import org.apache.jena.vocabulary.XSD; +import org.junit.Assert; +import org.junit.Test; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import edu.cornell.mannlib.vitro.webapp.beans.DataProperty; import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatementImpl; @@ -35,7 +38,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; -public class JenaBaseDaoTest { +public class JenaBaseDaoTest extends AbstractTestClass { String isDependentRelation = " <"+VitroVocabulary.PROPERTY_STUBOBJECTPROPERTYANNOT+"> \"true\"^^xsd:boolean .\n" ; @@ -457,6 +460,53 @@ public class JenaBaseDaoTest { Assert.assertEquals(m.size(), 2); // just rdf:type for Class1 and Prop } + + @Test + /** + * Test that a resource's labels in one language are correctly updated without + * affecting labels in other languages. + */ + public void testUpdateRDFSLabel() { + OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + WebappDaoFactoryJena wadf = new WebappDaoFactoryJena(m); + JenaBaseDao dao = new JenaBaseDao(wadf); + OntResource ontRes = m.createOntResource("http://example.com/i/n1"); + // update one language + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US")); + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES")); + dao.updateRDFSLabel(ontRes, "engLabel2", "en-US"); + assertEquals(2, m.size()); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel2", "en-US"))); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES"))); + m.removeAll(); + // update language-less + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US")); + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES")); + m.add(ontRes, RDFS.label, "languageLessLabel1"); + dao.updateRDFSLabel(ontRes, "languageLessLabel2", null); + assertEquals(3, m.size()); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US"))); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES"))); + Assert.assertTrue(m.contains(ontRes, RDFS.label, "languageLessLabel2")); + m.removeAll(); + // remove a language + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US")); + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES")); + m.add(ontRes, RDFS.label, m.createTypedLiteral("stringLabel1")); + dao.updateRDFSLabel(ontRes, null, "en-US"); + assertEquals(2, m.size()); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES"))); + Assert.assertTrue(m.contains(ontRes, RDFS.label, m.createTypedLiteral("stringLabel1"))); + m.removeAll(); + // remove language-less labels + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US")); + m.add(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES")); + m.add(ontRes, RDFS.label, ResourceFactory.createPlainLiteral("languageLessLabel1")); + dao.updateRDFSLabel(ontRes, null, null); + assertEquals(2, m.size()); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("engLabel1", "en-US"))); + Assert.assertTrue(m.contains(ontRes, RDFS.label, ResourceFactory.createLangLiteral("esLabel1", "es-ES"))); + } /** * Compare the contents of the expected model with the actual model (not counting modification times). diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao_2_Test.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao_2_Test.java index 9e448c651..5bbce84bf 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao_2_Test.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao_2_Test.java @@ -48,6 +48,8 @@ public class JenaBaseDao_2_Test extends AbstractTestClass { @Before public void initializeThings() { + super.setUp(); + ontModel = ModelFactory.createOntologyModel(); prop1 = ontModel.createProperty("property1"); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJenaTest.java index 59a8d8bc5..f7d5ca528 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/MenuDaoJenaTest.java @@ -27,7 +27,9 @@ public class MenuDaoJenaTest extends AbstractTestClass { OntModel displayModel; @Before - public void setUp() throws Exception { + public void setUp() { + super.setUp(); + // Suppress error logging. setLoggerLevel(RDFDefaultErrorHandler.class, Level.OFF); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJenaTest.java index 5df236060..32aab2131 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJenaTest.java @@ -9,6 +9,7 @@ import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.RDFS; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; @@ -136,9 +137,9 @@ public class ObjectPropertyDaoJenaTest extends AbstractTestClass { property1.setPropertyValue(RDFS.domain, subModel.createResource("http://thisIsTheDomainClassURI")); property1.setPropertyValue(RDFS.range, subModel.createResource("http://thisIsTheRangeClassURI")); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), subModel.createTypedLiteral("this is the example")); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), subModel.createTypedLiteral("this is the description")); - property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PUBLIC_DESCRIPTION_ANNOT), subModel.createTypedLiteral("this is the public description")); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), ResourceFactory.createLangLiteral("this is the example", lang)); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), ResourceFactory.createLangLiteral("this is the description", lang)); + property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PUBLIC_DESCRIPTION_ANNOT), ResourceFactory.createLangLiteral("this is the public description", lang)); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.DISPLAY_LIMIT), subModel.createTypedLiteral(6)); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PROPERTY_ENTITYSORTFIELD), subModel.createTypedLiteral("this is the entity sort field")); property1.setPropertyValue(subModel.createProperty(VitroVocabulary.PROPERTY_ENTITYSORTDIRECTION), subModel.createTypedLiteral("this is the entity sort direction")); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java index bc5c8bdef..6488a107b 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java @@ -4,12 +4,13 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; import static org.junit.Assert.*; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Test; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; -public class ObjectPropertyStatementDaoJenaTest { +public class ObjectPropertyStatementDaoJenaTest extends AbstractTestClass { /** * Test if jena lib can parse N3 that it generates. diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/OntModelSegementationTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/OntModelSegementationTest.java index 8867ce9cc..7800acedf 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/OntModelSegementationTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/OntModelSegementationTest.java @@ -2,6 +2,7 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Assert; import org.junit.Ignore; @@ -28,11 +29,12 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; * @author bjl23 * */ -public class OntModelSegementationTest { +public class OntModelSegementationTest extends AbstractTestClass { private WebappDaoFactoryJena wadf; @org.junit.Before public void setUpWebappDaoFactoryJena() { + super.setUp(); wadf = new WebappDaoFactoryJena(new SimpleOntModelSelector()); } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJenaTest.java index d4b55a540..76e681466 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJenaTest.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Assert; import org.apache.jena.ontology.OntModel; @@ -20,7 +21,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -public class PropertyInstanceDaoJenaTest { +public class PropertyInstanceDaoJenaTest extends AbstractTestClass { String isDependentRelation = " <"+VitroVocabulary.PROPERTY_STUBOBJECTPROPERTYANNOT+"> \"true\"^^xsd:boolean .\n" ; diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java index e6a301366..7f755bf42 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java @@ -74,6 +74,8 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass { @Before public void setup() throws IOException { + super.setUp(); + InputStream stream = UserAccountsDaoJenaTest.class .getResourceAsStream(N3_DATA_FILENAME); Model model = ModelFactory.createDefaultModel(); @@ -90,6 +92,8 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass { @Before public void createUserAccountValues() { + super.setUp(); + user1 = userAccount(URI_USER1, "email@able.edu", "Zack", "Roberts", "garbage", "" ,"", 0L, false, 5, 12345678L, Status.ACTIVE, "user1", false, collection(URI_ROLE1), false, EMPTY); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoTest.java index 4bca1fd7e..94068bf97 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassDaoTest.java @@ -3,19 +3,25 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.StringWriter; import java.util.List; -import static org.junit.Assert.*; -import org.junit.Test; - +import org.apache.jena.atlas.logging.Log; import org.apache.jena.ontology.ObjectProperty; import org.apache.jena.ontology.OntClass; import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.OWL; +import org.junit.Test; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; @@ -24,7 +30,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; * */ -public class VClassDaoTest { +public class VClassDaoTest extends AbstractTestClass { @Test // Test that the VClassDaoJena::updateVClass method will only update the jena model for @@ -62,9 +68,9 @@ public class VClassDaoTest { class1.setLabel(rdfsLabel,lang); //rdfs:label class1.setPropertyValue(subModel.createProperty(VitroVocabulary.IN_CLASSGROUP), subModel.createResource("http://thisIsTheClassGroupURI")); - class1.setPropertyValue(subModel.createProperty(VitroVocabulary.SHORTDEF), subModel.createTypedLiteral("this is the short definition")); - class1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), subModel.createTypedLiteral("this is the example - why is this a string?")); - class1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), subModel.createTypedLiteral("this is the description")); + class1.setPropertyValue(subModel.createProperty(VitroVocabulary.SHORTDEF), ResourceFactory.createLangLiteral("this is the short definition", lang)); + class1.setPropertyValue(subModel.createProperty(VitroVocabulary.EXAMPLE_ANNOT), ResourceFactory.createLangLiteral("this is the example - why is this a string?", lang)); + class1.setPropertyValue(subModel.createProperty(VitroVocabulary.DESCRIPTION_ANNOT), ResourceFactory.createLangLiteral("this is the description", lang)); class1.setPropertyValue(subModel.createProperty(VitroVocabulary.DISPLAY_LIMIT), subModel.createTypedLiteral(-1)); class1.setPropertyValue(subModel.createProperty(VitroVocabulary.DISPLAY_RANK_ANNOT), subModel.createTypedLiteral(-11)); class1.setPropertyValue(subModel.createProperty(VitroVocabulary.SEARCH_BOOST_ANNOT), subModel.createTypedLiteral(2.4f)); @@ -120,7 +126,7 @@ public class VClassDaoTest { wipeOutModTime(superModel); assertTrue(subModel.isIsomorphicWith(origSubModel)); - assertTrue(superModel.isIsomorphicWith(origSuperModel)); + assertTrue(superModel.isIsomorphicWith(origSuperModel)); } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCacheTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCacheTest.java index 684686a07..6a60adb2d 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCacheTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCacheTest.java @@ -4,6 +4,7 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; import java.io.StringReader; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -14,10 +15,12 @@ import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.rdf.model.Statement; import org.apache.jena.vocabulary.RDFS; -public class VClassGroupCacheTest { +public class VClassGroupCacheTest extends AbstractTestClass { @Before - public void setUp() throws Exception { + public void setUp() { + // Not calling super.setUp() because we do not want to change the logging level + super.useI18nStubBundles(); } @Test diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassJenaTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassJenaTest.java index e252292d0..3aeb732fc 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassJenaTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassJenaTest.java @@ -7,6 +7,7 @@ import java.net.URLEncoder; import java.util.Iterator; import java.util.List; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import org.junit.Assert; import org.junit.Test; @@ -49,7 +50,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; * */ -public class VClassJenaTest { +public class VClassJenaTest extends AbstractTestClass { @Test // NIHVIVO-1157 introduced VClassJena.java, a lazy-loading version of VClass.java. diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwoTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwoTest.java index 678ca76d1..4b3b27cfe 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwoTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/BasicValidationVTwoTest.java @@ -4,44 +4,46 @@ package edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo; import java.util.Collections; -import org.junit.Assert; +import javax.servlet.http.HttpServletRequest; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import stubs.edu.cornell.mannlib.vitro.webapp.i18n.I18nStub; +import stubs.javax.servlet.http.HttpServletRequestStub; + public class BasicValidationVTwoTest { + @Before + public void useI18nStubBundles() { + I18nStub.setup(); + } @SuppressWarnings("unchecked") @Test public void testHttpUrlValidate() { - BasicValidationVTwo bv = new BasicValidationVTwo(Collections.EMPTY_MAP); + HttpServletRequest req = new HttpServletRequestStub(); + BasicValidationVTwo bv = new BasicValidationVTwo(Collections.EMPTY_MAP, I18nStub.bundle(req)); String res; res = bv.validate("httpUrl", "http://example.com/index"); - Assert.assertEquals(res, BasicValidationVTwo.SUCCESS); + Assert.assertEquals(BasicValidationVTwo.SUCCESS, res); res = bv.validate("httpUrl", "http://example.com/index?bogus=skjd%20skljd&something=sdkf"); - Assert.assertEquals(res, BasicValidationVTwo.SUCCESS); + Assert.assertEquals(BasicValidationVTwo.SUCCESS, res); res = bv.validate("httpUrl", "http://example.com/index#2.23?bogus=skjd%20skljd&something=sdkf"); - Assert.assertEquals(res, BasicValidationVTwo.SUCCESS); + Assert.assertEquals(BasicValidationVTwo.SUCCESS, res); } @SuppressWarnings("unchecked") @Test public void testEmptyValidate(){ - BasicValidationVTwo bv = new BasicValidationVTwo(Collections.EMPTY_MAP); + HttpServletRequest req = new HttpServletRequestStub(); + BasicValidationVTwo bv = new BasicValidationVTwo(Collections.EMPTY_MAP, I18nStub.bundle(req)); - Assert.assertEquals( - bv.validate("nonempty", null) - , BasicValidationVTwo.REQUIRED_FIELD_EMPTY_MSG); - - - Assert.assertEquals( - bv.validate("nonempty", "") - , BasicValidationVTwo.REQUIRED_FIELD_EMPTY_MSG); - - Assert.assertEquals( - bv.validate("nonempty", "some value") - , BasicValidationVTwo.SUCCESS); + Assert.assertEquals(BasicValidationVTwo.REQUIRED_FIELD_EMPTY_MSG, bv.validate("nonempty", null)); + Assert.assertEquals(BasicValidationVTwo.REQUIRED_FIELD_EMPTY_MSG, bv.validate("nonempty", "")); + Assert.assertEquals(BasicValidationVTwo.SUCCESS, bv.validate("nonempty", "some value")); } } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfFormTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfFormTest.java index 79ee54365..56a4b4d7c 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfFormTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/ProcessRdfFormTest.java @@ -19,11 +19,13 @@ import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants; +import stubs.javax.servlet.http.HttpServletRequestStub; -public class ProcessRdfFormTest extends AbstractTestClass{ +public class ProcessRdfFormTest extends AbstractTestClass { @Test public void basicNewStatementTest() throws Exception{ @@ -40,14 +42,16 @@ public class ProcessRdfFormTest extends AbstractTestClass{ values.put("test3", (new String[] {"http://test.com/uri3"})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); /* test just the N3 substitution part */ Listreq = config.getN3Required(); Listopt = config.getN3Optional(); - processor.subInValuesToN3( config , submission, req, opt, null , null); + processor.subInValuesToN3( config , submission, req, opt, null , null, vreq); assertNotNull(req); assertTrue( req.size() > 0); assertNotNull(req.get(0)); @@ -102,7 +106,10 @@ public class ProcessRdfFormTest extends AbstractTestClass{ Map values = new HashMap(); values.put("testZ", (new String[] {testZURIChanged})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); AdditionsAndRetractions changes = processor.process( config, submission, null ); @@ -176,14 +183,17 @@ public class ProcessRdfFormTest extends AbstractTestClass{ values.put("test2", (new String[] {test2})); values.put("test3", (new String[] {test3})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); /* test just the N3 substitution part */ Listreq = config.getN3Required(); Listopt = config.getN3Optional(); - processor.subInValuesToN3( config , submission, req, opt, null , null); + processor.subInValuesToN3( config , submission, req, opt, null , null, vreq); assertNotNull(req); assertTrue( req.size() > 0); assertNotNull(req.get(0)); @@ -220,14 +230,16 @@ public class ProcessRdfFormTest extends AbstractTestClass{ values.put("test3", (new String[] {"http://test.com/uri3"})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); /* test just the N3 substitution part */ Listreq = config.getN3Required(); Listopt = config.getN3Optional(); - processor.subInValuesToN3( config , submission, req, opt, null , null); + processor.subInValuesToN3( config , submission, req, opt, null , null, vreq); assertNotNull(req); assertTrue( req.size() > 0); assertNotNull(req.get(0)); @@ -270,14 +282,16 @@ public class ProcessRdfFormTest extends AbstractTestClass{ values.put("test3", (new String[] {"http://test.com/uri3"})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); /* test just the N3 substitution part */ Listreq = config.getN3Required(); Listopt = config.getN3Optional(); - processor.subInValuesToN3( config , submission, req, opt, null , null); + processor.subInValuesToN3( config , submission, req, opt, null , null, vreq); assertNotNull(req); assertTrue( req.size() > 0); assertNotNull(req.get(0)); @@ -357,10 +371,13 @@ public class ProcessRdfFormTest extends AbstractTestClass{ values.put("testZ", (new String[] {testZURIChanged})); values.put("zLabel", (new String[] {"New Z Label"})); values.put("editKey", (new String[] {"mockEditKey"})); - MultiValueEditSubmission submission = new MultiValueEditSubmission(values, config); + + VitroRequest vreq = createRequestWithParameters(values); + + MultiValueEditSubmission submission = new MultiValueEditSubmission(vreq, config); ProcessRdfForm processor = new ProcessRdfForm(config,getMockNewURIMaker()); - AdditionsAndRetractions changes = processor.process( config, submission, null ); + AdditionsAndRetractions changes = processor.process( config, submission, vreq ); assertNotNull( changes ); assertNotNull( changes.getAdditions() ); @@ -396,4 +413,16 @@ public class ProcessRdfFormTest extends AbstractTestClass{ } }; } + + private VitroRequest createRequestWithParameters(Map parameters) { + HttpServletRequestStub req = new HttpServletRequestStub(); + for (String key : parameters.keySet()) { + for (String value : parameters.get(key)) { + req.addParameter(key, value); + } + } + + return new VitroRequest(req); + } + } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java new file mode 100644 index 000000000..d1f6b8433 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java @@ -0,0 +1,62 @@ +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.vocabulary.RDFS; +import org.junit.Test; +import org.testng.Assert; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; + +public class LimitRemovalsToLanguageTest extends AbstractTestClass { + + @Test + /** + * Test that retractions are properly limited to the specified language + */ + public void testPreprocess() { + LimitRemovalsToLanguage preproc = new LimitRemovalsToLanguage("en-US"); + Model additions = ModelFactory.createDefaultModel(); + Model retractions = ModelFactory.createDefaultModel(); + Resource res = ResourceFactory.createResource("http://example.com/i/n1"); + // eliminate Spanish retraction if only English is being edited + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US")); + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es")); + additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US")); + preproc.preprocess(retractions, additions, null); + Assert.assertEquals(retractions.size(), 1); + Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + Assert.assertEquals(additions.size(), 1); + Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); + additions.removeAll(); + retractions.removeAll(); + // Keep all retractions unmolested if no labels at all are being re-added. + // (The form may be trying to delete the entire individual.) + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US")); + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es")); + preproc.preprocess(retractions, additions, null); + Assert.assertEquals(retractions.size(), 2); + Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); + Assert.assertEquals(additions.size(), 0); + additions.removeAll(); + retractions.removeAll(); + // Keep both retractions if the form supplies new values for both languages + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US")); + retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es")); + additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US")); + additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es2", "es")); + preproc.preprocess(retractions, additions, null); + Assert.assertEquals(retractions.size(), 2); + Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); + Assert.assertEquals(additions.size(), 2); + Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); + Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es2", "es"))); + additions.removeAll(); + retractions.removeAll(); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/validators/AntiXssValidationTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/validators/AntiXssValidationTest.java index e9e2c463d..3c238facd 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/validators/AntiXssValidationTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/validators/AntiXssValidationTest.java @@ -9,10 +9,13 @@ import java.util.Map; import org.junit.Assert; import org.junit.Test; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.MultiValueEditSubmission; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo; +import stubs.javax.servlet.http.HttpServletRequestStub; + public class AntiXssValidationTest { @Test @@ -29,8 +32,10 @@ public class AntiXssValidationTest { String[] vals= { "some sort of string" }; params.put("X", vals); + VitroRequest vreq = createRequestWithParameters(params); + MultiValueEditSubmission mvEditSub = - new MultiValueEditSubmission(params,eConf); + new MultiValueEditSubmission(vreq,eConf); Map res = validator.validate(eConf, mvEditSub); Assert.assertEquals(null, res); @@ -53,8 +58,10 @@ public class AntiXssValidationTest { String[] strings2 = {"no problem 2"}; params.put("Z", strings2 ); + VitroRequest vreq = createRequestWithParameters(params); + MultiValueEditSubmission mvEditSub = - new MultiValueEditSubmission(params,eConf); + new MultiValueEditSubmission(vreq,eConf); Map res = validator.validate(eConf, mvEditSub); Assert.assertNull( res ); @@ -72,8 +79,10 @@ public class AntiXssValidationTest { Map params = new HashMap(); params.put("X", strings ); + VitroRequest vreq = createRequestWithParameters(params); + MultiValueEditSubmission mvEditSub = - new MultiValueEditSubmission(params,eConf); + new MultiValueEditSubmission(vreq,eConf); return validator.validate(eConf, mvEditSub); } @@ -125,5 +134,15 @@ public class AntiXssValidationTest { Assert.assertNotNull(result); } + private VitroRequest createRequestWithParameters(Map parameters) { + HttpServletRequestStub req = new HttpServletRequestStub(); + for (String key : parameters.keySet()) { + for (String value : parameters.get(key)) { + req.addParameter(key, value); + } + } + + return new VitroRequest(req); + } } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFServiceTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFServiceTest.java index 261718d6d..a062798f1 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFServiceTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LanguageFilteringRDFServiceTest.java @@ -282,12 +282,12 @@ public class LanguageFilteringRDFServiceTest extends AbstractTestClass { private Comparator buildRowIndexedLiteralSortByLang() { try { Class clazz = Class.forName(COLLATOR_CLASSNAME); - Class[] argTypes = { LanguageFilteringRDFService.class }; - Constructor constructor = clazz.getDeclaredConstructor(argTypes); + Class[] argTypes = { LanguageFilteringRDFService.class, List.class }; + Constructor constructor = clazz.getDeclaredConstructor(argTypes); constructor.setAccessible(true); - return (Comparator) constructor - .newInstance(filteringRDFService); + .newInstance(filteringRDFService, new AcceptableLanguages( + preferredLanguages)); } catch (Exception e) { throw new RuntimeException("Could not create a collator", e); } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/ThumbnailImageURLTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/ThumbnailImageURLTest.java index cd33c59f6..9f304f998 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/ThumbnailImageURLTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/ThumbnailImageURLTest.java @@ -37,7 +37,7 @@ public class ThumbnailImageURLTest extends AbstractTestClass{ * @throws java.lang.Exception */ @Before - public void setUp() throws Exception { + public void setUp() { setLoggerLevel(RDFDefaultErrorHandler.class, Level.OFF); ApplicationStub.setup(new ServletContextStub(), new SearchEngineStub()); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectPropertiesTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectPropertiesTest.java index 038c25453..7257f1ad7 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectPropertiesTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/AdditionalURIsForObjectPropertiesTest.java @@ -90,13 +90,14 @@ public class AdditionalURIsForObjectPropertiesTest { Assert.assertTrue("uris was empty", uris.size() > 0 ); Assert.assertTrue("uris didn't not contain test:cheese", uris.contains(testNS+"cheese")); + Assert.assertTrue("uris didn't not contain test:bob", uris.contains(testNS+"bob")); Assert.assertTrue("uris contained test:Person", !uris.contains(testNS+"Person")); Assert.assertTrue("uris contained owl:Thing", !uris.contains( OWL.Thing.getURI() )); Assert.assertTrue("uris contained test:onions", !uris.contains(testNS+"onions")); Assert.assertTrue("uris contained test:icecream", !uris.contains(testNS+"icecream")); - Assert.assertEquals(1, uris.size()); + Assert.assertEquals(2, uris.size()); } @Test diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtilsTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtilsTest.java index 5906724f6..4dd63f99d 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtilsTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtilsTest.java @@ -32,7 +32,7 @@ public class DataGetterUtilsTest extends AbstractTestClass{ String dataGetterX = "http://vitro.mannlib.cornell.edu/ontologies/display/1.1#pageDataGetterX"; @Before - public void setUp() throws Exception { + public void setUp() { // Suppress error logging. setLoggerLevel(RDFDefaultErrorHandler.class, Level.OFF); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetterTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetterTest.java index 8cb9279ef..a02516e1d 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetterTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlQueryDataGetterTest.java @@ -36,7 +36,7 @@ public class SparqlQueryDataGetterTest extends AbstractTestClass{ VitroRequest vreq; @Before - public void setUp() throws Exception { + public void setUp() { // Suppress error logging. setLoggerLevel(RDFDefaultErrorHandler.class, Level.OFF); diff --git a/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryStub.java b/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryStub.java index 24efa6885..732ae3209 100644 --- a/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryStub.java +++ b/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/dao/WebappDaoFactoryStub.java @@ -23,6 +23,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; import edu.cornell.mannlib.vitro.webapp.dao.VClassDao; import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; /** * A minimal implementation of the WebappDaoFactory. @@ -267,4 +268,9 @@ public class WebappDaoFactoryStub implements WebappDaoFactory { throw new RuntimeException("WebappDaoFactory.close() not implemented."); } + @Override + public I18nBundle getI18nBundle() { + throw new RuntimeException("WebappDaoFactory.getI18nBundle() not implemented."); + } + } diff --git a/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/i18n/I18nStub.java b/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/i18n/I18nStub.java index 76d3a3727..372eedc69 100644 --- a/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/i18n/I18nStub.java +++ b/api/src/test/java/stubs/edu/cornell/mannlib/vitro/webapp/i18n/I18nStub.java @@ -14,6 +14,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.i18n.I18n; import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle; +import stubs.javax.servlet.ServletContextStub; /** * An implementation of I18n for unit tests. Construct a new instance and it @@ -45,6 +46,7 @@ public class I18nStub extends I18n { /** Make it private, so they will use the setup() method. */ private I18nStub() { + super(new ServletContextStub()); // Nothing to initialize. } diff --git a/api/src/test/java/stubs/javax/servlet/http/HttpServletRequestStub.java b/api/src/test/java/stubs/javax/servlet/http/HttpServletRequestStub.java index 27bb5e69f..35b8da99c 100644 --- a/api/src/test/java/stubs/javax/servlet/http/HttpServletRequestStub.java +++ b/api/src/test/java/stubs/javax/servlet/http/HttpServletRequestStub.java @@ -510,8 +510,7 @@ public class HttpServletRequestStub implements HttpServletRequest { @Override public Locale getLocale() { - throw new RuntimeException( - "HttpServletRequestStub.getLocale() not implemented."); + return Locale.ENGLISH; } @Override diff --git a/api/src/test/java/stubs/org/apache/jena/rdf/model/LiteralStub.java b/api/src/test/java/stubs/org/apache/jena/rdf/model/LiteralStub.java index 56142fc7a..fac169042 100644 --- a/api/src/test/java/stubs/org/apache/jena/rdf/model/LiteralStub.java +++ b/api/src/test/java/stubs/org/apache/jena/rdf/model/LiteralStub.java @@ -44,6 +44,11 @@ public class LiteralStub implements Literal { return false; } + @Override + public boolean isStmtResource() { + return false; + } + @Override public boolean isURIResource() { return false; diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 01059dcf3..a5ac32627 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-dependencies - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom org.vivoweb vitro-project - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. @@ -176,7 +176,7 @@ org.apache.jena jena-arq - 3.11.0 + 3.16.0 @@ -192,23 +192,33 @@ org.apache.jena jena-core - 3.11.0 + 3.16.0 org.apache.jena jena-sdb - 3.11.0 + 3.16.0 org.apache.jena jena-tdb - 3.11.0 + 3.16.0 org.apache.solr solr-solrj 7.4.0 + + commons-codec + commons-codec + 1.15 + + + org.slf4j + slf4j-log4j12 + 1.7.26 + org.directwebremoting dwr diff --git a/home/pom.xml b/home/pom.xml index fdc0237f2..b93558ecd 100644 --- a/home/pom.xml +++ b/home/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-home - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom org.vivoweb vitro-project - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. diff --git a/home/src/main/resources/config/example.applicationSetup.n3 b/home/src/main/resources/config/example.applicationSetup.n3 index 5d0562be2..0df7bdb6e 100644 --- a/home/src/main/resources/config/example.applicationSetup.n3 +++ b/home/src/main/resources/config/example.applicationSetup.n3 @@ -26,7 +26,7 @@ :hasSearchIndexer :basicSearchIndexer ; :hasImageProcessor :iioImageProcessor ; :hasFileStorage :ptiFileStorage ; - :hasContentTripleSource :sdbContentTripleSource ; + :hasContentTripleSource :tdbContentTripleSource ; :hasConfigurationTripleSource :tdbConfigurationTripleSource ; :hasTBoxReasonerModule :jfactTBoxReasonerModule . @@ -82,22 +82,22 @@ # ---------------------------- # # Content triples source module: holds data contents -# The SDB-based implementation is the default option. It reads its parameters +# The TDB-based implementation is the default option. It reads its parameters # from the runtime.properties file, for backward compatibility. # -# Other implementations are based on a local TDB instance, a "standard" SPARQL +# Other implementations are based on an SDB instance, a "standard" SPARQL # endpoint, or a Virtuoso endpoint, with parameters as shown. # -:sdbContentTripleSource - a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB , - vitroWebapp:modules.tripleSource.ContentTripleSource . +#:sdbContentTripleSource +# a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB , +# vitroWebapp:modules.tripleSource.ContentTripleSource . -#:tdbContentTripleSource -# a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB , -# vitroWebapp:modules.tripleSource.ContentTripleSource ; -# # May be an absolute path, or relative to the Vitro home directory. -# :hasTdbDirectory "tdbContentModels" . +:tdbContentTripleSource + a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB , + vitroWebapp:modules.tripleSource.ContentTripleSource ; + # May be an absolute path, or relative to the Vitro home directory. + :hasTdbDirectory "tdbContentModels" . #:sparqlContentTripleSource # a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceSPARQL , diff --git a/home/src/main/resources/config/example.developer.properties b/home/src/main/resources/config/example.developer.properties index 67b119f92..7c5ab1ba4 100644 --- a/home/src/main/resources/config/example.developer.properties +++ b/home/src/main/resources/config/example.developer.properties @@ -49,6 +49,7 @@ # developer.i18n.defeatCache = false # developer.i18n.logStringRequests = false +# developer.i18n.onlineTranslation = false #------------------------------------------------------------------------------ diff --git a/home/src/main/resources/config/example.runtime.properties b/home/src/main/resources/config/example.runtime.properties index a0bf89a4f..8e287ca0e 100644 --- a/home/src/main/resources/config/example.runtime.properties +++ b/home/src/main/resources/config/example.runtime.properties @@ -24,51 +24,70 @@ Vitro.defaultNamespace = http://vivo.mydomain.edu/individual/ # # URL of Solr context used in local Vitro search. This will usually consist of: -# scheme + server_name + port + vitro_webapp_name + "solr" -# In the standard installation, the Solr context will be on the same server as Vitro, -# and in the same Tomcat instance. The path will be the Vitro webapp.name (specified -# above) + "solr" +# scheme + server_name + port + "solr" + solr_core_name +# In a standard Solr installation, the Solr service will be available on port +# 8983. The path will be /solr followed by the name used when adding a core +# for Vitro. # Example: -# vitro.local.solr.url = http://localhost:8080/vitrosolr -vitro.local.solr.url = http://localhost:8080/vitrosolr +# vitro.local.solr.url = http://localhost:8983/solr/vitrocore +# +vitro.local.solr.url = http://localhost:8983/solr/vitrocore # # Email parameters which VIVO can use to send mail. If these are left empty, # the "Contact Us" form will be disabled and users will not be notified of # changes to their accounts. -# -email.smtpHost = smtp.my.domain.edu -email.replyTo = vivoAdmin@my.domain.edu + # Example: + # email.smtpHost = smtp.mydomain.edu + # email.replyTo = vitroAdmin@mydomain.edu + # +email.smtpHost = +email.replyTo = # -# The basic parameters for a MySQL database connection. Change the end of the -# URL to reflect your database name (if it is not "vitro"). Change the username -# and password to match the authorized user you created in MySQL. +# NOTE: VitroConnection.DataSource.* properties are only used in conjuction with +# an SDB triple store. # -VitroConnection.DataSource.url = jdbc:mysql://localhost/vitro -VitroConnection.DataSource.username = vitroweb -VitroConnection.DataSource.password = vitrovitro +# The basic parameters for a database connection. Change the end of the +# URL to reflect your database name (if it is not "vitrodb"). Change the username +# and password to match the authorized database user you created. +# +# VitroConnection.DataSource.url = jdbc:mysql://localhost/vitrodb +# VitroConnection.DataSource.username = vitrodbUsername +# VitroConnection.DataSource.password = vitrodbPassword # # The maximum number of active connections in the database connection pool. # Increase this value to support a greater number of concurrent page requests. # -VitroConnection.DataSource.pool.maxActive = 40 +# VitroConnection.DataSource.pool.maxActive = 40 # # The maximum number of database connections that will be allowed # to remain idle in the connection pool. Default is 25% # of the maximum number of active connections. # -VitroConnection.DataSource.pool.maxIdle = 10 +# VitroConnection.DataSource.pool.maxIdle = 10 # -# Parameters to change in order to use VIVO with a database other than +# Parameters to change in order to use Vitro with a database other than # MySQL. # -VitroConnection.DataSource.dbtype = MySQL -VitroConnection.DataSource.driver = com.mysql.jdbc.Driver -VitroConnection.DataSource.validationQuery = SELECT 1 +# VitroConnection.DataSource.dbtype = MySQL +# VitroConnection.DataSource.driver = com.mysql.jdbc.Driver +# VitroConnection.DataSource.validationQuery = SELECT 1 + +# +# Include sections between +# tags when executing 'list view' queries that retrieve data +# for property lists on profile pages. +# +# Including these optional sections does not change the query +# semantics, but may improve performance. +# +# Default is true if not set. +# +# listview.usePreciseSubquery = true # # The email address of the root user for the VIVO application. The password @@ -144,9 +163,9 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing # # A list of supported languages or Locales that the user may choose to -# use instead of the one specified by the browser. Selection images must -# be available in the i18n/images directory of the theme. This affects -# RDF data retrieved from the model, if RDFService.languageFilter is true. +# use instead of the one specified by the browser. The selected language(s) +# must exist in the Vitro-languages repository. This affects RDF data +# retrieved from the model, if RDFService.languageFilter is true. # This also affects the text of pages that have been modified to support # multiple languages. # @@ -154,7 +173,15 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing # # languages.selectableLocales = en, es, fr -# Triple pattern fragments is a very fast, very simple means for querying a triple store. -# The triple pattern fragments API in VIVO puts little load on the server, providing a simple means for getting data from the triple store. The API has a web interface for manual use, can be used from the command line via curl, and can be used by programs. - +# Triple Pattern Fragments is a very fast, very simple means for querying a +# triple store. The Triple Pattern Fragments API in VIVO puts little load on +# the server, providing a simple means for getting data from the triple store. +# The API has a web interface for manual use, can be used from the command line +# via curl, and can be used by programs. +# +# Vitro's Triple Pattern Fragments API does not require authentication and +# makes the full RDF graph available regardless of display or publish levels +# set on particular properties. Enable Triple Pattern Fragments only if your +# Vitro does not contain restricted data that should not be shared with others. +# # tpf.activeFlag = true diff --git a/installer/home/pom.xml b/installer/home/pom.xml index be8bb3023..df47276db 100644 --- a/installer/home/pom.xml +++ b/installer/home/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-installer-home - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom org.vivoweb vitro-installer - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. diff --git a/installer/pom.xml b/installer/pom.xml index 0323adcf4..e9b742fff 100644 --- a/installer/pom.xml +++ b/installer/pom.xml @@ -7,9 +7,16 @@ org.vivoweb vitro-installer - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom + + org.vivoweb + vitro-project + 1.12.1-SNAPSHOT + .. + + Vitro Installer diff --git a/installer/solr/pom.xml b/installer/solr/pom.xml index ae67209fd..ab71836a3 100644 --- a/installer/solr/pom.xml +++ b/installer/solr/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-installer-solr - 1.11.0-SNAPSHOT + 1.12.1-SNAPSHOT war org.vivoweb vitro-installer - 1.11.0-SNAPSHOT + 1.12.1-SNAPSHOT .. diff --git a/installer/webapp/pom.xml b/installer/webapp/pom.xml index a17179d14..6f72babe3 100644 --- a/installer/webapp/pom.xml +++ b/installer/webapp/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-installer-webapp - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT war org.vivoweb vitro-installer - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. @@ -45,11 +45,11 @@ war - + @@ -148,12 +148,12 @@ war - + javax.servlet diff --git a/installer/webapp/src/main/webResources/META-INF/context.xml b/installer/webapp/src/main/webResources/META-INF/context.xml index 1f41e94bf..53ea22ae5 100644 --- a/installer/webapp/src/main/webResources/META-INF/context.xml +++ b/installer/webapp/src/main/webResources/META-INF/context.xml @@ -1,8 +1,8 @@ + type="java.lang.String" + name="vitro/home" + value="${vitro-dir}" override="true"/> diff --git a/pom.xml b/pom.xml index 13d2c8f1b..93743c032 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.vivoweb vitro-project - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT pom Vitro diff --git a/webapp/pom.xml b/webapp/pom.xml index a9b1a8fe5..14d58410c 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -7,13 +7,13 @@ org.vivoweb vitro-webapp - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT war org.vivoweb vitro-project - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT .. @@ -41,7 +41,7 @@ org.vivoweb vitro-api - 1.11.2-SNAPSHOT + 1.12.1-SNAPSHOT diff --git a/webapp/src/main/webapp/css/vitro.css b/webapp/src/main/webapp/css/vitro.css index 8bafa5922..f784acd7f 100644 --- a/webapp/src/main/webapp/css/vitro.css +++ b/webapp/src/main/webapp/css/vitro.css @@ -252,10 +252,11 @@ ul.dropdown li:hover > ul { /* -------------------------------------------------> */ ul.language-dropdown { position: relative; + min-width: 160px; } ul.language-dropdown li#language-menu { background: url(../images/arrowDownOverAccount.gif) right 9px no-repeat; - min-width:110px; + min-width: 170px; position: relative; margin-right: -10px !important; } diff --git a/webapp/src/main/webapp/i18n/all.properties b/webapp/src/main/webapp/i18n/all.properties deleted file mode 100644 index 5f7be8861..000000000 --- a/webapp/src/main/webapp/i18n/all.properties +++ /dev/null @@ -1,929 +0,0 @@ -# -# Text strings for the controllers and templates -# -# Default (English) -# -save_changes=Save changes -save_entry=Save entry -select_existing=Select existing -select_an_existing=Select an existing -add_an_entry_to=Add an entry of type -change_entry_for=Change entry for: -add_new_entry_for=Add new entry for: -change_text_for=Change text for: -cancel_link = Cancel -cancel_title = cancel -required_fields = required fields -or = or -alt_error_alert = Error alert icon -alt_confirmation = Confirmation icon -for=for -email_address = Email address -first_name = First name -last_name = Last name -roles = Roles -status = Status - -ascending_order = ascending order -descending_order = descending order -select_one = Select one - -type_more_characters = type more characters -no_match = no match - -request_failed = Request failed. Please contact your system administrator. - -# -# Image upload pages -# -upload_page_title = Upload image -upload_page_title_with_name = Upload image for {0} -upload_heading = Photo Upload - -replace_page_title = Replace image -replace_page_title_with_name = Replace image for {0} - -crop_page_title = Crop image -crop_page_title_with_name = Crop image for {0} - -current_photo = Current Photo -upload_photo = Upload a photo -replace_photo = Replace Photo -photo_types = (JPEG, GIF or PNG) -maximum_file_size = Maximum file size: {0} megabytes -minimum_image_dimensions = Minimum image dimensions: {0} x {1} pixels - -cropping_caption = Your profile photo will look like the image below. -cropping_note = To make adjustments, you can drag around and resize the photo to the right. \ -When you are happy with your photo click the "Save Photo" button. - -alt_thumbnail_photo = Individual photo -alt_image_to_crop = Image to be cropped -alt_preview_crop = Preview of photo cropped - -delete_link = Delete photo -submit_upload = Upload photo -submit_save = Save photo - -confirm_delete = Are you sure you want to delete this photo? - -imageUpload.errorNoURI = No entity URI was provided -imageUpload.errorUnrecognizedURI = This URI is not recognized as belonging to anyone: ''{0}'' -imageUpload.errorNoImageForCropping = There is no image file to be cropped. -imageUpload.errorImageTooSmall = The uploaded image should be at least {0} pixels high and {1} pixels wide. -imageUpload.errorUnknown = Sorry, we were unable to process the photo you provided. Please try another photo. -imageUpload.errorFileTooBig = Please upload an image smaller than {0} megabytes. -imageUpload.errorUnrecognizedFileType = ''{0}'' is not a recognized image file type. Please upload JPEG, GIF, or PNG files only. -imageUpload.errorNoPhotoSelected = Please browse and select a photo. -imageUpload.errorBadMultipartRequest = Failed to parse the multi-part request for uploading an image. -imageUpload.errorFormFieldMissing = The form did not contain a ''{0}'' field." - -# -# User Accounts pages -# -account_management = Account Management -user_accounts_link = User accounts -user_accounts_title = user accounts - -login_count = Login count -last_login = Last Login - -add_new_account = Add new account -edit_account = Edit account -external_auth_only = Externally Authenticated Only -reset_password = Reset password -reset_password_note = Note: Instructions for resetting the password will \ -be emailed to the address entered above. The password will not \ -be reset until the user follows the link provided in this email. -new_password = New password -confirm_password = Confirm new password -minimum_password_length = Minimum of {0} characters in length; maximum of {1}. -leave_password_unchanged = Leaving this blank means that the password will not be changed. -confirm_initial_password = Confirm initial password - -new_account_1 = A new account for -new_account_2 = was successfully created. -new_account_title = new account -new_account_notification = A notification email has been sent to {0} \ -with instructions for activating the account and creating a password. -updated_account_1 = The account for -updated_account_2 = has been updated. -updated_account_title = updated account -updated_account_notification = A confirmation email has been sent to {0} \ -with instructions for resetting a password. \ -The password will not be reset until the user follows the link provided in this email. -deleted_accounts = Deleted {0} {0, choice, 0#accounts|1#account|1= 200 && xhr.status <= 299; + } // `a.click()` doesn't work for all browsers (#465) + + + function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')); + } catch (e) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); + node.dispatchEvent(evt); + } + } // Detect WebView inside a native macOS app by ruling out all browsers + // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too + // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos + + + var isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent); + var saveAs = _global.saveAs || ( // probably in some web worker + typeof window !== 'object' || window !== _global ? function saveAs() {} + /* noop */ + // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView + : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) { + var URL = _global.URL || _global.webkitURL; + var a = document.createElement('a'); + name = name || blob.name || 'download'; + a.download = name; + a.rel = 'noopener'; // tabnabbing + // TODO: detect chrome extensions & packaged apps + // a.target = '_blank' + + if (typeof blob === 'string') { + // Support regular links + a.href = blob; + + if (a.origin !== location.origin) { + corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); + } else { + click(a); + } + } else { + // Support blobs + a.href = URL.createObjectURL(blob); + setTimeout(function () { + URL.revokeObjectURL(a.href); + }, 4E4); // 40s + + setTimeout(function () { + click(a); + }, 0); + } + } // Use msSaveOrOpenBlob as a second approach + : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { + name = name || blob.name || 'download'; + + if (typeof blob === 'string') { + if (corsEnabled(blob)) { + download(blob, name, opts); + } else { + var a = document.createElement('a'); + a.href = blob; + a.target = '_blank'; + setTimeout(function () { + click(a); + }); + } + } else { + navigator.msSaveOrOpenBlob(bom(blob, opts), name); + } + } // Fallback to using FileReader and a popup + : function saveAs(blob, name, opts, popup) { + // Open a popup immediately do go around popup blocker + // Mostly only available on user interaction and the fileReader is async so... + popup = popup || open('', '_blank'); + + if (popup) { + popup.document.title = popup.document.body.innerText = 'downloading...'; + } + + if (typeof blob === 'string') return download(blob, name, opts); + var force = blob.type === 'application/octet-stream'; + + var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; + + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); + + if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') { + // Safari doesn't allow downloading of blob URLs + var reader = new FileReader(); + + reader.onloadend = function () { + var url = reader.result; + url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); + if (popup) popup.location.href = url;else location = url; + popup = null; // reverse-tabnabbing #460 + }; + + reader.readAsDataURL(blob); + } else { + var URL = _global.URL || _global.webkitURL; + var url = URL.createObjectURL(blob); + if (popup) popup.location = url;else location.href = url; + popup = null; // reverse-tabnabbing #460 + + setTimeout(function () { + URL.revokeObjectURL(url); + }, 4E4); // 40s + } + }); + _global.saveAs = saveAs.saveAs = saveAs; + + if (typeof module !== 'undefined') { + module.exports = saveAs; + } +}); diff --git a/webapp/src/main/webapp/js/developer/developerPanel.js b/webapp/src/main/webapp/js/developer/developerPanel.js index b92f2abdf..d8cf42b47 100644 --- a/webapp/src/main/webapp/js/developer/developerPanel.js +++ b/webapp/src/main/webapp/js/developer/developerPanel.js @@ -52,6 +52,7 @@ function DeveloperPanel(developerAjaxUrl) { document.getElementById("developer_pageContents_logCustomShortView").disabled = !developerEnabled; document.getElementById("developer_i18n_defeatCache").disabled = !developerEnabled; document.getElementById("developer_i18n_logStringRequests").disabled = !developerEnabled; + document.getElementById("developer_i18n_onlineTranslation").disabled = !developerEnabled; document.getElementById("developer_loggingRDFService_enable").disabled = !developerEnabled; document.getElementById("developer_searchIndex_enable").disabled = !developerEnabled; document.getElementById("developer_searchIndex_logIndexingBreakdownTimings").disabled = !developerEnabled; diff --git a/webapp/src/main/webapp/js/developer/translations.js b/webapp/src/main/webapp/js/developer/translations.js new file mode 100644 index 000000000..38ad7c004 --- /dev/null +++ b/webapp/src/main/webapp/js/developer/translations.js @@ -0,0 +1,506 @@ +class PropAddr { + constructor(node, number, args) { + this.node = node; + this.number = number; + this.args = args; + } +} + +class PropInfo { + constructor(rawText, formText, address) { + this.rawText = rawText; + this.formText = formText; + this.addresses = []; + this.addresses.push(address); + } +} + + var pageTranslations = new Map(); + var overridenTranslations = new Map(); + var startSep = '\u25a4'; + var endSep = '\u25a5'; + var intSep = '\u25a6'; + var resultSep = '\u200b\uFEFF\u200b\uFEFF\u200b'; + var resultSepChars = '\u200b\uFEFF'; + + function saveTranslations() { + var storage = window.localStorage; + var serializedTranslations = JSON.stringify(Array.from(overridenTranslations.entries())); + storage.setItem("overridenTranslations", serializedTranslations); + } + + function readTranslations() { + var storage = window.localStorage; + var serializedTranslations = storage.getItem("overridenTranslations"); + if (serializedTranslations != null) { + overridenTranslations = new Map(JSON.parse(serializedTranslations)); + } + } + + function createTranslationPanel() { + var devPanel = document.getElementById("developerPanel"); + if (devPanel !== null) { + var container = document.createElement("div"); + container.setAttribute("id", "translationPanel"); + container.setAttribute("style", "font-size:0.8em !important;width: 440px; resize: horizontal; \ + overflow: auto; padding: 10px; position: absolute;background-color:#f7dd8a;border:1px dotted;z-index:10000"); + devPanel.parentNode.insertBefore(container, devPanel.nextSibling); + createTranslationControls(container); + createPageTranslationsTable(container); + } + } + + function createTranslationControls(container) { + var controls = document.createElement("div"); + controls.setAttribute("id", "translationControls"); + controls.setAttribute("style", "margin-bottom:8px;") + container.appendChild(controls); + + var cleanButton = document.createElement("button"); + cleanButton.textContent = "Clean All"; + cleanButton.setAttribute("onclick", "cleanTranslationStorage()"); + cleanButton.setAttribute("style", "margin-right:10px;"); + controls.appendChild(cleanButton); + + var exportAllButton = document.createElement("button"); + exportAllButton.textContent = "Export All"; + exportAllButton.setAttribute("onclick", "exportTranslations()"); + exportAllButton.setAttribute("style", "margin-right:10px;"); + controls.appendChild(exportAllButton); + + var updateFileInput = document.createElement("input"); + var updateFileButton = document.createElement("button"); + updateFileButton.setAttribute("style", "margin-right:10px;"); + updateFileInput.type = "file"; + updateFileInput.setAttribute("id", "exportFile"); + updateFileInput.setAttribute("style", "display:none;"); + updateFileInput.setAttribute("accept", ".properties"); + var updateFileLabel = document.createElement("label"); + updateFileLabel.setAttribute("for", "exportFile"); + updateFileLabel.textContent = "Update file"; + updateFileLabel.setAttribute("style", "margin:0px;color:black;") + updateFileButton.appendChild(updateFileLabel); + controls.appendChild(updateFileButton); + controls.appendChild(updateFileInput); + updateFileInput.addEventListener("change", updateTranslationsFile); + + var importFileInput = document.createElement("input"); + var importFileButton = document.createElement("button"); + importFileInput.type = "file"; + importFileInput.setAttribute("style", "display:none;"); + importFileInput.setAttribute("id", "importFile"); + importFileInput.setAttribute("accept", ".properties"); + var importFileLabel = document.createElement("label"); + importFileLabel.setAttribute("style", "margin:0px;color:black;") + importFileLabel.setAttribute("for", "importFile"); + importFileLabel.textContent = "Import from file"; + importFileButton.appendChild(importFileLabel); + controls.appendChild(importFileButton); + controls.appendChild(importFileInput); + importFileInput.addEventListener("change", importTranslationsFromFile); + } + + function cleanTranslationStorage() { + overridenTranslations.clear(); + saveTranslations(); + location.reload(); + } + + function importTranslationsFromFile(e) { + const fileList = e.target.files; + const numFiles = fileList.length; + if (numFiles > 0) { + const file = fileList[0]; + var reader = new FileReader(); + reader.onload = function(progressEvent) { + var lines = this.result.split(/\r\n|\n\r|\n|\r/); + var followLine = false; + var lineKey = null; + var lineValue = null; + for (var i = 0; i < lines.length; i++) { + if (!isCommentLine(lines[i])) { + if (followLine) { + followLine = isNextLineFollow(lines[i]); + lineValue = lines[i].replace(/\\$/, ""); + lineValue = unescapeHTML(lineValue); + lineValue = charCodesToString(lineValue); + overridenTranslations.set(lineKey, overridenTranslations.get(lineKey) + lineValue); + } else { + followLine = isNextLineFollow(lines[i]); + lineKey = getLineKey(lines[i]); + if (lineKey.trim() != "") { + lineValue = getLineValue(lines[i]); + lineValue = unescapeHTML(lineValue); + lineValue = charCodesToString(lineValue); + overridenTranslations.set(lineKey, lineValue); + } + } + } + } + saveTranslations(); + location.reload() + } + reader.readAsText(file); + } + } + + function updateTranslationsFile(e) { + const fileList = e.target.files; + const numFiles = fileList.length; + if (numFiles > 0) { + const file = fileList[0]; + var fileName = e.target.value.split(/(\\|\/)/g).pop(); + var reader = new FileReader(); + reader.onload = function(progressEvent) { + var lines = this.result.split(/\r\n|\n\r|\n|\r/); + var followLine = false; + var keyLineHasChanged = false; + var lineKey = null; + for (var i = 0; i < lines.length; i++) { + if (!isCommentLine(lines[i])) { + if (followLine) { + followLine = isNextLineFollow(lines[i]); + if (keyLineHasChanged) { + //clean line as it's upper content has changed + lines[i] = ""; + if (!followLine) { + keyLineHasChanged = false; + } + } + // skip line + } else { + keyLineHasChanged = false; + followLine = isNextLineFollow(lines[i]); + lineKey = getLineKey(lines[i]); + if (overridenTranslations.has(lineKey)) { + var value = overridenTranslations.get(lineKey); + value = toCharCodes(value); + value = escapeHTML(value); + lines[i] = lineKey + " = " + value; + keyLineHasChanged = true; + } + } + } + } + saveFile(fileName, lines); + } + reader.readAsText(file); + } + } + + function exportTranslations() { + var date = new Date; + var fileName = "export_" + date.toLocaleString() + "_all.properties"; + var lines = []; + var storeValue = null; + for (let [key, value] of overridenTranslations) { + storeValue = toCharCodes(value); + storeValue = escapeHTML(storeValue); + lines.push(key + " = " + storeValue); + } + saveFile(fileName, lines); + } + + function saveFile(fileName, lines) { + var blob = new Blob([lines.join("\n")], { type: 'text/plain;charset=utf-8' }); + saveAs(blob, fileName); + } + + function getLineKey(line) { + var matches = line.match(/^\s*[^=\s]*(?=\s*=)/); + var key; + if (matches == null) { + key = ""; + } else { + key = matches[0].trim(); + } + return key; + } + + function getLineValue(line) { + var value = line.replace(/^\s*[^=\s]*\s*=\s*/, ""); + value = value.replace(/\\$/, ""); + return value; + } + + function isNextLineFollow(line) { + return line.match(/\\(\\\\)*$/) != null; + } + + function isCommentLine(line) { + return line.match(/^\s*[#!]/) != null; + } + + function createPageTranslationsTable(container) { + var table = document.createElement("table"); + table.setAttribute("id", "translationsTable"); + table.setAttribute("style", "width:100%;"); + + document.getElementById("translationPanel").appendChild(table); + for (let [key, propInfo] of pageTranslations) { + var tr = document.createElement("tr"); + table.appendChild(tr); + var td1 = document.createElement("td"); + td1.setAttribute("style", " width:1%;white-space:nowrap;"); + var keyText = document.createTextNode(key); + var td2 = document.createElement("td"); + var rawText = document.createElement("input"); + rawText.setAttribute("style", "width:100%; "); + if (overridenTranslations.has(key)) { + rawText.value = overridenTranslations.get(key); + rawText.style.backgroundColor = "#8BAB2E"; + } else { + rawText.value = propInfo.rawText; + } + var rawTextHidden = document.createElement("input"); + rawTextHidden.setAttribute("style", "display:none;"); + rawTextHidden.value = propInfo.rawText; + td1.appendChild(keyText); + tr.appendChild(td1); + td2.appendChild(rawText); + td2.appendChild(rawTextHidden); + tr.appendChild(td2); + rawText.addEventListener("blur", function() { + updateTranslation(this); + }); + } + } + + function updateTranslation(input) { + if (input.value != input.nextSibling.value) { + var key = input.parentElement.previousSibling.firstChild.textContent; + if (input.value == "") { + input.value = input.nextSibling.value; + input.style.backgroundColor = "white"; + overridenTranslations.delete(key); + var value = input.nextSibling.value; + } else { + var value = input.value; + if (pageTranslations.get(key).rawText != escapeHTML(value)) { + input.style.backgroundColor = "#8BAB2E"; + overridenTranslations.set(key, value); + } else { + input.style.backgroundColor = "white"; + overridenTranslations.delete(key); + } + } + saveTranslations(); + if (isJSHasChanged(key)) { + location.reload(); + } + updateTranslationOnPage(key, value); + } + } + + function isJSHasChanged(key) { + var result = false; + if (pageTranslations.has(key)) { + var addresses = pageTranslations.get(key).addresses; + for (let i = 0; i < addresses.length; i++) { + var nodeName = addresses[i].node.nodeName; + if (nodeName == "SCRIPT") { + result = true; + } + } + } + return result; + } + + function updateTranslationOnPage(key, value) { + var propInfo = pageTranslations.get(key); + var addresses = propInfo.addresses; + for (let i = 0; i < addresses.length; i++) { + var node = addresses[i].node; + var number = addresses[i].number + 1; + var content = node.textContent; + var formattedValue = formatTranslation(value, addresses[i].args); + var regexStr = resultSep + "[^" + resultSepChars + "]*" + resultSep; + const regEx = new RegExp("^(?:[^" + resultSepChars + "]*" + regexStr + "){" + number + "}"); + var newString = content.replace(regEx, + function(x) { + return x.replace(RegExp(regexStr + "$"), resultSep + formattedValue + resultSep); + }); + node.textContent = newString; + } + } + + function formatTranslation(value, args) { + for (let i = 0; i < args.length; i++) { + value = value.replaceAll("{" + i + "}", args[i]); + } + return value; + } + + function parseHTMLTranslations() { + var translatedTexts = []; + var translatedAttrs = []; + var xpath = "//attribute::*[contains(., '" + startSep + "')]"; + var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null); + var node = null; + var node = null; + while (node = result.iterateNext()) { + translatedAttrs.push(node); + parsePropsInNode(node); + } + xpath = "//*[text()[contains(.,'" + startSep + "')]]"; + result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null); + while (node = result.iterateNext()) { + translatedTexts.push(node); + parsePropsInNode(node); + } + readTranslations(); + removePropInfoFromPage(); + updatePageWithOverridenTranslations(); + reloadJS(); + } + + function reloadJS() { + var scriptBlocks = document.getElementsByTagName('script'); + for (let i = 0; i < scriptBlocks.length; i++) { + var scriptBlock = scriptBlocks[i]; + if (scriptBlock.hasAttribute("src")) { + var srcAttr = scriptBlock.getAttribute("src"); + if (!srcAttr.includes("translations.js")) { + if (srcAttr.indexOf("?") == -1) { + srcAttr += "?" + new Date().getTime(); + } else { + srcAttr += "&" + new Date().getTime(); + } + scriptBlock.remove(); + addJSLink(srcAttr); + } + } else { + var content = scriptBlock.textContent; + scriptBlock.remove(); + addInlineJS(content); + } + } + } + + function addInlineJS(content) { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.textContent = content; + head.appendChild(script); + } + + function addJSLink(fileUrl) { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = "text/javascript"; + script.src = fileUrl; + head.appendChild(script); + } + + function updatePageWithOverridenTranslations() { + for (let [key, value] of overridenTranslations) { + if (pageTranslations.has(key)) { + updateTranslationOnPage(key, value); + } + } + } + + function removePropInfoFromPage() { + for (let [key, propInfo] of pageTranslations) { + var addresses = propInfo.addresses; + for (let i = 0; i < addresses.length; i++) { + var node = addresses[i].node; + var content = node.textContent; + var regexStr = startSep + "[^" + endSep + "]*" + intSep + "([^" + endSep + intSep + "]*)" + endSep; + const regEx = new RegExp(regexStr, "g"); + var newString = content.replaceAll(regEx, resultSep + "$1" + resultSep); + node.textContent = newString; + } + } + } + + function parsePropsInNode(node) { + + if (node.nodeType === 1){ + var childs = node.childNodes; + childs.forEach(function(child){ + if (child.nodeType === 3){ + parsePropsInTextNode(child); + } + }); + }else if(node.nodeType === 2){ + parsePropsInTextNode(node); + } + + } + function parsePropsInTextNode(node){ + var i = 0; + var textString = node.textContent; + while (textString.indexOf(startSep) >= 0) { + textString = textString.substring(textString.indexOf(startSep) + startSep.length); + var prop = textString.substring(0, textString.indexOf(endSep)); + var address = new PropAddr(node, i, []); + addToPageTranslations(prop, address); + i++; + } + } + + function addToPageTranslations(prop, address) { + var key = prop.substring(0, prop.indexOf(intSep)); + prop = prop.substring(prop.indexOf(intSep) + intSep.length); + var rawText = prop.substring(0, prop.indexOf(intSep)); + prop = prop.substring(prop.indexOf(intSep) + intSep.length); + var textArgs = []; + while (prop.indexOf(intSep) >= 0) { + var textArg = prop.substring(0, prop.indexOf(intSep)); + prop = prop.substring(prop.indexOf(intSep) + intSep.length); + textArgs.push(textArg); + } + address.args = textArgs; + var formText = prop; + var propInfo = null; + if (pageTranslations.has(key)) { + propInfo = pageTranslations.get(key); + propInfo.addresses.push(address); + } else { + propInfo = new PropInfo(rawText, formText, address); + pageTranslations.set(key, propInfo); + } + } + function toCharCodes(input) { + return input + .replace(/^\ /, "\\u0020") + .replace(/\ $/, "\\u0020"); + } + function charCodesToString(input) { + return input.replace(/\\u[\dA-F]{4}/gi, + function(match) { + return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); + }); + } + function escapeHTML(input) { + return input + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/‘/g, "‘") + .replace(/’/g, "’"); + } + function unescapeHTML(input) { + return input + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, "\"") + .replace(/'/g, "'") + .replace(/‘/g, "‘") + .replace(/’/g, "’"); + } + + +window.addEventListener('load', function() { + setTimeout(function() { + var developerSetting = document.getElementById("developer_i18n_onlineTranslation"); + if (developerSetting !== null && developerSetting.checked) { + parseHTMLTranslations(); + createTranslationPanel(); + } + }, 1000); +}) diff --git a/webapp/src/main/webapp/js/individual/manageLabelsForIndividual.js b/webapp/src/main/webapp/js/individual/manageLabelsForIndividual.js index 075a487a2..1510cc219 100644 --- a/webapp/src/main/webapp/js/individual/manageLabelsForIndividual.js +++ b/webapp/src/main/webapp/js/individual/manageLabelsForIndividual.js @@ -35,7 +35,6 @@ var manageLabels = { // Initial page setup. Called only at page load. initPage: function() { - var disableSubmit = true; if(this.submissionErrorsExist == "false") { //hide the form to add label this.addLabelForm.hide(); @@ -44,7 +43,11 @@ var manageLabels = { if(this.numberAvailableLocales == 0) { manageLabels.showFormButtonWrapper.hide(); this.showCancelOnlyButton.show(); - } else { + } else if(manageLabels.localeEntryExisting == "true") { + // if there is already an label for the selected langauge hide the add button + manageLabels.showFormButtonWrapper.hide(); + this.showCancelOnlyButton.show(); + } else{ //if the add label button is visible, don't need cancel only link this.showCancelOnlyButton.hide(); } @@ -53,19 +56,6 @@ var manageLabels = { //Display the form this.onShowAddForm(); - //Also make sure the save button is enabled in case there is a value selected for the drop down - if(this.labelLanguage.val() != "") { - disableSubmit = false; - } - - } - - - - if(disableSubmit) { - //disable submit until user selects a language - this.submit.attr('disabled', 'disabled'); - this.submit.addClass('disabledSubmit'); } this.bindEventListeners(); @@ -74,18 +64,6 @@ var manageLabels = { bindEventListeners: function() { - this.labelLanguage.change( function() { - //if language selected, allow submission, otherwise disallow - var selectedLanguage = manageLabels.labelLanguage.val(); - if(selectedLanguage != "") { - manageLabels.submit.attr('disabled', false); - manageLabels.submit.removeClass('disabledSubmit'); - } else { - manageLabels.submit.attr('disabled', 'disabled'); - manageLabels.submit.addClass('disabledSubmit'); - } - }); - //enable form to add label to be displayed or hidden this.showFormButton.click(function() { //clear the inputs for the label if the button is being clicked @@ -123,9 +101,6 @@ var manageLabels = { clearAddForm:function() { //clear inputs and select manageLabels.addLabelForm.find("input[type='text'],select").val(""); - //set the button for save to be disabled again - manageLabels.submit.attr('disabled', 'disabled'); - manageLabels.submit.addClass('disabledSubmit'); }, onShowAddForm:function() { manageLabels.addLabelForm.show(); @@ -226,12 +201,14 @@ var manageLabels = { var availableLocalesList = []; var listLen = selectLocalesFullList.length; var i; + var showAddButton = false; for(i = 0; i < listLen; i++) { var possibleLanguageInfo = selectLocalesFullList[i]; var possibleLanguageCode = possibleLanguageInfo["code"]; var possibleLangaugeLabel = possibleLanguageInfo["label"]; if(!(possibleLanguageCode in existingLanguages)) { //manageLabels.addLanguageCode(possibleLanguageCode, possibleLanguageLabel); + if (possibleLanguageCode == manageLabels.currentSelectedLocale) showAddButton = true; availableLocalesList.push(possibleLanguageInfo); } } @@ -242,8 +219,9 @@ var manageLabels = { var compB = b["label"]; return compA < compB ? -1 : 1; }); + //Re-show the add button and the form if they were hidden before - if(availableLocalesList.length > 0 && manageLabels.showFormButtonWrapper.is(":hidden")) { + if(availableLocalesList.length > 0 && manageLabels.showFormButtonWrapper.is(":hidden") && showAddButton) { manageLabels.showFormButtonWrapper.show(); //hide the cancel only button manageLabels.showCancelOnlyButton.hide(); diff --git a/webapp/src/main/webapp/js/menupage/browseByVClass.js b/webapp/src/main/webapp/js/menupage/browseByVClass.js index 4aec58fa6..940810bac 100644 --- a/webapp/src/main/webapp/js/menupage/browseByVClass.js +++ b/webapp/src/main/webapp/js/menupage/browseByVClass.js @@ -234,9 +234,9 @@ var browseByVClass = { var alpha = this.selectedAlpha(alpha); if ( alpha != "all" ) { - nothingToSeeHere = '

' + browseByVClass.thereAreNo + ' ' + vclass.name + ' ' + browseByVClass.indNamesStartWith + ' '+ alpha.toUpperCase() +'.

' + browseByVClass.tryAnotherLetter + '

'; + nothingToSeeHere = '

' + browseByVClass.thereAreNoEntriesStartingWith + ' '+ alpha.toUpperCase() +'.

' + browseByVClass.tryAnotherLetter + '

'; } else { - nothingToSeeHere = '

' + browseByVClass.thereAreNo + ' ' + vclass.name + ' ' + browseByVClass.indsInSystem + '

' + browseByVClass.selectAnotherClass + '

'; + nothingToSeeHere = '

' + browseByVClass.thereAreNoEntriesStartingWith + '

' + browseByVClass.selectAnotherClass + '

'; } browseByVClass.individualsContainer.prepend(nothingToSeeHere); diff --git a/webapp/src/main/webapp/js/search/query-builder.ru.js b/webapp/src/main/webapp/js/search/query-builder.ru.js new file mode 100644 index 000000000..c53708b74 --- /dev/null +++ b/webapp/src/main/webapp/js/search/query-builder.ru.js @@ -0,0 +1,77 @@ +/*! + * jQuery QueryBuilder 2.5.2 + * Locale: Russian (ru) + * Licensed under MIT (https://opensource.org/licenses/MIT) + */ + +(function(root, factory) { + if (typeof define == 'function' && define.amd) { + define(['jquery', 'query-builder'], factory); + } + else { + factory(root.jQuery); + } +}(this, function($) { +"use strict"; + +var QueryBuilder = $.fn.queryBuilder; + +QueryBuilder.regional['ru'] = { + "__locale": "Russian (ru)", + "add_rule": "Добавить условие", + "add_group": "Добавить группу", + "delete_rule": "Удалить", + "delete_group": "Удалить", + "conditions": { + "AND": "И", + "OR": "ИЛИ" + }, + "operators": { + "equal": "равно", + "not_equal": "не равно", + "in": "из указанных", + "not_in": "не из указанных", + "less": "меньше", + "less_or_equal": "меньше или равно", + "greater": "больше", + "greater_or_equal": "больше или равно", + "between": "между", + "begins_with": "начинается с", + "not_begins_with": "не начинается с", + "contains": "содержит", + "not_contains": "не содержит", + "ends_with": "оканчивается на", + "not_ends_with": "не оканчивается на", + "is_empty": "пустая строка", + "is_not_empty": "не пустая строка", + "is_null": "пусто", + "is_not_null": "не пусто" + }, + "errors": { + "no_filter": "Фильтр не выбран", + "empty_group": "Группа пуста", + "radio_empty": "Не выбранно значение", + "checkbox_empty": "Не выбранно значение", + "select_empty": "Не выбранно значение", + "string_empty": "Не заполненно", + "string_exceed_min_length": "Должен содержать больше {0} символов", + "string_exceed_max_length": "Должен содержать меньше {0} символов", + "string_invalid_format": "Неверный формат ({0})", + "number_nan": "Не число", + "number_not_integer": "Не число", + "number_not_double": "Не число", + "number_exceed_min": "Должно быть больше {0}", + "number_exceed_max": "Должно быть меньше, чем {0}", + "number_wrong_step": "Должно быть кратно {0}", + "datetime_empty": "Не заполненно", + "datetime_invalid": "Неверный формат даты ({0})", + "datetime_exceed_min": "Должно быть, после {0}", + "datetime_exceed_max": "Должно быть, до {0}", + "boolean_not_valid": "Не логическое", + "operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений" + }, + "invert": "Инвертировать" +}; + +QueryBuilder.defaults({ lang_code: 'ru' }); +})); diff --git a/webapp/src/main/webapp/js/search/query-builder.standalone.js b/webapp/src/main/webapp/js/search/query-builder.standalone.js new file mode 100644 index 000000000..c899f05b9 --- /dev/null +++ b/webapp/src/main/webapp/js/search/query-builder.standalone.js @@ -0,0 +1,6477 @@ +/*! + * jQuery.extendext 0.1.2 + * + * Copyright 2014-2016 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + * Licensed under MIT (http://opensource.org/licenses/MIT) + * + * Based on jQuery.extend by jQuery Foundation, Inc. and other contributors + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('jQuery.extendext', ['jquery'], factory); + } + else if (typeof module === 'object' && module.exports) { + module.exports = factory(require('jquery')); + } + else { + factory(root.jQuery); + } +}(this, function ($) { + "use strict"; + + $.extendext = function () { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false, + arrayMode = 'default'; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + + // Skip the boolean and the target + target = arguments[i++] || {}; + } + + // Handle array mode parameter + if (typeof target === "string") { + arrayMode = target.toLowerCase(); + if (arrayMode !== 'concat' && arrayMode !== 'replace' && arrayMode !== 'extend') { + arrayMode = 'default'; + } + + // Skip the string param + target = arguments[i++] || {}; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && !$.isFunction(target)) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if (i === length) { + target = this; + i--; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[i]) !== null) { + // Special operations for arrays + if ($.isArray(options) && arrayMode !== 'default') { + clone = target && $.isArray(target) ? target : []; + + switch (arrayMode) { + case 'concat': + target = clone.concat($.extend(deep, [], options)); + break; + + case 'replace': + target = $.extend(deep, [], options); + break; + + case 'extend': + options.forEach(function (e, i) { + if (typeof e === 'object') { + var type = $.isArray(e) ? [] : {}; + clone[i] = $.extendext(deep, arrayMode, clone[i] || type, e); + + } else if (clone.indexOf(e) === -1) { + clone.push(e); + } + }); + + target = clone; + break; + } + + } else { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && ( $.isPlainObject(copy) || + (copyIsArray = $.isArray(copy)) )) { + + if (copyIsArray) { + copyIsArray = false; + clone = src && $.isArray(src) ? src : []; + + } else { + clone = src && $.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = $.extendext(deep, arrayMode, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + } + + // Return the modified object + return target; + }; +})); + +// doT.js +// 2011-2014, Laura Doktorova, https://github.com/olado/doT +// Licensed under the MIT license. + +(function () { + "use strict"; + + var doT = { + name: "doT", + version: "1.1.1", + templateSettings: { + evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g, + interpolate: /\{\{=([\s\S]+?)\}\}/g, + encode: /\{\{!([\s\S]+?)\}\}/g, + use: /\{\{#([\s\S]+?)\}\}/g, + useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g, + define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, + defineParams:/^\s*([\w$]+):([\s\S]+)/, + conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g, + iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g, + varname: "it", + strip: true, + append: true, + selfcontained: false, + doNotSkipEncoded: false + }, + template: undefined, //fn, compile template + compile: undefined, //fn, for express + log: true + }, _globals; + + doT.encodeHTMLSource = function(doNotSkipEncoded) { + var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" }, + matchHTML = doNotSkipEncoded ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g; + return function(code) { + return code ? code.toString().replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : ""; + }; + }; + + _globals = (function(){ return this || (0,eval)("this"); }()); + + /* istanbul ignore else */ + if (typeof module !== "undefined" && module.exports) { + module.exports = doT; + } else if (typeof define === "function" && define.amd) { + define('doT', function(){return doT;}); + } else { + _globals.doT = doT; + } + + var startend = { + append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" }, + split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" } + }, skip = /$^/; + + function resolveDefs(c, block, def) { + return ((typeof block === "string") ? block : block.toString()) + .replace(c.define || skip, function(m, code, assign, value) { + if (code.indexOf("def.") === 0) { + code = code.substring(4); + } + if (!(code in def)) { + if (assign === ":") { + if (c.defineParams) value.replace(c.defineParams, function(m, param, v) { + def[code] = {arg: param, text: v}; + }); + if (!(code in def)) def[code]= value; + } else { + new Function("def", "def['"+code+"']=" + value)(def); + } + } + return ""; + }) + .replace(c.use || skip, function(m, code) { + if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) { + if (def[d] && def[d].arg && param) { + var rw = (d+":"+param).replace(/'|\\/g, "_"); + def.__exp = def.__exp || {}; + def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2"); + return s + "def.__exp['"+rw+"']"; + } + }); + var v = new Function("def", "return " + code)(def); + return v ? resolveDefs(c, v, def) : v; + }); + } + + function unescape(code) { + return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " "); + } + + doT.template = function(tmpl, c, def) { + c = c || doT.templateSettings; + var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv, + str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl; + + str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ") + .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""): str) + .replace(/'|\\/g, "\\$&") + .replace(c.interpolate || skip, function(m, code) { + return cse.start + unescape(code) + cse.end; + }) + .replace(c.encode || skip, function(m, code) { + needhtmlencode = true; + return cse.startencode + unescape(code) + cse.end; + }) + .replace(c.conditional || skip, function(m, elsecase, code) { + return elsecase ? + (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") : + (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='"); + }) + .replace(c.iterate || skip, function(m, iterate, vname, iname) { + if (!iterate) return "';} } out+='"; + sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate); + return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"} + * @readonly + */ + this.icons = this.settings.icons; + + /** + * List of operators + * @member {QueryBuilder.Operator[]} + * @readonly + */ + this.operators = this.settings.operators; + + /** + * List of templates + * @member {object.} + * @readonly + */ + this.templates = this.settings.templates; + + /** + * Plugins configuration + * @member {object.} + * @readonly + */ + this.plugins = this.settings.plugins; + + /** + * Translations object + * @member {object} + * @readonly + */ + this.lang = null; + + // translations : english << 'lang_code' << custom + if (QueryBuilder.regional['en'] === undefined) { + Utils.error('Config', '"i18n/en.js" not loaded.'); + } + this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang); + + // "allow_groups" can be boolean or int + if (this.settings.allow_groups === false) { + this.settings.allow_groups = 0; + } + else if (this.settings.allow_groups === true) { + this.settings.allow_groups = -1; + } + + // init templates + Object.keys(this.templates).forEach(function(tpl) { + if (!this.templates[tpl]) { + this.templates[tpl] = QueryBuilder.templates[tpl]; + } + if (typeof this.templates[tpl] == 'string') { + this.templates[tpl] = doT.template(this.templates[tpl]); + } + }, this); + + // ensure we have a container id + if (!this.$el.attr('id')) { + this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999)); + this.status.generated_id = true; + } + this.status.id = this.$el.attr('id'); + + // INIT + this.$el.addClass('query-builder form-inline'); + + this.filters = this.checkFilters(this.filters); + this.operators = this.checkOperators(this.operators); + this.bindEvents(); + this.initPlugins(); +}; + +$.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ { + /** + * Triggers an event on the builder container + * @param {string} type + * @returns {$.Event} + */ + trigger: function(type) { + var event = new $.Event(this._tojQueryEvent(type), { + builder: this + }); + + this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1)); + + return event; + }, + + /** + * Triggers an event on the builder container and returns the modified value + * @param {string} type + * @param {*} value + * @returns {*} + */ + change: function(type, value) { + var event = new $.Event(this._tojQueryEvent(type, true), { + builder: this, + value: value + }); + + this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2)); + + return event.value; + }, + + /** + * Attaches an event listener on the builder container + * @param {string} type + * @param {function} cb + * @returns {QueryBuilder} + */ + on: function(type, cb) { + this.$el.on(this._tojQueryEvent(type), cb); + return this; + }, + + /** + * Removes an event listener from the builder container + * @param {string} type + * @param {function} [cb] + * @returns {QueryBuilder} + */ + off: function(type, cb) { + this.$el.off(this._tojQueryEvent(type), cb); + return this; + }, + + /** + * Attaches an event listener called once on the builder container + * @param {string} type + * @param {function} cb + * @returns {QueryBuilder} + */ + once: function(type, cb) { + this.$el.one(this._tojQueryEvent(type), cb); + return this; + }, + + /** + * Appends `.queryBuilder` and optionally `.filter` to the events names + * @param {string} name + * @param {boolean} [filter=false] + * @returns {string} + * @private + */ + _tojQueryEvent: function(name, filter) { + return name.split(' ').map(function(type) { + return type + '.queryBuilder' + (filter ? '.filter' : ''); + }).join(' '); + } +}); + + +/** + * Allowed types and their internal representation + * @type {object.} + * @readonly + * @private + */ +QueryBuilder.types = { + 'string': 'string', + 'integer': 'number', + 'double': 'number', + 'date': 'datetime', + 'time': 'datetime', + 'datetime': 'datetime', + 'boolean': 'boolean' +}; + +/** + * Allowed inputs + * @type {string[]} + * @readonly + * @private + */ +QueryBuilder.inputs = [ + 'text', + 'number', + 'textarea', + 'radio', + 'checkbox', + 'select' +]; + +/** + * Runtime modifiable options with `setOptions` method + * @type {string[]} + * @readonly + * @private + */ +QueryBuilder.modifiable_options = [ + 'display_errors', + 'allow_groups', + 'allow_empty', + 'default_condition', + 'default_filter' +]; + +/** + * CSS selectors for common components + * @type {object.} + * @readonly + */ +QueryBuilder.selectors = { + group_container: '.rules-group-container', + rule_container: '.rule-container', + filter_container: '.rule-filter-container', + operator_container: '.rule-operator-container', + value_container: '.rule-value-container', + error_container: '.error-container', + condition_container: '.rules-group-header .group-conditions', + + rule_header: '.rule-header', + group_header: '.rules-group-header', + group_actions: '.group-actions', + rule_actions: '.rule-actions', + + rules_list: '.rules-group-body>.rules-list', + + group_condition: '.rules-group-header [name$=_cond]', + rule_filter: '.rule-filter-container [name$=_filter]', + rule_operator: '.rule-operator-container [name$=_operator]', + rule_value: '.rule-value-container [name*=_value_]', + + add_rule: '[data-add=rule]', + delete_rule: '[data-delete=rule]', + add_group: '[data-add=group]', + delete_group: '[data-delete=group]' +}; + +/** + * Template strings (see template.js) + * @type {object.} + * @readonly + */ +QueryBuilder.templates = {}; + +/** + * Localized strings (see i18n/) + * @type {object.} + * @readonly + */ +QueryBuilder.regional = {}; + +/** + * Default operators + * @type {object.} + * @readonly + */ +QueryBuilder.OPERATORS = { + equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, + not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, + in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, + not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, + less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, + less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, + greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, + greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, + between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, + not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, + begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, + is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, + is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, + is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, + is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] } +}; + +/** + * Default configuration + * @type {object} + * @readonly + */ +QueryBuilder.DEFAULTS = { + filters: [], + plugins: [], + + sort_filters: false, + display_errors: true, + allow_groups: -1, + allow_empty: false, + conditions: ['AND', 'OR'], + default_condition: 'AND', + inputs_separator: ' , ', + select_placeholder: '------', + display_empty_filter: true, + default_filter: null, + optgroups: {}, + + default_rule_flags: { + filter_readonly: false, + operator_readonly: false, + value_readonly: false, + no_delete: false + }, + + default_group_flags: { + condition_readonly: false, + no_add_rule: false, + no_add_group: false, + no_delete: false + }, + + templates: { + group: null, + rule: null, + filterSelect: null, + operatorSelect: null, + ruleValueSelect: null + }, + + lang_code: 'en', + lang: {}, + + operators: [ + 'equal', + 'not_equal', + 'in', + 'not_in', + 'less', + 'less_or_equal', + 'greater', + 'greater_or_equal', + 'between', + 'not_between', + 'begins_with', + 'not_begins_with', + 'contains', + 'not_contains', + 'ends_with', + 'not_ends_with', + 'is_empty', + 'is_not_empty', + 'is_null', + 'is_not_null' + ], + + icons: { + add_group: 'glyphicon glyphicon-plus-sign', + add_rule: 'glyphicon glyphicon-plus', + remove_group: 'glyphicon glyphicon-remove', + remove_rule: 'glyphicon glyphicon-remove', + error: 'glyphicon glyphicon-warning-sign' + } +}; + + +/** + * @module plugins + */ + +/** + * Definition of available plugins + * @type {object.} + */ +QueryBuilder.plugins = {}; + +/** + * Gets or extends the default configuration + * @param {object} [options] - new configuration + * @returns {undefined|object} nothing or configuration object (copy) + */ +QueryBuilder.defaults = function(options) { + if (typeof options == 'object') { + $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options); + } + else if (typeof options == 'string') { + if (typeof QueryBuilder.DEFAULTS[options] == 'object') { + return $.extend(true, {}, QueryBuilder.DEFAULTS[options]); + } + else { + return QueryBuilder.DEFAULTS[options]; + } + } + else { + return $.extend(true, {}, QueryBuilder.DEFAULTS); + } +}; + +/** + * Registers a new plugin + * @param {string} name + * @param {function} fct - init function + * @param {object} [def] - default options + */ +QueryBuilder.define = function(name, fct, def) { + QueryBuilder.plugins[name] = { + fct: fct, + def: def || {} + }; +}; + +/** + * Adds new methods to QueryBuilder prototype + * @param {object.} methods + */ +QueryBuilder.extend = function(methods) { + $.extend(QueryBuilder.prototype, methods); +}; + +/** + * Initializes plugins for an instance + * @throws ConfigError + * @private + */ +QueryBuilder.prototype.initPlugins = function() { + if (!this.plugins) { + return; + } + + if ($.isArray(this.plugins)) { + var tmp = {}; + this.plugins.forEach(function(plugin) { + tmp[plugin] = null; + }); + this.plugins = tmp; + } + + Object.keys(this.plugins).forEach(function(plugin) { + if (plugin in QueryBuilder.plugins) { + this.plugins[plugin] = $.extend(true, {}, + QueryBuilder.plugins[plugin].def, + this.plugins[plugin] || {} + ); + + QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]); + } + else { + Utils.error('Config', 'Unable to find plugin "{0}"', plugin); + } + }, this); +}; + +/** + * Returns the config of a plugin, if the plugin is not loaded, returns the default config. + * @param {string} name + * @param {string} [property] + * @throws ConfigError + * @returns {*} + */ +QueryBuilder.prototype.getPluginOptions = function(name, property) { + var plugin; + if (this.plugins && this.plugins[name]) { + plugin = this.plugins[name]; + } + else if (QueryBuilder.plugins[name]) { + plugin = QueryBuilder.plugins[name].def; + } + + if (plugin) { + if (property) { + return plugin[property]; + } + else { + return plugin; + } + } + else { + Utils.error('Config', 'Unable to find plugin "{0}"', name); + } +}; + + +/** + * Final initialisation of the builder + * @param {object} [rules] + * @fires QueryBuilder.afterInit + * @private + */ +QueryBuilder.prototype.init = function(rules) { + /** + * When the initilization is done, just before creating the root group + * @event afterInit + * @memberof QueryBuilder + */ + this.trigger('afterInit'); + + if (rules) { + this.setRules(rules); + delete this.settings.rules; + } + else { + this.setRoot(true); + } +}; + +/** + * Checks the configuration of each filter + * @param {QueryBuilder.Filter[]} filters + * @returns {QueryBuilder.Filter[]} + * @throws ConfigError + */ +QueryBuilder.prototype.checkFilters = function(filters) { + var definedFilters = []; + + if (!filters || filters.length === 0) { + Utils.error('Config', 'Missing filters list'); + } + + filters.forEach(function(filter, i) { + if (!filter.id) { + Utils.error('Config', 'Missing filter {0} id', i); + } + if (definedFilters.indexOf(filter.id) != -1) { + Utils.error('Config', 'Filter "{0}" already defined', filter.id); + } + definedFilters.push(filter.id); + + if (!filter.type) { + filter.type = 'string'; + } + else if (!QueryBuilder.types[filter.type]) { + Utils.error('Config', 'Invalid type "{0}"', filter.type); + } + + if (!filter.input) { + filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text'; + } + else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) { + Utils.error('Config', 'Invalid input "{0}"', filter.input); + } + + if (filter.operators) { + filter.operators.forEach(function(operator) { + if (typeof operator != 'string') { + Utils.error('Config', 'Filter operators must be global operators types (string)'); + } + }); + } + + if (!filter.field) { + filter.field = filter.id; + } + if (!filter.label) { + filter.label = filter.field; + } + + if (!filter.optgroup) { + filter.optgroup = null; + } + else { + this.status.has_optgroup = true; + + // register optgroup if needed + if (!this.settings.optgroups[filter.optgroup]) { + this.settings.optgroups[filter.optgroup] = filter.optgroup; + } + } + + switch (filter.input) { + case 'radio': + case 'checkbox': + if (!filter.values || filter.values.length < 1) { + Utils.error('Config', 'Missing filter "{0}" values', filter.id); + } + break; + + case 'select': + var cleanValues = []; + filter.has_optgroup = false; + + Utils.iterateOptions(filter.values, function(value, label, optgroup) { + cleanValues.push({ + value: value, + label: label, + optgroup: optgroup || null + }); + + if (optgroup) { + filter.has_optgroup = true; + + // register optgroup if needed + if (!this.settings.optgroups[optgroup]) { + this.settings.optgroups[optgroup] = optgroup; + } + } + }.bind(this)); + + if (filter.has_optgroup) { + filter.values = Utils.groupSort(cleanValues, 'optgroup'); + } + else { + filter.values = cleanValues; + } + + if (filter.placeholder) { + if (filter.placeholder_value === undefined) { + filter.placeholder_value = -1; + } + + filter.values.forEach(function(entry) { + if (entry.value == filter.placeholder_value) { + Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id); + } + }); + } + break; + } + }, this); + + if (this.settings.sort_filters) { + if (typeof this.settings.sort_filters == 'function') { + filters.sort(this.settings.sort_filters); + } + else { + var self = this; + filters.sort(function(a, b) { + return self.translate(a.label).localeCompare(self.translate(b.label)); + }); + } + } + + if (this.status.has_optgroup) { + filters = Utils.groupSort(filters, 'optgroup'); + } + + return filters; +}; + +/** + * Checks the configuration of each operator + * @param {QueryBuilder.Operator[]} operators + * @returns {QueryBuilder.Operator[]} + * @throws ConfigError + */ +QueryBuilder.prototype.checkOperators = function(operators) { + var definedOperators = []; + + operators.forEach(function(operator, i) { + if (typeof operator == 'string') { + if (!QueryBuilder.OPERATORS[operator]) { + Utils.error('Config', 'Unknown operator "{0}"', operator); + } + + operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]); + } + else { + if (!operator.type) { + Utils.error('Config', 'Missing "type" for operator {0}', i); + } + + if (QueryBuilder.OPERATORS[operator.type]) { + operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator); + } + + if (operator.nb_inputs === undefined || operator.apply_to === undefined) { + Utils.error('Config', 'Missing "nb_inputs" and/or "apply_to" for operator "{0}"', operator.type); + } + } + + if (definedOperators.indexOf(operator.type) != -1) { + Utils.error('Config', 'Operator "{0}" already defined', operator.type); + } + definedOperators.push(operator.type); + + if (!operator.optgroup) { + operator.optgroup = null; + } + else { + this.status.has_operator_optgroup = true; + + // register optgroup if needed + if (!this.settings.optgroups[operator.optgroup]) { + this.settings.optgroups[operator.optgroup] = operator.optgroup; + } + } + }, this); + + if (this.status.has_operator_optgroup) { + operators = Utils.groupSort(operators, 'optgroup'); + } + + return operators; +}; + +/** + * Adds all events listeners to the builder + * @private + */ +QueryBuilder.prototype.bindEvents = function() { + var self = this; + var Selectors = QueryBuilder.selectors; + + // group condition change + this.$el.on('change.queryBuilder', Selectors.group_condition, function() { + if ($(this).is(':checked')) { + var $group = $(this).closest(Selectors.group_container); + self.getModel($group).condition = $(this).val(); + } + }); + + // rule filter change + this.$el.on('change.queryBuilder', Selectors.rule_filter, function() { + var $rule = $(this).closest(Selectors.rule_container); + self.getModel($rule).filter = self.getFilterById($(this).val()); + }); + + // rule operator change + this.$el.on('change.queryBuilder', Selectors.rule_operator, function() { + var $rule = $(this).closest(Selectors.rule_container); + self.getModel($rule).operator = self.getOperatorByType($(this).val()); + }); + + // add rule button + this.$el.on('click.queryBuilder', Selectors.add_rule, function() { + var $group = $(this).closest(Selectors.group_container); + self.addRule(self.getModel($group)); + }); + + // delete rule button + this.$el.on('click.queryBuilder', Selectors.delete_rule, function() { + var $rule = $(this).closest(Selectors.rule_container); + self.deleteRule(self.getModel($rule)); + }); + + if (this.settings.allow_groups !== 0) { + // add group button + this.$el.on('click.queryBuilder', Selectors.add_group, function() { + var $group = $(this).closest(Selectors.group_container); + self.addGroup(self.getModel($group)); + }); + + // delete group button + this.$el.on('click.queryBuilder', Selectors.delete_group, function() { + var $group = $(this).closest(Selectors.group_container); + self.deleteGroup(self.getModel($group)); + }); + } + + // model events + this.model.on({ + 'drop': function(e, node) { + node.$el.remove(); + self.refreshGroupsConditions(); + }, + 'add': function(e, parent, node, index) { + if (index === 0) { + node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list)); + } + else { + node.$el.insertAfter(parent.rules[index - 1].$el); + } + self.refreshGroupsConditions(); + }, + 'move': function(e, node, group, index) { + node.$el.detach(); + + if (index === 0) { + node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list)); + } + else { + node.$el.insertAfter(group.rules[index - 1].$el); + } + self.refreshGroupsConditions(); + }, + 'update': function(e, node, field, value, oldValue) { + if (node instanceof Rule) { + switch (field) { + case 'error': + self.updateError(node); + break; + + case 'flags': + self.applyRuleFlags(node); + break; + + case 'filter': + self.updateRuleFilter(node, oldValue); + break; + + case 'operator': + self.updateRuleOperator(node, oldValue); + break; + + case 'value': + self.updateRuleValue(node, oldValue); + break; + } + } + else { + switch (field) { + case 'error': + self.updateError(node); + break; + + case 'flags': + self.applyGroupFlags(node); + break; + + case 'condition': + self.updateGroupCondition(node, oldValue); + break; + } + } + } + }); +}; + +/** + * Creates the root group + * @param {boolean} [addRule=true] - adds a default empty rule + * @param {object} [data] - group custom data + * @param {object} [flags] - flags to apply to the group + * @returns {Group} root group + * @fires QueryBuilder.afterAddGroup + */ +QueryBuilder.prototype.setRoot = function(addRule, data, flags) { + addRule = (addRule === undefined || addRule === true); + + var group_id = this.nextGroupId(); + var $group = $(this.getGroupTemplate(group_id, 1)); + + this.$el.append($group); + this.model.root = new Group(null, $group); + this.model.root.model = this.model; + + this.model.root.data = data; + this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags); + this.model.root.condition = this.settings.default_condition; + + this.trigger('afterAddGroup', this.model.root); + + if (addRule) { + this.addRule(this.model.root); + } + + return this.model.root; +}; + +/** + * Adds a new group + * @param {Group} parent + * @param {boolean} [addRule=true] - adds a default empty rule + * @param {object} [data] - group custom data + * @param {object} [flags] - flags to apply to the group + * @returns {Group} + * @fires QueryBuilder.beforeAddGroup + * @fires QueryBuilder.afterAddGroup + */ +QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) { + addRule = (addRule === undefined || addRule === true); + + var level = parent.level + 1; + + /** + * Just before adding a group, can be prevented. + * @event beforeAddGroup + * @memberof QueryBuilder + * @param {Group} parent + * @param {boolean} addRule - if an empty rule will be added in the group + * @param {int} level - nesting level of the group, 1 is the root group + */ + var e = this.trigger('beforeAddGroup', parent, addRule, level); + if (e.isDefaultPrevented()) { + return null; + } + + var group_id = this.nextGroupId(); + var $group = $(this.getGroupTemplate(group_id, level)); + var model = parent.addGroup($group); + + model.data = data; + model.flags = $.extend({}, this.settings.default_group_flags, flags); + model.condition = this.settings.default_condition; + + /** + * Just after adding a group + * @event afterAddGroup + * @memberof QueryBuilder + * @param {Group} group + */ + this.trigger('afterAddGroup', model); + + /** + * After any change in the rules + * @event rulesChanged + * @memberof QueryBuilder + */ + this.trigger('rulesChanged'); + + if (addRule) { + this.addRule(model); + } + + return model; +}; + +/** + * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`. + * @param {Group} group + * @returns {boolean} if the group has been deleted + * @fires QueryBuilder.beforeDeleteGroup + * @fires QueryBuilder.afterDeleteGroup + */ +QueryBuilder.prototype.deleteGroup = function(group) { + if (group.isRoot()) { + return false; + } + + /** + * Just before deleting a group, can be prevented + * @event beforeDeleteGroup + * @memberof QueryBuilder + * @param {Group} parent + */ + var e = this.trigger('beforeDeleteGroup', group); + if (e.isDefaultPrevented()) { + return false; + } + + var del = true; + + group.each('reverse', function(rule) { + del &= this.deleteRule(rule); + }, function(group) { + del &= this.deleteGroup(group); + }, this); + + if (del) { + group.drop(); + + /** + * Just after deleting a group + * @event afterDeleteGroup + * @memberof QueryBuilder + */ + this.trigger('afterDeleteGroup'); + + this.trigger('rulesChanged'); + } + + return del; +}; + +/** + * Performs actions when a group's condition changes + * @param {Group} group + * @param {object} previousCondition + * @fires QueryBuilder.afterUpdateGroupCondition + * @private + */ +QueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) { + group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() { + var $this = $(this); + $this.prop('checked', $this.val() === group.condition); + $this.parent().toggleClass('active', $this.val() === group.condition); + }); + + /** + * After the group condition has been modified + * @event afterUpdateGroupCondition + * @memberof QueryBuilder + * @param {Group} group + * @param {object} previousCondition + */ + this.trigger('afterUpdateGroupCondition', group, previousCondition); + + this.trigger('rulesChanged'); +}; + +/** + * Updates the visibility of conditions based on number of rules inside each group + * @private + */ +QueryBuilder.prototype.refreshGroupsConditions = function() { + (function walk(group) { + if (!group.flags || (group.flags && !group.flags.condition_readonly)) { + group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1) + .parent().toggleClass('disabled', group.rules.length <= 1); + } + + group.each(null, function(group) { + walk(group); + }, this); + }(this.model.root)); +}; + +/** + * Adds a new rule + * @param {Group} parent + * @param {object} [data] - rule custom data + * @param {object} [flags] - flags to apply to the rule + * @returns {Rule} + * @fires QueryBuilder.beforeAddRule + * @fires QueryBuilder.afterAddRule + * @fires QueryBuilder.changer:getDefaultFilter + */ +QueryBuilder.prototype.addRule = function(parent, data, flags) { + /** + * Just before adding a rule, can be prevented + * @event beforeAddRule + * @memberof QueryBuilder + * @param {Group} parent + */ + var e = this.trigger('beforeAddRule', parent); + if (e.isDefaultPrevented()) { + return null; + } + + var rule_id = this.nextRuleId(); + var $rule = $(this.getRuleTemplate(rule_id)); + var model = parent.addRule($rule); + + model.data = data; + model.flags = $.extend({}, this.settings.default_rule_flags, flags); + + /** + * Just after adding a rule + * @event afterAddRule + * @memberof QueryBuilder + * @param {Rule} rule + */ + this.trigger('afterAddRule', model); + + this.trigger('rulesChanged'); + + this.createRuleFilters(model); + + if (this.settings.default_filter || !this.settings.display_empty_filter) { + /** + * Modifies the default filter for a rule + * @event changer:getDefaultFilter + * @memberof QueryBuilder + * @param {QueryBuilder.Filter} filter + * @param {Rule} rule + * @returns {QueryBuilder.Filter} + */ + model.filter = this.change('getDefaultFilter', + this.getFilterById(this.settings.default_filter || this.filters[0].id), + model + ); + } + + return model; +}; + +/** + * Tries to delete a rule + * @param {Rule} rule + * @returns {boolean} if the rule has been deleted + * @fires QueryBuilder.beforeDeleteRule + * @fires QueryBuilder.afterDeleteRule + */ +QueryBuilder.prototype.deleteRule = function(rule) { + if (rule.flags.no_delete) { + return false; + } + + /** + * Just before deleting a rule, can be prevented + * @event beforeDeleteRule + * @memberof QueryBuilder + * @param {Rule} rule + */ + var e = this.trigger('beforeDeleteRule', rule); + if (e.isDefaultPrevented()) { + return false; + } + + rule.drop(); + + /** + * Just after deleting a rule + * @event afterDeleteRule + * @memberof QueryBuilder + */ + this.trigger('afterDeleteRule'); + + this.trigger('rulesChanged'); + + return true; +}; + +/** + * Creates the filters for a rule + * @param {Rule} rule + * @fires QueryBuilder.changer:getRuleFilters + * @fires QueryBuilder.afterCreateRuleFilters + * @private + */ +QueryBuilder.prototype.createRuleFilters = function(rule) { + /** + * Modifies the list a filters available for a rule + * @event changer:getRuleFilters + * @memberof QueryBuilder + * @param {QueryBuilder.Filter[]} filters + * @param {Rule} rule + * @returns {QueryBuilder.Filter[]} + */ + var filters = this.change('getRuleFilters', this.filters, rule); + var $filterSelect = $(this.getRuleFilterSelect(rule, filters)); + + rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect); + + /** + * After creating the dropdown for filters + * @event afterCreateRuleFilters + * @memberof QueryBuilder + * @param {Rule} rule + */ + this.trigger('afterCreateRuleFilters', rule); + + this.applyRuleFlags(rule); +}; + +/** + * Creates the operators for a rule and init the rule operator + * @param {Rule} rule + * @fires QueryBuilder.afterCreateRuleOperators + * @private + */ +QueryBuilder.prototype.createRuleOperators = function(rule) { + var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty(); + + if (!rule.filter) { + return; + } + + var operators = this.getOperators(rule.filter); + var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators)); + + $operatorContainer.html($operatorSelect); + + // set the operator without triggering update event + if (rule.filter.default_operator) { + rule.__.operator = this.getOperatorByType(rule.filter.default_operator); + } + else { + rule.__.operator = operators[0]; + } + + rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type); + + /** + * After creating the dropdown for operators + * @event afterCreateRuleOperators + * @memberof QueryBuilder + * @param {Rule} rule + * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule + */ + this.trigger('afterCreateRuleOperators', rule, operators); + + this.applyRuleFlags(rule); +}; + +/** + * Creates the main input for a rule + * @param {Rule} rule + * @fires QueryBuilder.afterCreateRuleInput + * @private + */ +QueryBuilder.prototype.createRuleInput = function(rule) { + var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty(); + + rule.__.value = undefined; + + if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) { + return; + } + + var self = this; + var $inputs = $(); + var filter = rule.filter; + + for (var i = 0; i < rule.operator.nb_inputs; i++) { + var $ruleInput = $(this.getRuleInput(rule, i)); + if (i > 0) $valueContainer.append(this.settings.inputs_separator); + $valueContainer.append($ruleInput); + $inputs = $inputs.add($ruleInput); + } + + $valueContainer.css('display', ''); + + $inputs.on('change ' + (filter.input_event || ''), function() { + if (!rule._updating_input) { + rule._updating_value = true; + rule.value = self.getRuleInputValue(rule); + rule._updating_value = false; + } + }); + + if (filter.plugin) { + $inputs[filter.plugin](filter.plugin_config || {}); + } + + /** + * After creating the input for a rule and initializing optional plugin + * @event afterCreateRuleInput + * @memberof QueryBuilder + * @param {Rule} rule + */ + this.trigger('afterCreateRuleInput', rule); + + if (filter.default_value !== undefined) { + rule.value = filter.default_value; + } + else { + rule._updating_value = true; + rule.value = self.getRuleInputValue(rule); + rule._updating_value = false; + } + + this.applyRuleFlags(rule); +}; + +/** + * Performs action when a rule's filter changes + * @param {Rule} rule + * @param {object} previousFilter + * @fires QueryBuilder.afterUpdateRuleFilter + * @private + */ +QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) { + this.createRuleOperators(rule); + this.createRuleInput(rule); + + rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1'); + + // clear rule data if the filter changed + if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) { + rule.data = undefined; + } + + /** + * After the filter has been updated and the operators and input re-created + * @event afterUpdateRuleFilter + * @memberof QueryBuilder + * @param {Rule} rule + * @param {object} previousFilter + */ + this.trigger('afterUpdateRuleFilter', rule, previousFilter); + + this.trigger('rulesChanged'); +}; + +/** + * Performs actions when a rule's operator changes + * @param {Rule} rule + * @param {object} previousOperator + * @fires QueryBuilder.afterUpdateRuleOperator + * @private + */ +QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) { + var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container); + + if (!rule.operator || rule.operator.nb_inputs === 0) { + $valueContainer.hide(); + + rule.__.value = undefined; + } + else { + $valueContainer.css('display', ''); + + if ($valueContainer.is(':empty') || !previousOperator || + rule.operator.nb_inputs !== previousOperator.nb_inputs || + rule.operator.optgroup !== previousOperator.optgroup + ) { + this.createRuleInput(rule); + } + } + + if (rule.operator) { + rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type); + + // refresh value if the format changed for this operator + rule.__.value = this.getRuleInputValue(rule); + } + + /** + * After the operator has been updated and the input optionally re-created + * @event afterUpdateRuleOperator + * @memberof QueryBuilder + * @param {Rule} rule + * @param {object} previousOperator + */ + this.trigger('afterUpdateRuleOperator', rule, previousOperator); + + this.trigger('rulesChanged'); +}; + +/** + * Performs actions when rule's value changes + * @param {Rule} rule + * @param {object} previousValue + * @fires QueryBuilder.afterUpdateRuleValue + * @private + */ +QueryBuilder.prototype.updateRuleValue = function(rule, previousValue) { + if (!rule._updating_value) { + this.setRuleInputValue(rule, rule.value); + } + + /** + * After the rule value has been modified + * @event afterUpdateRuleValue + * @memberof QueryBuilder + * @param {Rule} rule + * @param {*} previousValue + */ + this.trigger('afterUpdateRuleValue', rule, previousValue); + + this.trigger('rulesChanged'); +}; + +/** + * Changes a rule's properties depending on its flags + * @param {Rule} rule + * @fires QueryBuilder.afterApplyRuleFlags + * @private + */ +QueryBuilder.prototype.applyRuleFlags = function(rule) { + var flags = rule.flags; + var Selectors = QueryBuilder.selectors; + + rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly); + rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly); + rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly); + + if (flags.no_delete) { + rule.$el.find(Selectors.delete_rule).remove(); + } + + /** + * After rule's flags has been applied + * @event afterApplyRuleFlags + * @memberof QueryBuilder + * @param {Rule} rule + */ + this.trigger('afterApplyRuleFlags', rule); +}; + +/** + * Changes group's properties depending on its flags + * @param {Group} group + * @fires QueryBuilder.afterApplyGroupFlags + * @private + */ +QueryBuilder.prototype.applyGroupFlags = function(group) { + var flags = group.flags; + var Selectors = QueryBuilder.selectors; + + group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly) + .parent().toggleClass('readonly', flags.condition_readonly); + + if (flags.no_add_rule) { + group.$el.find(Selectors.add_rule).remove(); + } + if (flags.no_add_group) { + group.$el.find(Selectors.add_group).remove(); + } + if (flags.no_delete) { + group.$el.find(Selectors.delete_group).remove(); + } + + /** + * After group's flags has been applied + * @event afterApplyGroupFlags + * @memberof QueryBuilder + * @param {Group} group + */ + this.trigger('afterApplyGroupFlags', group); +}; + +/** + * Clears all errors markers + * @param {Node} [node] default is root Group + */ +QueryBuilder.prototype.clearErrors = function(node) { + node = node || this.model.root; + + if (!node) { + return; + } + + node.error = null; + + if (node instanceof Group) { + node.each(function(rule) { + rule.error = null; + }, function(group) { + this.clearErrors(group); + }, this); + } +}; + +/** + * Adds/Removes error on a Rule or Group + * @param {Node} node + * @fires QueryBuilder.changer:displayError + * @private + */ +QueryBuilder.prototype.updateError = function(node) { + if (this.settings.display_errors) { + if (node.error === null) { + node.$el.removeClass('has-error'); + } + else { + var errorMessage = this.translate('errors', node.error[0]); + errorMessage = Utils.fmt(errorMessage, node.error.slice(1)); + + /** + * Modifies an error message before display + * @event changer:displayError + * @memberof QueryBuilder + * @param {string} errorMessage - the error message (translated and formatted) + * @param {array} error - the raw error array (error code and optional arguments) + * @param {Node} node + * @returns {string} + */ + errorMessage = this.change('displayError', errorMessage, node.error, node); + + node.$el.addClass('has-error') + .find(QueryBuilder.selectors.error_container).eq(0) + .attr('title', errorMessage); + } + } +}; + +/** + * Triggers a validation error event + * @param {Node} node + * @param {string|array} error + * @param {*} value + * @fires QueryBuilder.validationError + * @private + */ +QueryBuilder.prototype.triggerValidationError = function(node, error, value) { + if (!$.isArray(error)) { + error = [error]; + } + + /** + * Fired when a validation error occurred, can be prevented + * @event validationError + * @memberof QueryBuilder + * @param {Node} node + * @param {string} error + * @param {*} value + */ + var e = this.trigger('validationError', node, error, value); + if (!e.isDefaultPrevented()) { + node.error = error; + } +}; + + +/** + * Destroys the builder + * @fires QueryBuilder.beforeDestroy + */ +QueryBuilder.prototype.destroy = function() { + /** + * Before the {@link QueryBuilder#destroy} method + * @event beforeDestroy + * @memberof QueryBuilder + */ + this.trigger('beforeDestroy'); + + if (this.status.generated_id) { + this.$el.removeAttr('id'); + } + + this.clear(); + this.model = null; + + this.$el + .off('.queryBuilder') + .removeClass('query-builder') + .removeData('queryBuilder'); + + delete this.$el[0].queryBuilder; +}; + +/** + * Clear all rules and resets the root group + * @fires QueryBuilder.beforeReset + * @fires QueryBuilder.afterReset + */ +QueryBuilder.prototype.reset = function() { + /** + * Before the {@link QueryBuilder#reset} method, can be prevented + * @event beforeReset + * @memberof QueryBuilder + */ + var e = this.trigger('beforeReset'); + if (e.isDefaultPrevented()) { + return; + } + + this.status.group_id = 1; + this.status.rule_id = 0; + + this.model.root.empty(); + + this.model.root.data = undefined; + this.model.root.flags = $.extend({}, this.settings.default_group_flags); + this.model.root.condition = this.settings.default_condition; + + this.addRule(this.model.root); + + /** + * After the {@link QueryBuilder#reset} method + * @event afterReset + * @memberof QueryBuilder + */ + this.trigger('afterReset'); + + this.trigger('rulesChanged'); +}; + +/** + * Clears all rules and removes the root group + * @fires QueryBuilder.beforeClear + * @fires QueryBuilder.afterClear + */ +QueryBuilder.prototype.clear = function() { + /** + * Before the {@link QueryBuilder#clear} method, can be prevented + * @event beforeClear + * @memberof QueryBuilder + */ + var e = this.trigger('beforeClear'); + if (e.isDefaultPrevented()) { + return; + } + + this.status.group_id = 0; + this.status.rule_id = 0; + + if (this.model.root) { + this.model.root.drop(); + this.model.root = null; + } + + /** + * After the {@link QueryBuilder#clear} method + * @event afterClear + * @memberof QueryBuilder + */ + this.trigger('afterClear'); + + this.trigger('rulesChanged'); +}; + +/** + * Modifies the builder configuration.
+ * Only options defined in QueryBuilder.modifiable_options are modifiable + * @param {object} options + */ +QueryBuilder.prototype.setOptions = function(options) { + $.each(options, function(opt, value) { + if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) { + this.settings[opt] = value; + } + }.bind(this)); +}; + +/** + * Returns the model associated to a DOM object, or the root model + * @param {jQuery} [target] + * @returns {Node} + */ +QueryBuilder.prototype.getModel = function(target) { + if (!target) { + return this.model.root; + } + else if (target instanceof Node) { + return target; + } + else { + return $(target).data('queryBuilderModel'); + } +}; + +/** + * Validates the whole builder + * @param {object} [options] + * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected + * @returns {boolean} + * @fires QueryBuilder.changer:validate + */ +QueryBuilder.prototype.validate = function(options) { + options = $.extend({ + skip_empty: false + }, options); + + this.clearErrors(); + + var self = this; + + var valid = (function parse(group) { + var done = 0; + var errors = 0; + + group.each(function(rule) { + if (!rule.filter && options.skip_empty) { + return; + } + + if (!rule.filter) { + self.triggerValidationError(rule, 'no_filter', null); + errors++; + return; + } + + if (!rule.operator) { + self.triggerValidationError(rule, 'no_operator', null); + errors++; + return; + } + + if (rule.operator.nb_inputs !== 0) { + var valid = self.validateValue(rule, rule.value); + + if (valid !== true) { + self.triggerValidationError(rule, valid, rule.value); + errors++; + return; + } + } + + done++; + + }, function(group) { + var res = parse(group); + if (res === true) { + done++; + } + else if (res === false) { + errors++; + } + }); + + if (errors > 0) { + return false; + } + else if (done === 0 && !group.isRoot() && options.skip_empty) { + return null; + } + else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) { + self.triggerValidationError(group, 'empty_group', null); + return false; + } + + return true; + + }(this.model.root)); + + /** + * Modifies the result of the {@link QueryBuilder#validate} method + * @event changer:validate + * @memberof QueryBuilder + * @param {boolean} valid + * @returns {boolean} + */ + return this.change('validate', valid); +}; + +/** + * Gets an object representing current rules + * @param {object} [options] + * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all' + * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid + * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected + * @returns {object} + * @fires QueryBuilder.changer:ruleToJson + * @fires QueryBuilder.changer:groupToJson + * @fires QueryBuilder.changer:getRules + */ +QueryBuilder.prototype.getRules = function(options) { + options = $.extend({ + get_flags: false, + allow_invalid: false, + skip_empty: false + }, options); + + var valid = this.validate(options); + if (!valid && !options.allow_invalid) { + return null; + } + + var self = this; + + var out = (function parse(group) { + var groupData = { + condition: group.condition, + rules: [] + }; + + if (group.data) { + groupData.data = $.extendext(true, 'replace', {}, group.data); + } + + if (options.get_flags) { + var flags = self.getGroupFlags(group.flags, options.get_flags === 'all'); + if (!$.isEmptyObject(flags)) { + groupData.flags = flags; + } + } + + group.each(function(rule) { + if (!rule.filter && options.skip_empty) { + return; + } + + var value = null; + if (!rule.operator || rule.operator.nb_inputs !== 0) { + value = rule.value; + } + + var ruleData = { + id: rule.filter ? rule.filter.id : null, + field: rule.filter ? rule.filter.field : null, + type: rule.filter ? rule.filter.type : null, + input: rule.filter ? rule.filter.input : null, + operator: rule.operator ? rule.operator.type : null, + value: value + }; + + if (rule.filter && rule.filter.data || rule.data) { + ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data); + } + + if (options.get_flags) { + var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all'); + if (!$.isEmptyObject(flags)) { + ruleData.flags = flags; + } + } + + /** + * Modifies the JSON generated from a Rule object + * @event changer:ruleToJson + * @memberof QueryBuilder + * @param {object} json + * @param {Rule} rule + * @returns {object} + */ + groupData.rules.push(self.change('ruleToJson', ruleData, rule)); + + }, function(model) { + var data = parse(model); + if (data.rules.length !== 0 || !options.skip_empty) { + groupData.rules.push(data); + } + }, this); + + /** + * Modifies the JSON generated from a Group object + * @event changer:groupToJson + * @memberof QueryBuilder + * @param {object} json + * @param {Group} group + * @returns {object} + */ + return self.change('groupToJson', groupData, group); + + }(this.model.root)); + + out.valid = valid; + + /** + * Modifies the result of the {@link QueryBuilder#getRules} method + * @event changer:getRules + * @memberof QueryBuilder + * @param {object} json + * @returns {object} + */ + return this.change('getRules', out); +}; + +/** + * Sets rules from object + * @param {object} data + * @param {object} [options] + * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid + * @throws RulesError, UndefinedConditionError + * @fires QueryBuilder.changer:setRules + * @fires QueryBuilder.changer:jsonToRule + * @fires QueryBuilder.changer:jsonToGroup + * @fires QueryBuilder.afterSetRules + */ +QueryBuilder.prototype.setRules = function(data, options) { + options = $.extend({ + allow_invalid: false + }, options); + + if ($.isArray(data)) { + data = { + condition: this.settings.default_condition, + rules: data + }; + } + + if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) { + Utils.error('RulesParse', 'Incorrect data object passed'); + } + + this.clear(); + this.setRoot(false, data.data, this.parseGroupFlags(data)); + + /** + * Modifies data before the {@link QueryBuilder#setRules} method + * @event changer:setRules + * @memberof QueryBuilder + * @param {object} json + * @param {object} options + * @returns {object} + */ + data = this.change('setRules', data, options); + + var self = this; + + (function add(data, group) { + if (group === null) { + return; + } + + if (data.condition === undefined) { + data.condition = self.settings.default_condition; + } + else if (self.settings.conditions.indexOf(data.condition) == -1) { + Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition "{0}"', data.condition); + data.condition = self.settings.default_condition; + } + + group.condition = data.condition; + + data.rules.forEach(function(item) { + var model; + + if (item.rules !== undefined) { + if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) { + Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups); + self.reset(); + } + else { + model = self.addGroup(group, false, item.data, self.parseGroupFlags(item)); + if (model === null) { + return; + } + + add(item, model); + } + } + else { + if (!item.empty) { + if (item.id === undefined) { + Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id'); + item.empty = true; + } + if (item.operator === undefined) { + item.operator = 'equal'; + } + } + + model = self.addRule(group, item.data, self.parseRuleFlags(item)); + if (model === null) { + return; + } + + if (!item.empty) { + model.filter = self.getFilterById(item.id, !options.allow_invalid); + } + + if (model.filter) { + model.operator = self.getOperatorByType(item.operator, !options.allow_invalid); + + if (!model.operator) { + model.operator = self.getOperators(model.filter)[0]; + } + } + + if (model.operator && model.operator.nb_inputs !== 0) { + if (item.value !== undefined) { + model.value = item.value; + } + else if (model.filter.default_value !== undefined) { + model.value = model.filter.default_value; + } + } + + /** + * Modifies the Rule object generated from the JSON + * @event changer:jsonToRule + * @memberof QueryBuilder + * @param {Rule} rule + * @param {object} json + * @returns {Rule} the same rule + */ + if (self.change('jsonToRule', model, item) != model) { + Utils.error('RulesParse', 'Plugin tried to change rule reference'); + } + } + }); + + /** + * Modifies the Group object generated from the JSON + * @event changer:jsonToGroup + * @memberof QueryBuilder + * @param {Group} group + * @param {object} json + * @returns {Group} the same group + */ + if (self.change('jsonToGroup', group, data) != group) { + Utils.error('RulesParse', 'Plugin tried to change group reference'); + } + + }(data, this.model.root)); + + /** + * After the {@link QueryBuilder#setRules} method + * @event afterSetRules + * @memberof QueryBuilder + */ + this.trigger('afterSetRules'); +}; + + +/** + * Performs value validation + * @param {Rule} rule + * @param {string|string[]} value + * @returns {array|boolean} true or error array + * @fires QueryBuilder.changer:validateValue + */ +QueryBuilder.prototype.validateValue = function(rule, value) { + var validation = rule.filter.validation || {}; + var result = true; + + if (validation.callback) { + result = validation.callback.call(this, value, rule); + } + else { + result = this._validateValue(rule, value); + } + + /** + * Modifies the result of the rule validation method + * @event changer:validateValue + * @memberof QueryBuilder + * @param {array|boolean} result - true or an error array + * @param {*} value + * @param {Rule} rule + * @returns {array|boolean} + */ + return this.change('validateValue', result, value, rule); +}; + +/** + * Default validation function + * @param {Rule} rule + * @param {string|string[]} value + * @returns {array|boolean} true or error array + * @throws ConfigError + * @private + */ +QueryBuilder.prototype._validateValue = function(rule, value) { + var filter = rule.filter; + var operator = rule.operator; + var validation = filter.validation || {}; + var result = true; + var tmp, tempValue; + + if (rule.operator.nb_inputs === 1) { + value = [value]; + } + + for (var i = 0; i < operator.nb_inputs; i++) { + if (!operator.multiple && $.isArray(value[i]) && value[i].length > 1) { + result = ['operator_not_multiple', operator.type, this.translate('operators', operator.type)]; + break; + } + + switch (filter.input) { + case 'radio': + if (value[i] === undefined || value[i].length === 0) { + if (!validation.allow_empty_value) { + result = ['radio_empty']; + } + break; + } + break; + + case 'checkbox': + if (value[i] === undefined || value[i].length === 0) { + if (!validation.allow_empty_value) { + result = ['checkbox_empty']; + } + break; + } + break; + + case 'select': + if (value[i] === undefined || value[i].length === 0 || (filter.placeholder && value[i] == filter.placeholder_value)) { + if (!validation.allow_empty_value) { + result = ['select_empty']; + } + break; + } + break; + + default: + tempValue = $.isArray(value[i]) ? value[i] : [value[i]]; + + for (var j = 0; j < tempValue.length; j++) { + switch (QueryBuilder.types[filter.type]) { + case 'string': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['string_empty']; + } + break; + } + if (validation.min !== undefined) { + if (tempValue[j].length < parseInt(validation.min)) { + result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min]; + break; + } + } + if (validation.max !== undefined) { + if (tempValue[j].length > parseInt(validation.max)) { + result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max]; + break; + } + } + if (validation.format) { + if (typeof validation.format == 'string') { + validation.format = new RegExp(validation.format); + } + if (!validation.format.test(tempValue[j])) { + result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format]; + break; + } + } + break; + + case 'number': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['number_nan']; + } + break; + } + if (isNaN(tempValue[j])) { + result = ['number_nan']; + break; + } + if (filter.type == 'integer') { + if (parseInt(tempValue[j]) != tempValue[j]) { + result = ['number_not_integer']; + break; + } + } + else { + if (parseFloat(tempValue[j]) != tempValue[j]) { + result = ['number_not_double']; + break; + } + } + if (validation.min !== undefined) { + if (tempValue[j] < parseFloat(validation.min)) { + result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min]; + break; + } + } + if (validation.max !== undefined) { + if (tempValue[j] > parseFloat(validation.max)) { + result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max]; + break; + } + } + if (validation.step !== undefined && validation.step !== 'any') { + var v = (tempValue[j] / validation.step).toPrecision(14); + if (parseInt(v) != v) { + result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step]; + break; + } + } + break; + + case 'datetime': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['datetime_empty']; + } + break; + } + + // we need MomentJS + if (validation.format) { + if (!('moment' in window)) { + Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com'); + } + + var datetime = moment(tempValue[j], validation.format); + if (!datetime.isValid()) { + result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format]; + break; + } + else { + if (validation.min) { + if (datetime < moment(validation.min, validation.format)) { + result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min]; + break; + } + } + if (validation.max) { + if (datetime > moment(validation.max, validation.format)) { + result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max]; + break; + } + } + } + } + break; + + case 'boolean': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['boolean_not_valid']; + } + break; + } + tmp = ('' + tempValue[j]).trim().toLowerCase(); + if (tmp !== 'true' && tmp !== 'false' && tmp !== '1' && tmp !== '0' && tempValue[j] !== 1 && tempValue[j] !== 0) { + result = ['boolean_not_valid']; + break; + } + } + + if (result !== true) { + break; + } + } + } + + if (result !== true) { + break; + } + } + + if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) { + switch (QueryBuilder.types[filter.type]) { + case 'number': + if (value[0] > value[1]) { + result = ['number_between_invalid', value[0], value[1]]; + } + break; + + case 'datetime': + // we need MomentJS + if (validation.format) { + if (!('moment' in window)) { + Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com'); + } + + if (moment(value[0], validation.format).isAfter(moment(value[1], validation.format))) { + result = ['datetime_between_invalid', value[0], value[1]]; + } + } + break; + } + } + + return result; +}; + +/** + * Returns an incremented group ID + * @returns {string} + * @private + */ +QueryBuilder.prototype.nextGroupId = function() { + return this.status.id + '_group_' + (this.status.group_id++); +}; + +/** + * Returns an incremented rule ID + * @returns {string} + * @private + */ +QueryBuilder.prototype.nextRuleId = function() { + return this.status.id + '_rule_' + (this.status.rule_id++); +}; + +/** + * Returns the operators for a filter + * @param {string|object} filter - filter id or filter object + * @returns {object[]} + * @fires QueryBuilder.changer:getOperators + * @private + */ +QueryBuilder.prototype.getOperators = function(filter) { + if (typeof filter == 'string') { + filter = this.getFilterById(filter); + } + + var result = []; + + for (var i = 0, l = this.operators.length; i < l; i++) { + // filter operators check + if (filter.operators) { + if (filter.operators.indexOf(this.operators[i].type) == -1) { + continue; + } + } + // type check + else if (this.operators[i].apply_to.indexOf(QueryBuilder.types[filter.type]) == -1) { + continue; + } + + result.push(this.operators[i]); + } + + // keep sort order defined for the filter + if (filter.operators) { + result.sort(function(a, b) { + return filter.operators.indexOf(a.type) - filter.operators.indexOf(b.type); + }); + } + + /** + * Modifies the operators available for a filter + * @event changer:getOperators + * @memberof QueryBuilder + * @param {QueryBuilder.Operator[]} operators + * @param {QueryBuilder.Filter} filter + * @returns {QueryBuilder.Operator[]} + */ + return this.change('getOperators', result, filter); +}; + +/** + * Returns a particular filter by its id + * @param {string} id + * @param {boolean} [doThrow=true] + * @returns {object|null} + * @throws UndefinedFilterError + * @private + */ +QueryBuilder.prototype.getFilterById = function(id, doThrow) { + if (id == '-1') { + return null; + } + + for (var i = 0, l = this.filters.length; i < l; i++) { + if (this.filters[i].id == id) { + return this.filters[i]; + } + } + + Utils.error(doThrow !== false, 'UndefinedFilter', 'Undefined filter "{0}"', id); + + return null; +}; + +/** + * Returns a particular operator by its type + * @param {string} type + * @param {boolean} [doThrow=true] + * @returns {object|null} + * @throws UndefinedOperatorError + * @private + */ +QueryBuilder.prototype.getOperatorByType = function(type, doThrow) { + if (type == '-1') { + return null; + } + + for (var i = 0, l = this.operators.length; i < l; i++) { + if (this.operators[i].type == type) { + return this.operators[i]; + } + } + + Utils.error(doThrow !== false, 'UndefinedOperator', 'Undefined operator "{0}"', type); + + return null; +}; + +/** + * Returns rule's current input value + * @param {Rule} rule + * @returns {*} + * @fires QueryBuilder.changer:getRuleValue + * @private + */ +QueryBuilder.prototype.getRuleInputValue = function(rule) { + var filter = rule.filter; + var operator = rule.operator; + var value = []; + + if (filter.valueGetter) { + value = filter.valueGetter.call(this, rule); + } + else { + var $value = rule.$el.find(QueryBuilder.selectors.value_container); + + for (var i = 0; i < operator.nb_inputs; i++) { + var name = Utils.escapeElementId(rule.id + '_value_' + i); + var tmp; + + switch (filter.input) { + case 'radio': + value.push($value.find('[name=' + name + ']:checked').val()); + break; + + case 'checkbox': + tmp = []; + // jshint loopfunc:true + $value.find('[name=' + name + ']:checked').each(function() { + tmp.push($(this).val()); + }); + // jshint loopfunc:false + value.push(tmp); + break; + + case 'select': + if (filter.multiple) { + tmp = []; + // jshint loopfunc:true + $value.find('[name=' + name + '] option:selected').each(function() { + tmp.push($(this).val()); + }); + // jshint loopfunc:false + value.push(tmp); + } + else { + value.push($value.find('[name=' + name + '] option:selected').val()); + } + break; + + default: + value.push($value.find('[name=' + name + ']').val()); + } + } + + value = value.map(function(val) { + if (operator.multiple && filter.value_separator && typeof val == 'string') { + val = val.split(filter.value_separator); + } + + if ($.isArray(val)) { + return val.map(function(subval) { + return Utils.changeType(subval, filter.type); + }); + } + else { + return Utils.changeType(val, filter.type); + } + }); + + if (operator.nb_inputs === 1) { + value = value[0]; + } + + // @deprecated + if (filter.valueParser) { + value = filter.valueParser.call(this, rule, value); + } + } + + /** + * Modifies the rule's value grabbed from the DOM + * @event changer:getRuleValue + * @memberof QueryBuilder + * @param {*} value + * @param {Rule} rule + * @returns {*} + */ + return this.change('getRuleValue', value, rule); +}; + +/** + * Sets the value of a rule's input + * @param {Rule} rule + * @param {*} value + * @private + */ +QueryBuilder.prototype.setRuleInputValue = function(rule, value) { + var filter = rule.filter; + var operator = rule.operator; + + if (!filter || !operator) { + return; + } + + rule._updating_input = true; + + if (filter.valueSetter) { + filter.valueSetter.call(this, rule, value); + } + else { + var $value = rule.$el.find(QueryBuilder.selectors.value_container); + + if (operator.nb_inputs == 1) { + value = [value]; + } + + for (var i = 0; i < operator.nb_inputs; i++) { + var name = Utils.escapeElementId(rule.id + '_value_' + i); + + switch (filter.input) { + case 'radio': + $value.find('[name=' + name + '][value="' + value[i] + '"]').prop('checked', true).trigger('change'); + break; + + case 'checkbox': + if (!$.isArray(value[i])) { + value[i] = [value[i]]; + } + // jshint loopfunc:true + value[i].forEach(function(value) { + $value.find('[name=' + name + '][value="' + value + '"]').prop('checked', true).trigger('change'); + }); + // jshint loopfunc:false + break; + + default: + if (operator.multiple && filter.value_separator && $.isArray(value[i])) { + value[i] = value[i].join(filter.value_separator); + } + $value.find('[name=' + name + ']').val(value[i]).trigger('change'); + break; + } + } + } + + rule._updating_input = false; +}; + +/** + * Parses rule flags + * @param {object} rule + * @returns {object} + * @fires QueryBuilder.changer:parseRuleFlags + * @private + */ +QueryBuilder.prototype.parseRuleFlags = function(rule) { + var flags = $.extend({}, this.settings.default_rule_flags); + + if (rule.readonly) { + $.extend(flags, { + filter_readonly: true, + operator_readonly: true, + value_readonly: true, + no_delete: true + }); + } + + if (rule.flags) { + $.extend(flags, rule.flags); + } + + /** + * Modifies the consolidated rule's flags + * @event changer:parseRuleFlags + * @memberof QueryBuilder + * @param {object} flags + * @param {object} rule - not a Rule object + * @returns {object} + */ + return this.change('parseRuleFlags', flags, rule); +}; + +/** + * Gets a copy of flags of a rule + * @param {object} flags + * @param {boolean} [all=false] - return all flags or only changes from default flags + * @returns {object} + * @private + */ +QueryBuilder.prototype.getRuleFlags = function(flags, all) { + if (all) { + return $.extend({}, flags); + } + else { + var ret = {}; + $.each(this.settings.default_rule_flags, function(key, value) { + if (flags[key] !== value) { + ret[key] = flags[key]; + } + }); + return ret; + } +}; + +/** + * Parses group flags + * @param {object} group + * @returns {object} + * @fires QueryBuilder.changer:parseGroupFlags + * @private + */ +QueryBuilder.prototype.parseGroupFlags = function(group) { + var flags = $.extend({}, this.settings.default_group_flags); + + if (group.readonly) { + $.extend(flags, { + condition_readonly: true, + no_add_rule: true, + no_add_group: true, + no_delete: true + }); + } + + if (group.flags) { + $.extend(flags, group.flags); + } + + /** + * Modifies the consolidated group's flags + * @event changer:parseGroupFlags + * @memberof QueryBuilder + * @param {object} flags + * @param {object} group - not a Group object + * @returns {object} + */ + return this.change('parseGroupFlags', flags, group); +}; + +/** + * Gets a copy of flags of a group + * @param {object} flags + * @param {boolean} [all=false] - return all flags or only changes from default flags + * @returns {object} + * @private + */ +QueryBuilder.prototype.getGroupFlags = function(flags, all) { + if (all) { + return $.extend({}, flags); + } + else { + var ret = {}; + $.each(this.settings.default_group_flags, function(key, value) { + if (flags[key] !== value) { + ret[key] = flags[key]; + } + }); + return ret; + } +}; + +/** + * Translate a label either by looking in the `lang` object or in itself if it's an object where keys are language codes + * @param {string} [category] + * @param {string|object} key + * @returns {string} + * @fires QueryBuilder.changer:translate + */ +QueryBuilder.prototype.translate = function(category, key) { + if (!key) { + key = category; + category = undefined; + } + + var translation; + if (typeof key === 'object') { + translation = key[this.settings.lang_code] || key['en']; + } + else { + translation = (category ? this.lang[category] : this.lang)[key] || key; + } + + /** + * Modifies the translated label + * @event changer:translate + * @memberof QueryBuilder + * @param {string} translation + * @param {string|object} key + * @param {string} [category] + * @returns {string} + */ + return this.change('translate', translation, key, category); +}; + +/** + * Returns a validation message + * @param {object} validation + * @param {string} type + * @param {string} def + * @returns {string} + * @private + */ +QueryBuilder.prototype.getValidationMessage = function(validation, type, def) { + return validation.messages && validation.messages[type] || def; +}; + + +QueryBuilder.templates.group = '\ +
\ +
\ +
\ + \ + {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \ + \ + {{?}} \ + {{? it.level>1 }} \ + \ + {{?}} \ +
\ +
\ + {{~ it.conditions: condition }} \ + \ + {{~}} \ +
\ + {{? it.settings.display_errors }} \ +
\ + {{?}} \ +
\ +
\ +
\ +
\ +
'; + +QueryBuilder.templates.rule = '\ +
\ +
\ +
\ + \ +
\ +
\ + {{? it.settings.display_errors }} \ +
\ + {{?}} \ +
\ +
\ +
\ +
'; + +QueryBuilder.templates.filterSelect = '\ +{{ var optgroup = null; }} \ +'; + +QueryBuilder.templates.operatorSelect = '\ +{{? it.operators.length === 1 }} \ + \ +{{= it.translate("operators", it.operators[0].type) }} \ + \ +{{?}} \ +{{ var optgroup = null; }} \ +'; + +QueryBuilder.templates.ruleValueSelect = '\ +{{ var optgroup = null; }} \ +'; + +/** + * Returns group's HTML + * @param {string} group_id + * @param {int} level + * @returns {string} + * @fires QueryBuilder.changer:getGroupTemplate + * @private + */ +QueryBuilder.prototype.getGroupTemplate = function(group_id, level) { + var h = this.templates.group({ + builder: this, + group_id: group_id, + level: level, + conditions: this.settings.conditions, + icons: this.icons, + settings: this.settings, + translate: this.translate.bind(this) + }); + + /** + * Modifies the raw HTML of a group + * @event changer:getGroupTemplate + * @memberof QueryBuilder + * @param {string} html + * @param {int} level + * @returns {string} + */ + return this.change('getGroupTemplate', h, level); +}; + +/** + * Returns rule's HTML + * @param {string} rule_id + * @returns {string} + * @fires QueryBuilder.changer:getRuleTemplate + * @private + */ +QueryBuilder.prototype.getRuleTemplate = function(rule_id) { + var h = this.templates.rule({ + builder: this, + rule_id: rule_id, + icons: this.icons, + settings: this.settings, + translate: this.translate.bind(this) + }); + + /** + * Modifies the raw HTML of a rule + * @event changer:getRuleTemplate + * @memberof QueryBuilder + * @param {string} html + * @returns {string} + */ + return this.change('getRuleTemplate', h); +}; + +/** + * Returns rule's filter HTML + * @param {Rule} rule + * @param {object[]} filters + * @returns {string} + * @fires QueryBuilder.changer:getRuleFilterTemplate + * @private + */ +QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) { + var h = this.templates.filterSelect({ + builder: this, + rule: rule, + filters: filters, + icons: this.icons, + settings: this.settings, + translate: this.translate.bind(this) + }); + + /** + * Modifies the raw HTML of the rule's filter dropdown + * @event changer:getRuleFilterSelect + * @memberof QueryBuilder + * @param {string} html + * @param {Rule} rule + * @param {QueryBuilder.Filter[]} filters + * @returns {string} + */ + return this.change('getRuleFilterSelect', h, rule, filters); +}; + +/** + * Returns rule's operator HTML + * @param {Rule} rule + * @param {object[]} operators + * @returns {string} + * @fires QueryBuilder.changer:getRuleOperatorTemplate + * @private + */ +QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) { + var h = this.templates.operatorSelect({ + builder: this, + rule: rule, + operators: operators, + icons: this.icons, + settings: this.settings, + translate: this.translate.bind(this) + }); + + /** + * Modifies the raw HTML of the rule's operator dropdown + * @event changer:getRuleOperatorSelect + * @memberof QueryBuilder + * @param {string} html + * @param {Rule} rule + * @param {QueryBuilder.Operator[]} operators + * @returns {string} + */ + return this.change('getRuleOperatorSelect', h, rule, operators); +}; + +/** + * Returns the rule's value select HTML + * @param {string} name + * @param {Rule} rule + * @returns {string} + * @fires QueryBuilder.changer:getRuleValueSelect + * @private + */ +QueryBuilder.prototype.getRuleValueSelect = function(name, rule) { + var h = this.templates.ruleValueSelect({ + builder: this, + name: name, + rule: rule, + icons: this.icons, + settings: this.settings, + translate: this.translate.bind(this) + }); + + /** + * Modifies the raw HTML of the rule's value dropdown (in case of a "select filter) + * @event changer:getRuleValueSelect + * @memberof QueryBuilder + * @param {string} html + * @param [string} name + * @param {Rule} rule + * @returns {string} + */ + return this.change('getRuleValueSelect', h, name, rule); +}; + +/** + * Returns the rule's value HTML + * @param {Rule} rule + * @param {int} value_id + * @returns {string} + * @fires QueryBuilder.changer:getRuleInput + * @private + */ +QueryBuilder.prototype.getRuleInput = function(rule, value_id) { + var filter = rule.filter; + var validation = rule.filter.validation || {}; + var name = rule.id + '_value_' + value_id; + var c = filter.vertical ? ' class=block' : ''; + var h = ''; + + if (typeof filter.input == 'function') { + h = filter.input.call(this, rule, name); + } + else { + switch (filter.input) { + case 'radio': + case 'checkbox': + Utils.iterateOptions(filter.values, function(key, val) { + h += ' ' + val + ' '; + }); + break; + + case 'select': + h = this.getRuleValueSelect(name, rule); + break; + + case 'textarea': + h += '";break;case"number":l+=' "})}})},{font:"glyphicons",color:"default"}),c.define("bt-selectpicker",function(r){$.fn.selectpicker&&$.fn.selectpicker.Constructor||h.error("MissingLibrary",'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select');var n=c.selectors;this.on("afterCreateRuleFilters",function(e,t){t.$el.find(n.rule_filter).removeClass("form-control").selectpicker(r)}),this.on("afterCreateRuleOperators",function(e,t){t.$el.find(n.rule_operator).removeClass("form-control").selectpicker(r)}),this.on("afterUpdateRuleFilter",function(e,t){t.$el.find(n.rule_filter).selectpicker("render")}),this.on("afterUpdateRuleOperator",function(e,t){t.$el.find(n.rule_operator).selectpicker("render")}),this.on("beforeDeleteRule",function(e,t){t.$el.find(n.rule_filter).selectpicker("destroy"),t.$el.find(n.rule_operator).selectpicker("destroy")})},{container:"body",style:"btn-inverse btn-xs",width:"auto",showIcon:!1}),c.define("bt-tooltip-errors",function(n){$.fn.tooltip&&$.fn.tooltip.Constructor&&$.fn.tooltip.Constructor.prototype.fixTitle||h.error("MissingLibrary",'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');var i=this;this.on("getRuleTemplate.filter getGroupTemplate.filter",function(e){var t=$(e.value);t.find(c.selectors.error_container).attr("data-toggle","tooltip"),e.value=t.prop("outerHTML")}),this.model.on("update",function(e,t,r){"error"==r&&i.settings.display_errors&&t.$el.find(c.selectors.error_container).eq(0).tooltip(n).tooltip("hide").tooltip("fixTitle")})},{placement:"right"}),c.extend({setFilters:function(e,t){var r=this;void 0===t&&(t=e,e=!1),t=this.checkFilters(t);var n=(t=this.change("setFilters",t)).map(function(e){return e.id});if(e||function e(t){t.each(function(e){e.filter&&-1===n.indexOf(e.filter.id)&&h.error("ChangeFilter",'A rule is using filter "{0}"',e.filter.id)},e)}(this.model.root),this.filters=t,function e(t){t.each(!0,function(e){e.filter&&-1===n.indexOf(e.filter.id)?(e.drop(),r.trigger("rulesChanged")):(r.createRuleFilters(e),e.$el.find(c.selectors.rule_filter).val(e.filter?e.filter.id:"-1"),r.trigger("afterUpdateRuleFilter",e))},e)}(this.model.root),this.settings.plugins&&(this.settings.plugins["unique-filter"]&&this.updateDisabledFilters(),this.settings.plugins["bt-selectpicker"]&&this.$el.find(c.selectors.rule_filter).selectpicker("render")),this.settings.default_filter)try{this.getFilterById(this.settings.default_filter)}catch(e){this.settings.default_filter=null}this.trigger("afterSetFilters",t)},addFilter:function(e,r){void 0===r||"#end"==r?r=this.filters.length:"#start"==r&&(r=0),$.isArray(e)||(e=[e]);var t=$.extend(!0,[],this.filters);parseInt(r)==r?Array.prototype.splice.apply(t,[r,0].concat(e)):this.filters.some(function(e,t){if(e.id==r)return r=t+1,!0})?Array.prototype.splice.apply(t,[r,0].concat(e)):Array.prototype.push.apply(t,e),this.setFilters(t)},removeFilter:function(t,e){var r=$.extend(!0,[],this.filters);"string"==typeof t&&(t=[t]),r=r.filter(function(e){return-1===t.indexOf(e.id)}),this.setFilters(e,r)}}),c.define("chosen-selectpicker",function(r){$.fn.chosen||h.error("MissingLibrary",'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen'),this.settings.plugins["bt-selectpicker"]&&h.error("Conflict","bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list");var n=c.selectors;this.on("afterCreateRuleFilters",function(e,t){t.$el.find(n.rule_filter).removeClass("form-control").chosen(r)}),this.on("afterCreateRuleOperators",function(e,t){t.$el.find(n.rule_operator).removeClass("form-control").chosen(r)}),this.on("afterUpdateRuleFilter",function(e,t){t.$el.find(n.rule_filter).trigger("chosen:updated")}),this.on("afterUpdateRuleOperator",function(e,t){t.$el.find(n.rule_operator).trigger("chosen:updated")}),this.on("beforeDeleteRule",function(e,t){t.$el.find(n.rule_filter).chosen("destroy"),t.$el.find(n.rule_operator).chosen("destroy")})}),c.define("filter-description",function(i){"inline"===i.mode?this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("p.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?(r=$('

')).appendTo(t.$el):r.css("display",""),r.html(' '+n)):r.hide()}):"popover"===i.mode?($.fn.popover&&$.fn.popover.Constructor&&$.fn.popover.Constructor.prototype.fixTitle||h.error("MissingLibrary",'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com'),this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("button.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?((r=$('')).prependTo(t.$el.find(c.selectors.rule_actions)),r.popover({placement:"left",container:"body",html:!0}),r.on("mouseout",function(){r.popover("hide")})):r.css("display",""),r.data("bs.popover").options.content=n,r.attr("aria-describedby")&&r.popover("show")):(r.hide(),r.data("bs.popover")&&r.popover("hide"))})):"bootbox"===i.mode&&("bootbox"in window||h.error("MissingLibrary",'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com'),this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("button.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?((r=$('')).prependTo(t.$el.find(c.selectors.rule_actions)),r.on("click",function(){bootbox.alert(r.data("description"))})):r.css("display",""),r.data("description",n)):r.hide()}))},{icon:"glyphicon glyphicon-info-sign",mode:"popover"}),c.extend({getFilterDescription:function(e,t){return e?"function"==typeof e.description?e.description.call(this,t):e.description:void 0}}),c.define("invert",function(r){var n=this,i=c.selectors;this.on("afterInit",function(){n.$el.on("click.queryBuilder","[data-invert=group]",function(){var e=$(this).closest(i.group_container);n.invert(n.getModel(e),r)}),r.display_rules_button&&r.invert_rules&&n.$el.on("click.queryBuilder","[data-invert=rule]",function(){var e=$(this).closest(i.rule_container);n.invert(n.getModel(e),r)})}),r.disable_template||(this.on("getGroupTemplate.filter",function(e){var t=$(e.value);t.find(i.condition_container).after('"),e.value=t.prop("outerHTML")}),r.display_rules_button&&r.invert_rules&&this.on("getRuleTemplate.filter",function(e){var t=$(e.value);t.find(i.rule_actions).prepend('"),e.value=t.prop("outerHTML")}))},{icon:"glyphicon glyphicon-random",recursive:!0,invert_rules:!0,display_rules_button:!1,silent_fail:!1,disable_template:!1}),c.defaults({operatorOpposites:{equal:"not_equal",not_equal:"equal",in:"not_in",not_in:"in",less:"greater_or_equal",less_or_equal:"greater",greater:"less_or_equal",greater_or_equal:"less",between:"not_between",not_between:"between",begins_with:"not_begins_with",not_begins_with:"begins_with",contains:"not_contains",not_contains:"contains",ends_with:"not_ends_with",not_ends_with:"ends_with",is_empty:"is_not_empty",is_not_empty:"is_empty",is_null:"is_not_null",is_not_null:"is_null"},conditionOpposites:{AND:"OR",OR:"AND"}}),c.extend({invert:function(e,t){if(!(e instanceof i)){if(!this.model.root)return;t=e,e=this.model.root}if("object"!=typeof t&&(t={}),void 0===t.recursive&&(t.recursive=!0),void 0===t.invert_rules&&(t.invert_rules=!0),void 0===t.silent_fail&&(t.silent_fail=!1),void 0===t.trigger&&(t.trigger=!0),e instanceof a){if(this.settings.conditionOpposites[e.condition]?e.condition=this.settings.conditionOpposites[e.condition]:t.silent_fail||h.error("InvertCondition",'Unknown inverse of condition "{0}"',e.condition),t.recursive){var r=$.extend({},t,{trigger:!1});e.each(function(e){t.invert_rules&&this.invert(e,r)},function(e){this.invert(e,r)},this)}}else if(e instanceof l&&e.operator&&!e.filter.no_invert)if(this.settings.operatorOpposites[e.operator.type]){var n=this.settings.operatorOpposites[e.operator.type];e.filter.operators&&-1==e.filter.operators.indexOf(n)||(e.operator=this.getOperatorByType(n))}else t.silent_fail||h.error("InvertOperator",'Unknown inverse of operator "{0}"',e.operator.type);t.trigger&&(this.trigger("afterInvert",e,t),this.trigger("rulesChanged"))}}),c.defaults({mongoOperators:{equal:function(e){return e[0]},not_equal:function(e){return{$ne:e[0]}},in:function(e){return{$in:e}},not_in:function(e){return{$nin:e}},less:function(e){return{$lt:e[0]}},less_or_equal:function(e){return{$lte:e[0]}},greater:function(e){return{$gt:e[0]}},greater_or_equal:function(e){return{$gte:e[0]}},between:function(e){return{$gte:e[0],$lte:e[1]}},not_between:function(e){return{$lt:e[0],$gt:e[1]}},begins_with:function(e){return{$regex:"^"+h.escapeRegExp(e[0])}},not_begins_with:function(e){return{$regex:"^(?!"+h.escapeRegExp(e[0])+")"}},contains:function(e){return{$regex:h.escapeRegExp(e[0])}},not_contains:function(e){return{$regex:"^((?!"+h.escapeRegExp(e[0])+").)*$",$options:"s"}},ends_with:function(e){return{$regex:h.escapeRegExp(e[0])+"$"}},not_ends_with:function(e){return{$regex:"(? '+n.translate("NOT")+""),e.value=t.prop("outerHTML")}),this.on("groupToJson.filter",function(e,t){e.value.not=t.not}),this.on("jsonToGroup.filter",function(e,t){e.value.not=!!t.not}),this.on("groupToSQL.filter",function(e,t){t.not&&(e.value="NOT ( "+e.value+" )")}),this.on("parseSQLNode.filter",function(e){e.value.name&&"NOT"==e.value.name.toUpperCase()&&(e.value=e.value.arguments.value[0],-1===["AND","OR"].indexOf(e.value.operation.toUpperCase())&&(e.value=new SQLParser.nodes.Op(n.settings.default_condition,e.value,null)),e.value.not=!0)}),this.on("sqlGroupsDistinct.filter",function(e,t,r,n){r.not&&0"+c.selectors.group_not).toggleClass("active",e.not).find("i").attr("class",e.not?t.icon_checked:t.icon_unchecked),this.trigger("afterUpdateGroupNot",e),this.trigger("rulesChanged")}}),c.define("sortable",function(n){var i,o,l,s;"interact"in window||h.error("MissingLibrary",'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io'),void 0!==n.default_no_sortable&&(h.error(!1,"Config",'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead'),this.settings.default_rule_flags.no_sortable=this.settings.default_group_flags.no_sortable=n.default_no_sortable),interact.dynamicDrop(!0),interact.pointerMoveTolerance(10),this.on("afterAddRule afterAddGroup",function(e,t){if(t!=i){var r=e.builder;n.inherit_no_sortable&&t.parent&&t.parent.flags.no_sortable&&(t.flags.no_sortable=!0),n.inherit_no_drop&&t.parent&&t.parent.flags.no_drop&&(t.flags.no_drop=!0),t.flags.no_sortable||interact(t.$el[0]).draggable({allowFrom:c.selectors.drag_handle,onstart:function(e){s=!1,l=r.getModel(e.target),o=l.$el.clone().appendTo(l.$el.parent()).width(l.$el.outerWidth()).addClass("dragging");var t=$('
 
').height(l.$el.outerHeight());i=l.parent.addRule(t,l.getPos()),l.$el.hide()},onmove:function(e){o[0].style.top=e.clientY-15+"px",o[0].style.left=e.clientX-15+"px"},onend:function(e){e.dropzone&&(u(l,$(e.relatedTarget),r),s=!0),o.remove(),o=void 0,i.drop(),i=void 0,l.$el.css("display",""),r.trigger("afterMove",l),r.trigger("rulesChanged")}}),t.flags.no_drop||(interact(t.$el[0]).dropzone({accept:c.selectors.rule_and_group_containers,ondragenter:function(e){u(i,$(e.target),r)},ondrop:function(e){s||u(l,$(e.target),r)}}),t instanceof a&&interact(t.$el.find(c.selectors.group_header)[0]).dropzone({accept:c.selectors.rule_and_group_containers,ondragenter:function(e){u(i,$(e.target),r)},ondrop:function(e){s||u(l,$(e.target),r)}}))}}),this.on("beforeDeleteRule beforeDeleteGroup",function(e,t){e.isDefaultPrevented()||(interact(t.$el[0]).unset(),t instanceof a&&interact(t.$el.find(c.selectors.group_header)[0]).unset())}),this.on("afterApplyRuleFlags afterApplyGroupFlags",function(e,t){t.flags.no_sortable&&t.$el.find(".drag-handle").remove()}),n.disable_template||(this.on("getGroupTemplate.filter",function(e,t){if(1'),e.value=r.prop("outerHTML")}}),this.on("getRuleTemplate.filter",function(e){var t=$(e.value);t.find(c.selectors.rule_header).after('
'),e.value=t.prop("outerHTML")}))},{inherit_no_sortable:!0,inherit_no_drop:!0,icon:"glyphicon glyphicon-sort",disable_template:!1}),c.selectors.rule_and_group_containers=c.selectors.rule_container+", "+c.selectors.group_container,c.selectors.drag_handle=".drag-handle",c.defaults({default_rule_flags:{no_sortable:!1,no_drop:!1},default_group_flags:{no_sortable:!1,no_drop:!1}}),c.define("sql-support",function(e){},{boolean_as_integer:!0}),c.defaults({sqlOperators:{equal:{op:"= ?"},not_equal:{op:"!= ?"},in:{op:"IN(?)",sep:", "},not_in:{op:"NOT IN(?)",sep:", "},less:{op:"< ?"},less_or_equal:{op:"<= ?"},greater:{op:"> ?"},greater_or_equal:{op:">= ?"},between:{op:"BETWEEN ?",sep:" AND "},not_between:{op:"NOT BETWEEN ?",sep:" AND "},begins_with:{op:"LIKE(?)",mod:"{0}%"},not_begins_with:{op:"NOT LIKE(?)",mod:"{0}%"},contains:{op:"LIKE(?)",mod:"%{0}%"},not_contains:{op:"NOT LIKE(?)",mod:"%{0}%"},ends_with:{op:"LIKE(?)",mod:"%{0}"},not_ends_with:{op:"NOT LIKE(?)",mod:"%{0}"},is_empty:{op:"= ''"},is_not_empty:{op:"!= ''"},is_null:{op:"IS NULL"},is_not_null:{op:"IS NOT NULL"}},sqlRuleOperator:{"=":function(e){return{val:e,op:""===e?"is_empty":"equal"}},"!=":function(e){return{val:e,op:""===e?"is_not_empty":"not_equal"}},LIKE:function(e){return"%"==e.slice(0,1)&&"%"==e.slice(-1)?{val:e.slice(1,-1),op:"contains"}:"%"==e.slice(0,1)?{val:e.slice(1),op:"ends_with"}:"%"==e.slice(-1)?{val:e.slice(0,-1),op:"begins_with"}:void h.error("SQLParse",'Invalid value for LIKE operator "{0}"',e)},"NOT LIKE":function(e){return"%"==e.slice(0,1)&&"%"==e.slice(-1)?{val:e.slice(1,-1),op:"not_contains"}:"%"==e.slice(0,1)?{val:e.slice(1),op:"not_ends_with"}:"%"==e.slice(-1)?{val:e.slice(0,-1),op:"not_begins_with"}:void h.error("SQLParse",'Invalid value for NOT LIKE operator "{0}"',e)},IN:function(e){return{val:e,op:"in"}},"NOT IN":function(e){return{val:e,op:"not_in"}},"<":function(e){return{val:e,op:"less"}},"<=":function(e){return{val:e,op:"less_or_equal"}},">":function(e){return{val:e,op:"greater"}},">=":function(e){return{val:e,op:"greater_or_equal"}},BETWEEN:function(e){return{val:e,op:"between"}},"NOT BETWEEN":function(e){return{val:e,op:"not_between"}},IS:function(e){return null!==e&&h.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_null"}},"IS NOT":function(e){return null!==e&&h.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_not_null"}}},sqlStatements:{question_mark:function(){var r=[];return{add:function(e,t){return r.push(t),"?"},run:function(){return r}}},numbered:function(r){(!r||1"==l&&(l="!=");var s=f.settings.sqlRuleOperator[l];void 0===s&&h.error("UndefinedSQLOperator",'Invalid SQL operation "{0}".',t.operation);var a,u=s.call(this,o,t.operation);"values"in t.left?a=t.left.values.join("."):"value"in t.left?a=t.left.value:h.error("SQLParse","Cannot find field name in {0}",JSON.stringify(t.left));var p=f.getSQLFieldID(a,o),d=f.change("sqlToRule",{id:p,field:a,operator:u.op,value:u.val},t);g.rules.push(d)}}(n,0),i},setRulesFromSQL:function(e,t){this.setRules(this.getRulesFromSQL(e,t))},getSQLFieldID:function(t,e){var r=this.filters.filter(function(e){return e.field.toLowerCase()===t.toLowerCase()});return 1===r.length?r[0].id:this.change("getSQLFieldID",t,e)}}),c.define("unique-filter",function(){this.status.used_filters={},this.on("afterUpdateRuleFilter",this.updateDisabledFilters),this.on("afterDeleteRule",this.updateDisabledFilters),this.on("afterCreateRuleFilters",this.applyDisabledFilters),this.on("afterReset",this.clearDisabledFilters),this.on("afterClear",this.clearDisabledFilters),this.on("getDefaultFilter.filter",function(t,r){var n=t.builder;(n.updateDisabledFilters(),t.value.id in n.status.used_filters)&&(n.filters.some(function(e){if(!(e.id in n.status.used_filters)||0 - -<#-- Confirmation that an account has been created. --> - -<#assign subject = "Your ${siteName} account has been created." /> - -<#assign html> - - - ${subject} - - -

- ${userAccount.firstName} ${userAccount.lastName} -

- -

- Congratulations! -

- -

- We have created your new account on ${siteName}, associated with ${userAccount.emailAddress}. -

- -

- If you did not request this new account you can safely ignore this email. - This request will expire if not acted upon for 30 days. -

- -

- Click the link below to create your password for your new account using our secure server. -

- -

- ${passwordLink} -

- -

- If the link above doesn't work, you can copy and paste the link directly into your browser's address bar. -

- -

- Thanks! -

- - - - -<#assign text> -${userAccount.firstName} ${userAccount.lastName} - -Congratulations! - -We have created your new account on ${siteName}, -associated with ${userAccount.emailAddress}. - -If you did not request this new account you can safely ignore this email. -This request will expire if not acted upon for 30 days. - -Paste the link below into your browser's address bar to create your password -for your new account using our secure server. - -${passwordLink} - -Thanks! - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-acctCreatedExternalOnlyEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-acctCreatedExternalOnlyEmail.ftl deleted file mode 100644 index b502008f4..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-acctCreatedExternalOnlyEmail.ftl +++ /dev/null @@ -1,43 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation that an account has been created. --> - -<#assign subject = "Your ${siteName} account has been created." /> - -<#assign html> - - - ${subject} - - -

- ${userAccount.firstName} ${userAccount.lastName} -

- -

- Congratulations! -

- -

- We have created your new VIVO account associated with ${userAccount.emailAddress}. -

- -

- Thanks! -

- - - - -<#assign text> -${userAccount.firstName} ${userAccount.lastName} - -Congratulations! - -We have created your new VIVO account associated with -${userAccount.emailAddress}. - -Thanks! - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-confirmEmailChangedEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-confirmEmailChangedEmail.ftl deleted file mode 100644 index cfa872e9f..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-confirmEmailChangedEmail.ftl +++ /dev/null @@ -1,38 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation that the user has changed his email account. --> - -<#assign subject = "Your ${siteName} email account has been changed." /> - -<#assign html> - - - ${subject} - - -

- Hi, ${userAccount.firstName} ${userAccount.lastName} -

- -

- You recently changed the email address associated with - ${userAccount.firstName} ${userAccount.lastName} -

- -

- Thank you. -

- - - - -<#assign text> -Hi, ${userAccount.firstName} ${userAccount.lastName} - -You recently changed the email address associated with -${userAccount.firstName} ${userAccount.lastName} - -Thank you. - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail.ftl deleted file mode 100644 index 1c43a27cc..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail.ftl +++ /dev/null @@ -1,43 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation that an account has been created for an externally-authenticated user. --> - -<#assign subject = "Your ${siteName} account has been created." /> - -<#assign html> - - - ${subject} - - -

- ${userAccount.firstName} ${userAccount.lastName} -

- -

- Congratulations! -

- -

- We have created your new VIVO account associated with ${userAccount.emailAddress}. -

- -

- Thanks! -

- - - - -<#assign text> -${userAccount.firstName} ${userAccount.lastName} - -Congratulations! - -We have created your new VIVO account associated with -${userAccount.emailAddress}. - -Thanks! - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-myProxiesPanel.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-myProxiesPanel.ftl index 7bd14ea42..c2ee386cf 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-myProxiesPanel.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-myProxiesPanel.ftl @@ -56,7 +56,7 @@ var proxyContextInfo = { ajaxUrl: '${formUrls.proxyAjax}' }; var i18nStrings = { - selectEditorAndProfile: '${i18n().select_editor_and_profile}' + selectEditorAndProfile: '${i18n().select_editor_and_profile?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordCreatedEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordCreatedEmail.ftl deleted file mode 100644 index 1321ad26b..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordCreatedEmail.ftl +++ /dev/null @@ -1,43 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation that an password has been created. --> - -<#assign subject = "Your ${siteName} password has successfully been created." /> - -<#assign html> - - - ${subject} - - -

- ${userAccount.firstName} ${userAccount.lastName} -

- -

- Password successfully created. -

- -

- Your new password associated with ${userAccount.emailAddress} has been created. -

- -

- Thank you. -

- - - - -<#assign text> -${userAccount.firstName} ${userAccount.lastName} - -Password successfully created. - -Your new password associated with ${userAccount.emailAddress} -has been created. - -Thank you. - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetCompleteEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetCompleteEmail.ftl deleted file mode 100644 index 93562b1b9..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetCompleteEmail.ftl +++ /dev/null @@ -1,44 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation that a password has been reset. --> - -<#assign subject = "Your ${siteName} password changed." /> - -<#assign html> - - - ${subject} - - - -

- ${userAccount.firstName} ${userAccount.lastName} -

- -

- Password successfully changed. -

- -

- Your new password associated with ${userAccount.emailAddress} has been changed. -

- -

- Thank you. -

- - - - -<#assign text> -${userAccount.firstName} ${userAccount.lastName} - -Password successfully changed. - -Your new password associated with ${userAccount.emailAddress} -has been changed. - -Thank you. - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetPendingEmail.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetPendingEmail.ftl deleted file mode 100644 index dbae3c6fc..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-passwordResetPendingEmail.ftl +++ /dev/null @@ -1,61 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Confirmation email for user account password reset --> - -<#assign subject = "${siteName} reset password request" /> - -<#assign html> - - - ${subject} - - -

- Dear ${userAccount.firstName} ${userAccount.lastName}: -

- -

- We have received a request to reset the password for your ${siteName} account (${userAccount.emailAddress}). -

- -

- Please follow the instructions below to proceed with your password reset. -

- -

- If you did not request this new account you can safely ignore this email. - This request will expire if not acted upon within 30 days. -

- -

- Click on the link below or paste it into your browser's address bar to reset your password - using our secure server. -

- -

${passwordLink}

- -

Thank you!

- - - - -<#assign text> -Dear ${userAccount.firstName} ${userAccount.lastName}: - -We have received a request to reset the password for your ${siteName} account -(${userAccount.emailAddress}). - -Please follow the instructions below to proceed with your password reset. - -If you did not request this new account you can safely ignore this email. -This request will expire if not acted upon within 30 days. - -Paste the link below into your browser's address bar to reset your password -using our secure server. - -${passwordLink} - -Thank you! - - -<@email subject=subject html=html text=text /> diff --git a/webapp/src/main/webapp/templates/freemarker/body/admin/admin-sparqlQueryForm.ftl b/webapp/src/main/webapp/templates/freemarker/body/admin/admin-sparqlQueryForm.ftl index dc356e970..8133c91b0 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/admin/admin-sparqlQueryForm.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/admin/admin-sparqlQueryForm.ftl @@ -3,14 +3,14 @@ <#-- Template that presents the SPARQL query form. -->
-

SPARQL Query

+

${i18n().sparql_query_title}

-

Query:

+

${i18n().sparql_query_header}:

-

Format for SELECT and ASK query results:

+

${i18n().sparql_query_select_ask_results}:

@@ -19,7 +19,7 @@
-

Format for CONSTRUCT and DESCRIBE query results:

+

${i18n().sparql_query_construct_describe_results}:

@@ -29,10 +29,10 @@
-
+
- +
diff --git a/webapp/src/main/webapp/templates/freemarker/body/contactForm/contactForm-form.ftl b/webapp/src/main/webapp/templates/freemarker/body/contactForm/contactForm-form.ftl index 303311e14..55c0fb688 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/contactForm/contactForm-form.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/contactForm/contactForm-form.ftl @@ -43,8 +43,8 @@ diff --git a/webapp/src/main/webapp/templates/freemarker/body/individual/individual-menu.ftl b/webapp/src/main/webapp/templates/freemarker/body/individual/individual-menu.ftl index 2b44602b5..a4d589553 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/individual/individual-menu.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/individual/individual-menu.ftl @@ -54,8 +54,8 @@ positionPredicate: '${positionPredicate}' }; var i18nStrings = { - dragDropMenus: '${i18n().drag_drop_to_reorder_menus}', - reorderingFailed: '${i18n().reordering_menus_failed}' + dragDropMenus: '${i18n().drag_drop_to_reorder_menus?js_string}', + reorderingFailed: '${i18n().reordering_menus_failed?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/individual/individual-vitro.ftl b/webapp/src/main/webapp/templates/freemarker/body/individual/individual-vitro.ftl index efbaedd82..2817b59d3 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/individual/individual-vitro.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/individual/individual-vitro.ftl @@ -87,14 +87,14 @@ @@ -110,5 +110,5 @@ ${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividual.ftl b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividual.ftl index 199b3aa29..24a4effa9 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividual.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividual.ftl @@ -31,6 +31,14 @@ <#else>

${i18n().manage_labels_capitalized}

+<#assign localeEntryExisting = true /> +<#if editConfiguration.pageData.localeEntryExisting?has_content> + <#assign localeEntryExisting = editConfiguration.pageData.localeEntryExisting /> + +<#assign currentSelectedLocale =" " /> +<#if editConfiguration.pageData.currentSelectedLocale?has_content> + <#assign currentSelectedLocale = editConfiguration.pageData.currentSelectedLocale /> + @@ -94,11 +102,13 @@ var customFormData = { individualUri: '${subjectUri!}', submissionErrorsExist: '${submissionErrorsExist}', selectLocalesFullList: selectLocalesFullList, - numberAvailableLocales:${availableLocalesNumber} + numberAvailableLocales:${availableLocalesNumber}, + localeEntryExisting : '${localeEntryExisting?c}', + currentSelectedLocale: '${currentSelectedLocale}' }; var i18nStrings = { - errorProcessingLabels: '${i18n().error_processing_labels}', - selectLocaleOptionString : '${i18n().select_locale}' + errorProcessingLabels: '${i18n().error_processing_labels?js_string}', + selectLocaleOptionString : '${i18n().select_locale?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl index a60e30d86..57b23a552 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl @@ -6,19 +6,6 @@

- - diff --git a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl index 117b4b918..3eef638c1 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl @@ -81,7 +81,7 @@ <#macro displayLabel labelSeq labelStr languageCode labelEditLink tagOrTypeStr editGenerator editable displayRemoveLink>
  • ${labelStr} <#if labelSeq?seq_contains(labelStr)> (duplicate value) - <#if editable> <#if labelEditLink?has_content> Edit + <#if editable> <#if labelEditLink?has_content> ${i18n().edit_capitalized} <#if displayRemoveLink> ${i18n().remove_capitalized} diff --git a/webapp/src/main/webapp/templates/freemarker/body/manageproxies/manageProxies-list.ftl b/webapp/src/main/webapp/templates/freemarker/body/manageproxies/manageProxies-list.ftl index d0c1bd307..cf192af8b 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/manageproxies/manageProxies-list.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/manageproxies/manageProxies-list.ftl @@ -189,7 +189,7 @@ var proxyContextInfo = { ajaxUrl: '${formUrls.ajax}' }; var i18nStrings = { - selectEditorAndProfile: '${i18n().select_editor_and_profile}' + selectEditorAndProfile: '${i18n().select_editor_and_profile?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/pagemanagement/pageList.ftl b/webapp/src/main/webapp/templates/freemarker/body/pagemanagement/pageList.ftl index af0636a8a..e00b4c448 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/pagemanagement/pageList.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/pagemanagement/pageList.ftl @@ -75,7 +75,7 @@ ${stylesheets.add('', diff --git a/webapp/src/main/webapp/templates/freemarker/body/partials/browse-classgroups.ftl b/webapp/src/main/webapp/templates/freemarker/body/partials/browse-classgroups.ftl index 28fb6e4ce..c3ed982b7 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/partials/browse-classgroups.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/partials/browse-classgroups.ftl @@ -72,8 +72,8 @@ ${stylesheets.add(' <#if tabCount = 1 > -
  • ${groupName?capitalize}
  • +
  • ${p.capitalizeGroupName(groupName)}
  •  
  • <#assign tabCount = 2> <#else> -
  • ${groupName?capitalize}
  • +
  • ${p.capitalizeGroupName(groupName)}
  •  
  • @@ -50,7 +50,7 @@ <#if groupName?has_content> <#--the function replaces spaces in the name with underscores, also called for the property group menu--> <#assign groupNameHtmlId = p.createPropertyGroupHtmlId(groupName) > - + <#else> diff --git a/webapp/src/main/webapp/templates/freemarker/body/partials/menupage/menupage-scripts.ftl b/webapp/src/main/webapp/templates/freemarker/body/partials/menupage/menupage-scripts.ftl index 3459054c4..9ad5892b6 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/partials/menupage/menupage-scripts.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/partials/menupage/menupage-scripts.ftl @@ -30,14 +30,13 @@ defaultBrowseVClassUri: firstBrowseClass //'${firstNonEmptyVClass}' }; var i18nStrings = { - pageString: '${i18n().page}', - viewPageString: '${i18n().view_page}', - ofTheResults: '${i18n().of_the_results}', - thereAreNo: '${i18n().there_are_no}', - indNamesStartWith: '${i18n().individuals_names_starting_with}', - tryAnotherLetter: '${i18n().try_another_letter}', - indsInSystem: '${i18n().individuals_in_system}', - selectAnotherClass: '${i18n().select_another_class}' + pageString: '${i18n().page?js_string}', + viewPageString: '${i18n().view_page?js_string}', + ofTheResults: '${i18n().of_the_results?js_string}', + thereAreNoEntriesStartingWith: '${i18n().there_are_no_entries_starting_with?js_string}', + tryAnotherLetter: '${i18n().try_another_letter?js_string}', + indsInSystem: '${i18n().individuals_in_system?js_string}', + selectAnotherClass: '${i18n().select_another_class?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-error.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-error.ftl new file mode 100644 index 000000000..fc96c044e --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-error.ftl @@ -0,0 +1,16 @@ +<#-- $This file is distributed under the terms of the license in LICENSE$ --> + +<#-- Template for displaying search error message --> +<#include "queryBuilder.ftl"> + +<#if title??> +
    +

    ${title?html}

    +
    + +
    +

    + ${message?html} +

    +
    +<#include "search-help.ftl" > diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-pagedResults.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-pagedResults.ftl new file mode 100644 index 000000000..75d6882cf --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/body/search/extendedsearch-pagedResults.ftl @@ -0,0 +1,251 @@ +<#-- $This file is distributed under the terms of the license in LICENSE$ --> + +<#-- Template for displaying paged search results --> +<#include "queryBuilder.ftl"> + +

    + +<#escape x as x?html> +
    ${i18n().search_results_for} '${querytext}'
    +
    <#if classGroupName?has_content>${i18n().limited_to_type} '${classGroupName}'
    +
    <#if typeName?has_content>${i18n().limited_to_type} '${typeName}'
    + + + + ${i18n().download_results} +<#-- --> +

    + +${i18n().not_expected_results} +
    + + <#-- Refinement links --> + <#if classGroupLinks?has_content && classGroupLinks?size gt 1> +
    +

    ${i18n().display_only}

    +
      + <#list classGroupLinks as link> +
    • ${link.text}(${link.count})
    • + +
    +
    + + + <#if classLinks?has_content && classLinks?size gt 1 > +
    + <#if classGroupName?has_content> +

    ${i18n().limit} ${classGroupName} ${i18n().to}

    + <#else> +

    ${i18n().limit_to}

    + +
      + <#list classLinks as link> +
    • ${link.text}(${link.count})
    • + +
    +
    + + + +
    + + <#if user.loggedIn> + + +
    + + + + <#-- Search results --> +
      + <#list individuals as individual> +
    • + <@shortView uri=individual.uri viewContext="search" /> +
    • + +
    + + + <#-- Paging controls --> + <#if (pagingLinks?size > 0)> +
    + ${i18n().pages}: + <#if prevPage??> + <#list pagingLinks as link> + <#if link.url??> + ${link.text} + <#else> + ${link.text} <#-- no link if current page --> + + + <#if nextPage??> +
    + +
    + + <#-- VIVO OpenSocial Extension by UCSF --> + <#if openSocial??> + <#if openSocial.visible> +

    OpenSocial

    + + + + + + +
    + + + +${stylesheets.add('', + '', + '')} + +${headScripts.add('', + '', + '' + )} + +${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/queryBuilder.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/queryBuilder.ftl new file mode 100644 index 000000000..894b07c0c --- /dev/null +++ b/webapp/src/main/webapp/templates/freemarker/body/search/queryBuilder.ftl @@ -0,0 +1,279 @@ +
    +
    +
    +
    ${i18n().extended_search_label}
    +
    +
    +
    +
    +
    + + + +
    + + <@selectHitsPerPage/> +
    +
    +
    + +
    +
    +
    +
    + + + +<#macro freeField field > + { + id: '${field.field}', + label: '${field.name}', + type: 'string', + operators: ['contains', 'not_contains'] + }, + + +<#macro multivalueField field > + { + id: '${field.field}', + label: '${field.name}', + type: 'string', + input: 'select', + values: { + + <#if searchFields??> + <#list searchFilters as filter> + <#if filter.field == field.field> + '"${filter.id}"':'${filter.name}', + + + <#else> + { + id: 'ALLTEXT', + label: 'Everywhere', + type: 'string', + operators: ['contains', 'not_contains'] + }, + + }, + operators: ['contains', 'not_contains'] + }, + + +<#macro selectHitsPerPage> + <#if !hitsPerPage?? > + <#assign hitsPerPage = 20 > + + <#assign hitsValues= [20,40,60,80,100]> + + diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl deleted file mode 100644 index b69e4a57f..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl +++ /dev/null @@ -1,43 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#if origination?has_content && origination == "helpLink"> -

    Search Tips

    - - Back to results - -<#else> -

    Search Tips

    - -
      -
    • Keep it simple! Use short, single terms unless your searches are returning too many results.
    • -
    • Use quotes to search for an entire phrase -- e.g., "protein folding".
    • -
    • Except for boolean operators, searches are not case-sensitive, so "Geneva" and "geneva" are equivalent
    • -
    • If you are unsure of the correct spelling, put ~ at the end of your search term -- e.g., cabage~ finds cabbage, steven~ finds Stephen and Stefan (as well as other similar names).
    • -
    - -

    Advanced Tips

    - - -${stylesheets.add('')} - diff --git a/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-classHierarchy.ftl b/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-classHierarchy.ftl index 3cb450f96..6b48e53c9 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-classHierarchy.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-classHierarchy.ftl @@ -39,15 +39,15 @@ diff --git a/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-objectPropHierarchy.ftl b/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-objectPropHierarchy.ftl index fac6c3b9e..9244c1403 100644 --- a/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-objectPropHierarchy.ftl +++ b/webapp/src/main/webapp/templates/freemarker/body/siteAdmin/siteAdmin-objectPropHierarchy.ftl @@ -47,19 +47,19 @@ var json = [${jsonTree!}]; var propertyType = '${propType}'; var i18nStrings = { - hideProperties: '${i18n().hide_properties}', - showProperties: '${i18n().show_properties}', - localNameString: '${i18n().local_name}', - groupString: '${i18n().group_capitalized}', - domainClass: '${i18n().domain_class}', - rangeClass: '${i18n().range_class}', - rangeDataType: '${i18n().range_data_type}', - expandAll: '${i18n().expand_all}', - collapseAll: '${i18n().collapse_all}', - subProperties: '${i18n().sub_properties}', - displayRank: '${i18n().display_rank}', - subProperty: '${i18n().subproperty}', - propertiesString: '${i18n().properties_capitalized}' + hideProperties: '${i18n().hide_properties?js_string}', + showProperties: '${i18n().show_properties?js_string}', + localNameString: '${i18n().local_name?js_string}', + groupString: '${i18n().group_capitalized?js_string}', + domainClass: '${i18n().domain_class?js_string}', + rangeClass: '${i18n().range_class?js_string}', + rangeDataType: '${i18n().range_data_type?js_string}', + expandAll: '${i18n().expand_all?js_string}', + collapseAll: '${i18n().collapse_all?js_string}', + subProperties: '${i18n().sub_properties?js_string}', + displayRank: '${i18n().display_rank?js_string}', + subProperty: '${i18n().subproperty?js_string}', + propertiesString: '${i18n().properties_capitalized?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/body/termsOfUse.ftl b/webapp/src/main/webapp/templates/freemarker/body/termsOfUse.ftl deleted file mode 100644 index 205a0012e..000000000 --- a/webapp/src/main/webapp/templates/freemarker/body/termsOfUse.ftl +++ /dev/null @@ -1,34 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -
    -

    Terms of Use

    - -

    Disclaimers

    -

    This ${termsOfUse.siteName} website contains material—text information, publication - citations, links, and images—provided by ${termsOfUse.siteHost} and by various - third parties, both individuals and organizations, commercial and otherwise. To the extent copyrightable, - the information presented on the VIVO website and available as Resource Description Framework (RDF) data - from VIVO at ${termsOfUse.siteHost} is intended for public use and is freely distributed under the terms of the - Creative Commons CC-BY 3.0 license which allows you - to copy, distribute, display and make derivatives of this information provided you give credit to - ${termsOfUse.siteHost}. Any non-copyrightable information is available to you under a - CC0 waiver. However, source documents, - images or web pages attached to or linked from VIVO may contain copyrighted information and should only be - used or distributed under terms included with each source or in accordance with the principles of fair use. -

    - -

    Disclaimer of Liability

    -

    ${termsOfUse.siteHost?cap_first} makes no warranty, expressed or implied, including the warranties of merchantability - and fitness for a particular purpose, or assumes any legal liability or responsibility for the accuracy, - completeness, currency or usefulness of any material displayed or distributed through the - ${termsOfUse.siteName} website or represents that its use would not infringe privately owned rights. - ${termsOfUse.siteHost?cap_first} disclaims all warranties with regard to the information provided. Any reliance upon such information - is at your own risk. In no event will ${termsOfUse.siteHost} be liable to you for any damages or losses whatsoever resulting - from or caused by the ${siteName} website or its contents.

    - -

    Disclaimer of Endorsement

    -

    Reference herein to any specific commercial product, process, or service by trade name, - trademark, manufacturer, or otherwise, does not necessarily constitute or imply its endorsement or recommendation - by ${termsOfUse.siteHost}. The views and opinions of authors expressed herein do not necessarily state or reflect those of - ${termsOfUse.siteHost} and shall not be used for advertising or product endorsement purposes.

    -
    diff --git a/webapp/src/main/webapp/templates/freemarker/edit/dateTimeWithPrecision.ftl b/webapp/src/main/webapp/templates/freemarker/edit/dateTimeWithPrecision.ftl index 5f050e0b9..cc00ed613 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/dateTimeWithPrecision.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/dateTimeWithPrecision.ftl @@ -78,9 +78,9 @@ precisionConstants.second -- URI for precision <#if precLevel gte 2> - + required > - + <#assign numDays = 31 /> <#list 1..numDays as currentDay> @@ -101,9 +101,9 @@ precisionConstants.second -- URI for precision <#if precLevel gte 4> <#-- We'll need to make this more flexible to support 24 hour display down the road. For now assuming 12h with am/pm --> - + required > - + <#assign numMinutes = 59 /> <#list 1..numMinutes as currentMinute> @@ -135,9 +135,9 @@ precisionConstants.second -- URI for precision <#if precLevel gte 6> - + - or + ${i18n().or} ${i18n().cancel_link}

    @@ -124,9 +124,9 @@ Also multiple types parameter set to true only if more than one type returned--> baseHref: '${urls.base}/individual?uri=' }; var i18nStrings = { - selectAnExisting: '${i18n().select_an_existing}', - orCreateNewOne: '${i18n().or_create_new_one}', - selectedString: '${i18n().selected}' + selectAnExisting: '${i18n().select_an_existing?js_string}', + orCreateNewOne: '${i18n().or_create_new_one?js_string}', + selectedString: '${i18n().selected?js_string}' }; <#-- diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/confirmDeleteIndividualForm.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/confirmDeleteIndividualForm.ftl index 22bb8bea1..c22660d8e 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/confirmDeleteIndividualForm.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/confirmDeleteIndividualForm.ftl @@ -16,7 +16,6 @@ -

    <#if individualType??> ${individualType} @@ -25,7 +24,6 @@ ${individualName}

    -

    diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/dateTimeEntryForm.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/dateTimeEntryForm.ftl index 45b992577..b80f63688 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/dateTimeEntryForm.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/dateTimeEntryForm.ftl @@ -3,13 +3,13 @@

    <#if datatype?contains("#date") || datatype?contains("Year") > - + <#if datatype?contains("#date") || datatype?contains("Month") > - + <#if datatype?contains("#date") > - + <#if datatype?contains("#dateTime") || datatype?contains("#time") > - + - + - + - or + ${i18n().or} ${i18n().cancel_link}

    diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/defaultDataPropertyForm.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/defaultDataPropertyForm.ftl index 62ab74c31..1e3750385 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/defaultDataPropertyForm.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/defaultDataPropertyForm.ftl @@ -72,7 +72,7 @@ the default label for default data/object property editing is returned from Edit Configuration Template Model, but that method may not return the correct result for other custom forms--> - or + ${i18n().or} ${i18n().cancel_link} @@ -85,14 +85,14 @@ var datatype = "${datatype!}"; var i18nStrings = { - four_digit_year: '${i18n().four_digit_year}', - year_numeric: '${i18n().year_numeric}', - year_month_day: '${i18n().year_month_day}', - minimum_ymd: '${i18n().minimum_ymd}', - minimum_hour: '${i18n().minimum_hour}', - year_month: '${i18n().year_month}', - decimal_only: '${i18n().decimal_only}', - whole_number: '${i18n().whole_number}' + four_digit_year: '${i18n().four_digit_year?js_string}', + year_numeric: '${i18n().year_numeric?js_string}', + year_month_day: '${i18n().year_month_day?js_string}', + minimum_ymd: '${i18n().minimum_ymd?js_string}', + minimum_hour: '${i18n().minimum_hour?js_string}', + year_month: '${i18n().year_month?js_string}', + decimal_only: '${i18n().decimal_only?js_string}', + whole_number: '${i18n().whole_number?js_string}' }; diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/menuManagement.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/menuManagement.ftl index 4643480ed..54b5b0296 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/menuManagement.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/menuManagement.ftl @@ -99,13 +99,13 @@ ${stylesheets.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseClassGroups.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseClassGroups.ftl index d912e1641..9f4688aed 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseClassGroups.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--browseClassGroups.ftl @@ -59,10 +59,10 @@ <#--Include JavaScript specific to the types of data getters related to this content--> diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--fixedHtml.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--fixedHtml.ftl index d7252b2eb..727c76a35 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--fixedHtml.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--fixedHtml.ftl @@ -13,11 +13,11 @@ ${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--searchIndividuals.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--searchIndividuals.ftl index 737bbbf89..1a9a55dce 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--searchIndividuals.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--searchIndividuals.ftl @@ -22,12 +22,12 @@ ${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--sparqlQuery.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--sparqlQuery.ftl index 2a1bc7db5..e04796764 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--sparqlQuery.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement--sparqlQuery.ftl @@ -15,11 +15,11 @@ ${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl index 64b7473da..550ae8b39 100644 --- a/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl +++ b/webapp/src/main/webapp/templates/freemarker/edit/forms/pageManagement.ftl @@ -176,21 +176,21 @@ <#include "pageManagement--customDataScript.ftl"> diff --git a/webapp/src/main/webapp/templates/freemarker/lib/lib-properties.ftl b/webapp/src/main/webapp/templates/freemarker/lib/lib-properties.ftl index 34b00e51c..4574ec6f7 100644 --- a/webapp/src/main/webapp/templates/freemarker/lib/lib-properties.ftl +++ b/webapp/src/main/webapp/templates/freemarker/lib/lib-properties.ftl @@ -183,16 +183,16 @@ name will be used as the label. --> <#local url = statement.editUrl> <#if url?has_content> <#if propertyLocalName?contains("ARG_2000028")> - <#if rangeUri?contains("Address")> - <#local url = url + "&addressUri=" + "${statement.address!}"> - <#elseif rangeUri?contains("Telephone") || rangeUri?contains("Fax")> - <#local url = url + "&phoneUri=" + "${statement.phone!}"> - <#elseif rangeUri?contains("Work") || rangeUri?contains("Email")> - <#local url = url + "&emailUri=" + "${statement.email!}"> - <#elseif rangeUri?contains("Name")> - <#local url = url + "&fullNameUri=" + "${statement.fullName!}"> - <#elseif rangeUri?contains("Title")> - <#local url = url + "&titleUri=" + "${statement.title!}"> + <#if rangeUri?contains("Address") && statement.address??> + <#local url = url + "&addressUri=" + "${statement.address?url}"> + <#elseif (rangeUri?contains("Telephone") || rangeUri?contains("Fax")) && statement.phone??> + <#local url = url + "&phoneUri=" + "${statement.phone?url}"> + <#elseif (rangeUri?contains("Work") || rangeUri?contains("Email")) && statement.email??> + <#local url = url + "&emailUri=" + "${statement.email?url}"> + <#elseif rangeUri?contains("Name") && statement.fullName??> + <#local url = url + "&fullNameUri=" + "${statement.fullName?url}"> + <#elseif rangeUri?contains("Title") && statement.title??> + <#local url = url + "&titleUri=" + "${statement.title?url}"> <@showEditLink propertyLocalName rangeUri url /> @@ -204,7 +204,7 @@ name will be used as the label. --> <#macro deleteIndividualLink individual redirectUrl="/"> - <#local url = individual.deleteUrl + "&redirectUrl=" + "${redirectUrl}"> + <#local url = individual.deleteUrl + "&redirectUrl=" + "${redirectUrl}"> <@showDeleteIndividualLink url /> @@ -352,3 +352,6 @@ name will be used as the label. --> <#return groupName> +<#function capitalizeGroupName propertyGroupName> + <#return propertyGroupName?capitalize> + diff --git a/webapp/src/main/webapp/templates/freemarker/page/partials/developer.ftl b/webapp/src/main/webapp/templates/freemarker/page/partials/developer.ftl index 359935a28..70489d4fd 100644 --- a/webapp/src/main/webapp/templates/freemarker/page/partials/developer.ftl +++ b/webapp/src/main/webapp/templates/freemarker/page/partials/developer.ftl @@ -7,3 +7,5 @@ ${scripts.add('')} ${scripts.add('')} +${scripts.add('')} +${scripts.add('')} diff --git a/webapp/src/main/webapp/templates/freemarker/page/partials/developerPanel.ftl b/webapp/src/main/webapp/templates/freemarker/page/partials/developerPanel.ftl index 4ab08a563..06ad70855 100644 --- a/webapp/src/main/webapp/templates/freemarker/page/partials/developerPanel.ftl +++ b/webapp/src/main/webapp/templates/freemarker/page/partials/developerPanel.ftl @@ -19,6 +19,9 @@ <#elseif !settings.mayControl>

    ${siteName} is running in developer mode.

    +
    <#else>
    @@ -56,6 +59,8 @@ "Defeat the cache of language property files" /> <@showCheckbox "developer_i18n_logStringRequests", "Log the retrieval of language strings" /> + <@showCheckbox "developer_i18n_onlineTranslation", + "Enable online translation" />
    diff --git a/webapp/src/main/webapp/templates/freemarker/page/partials/doctype.html b/webapp/src/main/webapp/templates/freemarker/page/partials/doctype.html index 54a77eca4..6c12f1115 100644 --- a/webapp/src/main/webapp/templates/freemarker/page/partials/doctype.html +++ b/webapp/src/main/webapp/templates/freemarker/page/partials/doctype.html @@ -1,3 +1,3 @@ - + diff --git a/webapp/src/main/webapp/templates/freemarker/page/partials/headScripts.ftl b/webapp/src/main/webapp/templates/freemarker/page/partials/headScripts.ftl index bc71431c8..4cb139f9d 100644 --- a/webapp/src/main/webapp/templates/freemarker/page/partials/headScripts.ftl +++ b/webapp/src/main/webapp/templates/freemarker/page/partials/headScripts.ftl @@ -3,7 +3,7 @@ <#-- Template for scripts that must be loaded in the head --> diff --git a/webapp/src/main/webapp/templates/freemarker/page/partials/languageSelector.ftl b/webapp/src/main/webapp/templates/freemarker/page/partials/languageSelector.ftl index 80a0c633f..9bb4b9047 100644 --- a/webapp/src/main/webapp/templates/freemarker/page/partials/languageSelector.ftl +++ b/webapp/src/main/webapp/templates/freemarker/page/partials/languageSelector.ftl @@ -1,18 +1,12 @@ <#-- $This file is distributed under the terms of the license in LICENSE$ --> -<#-- - How can this done with images instead of buttons containing images? - Why don't the "alt" values show as tooltips?" - What was the right way to do this? - --> - <#-- This is included by identity.ftl --> <#if selectLocale??> -
    • ${i18n().select_a_language}