diff --git a/.gitattributes b/.gitattributes index 746b59d5d..22a4a68aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,7 +4,6 @@ # Explicitly declare text files we want to always be normalized and converted # to native line endings on checkout. -*.properties *.conf text *.config text diff --git a/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/InfoResponseParser.java b/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/InfoResponseParser.java deleted file mode 100644 index 1f3464046..000000000 --- a/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/InfoResponseParser.java +++ /dev/null @@ -1,123 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.utilities.revisioninfo; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Parse the response that we got from SVN info. - * - * Not thread-safe. - */ -public class InfoResponseParser { - private static final Pattern URL_PATTERN = Pattern.compile("URL: (\\S+)"); - private static final Pattern ROOT_PATTERN = Pattern - .compile("Repository Root: (\\S+)"); - - private static final String TRUNK_PREFIX = "/trunk"; - private static final String TAGS_PREFIX = "/tags/"; - private static final String BRANCHES_PREFIX = "/branches/"; - - private final String infoResponse; - private String path; - - public InfoResponseParser(String infoResponse) { - this.infoResponse = infoResponse; - } - - public String parse() { - try { - path = figurePath(); - - if (isTrunkPath()) { - return "trunk"; - } else if (isTagPath()) { - return "tag " + getTagName(); - } else if (isBranchPath()) { - return "branch " + getBranchName(); - } else { - return null; - } - } catch (Exception e) { - System.err.println(e); - return null; - } - } - - private String figurePath() throws Exception { - if (infoResponse == null) { - throw new Exception("infoResponse is null."); - } - - String url = getUrlFromResponse(); - String root = getRootFromResponse(); - - if (!url.startsWith(root)) { - throw new Exception("url doesn't start with root."); - } - - return url.substring(root.length()); - } - - private String getUrlFromResponse() throws Exception { - return findNonEmptyMatch(URL_PATTERN, 1); - } - - private String getRootFromResponse() throws Exception { - return findNonEmptyMatch(ROOT_PATTERN, 1); - } - - private String findNonEmptyMatch(Pattern pattern, int groupIndex) - throws Exception { - Matcher matcher = pattern.matcher(infoResponse); - if (!matcher.find()) { - throw new Exception("no match with '" + pattern + "'. Is your Subversion client out of date?"); - } - - String value = matcher.group(groupIndex); - if ((value == null) || (value.isEmpty())) { - throw new Exception("match with '" + pattern + "' is empty."); - } - - return value; - } - - private boolean isTrunkPath() { - return path.startsWith(TRUNK_PREFIX); - } - - private boolean isTagPath() { - return path.startsWith(TAGS_PREFIX); - } - - private String getTagName() { - return getFirstLevel(discardPrefix(path, TAGS_PREFIX)); - } - - private boolean isBranchPath() { - return path.startsWith(BRANCHES_PREFIX); - } - - private String getBranchName() { - return getFirstLevel(discardPrefix(path, BRANCHES_PREFIX)); - } - - private String discardPrefix(String string, String prefix) { - if (string.length() < prefix.length()) { - return ""; - } else { - return string.substring(prefix.length()); - } - } - - private String getFirstLevel(String string) { - int slashHere = string.indexOf('/'); - if (slashHere == -1) { - return string; - } else { - return string.substring(0, slashHere); - } - } - -} diff --git a/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/RevisionInfoBuilder.java b/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/RevisionInfoBuilder.java index f533b912e..cf3ea4043 100644 --- a/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/RevisionInfoBuilder.java +++ b/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/revisioninfo/RevisionInfoBuilder.java @@ -1,230 +1,253 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.utilities.revisioninfo; - -import java.io.BufferedReader; -import java.io.EOFException; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.Arrays; -import java.util.List; - -import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException; - -/** - * Get release and revision information to display on screen. Put this - * information into a single line and append it to the specified file. - * - * Ask Subversion for the information. If Subversion is available, and if this - * is a working directory, then we can build the info from the responses we get - * from "svn info" and "svnversion". - * - * If that doesn't work, read the information from the "revisionInfo" file in - * the product directory. Presumably, that file was created when the source was - * exported from Subversion. - * - * If that doesn't work either, return something like this: - * "productName ~ unknown ~ unknown" - */ -public class RevisionInfoBuilder { - - /** - * Indicates a problem with the command-line arguments. - */ - private static class UsageException extends Exception { - UsageException(String message) { - super(message); - } - } - - /** - * An object that holds the revision information and a message about how we - * obtained it. - */ - private static class Results { - final String message; - final String infoLine; - - Results(String message, String infoLine) { - this.message = message; - this.infoLine = infoLine; - } - - public String toString() { - return message + ": " + infoLine; - } - } - - private static final String SVN_DIRECTORY_NAME = ".svn"; - private static final String[] SVNVERSION_COMMAND = { "svnversion", "." }; - private static final String[] SVN_INFO_COMMAND = { "svn", "info" }; - private static final String INFO_LINE_DELIMITER = " ~ "; - private static final String REVISION_INFO_FILENAME = "revisionInfo"; - - private final String productName; - private final File productDirectory; - private final File resultFile; - - private Results results; - - public RevisionInfoBuilder(String[] args) throws UsageException { - if (args.length != 3) { - throw new UsageException( - "RevisionInfoBuilder requires 3 arguments, not " - + args.length); - } - - productName = args[0]; - productDirectory = new File(args[1]); - resultFile = new File(args[2]); - - if (!productDirectory.isDirectory()) { - throw new UsageException("Directory '" - + productDirectory.getAbsolutePath() + "' does not exist."); - } - - if (!resultFile.getParentFile().exists()) { - throw new UsageException("Result file '" - + resultFile.getAbsolutePath() - + "' does not exist, and we can't create it " - + "because it's parent directory doesn't exist either."); - } - } - - private void buildInfo() { - results = buildInfoFromSubversion(); - if (results == null) { - results = buildInfoFromFile(); - } - if (results == null) { - results = buildDummyInfo(); - } - } - - private Results buildInfoFromSubversion() { - if (!isThisASubversionWorkspace()) { - System.out.println("Not a Subversion workspace"); - return null; - } - - String release = assembleReleaseNameFromSubversion(); - if (release == null) { - System.out.println("Couldn't get release name from Subversion"); - return null; - } - - String revision = obtainRevisionLevelFromSubversion(); - if (revision == null) { - System.out.println("Couldn't get revision level from Subversion"); - return null; - } - - return new Results("Info from Subversion", buildLine(release, revision)); - } - - private boolean isThisASubversionWorkspace() { - File svnDirectory = new File(productDirectory, SVN_DIRECTORY_NAME); - return svnDirectory.isDirectory(); - } - - private String assembleReleaseNameFromSubversion() { - String infoResponse = runSubProcess(SVN_INFO_COMMAND); - return new InfoResponseParser(infoResponse).parse(); - } - - private String obtainRevisionLevelFromSubversion() { - String response = runSubProcess(SVNVERSION_COMMAND); - return (response == null) ? null : response.trim(); - } - - private String runSubProcess(String[] cmdArray) { - List command = Arrays.asList(cmdArray); - try { - ProcessRunner runner = new ProcessRunner(); - runner.setWorkingDirectory(productDirectory); - - runner.run(command); - - int rc = runner.getReturnCode(); - if (rc != 0) { - throw new ProcessRunner.ProcessException("Return code from " - + command + " was " + rc); - } - - String output = runner.getStdOut(); - // System.err.println(command + " response was '" + output + "'"); - return output; - } catch (ProcessException e) { - return null; - } - } - - private Results buildInfoFromFile() { - try { - File revisionInfoFile = new File(productDirectory, - REVISION_INFO_FILENAME); - BufferedReader reader = new BufferedReader(new FileReader( - revisionInfoFile)); - - String release = reader.readLine(); - if (release == null) { - throw new EOFException("No release line in file."); - } - - String revision = reader.readLine(); - if (revision == null) { - throw new EOFException("No revision line in file."); - } - - return new Results("Info from file", buildLine(release, revision)); - } catch (IOException e) { - System.out.println("No information from file: " + e); - return null; - } - } - - private Results buildDummyInfo() { - String line = buildLine(null, null); - return new Results("Using dummy info", line); - } - - private String buildLine(String release, String revision) { - if (release == null) { - release = "unknown"; - } - if (revision == null) { - revision = "unknown"; - } - return productName + INFO_LINE_DELIMITER + release.trim() - + INFO_LINE_DELIMITER + revision.trim(); - } - - private void writeLine() throws IOException { - Writer writer = null; - writer = new FileWriter(resultFile, true); - writer.write(results.infoLine + "\n"); - writer.close(); - - System.out.println(results); - } - - public static void main(String[] args) { - try { - RevisionInfoBuilder builder = new RevisionInfoBuilder(args); - builder.buildInfo(); - builder.writeLine(); - } catch (UsageException e) { - System.err.println(e); - System.err.println("usage: RevisionInfoBuilder [product_name] " - + "[product_directory] [output_file]"); - System.exit(1); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } - -} +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.utilities.revisioninfo; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.List; + +import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException; + +/** + * Get release and revision information to display on screen. Put this + * information into a single line and append it to the specified file. + * + * Ask Git for the information. If Git is available, and if this is a working + * directory, then we can build the info from the responses we get from + * "git describe", "git symbolic-ref" and "git log". + * + * If that doesn't work, read the information from the "revisionInfo" file in + * the product directory. Presumably, that file was created when the source was + * exported from Git. + * + * If that doesn't work either, return something like this: + * "productName ~ unknown ~ unknown" + */ +public class RevisionInfoBuilder { + + /** + * Indicates a problem with the command-line arguments. + */ + private static class UsageException extends Exception { + UsageException(String message) { + super(message); + } + } + + /** + * An object that holds the revision information and a message about how we + * obtained it. + */ + private static class Results { + final String message; + final String infoLine; + + Results(String message, String infoLine) { + this.message = message; + this.infoLine = infoLine; + } + + @Override + public String toString() { + return message + ": " + infoLine; + } + } + + private static final String GIT_DIRECTORY_NAME = ".git"; + private static final String[] GIT_DESCRIBE_COMMAND = { "git", "describe" }; + private static final String[] GIT_SREF_COMMAND = { "git", "symbolic-ref", + "HEAD" }; + private static final String[] GIT_LOG_COMMAND = { "git", "log", + "--pretty=format:%h", "-1" }; + private static final String INFO_LINE_DELIMITER = " ~ "; + private static final String REVISION_INFO_FILENAME = "revisionInfo"; + + private final String productName; + private final File productDirectory; + private final File resultFile; + + private Results results; + + public RevisionInfoBuilder(String[] args) throws UsageException { + if (args.length != 3) { + throw new UsageException( + "RevisionInfoBuilder requires 3 arguments, not " + + args.length); + } + + productName = args[0]; + productDirectory = new File(args[1]); + resultFile = new File(args[2]); + + if (!productDirectory.isDirectory()) { + throw new UsageException("Directory '" + + productDirectory.getAbsolutePath() + "' does not exist."); + } + + if (!resultFile.getParentFile().exists()) { + throw new UsageException("Result file '" + + resultFile.getAbsolutePath() + + "' does not exist, and we can't create it " + + "because it's parent directory doesn't exist either."); + } + } + + private void buildInfo() { + results = buildInfoFromGit(); + if (results == null) { + results = buildInfoFromFile(); + } + if (results == null) { + results = buildDummyInfo(); + } + } + + private Results buildInfoFromGit() { + if (!isThisAGitWorkspace()) { + System.out.println("Not a git workspace"); + return null; + } + + String release = assembleReleaseNameFromGit(); + if (release == null) { + System.out.println("Couldn't get release name from Git"); + } + + String revision = obtainCommitIdFromGit(); + if (revision == null) { + System.out.println("Couldn't get commit ID from Git"); + } + + if ((revision == null) && (release == null)) { + return null; + } + + return new Results("Info from Git", buildLine(release, revision)); + } + + private boolean isThisAGitWorkspace() { + File gitDirectory = new File(productDirectory, GIT_DIRECTORY_NAME); + return gitDirectory.isDirectory(); + } + + private String assembleReleaseNameFromGit() { + String describeResponse = runSubProcess(GIT_DESCRIBE_COMMAND); + String srefResponse = runSubProcess(GIT_SREF_COMMAND); + return parseReleaseName(describeResponse, srefResponse); + } + + private String obtainCommitIdFromGit() { + String logResponse = runSubProcess(GIT_LOG_COMMAND); + return parseLogResponse(logResponse); + } + + private String parseReleaseName(String describeResponse, String srefResponse) { + if (describeResponse != null) { + return describeResponse.trim() + " tag"; + } else if (srefResponse != null) { + return srefResponse.substring(srefResponse.lastIndexOf('/') + 1) + .trim() + " branch"; + } else { + return null; + } + } + + private String parseLogResponse(String logResponse) { + return logResponse; + } + + private String runSubProcess(String[] cmdArray) { + List command = Arrays.asList(cmdArray); + try { + ProcessRunner runner = new ProcessRunner(); + runner.setWorkingDirectory(productDirectory); + + runner.run(command); + + int rc = runner.getReturnCode(); + if (rc != 0) { + throw new ProcessRunner.ProcessException("Return code from " + + command + " was " + rc); + } + + String output = runner.getStdOut(); +// System.err.println(command + " response was '" + output + "'"); + return output; + } catch (ProcessException e) { +// System.out.println(e); + return null; + } + } + + private Results buildInfoFromFile() { + try { + File revisionInfoFile = new File(productDirectory, + REVISION_INFO_FILENAME); + BufferedReader reader = new BufferedReader(new FileReader( + revisionInfoFile)); + + String release = reader.readLine(); + if (release == null) { + throw new EOFException("No release line in file."); + } + + String revision = reader.readLine(); + if (revision == null) { + throw new EOFException("No revision line in file."); + } + + return new Results("Info from file", buildLine(release, revision)); + } catch (IOException e) { + System.out.println("No information from file: " + e); + return null; + } + } + + private Results buildDummyInfo() { + String line = buildLine(null, null); + return new Results("Using dummy info", line); + } + + private String buildLine(String release, String revision) { + if (release == null) { + release = "unknown"; + } + if (revision == null) { + revision = "unknown"; + } + return productName + INFO_LINE_DELIMITER + release.trim() + + INFO_LINE_DELIMITER + revision.trim(); + } + + private void writeLine() throws IOException { + Writer writer = null; + writer = new FileWriter(resultFile, true); + writer.write(results.infoLine + "\n"); + writer.close(); + + System.out.println(results); + } + + public static void main(String[] args) { + try { + RevisionInfoBuilder builder = new RevisionInfoBuilder(args); + builder.buildInfo(); + builder.writeLine(); + } catch (UsageException e) { + System.err.println(e); + System.err.println("usage: RevisionInfoBuilder [product_name] " + + "[product_directory] [output_file]"); + System.exit(1); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + +} diff --git a/utilities/licenser/licenser.rb b/utilities/licenser/licenser.rb index aa939d201..1d507b2d6 100644 --- a/utilities/licenser/licenser.rb +++ b/utilities/licenser/licenser.rb @@ -1,411 +1,300 @@ -=begin --------------------------------------------------------------------------------- - -Scan the source directory, checking for expected "magic license tags", - or -Copy the source directory, inserting licensing information into the files. - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- -=end -$: << File.dirname(File.expand_path(__FILE__)) -require 'date' -require 'fileutils' -require 'pathname' -require 'property_file_reader' -require 'licenser_stats' - -class Licenser - - MAGIC_STRING = '$This file is distributed under the terms of the license in /doc/license.txt$' - - # ------------------------------------------------------------------------------------ - private - # ------------------------------------------------------------------------------------ - # - # Some paths in the properties file, if they are relative, should be relative to the - # properties file itself. - def relative_to_properties(properties, key) - path = properties[key] - base = File.dirname(properties['properties_file_path']) - - return nil if path == nil - return File.expand_path(path) if Pathname.new(path).absolute? - return File.expand_path(File.join(base, path)) - end - - # Some paths in the properties file, if they are relative, should be relative to the - # source directory. - def relative_to_source(properties, key) - path = properties[key] - base = @source_dir ? @source_dir : '' - - return nil if path == nil - return path if Pathname.new(path).absolute? - return File.expand_path(File.join(base, path)) - end - - # Confirm that the parameters are reasonable. - # - def sanity_checks_on_parameters() - # Check that all necessary properties are here. - raise("Properties file must contain a value for 'scan_only'") if @scan_only_string == nil - raise("Properties file must contain a value for 'source_dir'") if @source_dir == nil - raise("Properties file must contain a value for 'known_exceptions'") if @known_exceptions_file == nil - raise("Properties file must contain a value for 'skip_directories'") if @skip_directories_list == nil - raise("Properties file must contain a value for 'file_matchers'") if @file_match_list == nil - raise("Properties file must contain a value for 'report_level'") if @report_level_string == nil - - if !File.exist?(@source_dir) - raise "Source directory does not exist: #{@source_dir}" - end - - if !File.exist?(@known_exceptions_file) - raise "Known exceptions file does not exist: #{@known_exceptions_file}" - end - - if !@scan_only - raise("Properties file must contain a value for 'target_dir'") if @target_dir == nil - raise("Properties file must contain a value for 'license_file'") if @license_file == nil - - if File.exist?(@target_dir) - raise "Target directory already exists: #{@target_dir}" - end - - target_parent = File.dirname(@target_dir) - if !File.exist?(target_parent) - raise "Path to target directory doesn't exist: #{target_parent}" - end - - if !File.exist?(@license_file) - raise "License file does not exist: #{@license_file}" - end - end - end - - # Prepare the license as an array of lines of text, - # with the current year substituted in for ${year} - # - def prepare_license_text(license_file) - if (license_file == nil) - return [] - end - - year_string = DateTime.now.year.to_s - - text = [] - File.open(license_file) do |file| - file.each do |line| - text << line.gsub('${year}', year_string) - end - end - return text - end - - # The globs in the exceptions file are assumed to be - # relative to the source directory. Make them explicitly so. - # - # Ignore any blank lines or lines that start with a '#' - # - def prepare_exception_globs(exceptions_file, source_dir) - source_path = File.expand_path(source_dir) - globs = [] - File.open(exceptions_file) do |file| - file.each do |line| - glob = line.strip - if (glob.length > 0) && (glob[0..0] != '#') - globs << "#{source_path}/#{glob}".gsub('//', '/') - end - end - end - return globs - end - - # Recursively scan this directory, and copy if we are not scan-only. - # - def scan_dir(source_dir, target_dir) - @stats.enter_directory(source_dir) - - Dir.mkdir(File.expand_path(target_dir, @target_dir)) if !@scan_only - - Dir.foreach(File.join(@source_dir, source_dir)) do |filename| - source_path_relative = File.join(source_dir, filename) - source_path = File.join(@source_dir, source_path_relative) - target_path_relative = File.join(target_dir, filename) - target_path = File.join(@target_dir, target_path_relative) - - # What kind of beast is this? - if filename == '.' || filename == '..' - is_skipped_directory = true - else - if File.directory?(source_path) - if (path_matches_skipped?(source_path_relative)) - is_skipped_directory = true - else - is_directory = true - end - else - if filename_matches_pattern?(filename) - if path_matches_exception?(source_path_relative) - is_exception = true - else - is_match = true - end - else - is_ignored = true - end - end - end - - if is_skipped_directory - # do nothing - elsif is_directory - scan_dir(source_path_relative, target_path_relative) - elsif is_match - if @scan_only - @stats.record_scan_matching(filename) - scan_file(source_path, filename) - else - @stats.record_copy_matching(filename) - copy_file_with_license(source_path, target_path, filename) - end - elsif is_exception - @stats.record_known_exception(filename) - if @scan_only - # do nothing - else - copy_file_without_license(source_path, target_path) - end - else # not a match - if @scan_only - @stats.record_scan_non_matching(filename) - else - @stats.record_copy_non_matching(filename) - copy_file_without_license(source_path, target_path) - end - end - end - end - - # Is this directory one of the skipped? - # - def path_matches_skipped?(relative_path) - @skip_directories.each do |glob| - return true if File.fnmatch(glob, relative_path) - end - return false - end - - # Does this file path match any of the exceptions? - # - def path_matches_exception?(relative_path) - path = File.expand_path(File.join(@source_dir, relative_path)) - @known_exceptions.each do |pattern| - return true if File.fnmatch(pattern, path) - end - return false - end - - # Does this filename match any of the patterns? - # - def filename_matches_pattern?(filename) - @file_matchers.each do |pattern| - return true if File.fnmatch(pattern, filename) - end - return false - end - - # This file would be eligible for licensing if we weren't in scan-only mode. - # - def scan_file(source_path, filename) - found = 0 - File.open(source_path) do |source_file| - source_file.each do |line| - if line.include?(MAGIC_STRING) - found += 1 - end - end - end - - if found == 0 - @stats.record_no_tag(filename, source_path) - elsif found == 1 - @stats.record_tag(filename) - else - raise("File contains #{found} license lines: #{source_path}") - end - end - - # This file matches at least one of the file-matching strings, and does not - # match any exceptions. Replace the magic string with the license text. - # - def copy_file_with_license(source_path, target_path, filename) - found = 0 - File.open(source_path) do |source_file| - File.open(target_path, "w") do |target_file| - source_file.each do |line| - if line.include?(MAGIC_STRING) - found += 1 - insert_license_text(target_file, line) - else - target_file.print line - end - end - end - end - - if found == 0 - @stats.record_no_tag(filename, source_path) - elsif found == 1 - @stats.record_tag(filename) - else - raise("File contains #{found} license lines: #{source_path}") - end - end - - # Figure out the comment characters and write the license text to the file. - # - def insert_license_text(target_file, line) - ends = line.split(MAGIC_STRING) - if ends.size != 2 - raise ("Can't parse this license line: #{line}") - end - - target_file.print "#{ends[0].strip}\n" - - @license_text.each do |text| - target_file.print "#{text.rstrip}\n" - end - - target_file.print "#{ends[1].strip}\n" - end - - # This file either doesn't match any of the file-matching strings, or - # matches an exception - # - def copy_file_without_license(source_path, target_path) - FileUtils.cp(source_path, target_path) - end - - # ------------------------------------------------------------------------------------ - public - # ------------------------------------------------------------------------------------ - - # Setup and get ready to process. - # - # * properties is a map of keys to values, probably parsed from a properties file. - # - def initialize(properties) - @scan_only_string = properties['scan_only'] - @scan_only = 'false' != @scan_only_string - - @file_match_list = properties['file_matchers'] - @skip_directories_list = properties['skip_directories'] - @report_level_string = properties['report_level'] - - # These properties contain paths, and if they are relative paths, they - # should be relative to the properties file itself. - @source_dir = relative_to_properties(properties, 'source_dir') - @target_dir = relative_to_properties(properties, 'target_dir') - - # These properties contain paths, and if they are relative paths, they - # should be relative to the source_directory. - @license_file = relative_to_source(properties, 'license_file') - @known_exceptions_file = relative_to_source(properties, 'known_exceptions') - - sanity_checks_on_parameters() - - @full_report = @report_level_string === 'full' - @short_report = @report_level_string === 'short' - @file_matchers = @file_match_list.strip.split(/,\s*/) - @skip_directories = @skip_directories_list.strip.split(/,\s*/) - @license_text = prepare_license_text(@license_file) - @known_exceptions = prepare_exception_globs(@known_exceptions_file, @source_dir) - - @stats = LicenserStats.new(@source_dir, @file_matchers, @full_report) - end - - # Start the recursive scanning (and copying). - def process() - scan_dir('.', '.') - end - - # Report the summary statistics - def report(properties) - verb = @scan_only ? "scanned" : "copied" - if (@short_report) - subs = 0 - @stats.substitutions.each {|line| subs += line[1] } - known = 0 - @stats.known_exceptions.each {|line| known += line[1] } - missing = 0 - @stats.missing_tags.each {|line| missing += line[1] } - - puts "Licenser: #{verb} #{@stats.file_count} files in #{@stats.dir_count} directories." - printf(" Substitutions: %5d\n", subs) - printf(" Known exceptions: %5d\n", known) - printf(" Missing tags: %5d\n", missing) - else - puts "Licenser: run completed at #{DateTime.now.strftime("%H:%M:%S on %b %d, %Y")}" - puts " #{verb} #{@stats.file_count} files in #{@stats.dir_count} directories." - puts - puts 'Substitutions' - @stats.substitutions.sort.each do |line| - printf("%5d %s\n", line[1], line[0]) - end - puts - puts 'Known non-licensed files' - @stats.known_exceptions.sort.each do |line| - printf("%5d %s\n", line[1], line[0]) - end - puts - puts 'Missing tags' - @stats.missing_tags.sort.each do |line| - printf("%5d %s\n", line[1], line[0]) - end - puts - puts 'properties:' - properties.each do |key, value| - puts " #{key} = #{value}" - end - end - end - - # Were we successful or not? - def success? - return @stats.missing_tags.empty? - end -end - -# -# -# ------------------------------------------------------------------------------------ -# Standalone calling. -# -# Do this if this program was called from the command line. That is, if the command -# expands to the path of this file. -# ------------------------------------------------------------------------------------ -# - -if File.expand_path($0) == File.expand_path(__FILE__) - if ARGV.length == 0 - raise("No arguments - usage is: ruby licenser.rb ") - end - if !File.file?(ARGV[0]) - raise "File does not exist: '#{ARGV[0]}'." - end - - properties = PropertyFileReader.read(ARGV[0]) - - l = Licenser.new(properties) - l.process - l.report(properties) - - if l.success? - puts "Licenser was successful." - exit 0 - else - puts "Licenser found problems." - exit 1 - end -end +=begin +-------------------------------------------------------------------------------- + +Scan the source directory, checking for expected "magic license tags", + or +Copy the source directory, inserting licensing information into the files. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end +$: << File.dirname(File.expand_path(__FILE__)) +require 'date' +require 'fileutils' +require 'pathname' +require 'property_file_reader' +require 'licenser_stats' + +class Licenser + + MAGIC_STRING = '$This file is distributed under the terms of the license in /doc/license.txt$' + + # ------------------------------------------------------------------------------------ + private + # ------------------------------------------------------------------------------------ + # + # Some paths in the properties file, if they are relative, should be relative to the + # properties file itself. + def relative_to_properties(properties, key) + path = properties[key] + base = File.dirname(properties['properties_file_path']) + + return nil if path == nil + return File.expand_path(path) if Pathname.new(path).absolute? + return File.expand_path(File.join(base, path)) + end + + # Some paths in the properties file, if they are relative, should be relative to the + # source directory. + def relative_to_source(properties, key) + path = properties[key] + base = @source_dir ? @source_dir : '' + + return nil if path == nil + return path if Pathname.new(path).absolute? + return File.expand_path(File.join(base, path)) + end + + # Confirm that the parameters are reasonable. + # + def sanity_checks_on_parameters() + # Check that all necessary properties are here. + raise("Properties file must contain a value for 'source_dir'") if @source_dir == nil + raise("Properties file must contain a value for 'known_exceptions'") if @known_exceptions_file == nil + raise("Properties file must contain a value for 'skip_directories'") if @skip_directories_list == nil + raise("Properties file must contain a value for 'file_matchers'") if @file_match_list == nil + raise("Properties file must contain a value for 'report_level'") if @report_level_string == nil + + if !File.exist?(@source_dir) + raise "Source directory does not exist: #{@source_dir}" + end + + if !File.exist?(@known_exceptions_file) + raise "Known exceptions file does not exist: #{@known_exceptions_file}" + end + end + + # The globs in the exceptions file are assumed to be + # relative to the source directory. Make them explicitly so. + # + # Ignore any blank lines or lines that start with a '#' + # + def prepare_exception_globs(exceptions_file, source_dir) + source_path = File.expand_path(source_dir) + globs = [] + File.open(exceptions_file) do |file| + file.each do |line| + glob = line.strip + if (glob.length > 0) && (glob[0..0] != '#') + globs << "#{source_path}/#{glob}".gsub('//', '/') + end + end + end + return globs + end + + # Recursively scan this directory, and copy if we are not scan-only. + # + def scan_dir(source_dir, target_dir) + @stats.enter_directory(source_dir) + + Dir.foreach(File.join(@source_dir, source_dir)) do |filename| + source_path_relative = File.join(source_dir, filename) + source_path = File.join(@source_dir, source_path_relative) + target_path_relative = File.join(target_dir, filename) + target_path = File.join(@target_dir, target_path_relative) + + # What kind of beast is this? + if filename == '.' || filename == '..' + is_skipped_directory = true + else + if File.directory?(source_path) + if (path_matches_skipped?(source_path_relative)) + is_skipped_directory = true + else + is_directory = true + end + else + if filename_matches_pattern?(filename) + if path_matches_exception?(source_path_relative) + is_exception = true + else + is_match = true + end + else + is_ignored = true + end + end + end + + if is_skipped_directory + # do nothing + elsif is_directory + scan_dir(source_path_relative, target_path_relative) + elsif is_match + @stats.record_scan_matching(filename) + scan_file(source_path, filename) + elsif is_exception + @stats.record_known_exception(filename) + else # not a match + @stats.record_scan_non_matching(filename) + end + end + end + + # Is this directory one of the skipped? + # + def path_matches_skipped?(relative_path) + @skip_directories.each do |glob| + return true if File.fnmatch(glob, relative_path) + end + return false + end + + # Does this file path match any of the exceptions? + # + def path_matches_exception?(relative_path) + path = File.expand_path(File.join(@source_dir, relative_path)) + @known_exceptions.each do |pattern| + return true if File.fnmatch(pattern, path) + end + return false + end + + # Does this filename match any of the patterns? + # + def filename_matches_pattern?(filename) + @file_matchers.each do |pattern| + return true if File.fnmatch(pattern, filename) + end + return false + end + + # This file should contain a license tag. + # + def scan_file(source_path, filename) + found = 0 + File.open(source_path) do |source_file| + source_file.each do |line| + if line.include?(MAGIC_STRING) + found += 1 + end + end + end + + if found == 0 + @stats.record_no_tag(filename, source_path) + elsif found == 1 + @stats.record_tag(filename) + else + raise("File contains #{found} license lines: #{source_path}") + end + end + + # ------------------------------------------------------------------------------------ + public + # ------------------------------------------------------------------------------------ + + # Setup and get ready to process. + # + # * properties is a map of keys to values, probably parsed from a properties file. + # + def initialize(properties) + @file_match_list = properties['file_matchers'] + @skip_directories_list = properties['skip_directories'] + @report_level_string = properties['report_level'] + + # These properties contain paths, and if they are relative paths, they + # should be relative to the properties file itself. + @source_dir = relative_to_properties(properties, 'source_dir') + @target_dir = relative_to_properties(properties, 'target_dir') + + # These properties contain paths, and if they are relative paths, they + # should be relative to the source_directory. + @license_file = relative_to_source(properties, 'license_file') + @known_exceptions_file = relative_to_source(properties, 'known_exceptions') + + sanity_checks_on_parameters() + + @full_report = @report_level_string === 'full' + @short_report = @report_level_string === 'short' + @file_matchers = @file_match_list.strip.split(/,\s*/) + @skip_directories = @skip_directories_list.strip.split(/,\s*/) + @known_exceptions = prepare_exception_globs(@known_exceptions_file, @source_dir) + + @stats = LicenserStats.new(@source_dir, @file_matchers, @full_report) + end + + # Start the recursive scanning (and copying). + def process() + scan_dir('.', '.') + end + + # Report the summary statistics + def report(properties) + if (@short_report) + tags = 0 + @stats.tagged_files.each {|line| tags += line[1] } + known = 0 + @stats.known_exceptions.each {|line| known += line[1] } + missing = 0 + @stats.missing_tags.each {|line| missing += line[1] } + + puts "Licenser: scanned #{@stats.file_count} files in #{@stats.dir_count} directories." + printf(" Licensed files: %5d\n", tags) + printf(" Known exceptions: %5d\n", known) + printf(" Missing tags: %5d\n", missing) + else + puts "Licenser: run completed at #{DateTime.now.strftime("%H:%M:%S on %b %d, %Y")}" + puts " scanned #{@stats.file_count} files in #{@stats.dir_count} directories." + puts + puts 'Licensed files' + @stats.tagged_files.sort.each do |line| + printf("%5d %s\n", line[1], line[0]) + end + puts + puts 'Known non-licensed files' + @stats.known_exceptions.sort.each do |line| + printf("%5d %s\n", line[1], line[0]) + end + puts + puts 'Missing tags' + @stats.missing_tags.sort.each do |line| + printf("%5d %s\n", line[1], line[0]) + end + puts + puts 'properties:' + properties.each do |key, value| + puts " #{key} = #{value}" + end + end + end + + # Were we successful or not? + def success? + return @stats.missing_tags.empty? + end +end + +# +# +# ------------------------------------------------------------------------------------ +# Standalone calling. +# +# Do this if this program was called from the command line. That is, if the command +# expands to the path of this file. +# ------------------------------------------------------------------------------------ +# + +if File.expand_path($0) == File.expand_path(__FILE__) + if ARGV.length == 0 + raise("No arguments - usage is: ruby licenser.rb ") + end + if !File.file?(ARGV[0]) + raise "File does not exist: '#{ARGV[0]}'." + end + + properties = PropertyFileReader.read(ARGV[0]) + + l = Licenser.new(properties) + l.process + l.report(properties) + + if l.success? + puts "Licenser was successful." + exit 0 + else + puts "Licenser found problems." + exit 1 + end +end diff --git a/utilities/licenser/licenser_stats.rb b/utilities/licenser/licenser_stats.rb index cac01b42a..d379b919e 100644 --- a/utilities/licenser/licenser_stats.rb +++ b/utilities/licenser/licenser_stats.rb @@ -1,95 +1,95 @@ -=begin --------------------------------------------------------------------------------- - -Collect the statistics of a licenser run. - --------------------------------------------------------------------------------- -=end - -class LicenserStats - attr_reader :substitutions - attr_reader :missing_tags - attr_reader :known_exceptions - attr_reader :file_count - attr_reader :dir_count - - # ------------------------------------------------------------------------------------ - private - # ------------------------------------------------------------------------------------ - # - def which_match(filename) - @file_matchers.each do |matcher| - return matcher if File.fnmatch(matcher, filename) - end - raise("filename matches no matchers!: #{filename}") - end - - # ------------------------------------------------------------------------------------ - public - # ------------------------------------------------------------------------------------ - - def initialize(root_dir, file_matchers, full) - @root_dir = "#{root_dir}/".gsub('//', '/') - @file_matchers = file_matchers - @full = full - - # keep track of how many substitutions for all file types - @substitutions = Hash.new() - file_matchers.each do |matcher| - @substitutions[matcher] = 0 - end - - # keep track of missing tags, only in file types that have missing tags - @missing_tags = Hash.new(0) - - # keep track of how many known non-licensed files we encounter, and what types. - @known_exceptions = Hash.new(0) - - # keep track of how many files are copied - @file_count = 0 - - #keep track of how many directories are copied - @dir_count = 0 - end - - def enter_directory(path) - @dir_count += 1 - puts "Entering directory: #{path}" if @full - end - - def record_scan_non_matching(filename) - @file_count += 1 - puts " Scan without mods: #{filename}" if @full - end - - def record_copy_non_matching(filename) - @file_count += 1 - puts " Copy without mods: #{filename}" if @full - end - - def record_scan_matching(filename) - @file_count += 1 - puts " Scan with mods: #{filename}" if @full - end - - def record_copy_matching(filename) - @file_count += 1 - puts " Copy with mods: #{filename}" if @full - end - - def record_known_exception(filename) - @file_count += 1 - puts " Known exception: #{filename}" if @full - @known_exceptions[which_match(filename)] += 1 - end - - def record_tag(filename) - puts " Substituted license text into #{filename}" if @full - @substitutions[which_match(filename)] += 1 - end - - def record_no_tag(filename, source_path) - puts "ERROR: Found no license tag in #{source_path.sub(@root_dir, '')}" - @missing_tags[which_match(filename)] += 1 - end -end +=begin +-------------------------------------------------------------------------------- + +Collect the statistics of a licenser run. + +-------------------------------------------------------------------------------- +=end + +class LicenserStats + attr_reader :tagged_files + attr_reader :missing_tags + attr_reader :known_exceptions + attr_reader :file_count + attr_reader :dir_count + + # ------------------------------------------------------------------------------------ + private + # ------------------------------------------------------------------------------------ + # + def which_match(filename) + @file_matchers.each do |matcher| + return matcher if File.fnmatch(matcher, filename) + end + raise("filename matches no matchers!: #{filename}") + end + + # ------------------------------------------------------------------------------------ + public + # ------------------------------------------------------------------------------------ + + def initialize(root_dir, file_matchers, full) + @root_dir = "#{root_dir}/".gsub('//', '/') + @file_matchers = file_matchers + @full = full + + # keep track of how many tags are found in all file types + @tagged_files = Hash.new() + file_matchers.each do |matcher| + @tagged_files[matcher] = 0 + end + + # keep track of missing tags, only in file types that have missing tags + @missing_tags = Hash.new(0) + + # keep track of how many known non-licensed files we encounter, and what types. + @known_exceptions = Hash.new(0) + + # keep track of how many files are copied + @file_count = 0 + + #keep track of how many directories are copied + @dir_count = 0 + end + + def enter_directory(path) + @dir_count += 1 + puts "Entering directory: #{path}" if @full + end + + def record_scan_non_matching(filename) + @file_count += 1 + puts " Scan without mods: #{filename}" if @full + end + + def record_copy_non_matching(filename) + @file_count += 1 + puts " Copy without mods: #{filename}" if @full + end + + def record_scan_matching(filename) + @file_count += 1 + puts " Scan with mods: #{filename}" if @full + end + + def record_copy_matching(filename) + @file_count += 1 + puts " Copy with mods: #{filename}" if @full + end + + def record_known_exception(filename) + @file_count += 1 + puts " Known exception: #{filename}" if @full + @known_exceptions[which_match(filename)] += 1 + end + + def record_tag(filename) + puts " Found license tag into #{filename}" if @full + @tagged_files[which_match(filename)] += 1 + end + + def record_no_tag(filename, source_path) + puts "ERROR: Found no license tag in #{source_path.sub(@root_dir, '')}" + @missing_tags[which_match(filename)] += 1 + end +end diff --git a/webapp/build.xml b/webapp/build.xml index 05e3edec9..0387483af 100644 --- a/webapp/build.xml +++ b/webapp/build.xml @@ -69,6 +69,9 @@ + + + @@ -144,6 +147,32 @@ - - - - - - - - - - - - - - - - - --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -369,12 +434,22 @@ + + + + + + + + @@ -399,6 +474,7 @@ + diff --git a/webapp/config/licenser/licenser.properties b/webapp/config/licenser/licenser.properties index 7d98dd3cd..1b0aaa076 100644 --- a/webapp/config/licenser/licenser.properties +++ b/webapp/config/licenser/licenser.properties @@ -1,37 +1,34 @@ -# -------------------------------------------------------------------------- -# Properties for running the licenser utility in Vitro core. -# -------------------------------------------------------------------------- - -# The path to the top level directory to be scanned or copied -# (if relative, then relative to this file) -source_dir = ../../../ - -# The path to the top level directory to copy into (ignored if only scanning) -# (if relative, then relative to this file) -target_dir = - -# A list of filename globs that match the files we want to license, -# delimited by commas with optional white-space. -file_matchers = *.java, *.jsp, *.tld, *.xsl, *.xslt, *.css, *.js, *.ftl, *.xml - -# "globs" that describe paths that we won't follow for scanning OR FOR COPYING. -# (relative to the source_dir) -skip_directories = ./bin, ./.svn, ./**/.svn, ./webapp/.build - -# The path to a file containing filename/path globs that match the files that -# we know should have no license tags in them. -# The file contains one glob per line; blank lines and comments ("#") are ignored. -# (if relative, then relative to the source directory) -known_exceptions = webapp/config/licenser/known_exceptions.txt - -# The path to the text of the license agreement (ignored if only scanning) -# If the agreement contains a ${year} token, the current year will be substituted. -# (if relative, then relative to the source directory) -license_file = doc/license.txt - -# Set to 'full' for a full report, 'short' for a brief statment, or to anything -# else for a medium-length summary. -report_level = short - -# if true, we are just scanning, not copying. -scan_only = true +# -------------------------------------------------------------------------- +# Properties for running the licenser utility in Vitro core. +# -------------------------------------------------------------------------- + +# The path to the top level directory to be scanned or copied +# (if relative, then relative to this file) +source_dir = ../../../ + +# The path to the top level directory to copy into (ignored if only scanning) +# (if relative, then relative to this file) +target_dir = + +# A list of filename globs that match the files we want to license, +# delimited by commas with optional white-space. +file_matchers = *.java, *.jsp, *.tld, *.xsl, *.xslt, *.css, *.js, *.ftl, *.xml + +# "globs" that describe paths that we won't follow for scanning OR FOR COPYING. +# (relative to the source_dir) +skip_directories = ./bin, ./.svn, ./**/.svn, ./webapp/.build + +# The path to a file containing filename/path globs that match the files that +# we know should have no license tags in them. +# The file contains one glob per line; blank lines and comments ("#") are ignored. +# (if relative, then relative to the source directory) +known_exceptions = webapp/config/licenser/known_exceptions.txt + +# The path to the text of the license agreement (ignored if only scanning) +# If the agreement contains a ${year} token, the current year will be substituted. +# (if relative, then relative to the source directory) +license_file = doc/license.txt + +# Set to 'full' for a full report, 'short' for a brief statment, or to anything +# else for a medium-length summary. +report_level = short diff --git a/webapp/web/i18n/all_es.properties b/webapp/languages/example/i18n/all_es.properties similarity index 100% rename from webapp/web/i18n/all_es.properties rename to webapp/languages/example/i18n/all_es.properties diff --git a/webapp/web/templates/freemarker/body/search/search-help_es.ftl b/webapp/languages/example/templates/freemarker/search-help_es.ftl similarity index 100% rename from webapp/web/templates/freemarker/body/search/search-help_es.ftl rename to webapp/languages/example/templates/freemarker/search-help_es.ftl diff --git a/webapp/web/templates/freemarker/body/termsOfUse_es.ftl b/webapp/languages/example/templates/freemarker/termsOfUse_es.ftl similarity index 100% rename from webapp/web/templates/freemarker/body/termsOfUse_es.ftl rename to webapp/languages/example/templates/freemarker/termsOfUse_es.ftl diff --git a/webapp/lib/easymock-3.2.jar b/webapp/lib/easymock-3.2.jar new file mode 100644 index 000000000..7c439f82c Binary files /dev/null and b/webapp/lib/easymock-3.2.jar differ diff --git a/webapp/lib/jackson-annotations-2.2.1.jar b/webapp/lib/jackson-annotations-2.2.1.jar new file mode 100644 index 000000000..b49a3564e Binary files /dev/null and b/webapp/lib/jackson-annotations-2.2.1.jar differ diff --git a/webapp/lib/jackson-core-2.2.1.jar b/webapp/lib/jackson-core-2.2.1.jar new file mode 100644 index 000000000..42fd4aecf Binary files /dev/null and b/webapp/lib/jackson-core-2.2.1.jar differ diff --git a/webapp/lib/jackson-databind-2.2.1.jar b/webapp/lib/jackson-databind-2.2.1.jar new file mode 100644 index 000000000..1b018e003 Binary files /dev/null and b/webapp/lib/jackson-databind-2.2.1.jar differ diff --git a/webapp/lib/jsonld-java-0.3-SNAPSHOT.jar b/webapp/lib/jsonld-java-0.3-SNAPSHOT.jar new file mode 100644 index 000000000..879586189 Binary files /dev/null and b/webapp/lib/jsonld-java-0.3-SNAPSHOT.jar differ diff --git a/webapp/lib/jsonld-java-jena-0.3-SNAPSHOT.jar b/webapp/lib/jsonld-java-jena-0.3-SNAPSHOT.jar new file mode 100644 index 000000000..a8754a44d Binary files /dev/null and b/webapp/lib/jsonld-java-jena-0.3-SNAPSHOT.jar differ diff --git a/webapp/web/WEB-INF/resources/permission_config.n3 b/webapp/rdf/auth/everytime/permission_config.n3 similarity index 100% rename from webapp/web/WEB-INF/resources/permission_config.n3 rename to webapp/rdf/auth/everytime/permission_config.n3 diff --git a/webapp/web/WEB-INF/ontologies/app/loadedAtStartup/displayModelListViews.rdf b/webapp/rdf/display/everytime/displayModelListViews.rdf similarity index 100% rename from webapp/web/WEB-INF/ontologies/app/loadedAtStartup/displayModelListViews.rdf rename to webapp/rdf/display/everytime/displayModelListViews.rdf diff --git a/webapp/web/WEB-INF/ontologies/app/application.owl b/webapp/rdf/display/firsttime/application.owl similarity index 100% rename from webapp/web/WEB-INF/ontologies/app/application.owl rename to webapp/rdf/display/firsttime/application.owl diff --git a/webapp/web/WEB-INF/ontologies/app/menu.n3 b/webapp/rdf/display/firsttime/menu.n3 similarity index 100% rename from webapp/web/WEB-INF/ontologies/app/menu.n3 rename to webapp/rdf/display/firsttime/menu.n3 diff --git a/webapp/web/WEB-INF/ontologies/app/profilePageType.n3 b/webapp/rdf/display/firsttime/profilePageType.n3 similarity index 100% rename from webapp/web/WEB-INF/ontologies/app/profilePageType.n3 rename to webapp/rdf/display/firsttime/profilePageType.n3 diff --git a/webapp/web/WEB-INF/ontologies/app/menuload/displayDisplay.n3 b/webapp/rdf/displayDisplay/everytime/displayDisplay.n3 similarity index 62% rename from webapp/web/WEB-INF/ontologies/app/menuload/displayDisplay.n3 rename to webapp/rdf/displayDisplay/everytime/displayDisplay.n3 index 35f95df09..8b30c3945 100644 --- a/webapp/web/WEB-INF/ontologies/app/menuload/displayDisplay.n3 +++ b/webapp/rdf/displayDisplay/everytime/displayDisplay.n3 @@ -14,4 +14,14 @@ rdf:type "true"^^xsd:boolean ; - \ No newline at end of file + +display:requiresAction + a owl:ObjectProperty ; + rdfs:label "Required Action"@en-US ; + rdfs:comment "Indicates that a resource has a required action that may need to be authorized" . + rdfs:range display:RequiredAction . + + "true"^^xsd:boolean ; + + "true"^^xsd:boolean ; + diff --git a/webapp/rdf/displayTbox/everytime/displayTBOX.n3 b/webapp/rdf/displayTbox/everytime/displayTBOX.n3 new file mode 100644 index 000000000..dd3bf4ea7 --- /dev/null +++ b/webapp/rdf/displayTbox/everytime/displayTBOX.n3 @@ -0,0 +1,222 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix rdfs: . +@prefix xsd: . +@prefix owl: . +@prefix rdf: . +@prefix display: . +@prefix vitro: . + +#########Classes######### +###Basic +owl:Class a owl:Class . +owl:Ontology a owl:Class . +owl:AnnotationProperty a owl:Class . +owl:DatatypeProperty a owl: +owl:ObjectProperty a owl:Class . + +###Display Model +display:MainMenu + a owl:Class ; + vitro:customDisplayViewAnnot "individual-menu.ftl"^^xsd:string . + +display:NavigationElement a owl:Class . + +display:Page a owl:Class . + +display:HomePage a owl:Class . + +display:ClassGroupPage a owl:Class . + +display:IndividualsForClassesPage a owl:Class . + +display:InternalClassesPage a owl:Class . + +display:DataGetter a owl:Class . + +display:RequiredAction a owl:Class ; + rdfs:comment "Represents a action that may need authorization to perform.". + + + a owl:Class ; + rdfs:comment "Data getter for running a SPARQL query." + + + a owl:Class ; + rdfs:comment "A data getter for a Solr Class search, i.e. get individuals for VClass" . + + + a owl:Class ; + rdfs:comment "A data getter that runs a SPARQL Update" . + + + a owl:Class ; + rdfs:comment "A data getter for a standard Vitro browse page" . + + + a owl:Class ; + rdfs:comment "A data getter for a VClassGroup page". + + + a owl:Class ; + rdfs:comment "A data getter for a Fixed piece of HTML stored in RDF". + + + a owl:Class . + +########Data Properties######### + +###Basic + +rdfs:comment + a owl:DatatypeProperty . +rdfs:label + a owl:DatatypeProperty . +owl:versionInfo + a owl:DatatypeProperty . + +###Vitro model + +vitro:modTime + a owl:DatatypeProperty . +vitro:displayRank + a owl:DatatypeProperty . +vitro:customDisplayViewAnnot + a owl:DatatypeProperty . + +###Display model + +display:listViewConfigFile + a owl:DatatypeProperty . + +display:title + a owl:DatatypeProperty . + +display:urlMapping + a owl:DatatypeProperty . + +display:requiresBodyTemplate + a owl:DatatypeProperty . + +display:isSelfContainedTemplate + a owl:DatatypeProperty . + +display:menuPosition + a owl:DatatypeProperty ; + vitro:displayLimitAnnot + "1"^^xsd:int . + +display:linkText + a owl:DatatypeProperty . + +display:hasMenuText + a owl:DatatypeProperty . + +display:usesDataGetterClass + a owl:DatatypeProperty . + +display:query + a owl:DatatypeProperty . + +display:saveToVar + a owl:DatatypeProperty. + +display:queryModel + a owl:DatatypeProperty. + +display:htmlValue + a owl:DatatypeProperty. + +display:cannotDeletePage + a owl:DatatypeProperty. + +######### Object Properties######### +###Basic +rdfs:range + a owl:ObjectProperty . +rdfs:domain + a owl:ObjectProperty . +owl:topObjectProperty + a owl:ObjectProperty . + +display:hasVClassId + a owl:ObjectProperty ; + rdfs:comment "Object property defining class for solr data getter" . + +###Vitro properties without which individual templates throw errors as are required + + + a owl:ObjectProperty ; + rdfs:range ; + rdfs:subPropertyOf , owl:topObjectProperty . + +vitro:primaryLink + a owl:ObjectProperty ; + rdfs:label "Primary Link"@en-US ; + rdfs:range vitro:Link ; + rdfs:subPropertyOf vitro:primaryLink , owl:topObjectProperty ; + vitro:customEntryFormAnnot + "defaultLinkForm.jsp"^^xsd:string ; + vitro:forceStubDeletionAnnot + "true"^^xsd:boolean ; + vitro:offerCreateNewOptionAnnot + "true"^^xsd:boolean ; + vitro:selectFromExistingAnnot + "false"^^xsd:boolean ; + vitro:stubObjectPropertyAnnot + "true"^^xsd:boolean . + +rdf:type + a owl:ObjectProperty ; + rdfs:label "RDF Type"@en-US ; + rdfs:range owl:Class ; + vitro:customEntryFormAnnot + "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RdfTypeGenerator"^^xsd:string ; + vitro:offerCreateNewOptionAnnot + "true"^^xsd:boolean ; + vitro:selectFromExistingAnnot + "true"^^xsd:boolean . + + +vitro:additionalLink + a owl:ObjectProperty ; + rdfs:label "Additional Link"@en-US ; + rdfs:range vitro:Link ; + rdfs:subPropertyOf vitro:additionalLink , owl:topObjectProperty ; + vitro:customEntryFormAnnot + "defaultLinkForm.jsp"^^xsd:string ; + vitro:forceStubDeletionAnnot + "true"^^xsd:boolean ; + vitro:offerCreateNewOptionAnnot + "true"^^xsd:boolean ; + vitro:selectFromExistingAnnot + "false"^^xsd:boolean ; + vitro:stubObjectPropertyAnnot + "true"^^xsd:boolean . + +###Display model + +display:hasElement + a owl:ObjectProperty . + +display:excludeClass + a owl:ObjectProperty . + +display:toPage + a owl:ObjectProperty . + +display:forClassGroup + a owl:ObjectProperty . + +display:hasDataGetter + a owl:ObjectProperty . + +display:getIndividualsForClass + a owl:ObjectProperty . + +display:restrictResultsByClass + a owl:ObjectProperty . + +display:requiresAction + a owl:ObjectProperty . + diff --git a/webapp/web/WEB-INF/filegraph/tbox/vitro-0.7.owl b/webapp/rdf/tbox/filegraph/vitro-0.7.owl similarity index 100% rename from webapp/web/WEB-INF/filegraph/tbox/vitro-0.7.owl rename to webapp/rdf/tbox/filegraph/vitro-0.7.owl diff --git a/webapp/web/WEB-INF/filegraph/tbox/vitroPublic.owl b/webapp/rdf/tbox/filegraph/vitroPublic.owl similarity index 100% rename from webapp/web/WEB-INF/filegraph/tbox/vitroPublic.owl rename to webapp/rdf/tbox/filegraph/vitroPublic.owl diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSets.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSets.java new file mode 100644 index 000000000..f5931c7df --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSets.java @@ -0,0 +1,20 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.permissions; + +import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.VITRO_AUTH; + +/** + * Constants and static methods to help manipulate PermissionSet instances. + */ +public class PermissionSets { + public static final String URI_SELF_EDITOR = VITRO_AUTH + "SELF_EDITOR"; + public static final String URI_EDITOR = VITRO_AUTH + "EDITOR"; + public static final String URI_CURATOR = VITRO_AUTH + "CURATOR"; + public static final String URI_DBA = VITRO_AUTH + "ADMIN"; + + /** No need to create an instance. */ + private PermissionSets() { + // Nothing to initialize. + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java deleted file mode 100644 index cb2598e6f..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsLoader.java +++ /dev/null @@ -1,355 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.auth.permissions; - -import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.VITRO_AUTH; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.Selector; -import com.hp.hpl.jena.rdf.model.SimpleSelector; -import com.hp.hpl.jena.rdf.model.Statement; -import com.hp.hpl.jena.rdf.model.StmtIterator; -import com.hp.hpl.jena.shared.Lock; -import com.hp.hpl.jena.util.iterator.ClosableIterator; -import com.hp.hpl.jena.vocabulary.RDF; -import com.hp.hpl.jena.vocabulary.RDFS; - -import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; -import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; -import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; -import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; -import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; - -/** - * Load the initial configuration of PermissionSets and Permissions. - * - * The UserAccounts model must be created before this runs. - * - * The PermissionRegistry must be created before this runs. - */ -public class PermissionSetsLoader implements ServletContextListener { - private static final Log log = LogFactory - .getLog(PermissionSetsLoader.class); - - public static final String FILE_OF_PERMISSION_SETS_INFO = "/WEB-INF/resources/permission_config.n3"; - - public static final String URI_SELF_EDITOR = VITRO_AUTH + "SELF_EDITOR"; - public static final String URI_EDITOR = VITRO_AUTH + "EDITOR"; - public static final String URI_CURATOR = VITRO_AUTH + "CURATOR"; - public static final String URI_DBA = VITRO_AUTH + "ADMIN"; - - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - StartupStatus ss = StartupStatus.getBean(ctx); - - try { - new Loader(this, ctx, ss).load(); - new SmokeTester(this, ctx, ss).test(); - } catch (Exception e) { - ss.fatal(this, "Failed to load the PermissionSets", e); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - // Nothing to tear down. - } - - // ---------------------------------------------------------------------- - // Loader class - // ---------------------------------------------------------------------- - - private static class Loader { - private static final int MAX_STATEMENTS_IN_WARNING = 5; - - private ServletContextListener listener; - private final ServletContext ctx; - private final StartupStatus ss; - - private final OntModel userAccountsModel; - private final Property permissionSetType; - - private Model modelFromFile; - private Model filteredModel; - - private int howManyNewPermissionSets; - private int howManyOldPermissionSets; - - public Loader(ServletContextListener listener, ServletContext ctx, - StartupStatus ss) { - this.listener = listener; - this.ctx = ctx; - this.ss = ss; - - this.userAccountsModel = ModelAccess.on(ctx).getUserAccountsModel(); - this.permissionSetType = this.userAccountsModel - .getProperty(VitroVocabulary.PERMISSIONSET); - - } - - public void load() { - try { - createModelFromFile(); - filterModelFromFile(); - checkForLeftoverStatements(); - removeExistingPermissionSetsFromUserAccountsModel(); - addNewStatementsToUserAccountsModel(); - - ss.info(listener, buildInfoMessage()); - } catch (LoaderException e) { - Throwable cause = e.getCause(); - if (cause == null) { - ss.warning(listener, e.getMessage()); - } else { - ss.warning(listener, e.getMessage(), e.getCause()); - } - } - } - - private void createModelFromFile() throws LoaderException { - InputStream stream = ctx - .getResourceAsStream(FILE_OF_PERMISSION_SETS_INFO); - - if (stream == null) { - throw new LoaderException("The permission sets config file " - + "doesn't exist in the servlet context: '" - + FILE_OF_PERMISSION_SETS_INFO + "'"); - } - - try { - modelFromFile = ModelFactory.createDefaultModel(); - modelFromFile.read(stream, null, "N3"); - } finally { - try { - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - log.debug("Loaded " + modelFromFile.size() + " statements"); - } - - /** - * Move all statements that relate to PermissionSets from the loaded - * model to the filtered model. - */ - private void filterModelFromFile() { - filteredModel = ModelFactory.createDefaultModel(); - - for (Resource r : iterable(modelFromFile.listResourcesWithProperty( - RDF.type, permissionSetType))) { - moveStatementsToFilteredModel(r); - howManyNewPermissionSets++; - } - - log.debug("Filtered " + filteredModel.size() + " statements for " - + howManyNewPermissionSets + " PermissionSets; " - + modelFromFile.size() + " extraneous statements."); - } - - /** - * Move the statements about this PermissionSet from the loaded model to - * the filtered model. - */ - private void moveStatementsToFilteredModel(Resource ps) { - Selector sel = new SimpleSelector(ps, null, (String) null); - for (Statement stmt : iterable(modelFromFile.listStatements(sel))) { - filteredModel.add(stmt); - modelFromFile.remove(stmt); - } - } - - /** - * Complain about any statements that were not moved to the filtered - * model. - */ - private void checkForLeftoverStatements() { - List list = iterable(modelFromFile.listStatements()); - if (list.isEmpty()) { - return; - } - - String message = "The PermissionSets configuration file contained " - + list.size() - + " statements that didn't describe PermissionSets: "; - for (int i = 0; i < Math - .min(list.size(), MAX_STATEMENTS_IN_WARNING); i++) { - Statement stmt = list.get(i); - message += "(" + stmt.asTriple() + ") "; - } - if (list.size() > MAX_STATEMENTS_IN_WARNING) { - message += ", ..."; - } - - ss.warning(listener, message); - } - - private void removeExistingPermissionSetsFromUserAccountsModel() { - userAccountsModel.enterCriticalSection(Lock.WRITE); - try { - for (Resource r : iterable(userAccountsModel - .listResourcesWithProperty(RDF.type, permissionSetType))) { - Selector sel = new SimpleSelector(r, null, (String) null); - StmtIterator stmts = userAccountsModel.listStatements(sel); - userAccountsModel.remove(stmts); - howManyOldPermissionSets++; - } - } finally { - userAccountsModel.leaveCriticalSection(); - } - - log.debug("Deleted " + howManyOldPermissionSets - + " old PermissionSets from the user model."); - } - - private void addNewStatementsToUserAccountsModel() { - userAccountsModel.enterCriticalSection(Lock.WRITE); - try { - userAccountsModel.add(filteredModel); - } finally { - userAccountsModel.leaveCriticalSection(); - } - } - - private String buildInfoMessage() { - String message = "Loaded " + howManyNewPermissionSets - + " PermissionSets: "; - Selector sel = new SimpleSelector(null, RDFS.label, (String) null); - for (Statement stmt : iterable(filteredModel.listStatements(sel))) { - String label = stmt.getObject().asLiteral().getString(); - message += "'" + label + "' "; - } - return message; - } - - private List iterable(ClosableIterator iterator) { - List list = new ArrayList(); - try { - while (iterator.hasNext()) { - list.add(iterator.next()); - } - } finally { - iterator.close(); - } - return list; - } - - } - - // ---------------------------------------------------------------------- - // SmokeTester class - // ---------------------------------------------------------------------- - - private static class SmokeTester { - private ServletContextListener listener; - private final ServletContext ctx; - private final StartupStatus ss; - - private final UserAccountsDao uaDao; - - public SmokeTester(ServletContextListener listener, ServletContext ctx, - StartupStatus ss) { - this.listener = listener; - this.ctx = ctx; - this.ss = ss; - - WebappDaoFactory wadf = ModelAccess.on(ctx).getWebappDaoFactory(); - if (wadf == null) { - throw new IllegalStateException( - "No webappDaoFactory on the servlet context"); - } - this.uaDao = wadf.getUserAccountsDao(); - } - - public void test() { - checkForPermissionSetsWithoutLabels(); - checkForReferencesToNonexistentPermissionSets(); - checkForReferencesToNonexistentPermissions(); - warnIfNoPermissionSetsForNewUsers(); - } - - private void checkForPermissionSetsWithoutLabels() { - for (PermissionSet ps : uaDao.getAllPermissionSets()) { - if (ps.getLabel().isEmpty()) { - ss.warning(listener, "This PermissionSet has no label: " - + ps.getUri()); - } - } - } - - private void checkForReferencesToNonexistentPermissionSets() { - for (UserAccount user : uaDao.getAllUserAccounts()) { - for (String psUri : user.getPermissionSetUris()) { - if (uaDao.getPermissionSetByUri(psUri) == null) { - ss.warning(listener, "The user '" + user.getFirstName() - + " " + user.getLastName() - + "' has the PermissionSet '" + psUri - + "', but the PermissionSet doesn't exist."); - } - } - } - } - - private void checkForReferencesToNonexistentPermissions() { - PermissionRegistry registry = PermissionRegistry.getRegistry(ctx); - for (PermissionSet ps : uaDao.getAllPermissionSets()) { - for (String pUri : ps.getPermissionUris()) { - if (!registry.isPermission(pUri)) { - ss.warning(listener, - "The PermissionSet '" + ps.getLabel() - + "' has the Permission '" + pUri - + "', but the Permission " - + "is not found in the registry."); - } - } - } - } - - private void warnIfNoPermissionSetsForNewUsers() { - for (PermissionSet ps : uaDao.getAllPermissionSets()) { - if (ps.isForNewUsers()) { - return; - } - } - ss.warning(listener, "No PermissionSet has been declared to be a " - + "PermissionSet for new users."); - } - - } - - // ---------------------------------------------------------------------- - // Handy dandy exception. - // ---------------------------------------------------------------------- - - private static class LoaderException extends Exception { - - public LoaderException(String message) { - super(message); - } - - public LoaderException(String message, Throwable cause) { - super(message, cause); - } - - } -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsSmokeTest.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsSmokeTest.java new file mode 100644 index 000000000..74c0f8c08 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/PermissionSetsSmokeTest.java @@ -0,0 +1,124 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.permissions; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Load the initial configuration of PermissionSets and Permissions. + * + * The UserAccounts model must be created before this runs. + * + * The PermissionRegistry must be created before this runs. + */ +public class PermissionSetsSmokeTest implements ServletContextListener { + @SuppressWarnings("unused") + private static final Log log = LogFactory + .getLog(PermissionSetsSmokeTest.class); + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + StartupStatus ss = StartupStatus.getBean(ctx); + + try { + new SmokeTester(this, ctx, ss).test(); + } catch (Exception e) { + ss.fatal(this, "Found a problem while testing the PermissionSets", + e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // Nothing to tear down. + } + + // ---------------------------------------------------------------------- + // SmokeTester class + // ---------------------------------------------------------------------- + + private static class SmokeTester { + private ServletContextListener listener; + private final ServletContext ctx; + private final StartupStatus ss; + + private final UserAccountsDao uaDao; + + public SmokeTester(ServletContextListener listener, ServletContext ctx, + StartupStatus ss) { + this.listener = listener; + this.ctx = ctx; + this.ss = ss; + + this.uaDao = ModelAccess.on(ctx).getWebappDaoFactory() + .getUserAccountsDao(); + } + + public void test() { + checkForPermissionSetsWithoutLabels(); + checkForReferencesToNonexistentPermissionSets(); + checkForReferencesToNonexistentPermissions(); + warnIfNoPermissionSetsForNewUsers(); + } + + private void checkForPermissionSetsWithoutLabels() { + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + if (ps.getLabel().isEmpty()) { + ss.warning(listener, "This PermissionSet has no label: " + + ps.getUri()); + } + } + } + + private void checkForReferencesToNonexistentPermissionSets() { + for (UserAccount user : uaDao.getAllUserAccounts()) { + for (String psUri : user.getPermissionSetUris()) { + if (uaDao.getPermissionSetByUri(psUri) == null) { + ss.warning(listener, "The user '" + user.getFirstName() + + " " + user.getLastName() + + "' has the PermissionSet '" + psUri + + "', but the PermissionSet doesn't exist."); + } + } + } + } + + private void checkForReferencesToNonexistentPermissions() { + PermissionRegistry registry = PermissionRegistry.getRegistry(ctx); + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + for (String pUri : ps.getPermissionUris()) { + if (!registry.isPermission(pUri)) { + ss.warning(listener, + "The PermissionSet '" + ps.getLabel() + + "' has the Permission '" + pUri + + "', but the Permission " + + "is not found in the registry."); + } + } + } + } + + private void warnIfNoPermissionSetsForNewUsers() { + for (PermissionSet ps : uaDao.getAllPermissionSets()) { + if (ps.isForNewUsers()) { + return; + } + } + ss.warning(listener, "No PermissionSet has been declared to be a " + + "PermissionSet for new users."); + } + + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java index 80b32b478..ab6df594e 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java @@ -27,55 +27,55 @@ public class SimplePermission extends Permission { private static final Map allInstances = new HashMap(); public static final SimplePermission ACCESS_SPECIAL_DATA_MODELS = new SimplePermission( - "AccessSpecialDataModels"); + NAMESPACE + "AccessSpecialDataModels"); public static final SimplePermission DO_BACK_END_EDITING = new SimplePermission( - "DoBackEndEditing"); + NAMESPACE + "DoBackEndEditing"); public static final SimplePermission DO_FRONT_END_EDITING = new SimplePermission( - "DoFrontEndEditing"); + NAMESPACE + "DoFrontEndEditing"); public static final SimplePermission EDIT_ONTOLOGY = new SimplePermission( - "EditOntology"); + NAMESPACE + "EditOntology"); public static final SimplePermission EDIT_OWN_ACCOUNT = new SimplePermission( - "EditOwnAccount"); + NAMESPACE + "EditOwnAccount"); public static final SimplePermission EDIT_SITE_INFORMATION = new SimplePermission( - "EditSiteInformation"); + NAMESPACE + "EditSiteInformation"); public static final SimplePermission LOGIN_DURING_MAINTENANCE = new SimplePermission( - "LoginDuringMaintenance"); + NAMESPACE + "LoginDuringMaintenance"); public static final SimplePermission MANAGE_MENUS = new SimplePermission( - "ManageMenus"); + NAMESPACE + "ManageMenus"); public static final SimplePermission MANAGE_OWN_PROXIES = new SimplePermission( - "ManageOwnProxies"); + NAMESPACE + "ManageOwnProxies"); public static final SimplePermission MANAGE_PORTALS = new SimplePermission( - "ManagePortals"); + NAMESPACE + "ManagePortals"); public static final SimplePermission MANAGE_PROXIES = new SimplePermission( - "ManageProxies"); + NAMESPACE + "ManageProxies"); public static final SimplePermission MANAGE_SEARCH_INDEX = new SimplePermission( - "ManageSearchIndex"); + NAMESPACE + "ManageSearchIndex"); public static final SimplePermission MANAGE_TABS = new SimplePermission( - "ManageTabs"); + NAMESPACE + "ManageTabs"); public static final SimplePermission MANAGE_USER_ACCOUNTS = new SimplePermission( - "ManageUserAccounts"); + NAMESPACE + "ManageUserAccounts"); public static final SimplePermission QUERY_FULL_MODEL = new SimplePermission( - "QueryFullModel"); + NAMESPACE + "QueryFullModel"); public static final SimplePermission QUERY_USER_ACCOUNTS_MODEL = new SimplePermission( - "QueryUserAccountsModel"); + NAMESPACE + "QueryUserAccountsModel"); public static final SimplePermission REBUILD_VCLASS_GROUP_CACHE = new SimplePermission( - "RebuildVClassGroupCache"); + NAMESPACE + "RebuildVClassGroupCache"); public static final SimplePermission REFRESH_VISUALIZATION_CACHE = new SimplePermission( - "RefreshVisualizationCache"); + NAMESPACE + "RefreshVisualizationCache"); public static final SimplePermission SEE_INDVIDUAL_EDITING_PANEL = new SimplePermission( - "SeeIndividualEditingPanel"); + NAMESPACE + "SeeIndividualEditingPanel"); public static final SimplePermission SEE_REVISION_INFO = new SimplePermission( - "SeeRevisionInfo"); + NAMESPACE + "SeeRevisionInfo"); public static final SimplePermission SEE_SITE_ADMIN_PAGE = new SimplePermission( - "SeeSiteAdminPage"); + NAMESPACE + "SeeSiteAdminPage"); public static final SimplePermission SEE_STARTUP_STATUS = new SimplePermission( - "SeeStartupStatus"); + NAMESPACE + "SeeStartupStatus"); public static final SimplePermission SEE_VERBOSE_PROPERTY_INFORMATION = new SimplePermission( - "SeeVerbosePropertyInformation"); + NAMESPACE + "SeeVerbosePropertyInformation"); public static final SimplePermission USE_ADVANCED_DATA_TOOLS_PAGES = new SimplePermission( - "UseAdvancedDataToolsPages"); + NAMESPACE + "UseAdvancedDataToolsPages"); public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission( - "UseSparqlQueryPage"); + NAMESPACE + "UseSparqlQueryPage"); // ---------------------------------------------------------------------- // These instances are "catch all" permissions to cover poorly defined @@ -84,33 +84,35 @@ public class SimplePermission extends Permission { // ---------------------------------------------------------------------- public static final SimplePermission USE_BASIC_AJAX_CONTROLLERS = new SimplePermission( - "UseBasicAjaxControllers"); + NAMESPACE + "UseBasicAjaxControllers"); public static final SimplePermission USE_MISCELLANEOUS_ADMIN_PAGES = new SimplePermission( - "UseMiscellaneousAdminPages"); + NAMESPACE + "UseMiscellaneousAdminPages"); public static final SimplePermission USE_MISCELLANEOUS_CURATOR_PAGES = new SimplePermission( - "UseMiscellaneousCuratorPages"); + NAMESPACE + "UseMiscellaneousCuratorPages"); public static final SimplePermission USE_MISCELLANEOUS_EDITOR_PAGES = new SimplePermission( - "UseMiscellaneousEditorPages"); + NAMESPACE + "UseMiscellaneousEditorPages"); public static final SimplePermission USE_MISCELLANEOUS_PAGES = new SimplePermission( - "UseMiscellaneousPages"); + NAMESPACE + "UseMiscellaneousPages"); public static List getAllInstances() { return new ArrayList(allInstances.values()); } - private final String localName; + //private final String localName; + private final String uri; public final RequestedAction ACTION; public final Actions ACTIONS; - public SimplePermission(String localName) { - super(NAMESPACE + localName); + public SimplePermission(String uri) { + super(uri); - if (localName == null) { - throw new NullPointerException("name may not be null."); + if (uri == null) { + throw new NullPointerException("uri may not be null."); } - this.localName = localName; - this.ACTION = new SimpleRequestedAction(localName); + //this.localName = localName; + this.uri = uri; + this.ACTION = new SimpleRequestedAction(uri); this.ACTIONS = new Actions(this.ACTION); if (allInstances.containsKey(this.uri)) { @@ -120,14 +122,6 @@ public class SimplePermission extends Permission { allInstances.put(uri, this); } - public String getLocalName() { - return this.localName; - } - - public String getNamespace() { - return NAMESPACE; - } - @Override public boolean isAuthorized(RequestedAction whatToAuth) { if (whatToAuth != null) { @@ -142,7 +136,7 @@ public class SimplePermission extends Permission { @Override public String toString() { - return "SimplePermission['" + localName + "']"; + return "SimplePermission['" + uri+ "']"; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/CompositPolicyDecision.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/CompositPolicyDecision.java new file mode 100644 index 000000000..538ea6a76 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/CompositPolicyDecision.java @@ -0,0 +1,23 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy; + +import java.util.List; + +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; + +/** + * Policy decision that is made from some analysis of a set of decisions. + * @author bdc34 + * + */ +public class CompositPolicyDecision extends BasicPolicyDecision implements PolicyDecision { + List subDecisions; + + public CompositPolicyDecision(Authorization auth, String message, List subDecisions){ + super( auth, message); + this.subDecisions = subDecisions; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java index 69e8839ce..11c351044 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java @@ -17,8 +17,10 @@ import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.ActiveIdentifierBundleFactories; import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers; +import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; @@ -26,6 +28,9 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddDataPro import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddObjectPropertyStatement; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.DropDataPropertyStatement; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.DropObjectPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator; +import edu.cornell.mannlib.vitro.webapp.controller.authenticate.BasicAuthenticator; /** * A collection of static methods to help determine whether requested actions @@ -53,7 +58,7 @@ public class PolicyHelper { IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req); return isAuthorizedForActions(ids, policy, actions); } - + /** * Are these actions authorized for these identifiers by these policies? */ @@ -62,6 +67,48 @@ public class PolicyHelper { return Actions.notNull(actions).isAuthorized(policy, ids); } + /** + * Is the email/password authorized for these actions? + * This should be used when a controller or something needs allow + * actions if the user passes in their email and password. + * + * It may be better to check this as part of a servlet Filter and + * add an identifier bundle. + */ + public static boolean isAuthorizedForActions( HttpServletRequest req, + String email, String password, + Actions actions){ + + if( password == null || email == null || + password.isEmpty() || email.isEmpty()){ + return false; + } + + try{ + Authenticator basicAuth = new BasicAuthenticator(req); + UserAccount user = basicAuth.getAccountForInternalAuth( email ); + log.debug("userAccount is " + user==null?"null":user.getUri() ); + + if( ! basicAuth.isCurrentPassword( user, password ) ){ + log.debug(String.format("UNAUTHORIZED, password not accepted for %s, account URI: %s", + user.getEmailAddress(), user.getUri())); + return false; + }else{ + log.debug(String.format("password accepted for %s, account URI: %s", + user.getEmailAddress(), user.getUri() )); + // figure out if that account can do the actions + IdentifierBundle ids = + ActiveIdentifierBundleFactories.getUserIdentifierBundle(req,user); + PolicyIface policy = ServletPolicyList.getPolicies(req); + return PolicyHelper.isAuthorizedForActions( ids, policy, actions ); + } + + }catch(Exception ex){ + log.error("Error while attempting to authorize actions " + actions.toString(), ex); + return false; + } + } + /** * Do the current policies authorize the current user to add this statement * to this model? @@ -263,6 +310,7 @@ public class PolicyHelper { + stmt.getObject() + ">"; } + /** * No need to instantiate this helper class - all methods are static. */ diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java index d05f04bab..ca6e047ff 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java @@ -4,6 +4,8 @@ package edu.cornell.mannlib.vitro.webapp.auth.policy; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -12,6 +14,8 @@ import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AllRequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AnyRequestedAction; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; /** @@ -38,8 +42,68 @@ public class PolicyList extends ArrayList implements PolicyIface{ @Override public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, RequestedAction whatToAuth) { - PolicyDecision pd = null; - for(PolicyIface policy : this){ + + if( whatToAuth instanceof AllRequestedAction ){ + return doAllAction( whoToAuth, ((AllRequestedAction)whatToAuth)); + }else if ( whatToAuth instanceof AnyRequestedAction ){ + return doAnyAction(whoToAuth, ((AnyRequestedAction)whatToAuth)); + }else{ + return checkAgainstPolicys( whoToAuth , whatToAuth); + } + } + + /** + * Checks that at least one of the subRequestedActions are authorized. + */ + private PolicyDecision doAnyAction(IdentifierBundle whoToAuth, + AnyRequestedAction whatToAuth) { + boolean anyAuth = false; + List subPd = new LinkedList(); + for( RequestedAction subAct : whatToAuth.getRequestedActions()){ + PolicyDecision pd = isAuthorized(whoToAuth,subAct); + subPd.add(pd); + if( pd.getAuthorized() == Authorization.AUTHORIZED){ + anyAuth = true; + break; + } + } + if( anyAuth ) + return new CompositPolicyDecision( Authorization.AUTHORIZED, + "Some sub-RequestedAction authorized", subPd); + else + return new CompositPolicyDecision( Authorization.UNAUTHORIZED, + "None of the sub-RequestedAction were authorized", subPd); + } + + /** + * Checks that all the subRequestedActions are authorized. + */ + private PolicyDecision doAllAction(IdentifierBundle whoToAuth, AllRequestedAction whatToAuth) { + boolean allAuth = true; + + List subPd = new LinkedList(); + for( RequestedAction subAct : whatToAuth.getRequestedActions()){ + PolicyDecision pd = isAuthorized( whoToAuth, subAct) ; + subPd.add( pd ); + if( pd.getAuthorized() != Authorization.AUTHORIZED ){ + allAuth = false; + break; + } + } + + if( allAuth ) + return new CompositPolicyDecision( + Authorization.AUTHORIZED, + "All sub-RequestedActions authorized.", subPd ); + else + return new CompositPolicyDecision( + Authorization.UNAUTHORIZED, + "Not all sub-RequestedActions authorized.", subPd ); + } + + protected PolicyDecision checkAgainstPolicys( IdentifierBundle whoToAuth, RequestedAction whatToAuth){ + PolicyDecision pd = null; + for(PolicyIface policy : this){ try{ pd = policy.isAuthorized(whoToAuth, whatToAuth); if( pd != null ){ @@ -47,8 +111,8 @@ public class PolicyList extends ArrayList implements PolicyIface{ break; if( pd.getAuthorized() == Authorization.UNAUTHORIZED ) break; - // if( pd.getAuthorized() == Authorization.INCONCLUSIVE ) - // continue; + if( pd.getAuthorized() == Authorization.INCONCLUSIVE ) + continue; } else{ log.debug("policy " + policy.toString() + " returned a null PolicyDecision"); } @@ -58,6 +122,6 @@ public class PolicyList extends ArrayList implements PolicyIface{ } log.debug("decision " + pd + " for " + whatToAuth); return pd; - } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/ifaces/PolicyDecision.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/ifaces/PolicyDecision.java index f5e942240..6cd869cca 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/ifaces/PolicyDecision.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/ifaces/PolicyDecision.java @@ -2,6 +2,11 @@ package edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces; +/** + * Object to represent a decision from a policy. The intent is + * that the message would be presented to users to indicate why + * they are not authorized for some action. + */ public interface PolicyDecision { public Authorization getAuthorized(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java index ad668aa6d..04f79e966 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/Actions.java @@ -21,12 +21,16 @@ import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; /** - * An immutable list of OR and AND relationships for the required - * authorizations. A group of AND relationships is a "clause", and the list of - * clauses are in an OR relationship. + * A list of RequiredAction objects. * - * Authorization is successful if ALL of the actions in ANY of the clauses are + * Authorization is considered successful if ALL of the actions are * authorized, or if there are NO clauses. + * + * A previous version of this class had a capability to do OR clauses but + * this feature was unused and hindered composition of Actions + * objects. The ability to do an OR has been moved to AnyRequestedAction + * and AllRequestedAction. + * */ public class Actions { private static final Log log = LogFactory.getLog(Actions.class); @@ -39,104 +43,194 @@ public class Actions { return (actions == null) ? AUTHORIZED : actions; } - private final List> clauseList; + /** + * This is a set of RequestedActions that get ANDed together. + * + * If all of the RequestedAction objects from the + * Sets are authorized, then the Actions object should + * be considered authorized. + */ + private Set requestedActions; + + public Actions(){ + requestedActions= Collections.emptySet(); + } + + /** + * AND together all the RequestedAction from all the actions. + */ + public Actions(Actions... actions){ + Set newActs = new HashSet(); + + for( Actions actionToAnd : actions){ + if( actionToAnd != null && actionToAnd.requestedActions != null ){ + newActs.addAll( actionToAnd.requestedActions ); + } + } + this.requestedActions = Collections.unmodifiableSet( newActs ); + } + public Actions(RequestedAction... actions) { this(Arrays.asList(actions)); } - + public Actions(Collection actions) { - this(Collections.> emptyList(), actions); + this(Collections. emptySet(), actions); } - - private Actions(List> oldList, - Collection newActions) { - List> newList = new ArrayList>(); - newList.addAll(oldList); - - Set newActionSet = new HashSet( - newActions); - if (!newActionSet.isEmpty()) { - newList.add(Collections.unmodifiableSet(newActionSet)); + + private Actions(Set oldList, + Collection newActions) { + + Set newActs = new HashSet(); + + if( oldList != null ){ + newActs.addAll(oldList); } - this.clauseList = Collections.unmodifiableList(newList); + + if ( newActions != null ) { + newActs.addAll( newActions ); + } + + this.requestedActions = Collections.unmodifiableSet(newActs); } - + + /** require all RequestedActions on this and the ones in newActions to authorize.*/ + public Actions and(RequestedAction... newActions){ + return and(Arrays.asList( newActions)); + } + + /** require all RequestedActions on this and the ones in newActions to authorize.*/ + public Actions and(Collection newActions){ + if( newActions == null || newActions.size() == 0) + return this; + else + return new Actions( this.requestedActions, newActions); + } + + /** require all RequestedActions on this and the ones in newActions to authorize.*/ + public Actions and(Actions newActions){ + return new Actions( this.requestedActions, newActions.requestedActions); + } + public Actions or(RequestedAction... newActions) { return or(Arrays.asList(newActions)); } - public Actions or(Collection newActions) { - return new Actions(this.clauseList, newActions); + /** + * OR together this.requestedActions and newActions. + */ + public Actions or(Collection newActions) { + RequestedAction acts; + + if( newActions == null || newActions.size() == 0 ){ + return this; + } + + int thisActionCount = this.requestedActions.size(); + int newActionCount = newActions.size(); + + /* This minimizes the number of extra RequestedActions + * that get created when there is only one in this + * or newActions.*/ + if( thisActionCount == 1 && newActionCount == 1 ){ + return new Actions( + new AnyRequestedAction( + this.requestedActions.iterator().next(), + newActions.iterator().next() )); + } + + if( thisActionCount == 1 && newActionCount > 1 ){ + return new Actions( + new AnyRequestedAction( + this.requestedActions.iterator().next(), + new AllRequestedAction( newActions ))); + } + + if( thisActionCount > 1 && newActionCount == 1){ + return new Actions( new AnyRequestedAction( + new AllRequestedAction( this.requestedActions), + newActions.iterator().next())); + } + + if( thisActionCount > 1 && newActionCount > 1 ){ + return new Actions( + new AnyRequestedAction( + new AllRequestedAction( this.requestedActions ), + new AllRequestedAction( newActions ))); + } + //should never be reached. + log.error("Could not properly create disjunction"); + return null; } public boolean isEmpty() { - for (Set clause : clauseList) { - if (!clause.isEmpty()) { - return false; - } - } - return true; + return this.requestedActions.isEmpty(); } - /** No clauses means everything is authorized */ + + /** + * Are the RequestedAction objects for this Actions authorized + * with the ids and policy? + */ public boolean isAuthorized(PolicyIface policy, IdentifierBundle ids) { - if (clauseList.isEmpty()) { + /* No clauses means everything is authorized */ + if (requestedActions.isEmpty()) { log.debug("Empty Actions is authorized"); return true; } - return isAuthorizedForClauseList(policy, ids); - } - - /** Any entire clause is good enough. */ - private boolean isAuthorizedForClauseList(PolicyIface policy, - IdentifierBundle ids) { - for (Set clause : clauseList) { - if (isAuthorizedForClause(policy, ids, clause)) { - return true; - } + + /* Are all the RequestedAction object authorized? */ + List decisions = new ArrayList(); + for (RequestedAction action : requestedActions) { + PolicyDecision decision = policy.isAuthorized(ids, action); + log.debug("decision for '" + action.getClass().getSimpleName() + "' was: " + + decision); + decisions.add( decision ); } - return false; + return areAllAuthorized( decisions ); } - /** All actions in a clause must be authorized. */ - private static boolean isAuthorizedForClause(PolicyIface policy, - IdentifierBundle ids, Set clause) { - for (RequestedAction action : clause) { - if (!isAuthorizedForAction(policy, ids, action)) { - log.debug("not authorized"); - return false; - } - } - return true; - } - - /** Is this action authorized? */ - private static boolean isAuthorizedForAction(PolicyIface policy, - IdentifierBundle ids, RequestedAction action) { - PolicyDecision decision = policy.isAuthorized(ids, action); - log.debug("decision for '" + action.getClass().getSimpleName() + "' was: " - + decision); - return (decision != null) - && (decision.getAuthorized() == Authorization.AUTHORIZED); + private boolean areAllAuthorized( List decisions ){ + for( PolicyDecision dec : decisions){ + if( dec == null || dec.getAuthorized() != Authorization.AUTHORIZED ){ + return false; + } + } + return true; } + +// /** All actions in a clause must be authorized. */ +// private static boolean isAuthorizedForClause(PolicyIface policy, +// IdentifierBundle ids, Set clause) { +// for (RequestedAction action : clause) { +// if (!isAuthorizedForAction(policy, ids, action)) { +// log.debug("not authorized"); +// return false; +// } +// } +// return true; +// } +// +// /** Is this action authorized? */ +// private static boolean isAuthorizedForAction(PolicyIface policy, +// IdentifierBundle ids, RequestedAction action) { +// PolicyDecision decision = policy.isAuthorized(ids, action); +// log.debug("decision for '" + action.getClass().getSimpleName() + "' was: " +// + decision); +// return (decision != null) +// && (decision.getAuthorized() == Authorization.AUTHORIZED); +// } @Override public String toString() { StringBuffer sb = new StringBuffer("Actions["); - for (Iterator> cit = clauseList.iterator(); cit.hasNext();) { - Set clause = cit.next(); - sb.append("("); - for (Iterator it = clause.iterator(); it.hasNext();) { - RequestedAction action = it.next(); - sb.append(action.getClass().getSimpleName()); - if (it.hasNext()) { - sb.append(", "); - } - } - sb.append(")"); - if (cit.hasNext()) { - sb.append(" or "); + Iterator it = this.requestedActions.iterator(); + while( it.hasNext() ){ + RequestedAction act = it.next(); + sb.append( act.toString() ); + if (it.hasNext()) { + sb.append(", "); } } sb.append("]"); @@ -144,10 +238,46 @@ public class Actions { } /** - * Nobody knows about this action class, so only the root user should be - * authorized for it. + * AND for Actions. + * ANDing with an Action with multiple disjoint clauses is not supported. + * + * To do the AND, we take each ORed clause, and add all of the RequestedActions + * so now in each of the alternative clauses, all of the singleClauseToAnd + * RequestedActions are required. + * + * @throws Exception when multiple disjoint clauses are present on both Actions. */ - private static class UnauthorizedAction extends RequestedAction { - // no members - } + //private void andWithAction( Actions otherAct ) throws Exception{ +// Set singleClauseToAnd; +// List> clauses; +// +// if( otherAct.singleAndClause() ){ +// clauses = this.requestedActions; +// singleClauseToAnd = otherAct.requestedActions.get(0); +// }else if( this.singleAndClause() ){ +// clauses = new ArrayList>( otherAct.requestedActions ); +// singleClauseToAnd = this.requestedActions.get(0); +// }else{ +// //both have multiple ORed clauses, give up +// throw new Exception("ANDing with an Action with multiple disjoint clauses is not supported."); +// } +// +// // +// for( Set clause : clauses){ +// clause.addAll( singleClauseToAnd ); +// } +// this.requestedActions = clauses; + //} + +// private boolean singleAndClause(){ +// return requestedActions.size() == 1; +// } + +// /** +// * Nobody knows about this action class, so only the root user should be +// * authorized for it. +// */ +// private static class UnauthorizedAction extends RequestedAction { +// // no members +// } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AllRequestedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AllRequestedAction.java new file mode 100644 index 000000000..19408d57c --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AllRequestedAction.java @@ -0,0 +1,28 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +/** + * This action should be authorized if all of its subActions are authorized. + * @author bdc34 + */ +public class AllRequestedAction extends RequestedAction{ + private final Collection subActions ; + + public AllRequestedAction(RequestedAction... actions ){ + this( Arrays.asList( actions )); + } + public AllRequestedAction(Collection subActions){ + this.subActions = Collections.unmodifiableCollection( subActions ); + } + + public Collection getRequestedActions(){ + return subActions; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AnyRequestedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AnyRequestedAction.java new file mode 100644 index 000000000..e90eb2d95 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AnyRequestedAction.java @@ -0,0 +1,27 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +public class AnyRequestedAction extends RequestedAction { + private final Collection subActions ; + + public AnyRequestedAction(RequestedAction... acts){ + this( Arrays.asList( acts) ); + } + + public AnyRequestedAction(Collection subActions){ + this.subActions = Collections.unmodifiableCollection( subActions ); + } + + public Collection getRequestedActions(){ + return subActions; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizedAction.java new file mode 100644 index 000000000..6fed03dc1 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizedAction.java @@ -0,0 +1,13 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +/** + * Action that should always be authorized. Mainly for testing. + * @author bdc34 + */ +public class AuthorizedAction extends RequestedAction{ + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/SimpleRequestedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/SimpleRequestedAction.java index 89beacf13..117f89fda 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/SimpleRequestedAction.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/SimpleRequestedAction.java @@ -8,31 +8,31 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAct * A RequestedAction that can be recognized by a SimplePermission. */ public class SimpleRequestedAction extends RequestedAction { - private final String localName; + private final String uri; - public SimpleRequestedAction(String localName) { - if (localName == null) { - throw new NullPointerException("localName may not be null."); + public SimpleRequestedAction(String uri) { + if (uri == null) { + throw new NullPointerException("uri may not be null."); } - this.localName = localName; + this.uri = uri; } @Override public String getURI() { - return "java:" + this.getClass().getName() + "#" + localName; + return uri; } @Override public int hashCode() { - return (localName == null) ? 0 : localName.hashCode(); + return uri.hashCode(); } @Override public boolean equals(Object o) { if (o instanceof SimpleRequestedAction) { SimpleRequestedAction that = (SimpleRequestedAction) o; - return equivalent(this.localName, that.localName); + return equivalent(this.uri, that.uri); } return false; } @@ -43,7 +43,7 @@ public class SimpleRequestedAction extends RequestedAction { @Override public String toString() { - return "SimpleRequestedAction['" + localName + "']"; + return "SimpleRequestedAction['" + uri + "']"; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/UnauthorizedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/UnauthorizedAction.java new file mode 100644 index 000000000..011da2862 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/UnauthorizedAction.java @@ -0,0 +1,13 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +/** + * Action that is always unauthorized. Mainly for testing. + * @author bdc34 + */ +public class UnauthorizedAction extends RequestedAction{ + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequiresActions.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequiresActions.java new file mode 100644 index 000000000..625e48a80 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequiresActions.java @@ -0,0 +1,36 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; + +/** + * Interface to objects that provide a list of Actions that are + * required for the object to be used. + * + * This is intended to provide a way to setup DataGetter + * objects to be used with the FreemarkerHttpServlet.requiredActions() + * method. + * + * @author bdc34 + */ +public interface RequiresActions { + + /** + * Returns Actions that are required to be authorized for + * the object to be used. + * + * The code that is calling this method + * could use methods from PolicyHelper to check if the + * request has authorization to do these Actions. The code + * calling this method would then have the ability to + * deny the action if it is not authorized. + * + * @param vreq + * @return Should not be null. Return Actions.AUTHORIZED + * if no authorization is required to do use the object. + */ + public Actions requiredActions(VitroRequest vreq) ; + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/BaseResourceBean.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/BaseResourceBean.java index aee28c2d6..271fbfbd1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/BaseResourceBean.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/BaseResourceBean.java @@ -13,7 +13,7 @@ import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.ResourceFactory; import edu.cornell.mannlib.vedit.beans.LoginStatusBean; -import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader; +import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; public class BaseResourceBean implements ResourceBean { @@ -80,13 +80,13 @@ public class BaseResourceBean implements ResourceBean { } Set roles = u.getPermissionSetUris(); - if (roles.contains(PermissionSetsLoader.URI_DBA)) { + if (roles.contains(PermissionSets.URI_DBA)) { return DB_ADMIN; - } else if (roles.contains(PermissionSetsLoader.URI_CURATOR)) { + } else if (roles.contains(PermissionSets.URI_CURATOR)) { return CURATOR; - } else if (roles.contains(PermissionSetsLoader.URI_EDITOR)) { + } else if (roles.contains(PermissionSets.URI_EDITOR)) { return EDITOR; - } else if (roles.contains(PermissionSetsLoader.URI_SELF_EDITOR)) { + } else if (roles.contains(PermissionSets.URI_SELF_EDITOR)) { return SELF; } else { // Logged in but with no recognized role? Make them SELF diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServlet.java index 6cf006637..ab6d52616 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServlet.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.controller; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; @@ -21,6 +22,10 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.github.jsonldjava.core.JSONLD; +import com.github.jsonldjava.core.JSONLDProcessingError; +import com.github.jsonldjava.impl.JenaRDFParser; +import com.github.jsonldjava.utils.JSONUtils; import com.hp.hpl.jena.query.Query; import com.hp.hpl.jena.query.QuerySolution; import com.hp.hpl.jena.query.ResultSet; @@ -37,6 +42,8 @@ import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.beans.Ontology; import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ModelSerializationFormat; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ResultFormat; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryUtils; @@ -44,42 +51,40 @@ import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryUtils; /** * Services a sparql query. This will return a simple error message and a 501 if - * there is no jena Model. + * there is no Model. * * @author bdc34 * */ public class SparqlQueryServlet extends BaseEditController { private static final Log log = LogFactory.getLog(SparqlQueryServlet.class.getName()); - - protected static HashMapformatSymbols = new HashMap(); - static{ - formatSymbols.put( ResultSetFormat.syntaxXML.getSymbol(), ResultSetFormat.syntaxXML); - formatSymbols.put( ResultSetFormat.syntaxRDF_XML.getSymbol(), ResultSetFormat.syntaxRDF_XML); - formatSymbols.put( ResultSetFormat.syntaxRDF_N3.getSymbol(), ResultSetFormat.syntaxRDF_N3); - formatSymbols.put( ResultSetFormat.syntaxText.getSymbol() , ResultSetFormat.syntaxText); - formatSymbols.put( ResultSetFormat.syntaxJSON.getSymbol() , ResultSetFormat.syntaxJSON); - formatSymbols.put( "vitro:csv", null); - } - - protected static HashMap rdfFormatSymbols = new HashMap(); - static { - rdfFormatSymbols.put( "RDF/XML", "application/rdf+xml" ); - rdfFormatSymbols.put( "RDF/XML-ABBREV", "application/rdf+xml" ); - rdfFormatSymbols.put( "N3", "text/n3" ); - rdfFormatSymbols.put( "N-TRIPLE", "text/plain" ); - rdfFormatSymbols.put( "TTL", "application/x-turtle" ); - } - protected static HashMapmimeTypes = new HashMap(); - static{ - mimeTypes.put( ResultSetFormat.syntaxXML.getSymbol() , "text/xml" ); - mimeTypes.put( ResultSetFormat.syntaxRDF_XML.getSymbol(), "application/rdf+xml" ); - mimeTypes.put( ResultSetFormat.syntaxRDF_N3.getSymbol(), "text/plain" ); - mimeTypes.put( ResultSetFormat.syntaxText.getSymbol() , "text/plain"); - mimeTypes.put( ResultSetFormat.syntaxJSON.getSymbol(), "application/javascript" ); - mimeTypes.put( "vitro:csv", "text/csv"); - } + private final static boolean CONVERT = true; + + /** + * format configurations for SELECT queries. + */ + protected static HashMap rsFormats = new HashMap(); + + private static RSFormatConfig[] rsfs = { + new RSFormatConfig( "RS_XML", !CONVERT, ResultFormat.XML, null, "text/xml"), + new RSFormatConfig( "RS_TEXT", !CONVERT, ResultFormat.TEXT, null, "text/plain"), + new RSFormatConfig( "vitro:csv", !CONVERT, ResultFormat.CSV, null, "text/csv"), + new RSFormatConfig( "RS_JSON", !CONVERT, ResultFormat.JSON, null, "application/javascript") }; + + /** + * format configurations for CONSTRUCT/DESCRIBE queries. + */ + protected static HashMap modelFormats = + new HashMap(); + + private static ModelFormatConfig[] fmts = { + new ModelFormatConfig("RDF/XML", !CONVERT, ModelSerializationFormat.RDFXML, null, "application/rdf+xml" ), + new ModelFormatConfig("RDF/XML-ABBREV", CONVERT, ModelSerializationFormat.N3, "RDF/XML-ABBREV", "application/rdf+xml" ), + new ModelFormatConfig("N3", !CONVERT, ModelSerializationFormat.N3, null, "text/n3" ), + new ModelFormatConfig("N-TRIPLE", !CONVERT, ModelSerializationFormat.NTRIPLE, null, "text/plain" ), + new ModelFormatConfig("TTL", CONVERT, ModelSerializationFormat.N3, "TTL", "application/x-turtle" ), + new ModelFormatConfig("JSON-LD", CONVERT, ModelSerializationFormat.N3, "JSON-LD", "application/javascript" ) }; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) @@ -119,9 +124,9 @@ public class SparqlQueryServlet extends BaseEditController { if( queryParam == null || "".equals(queryParam) || resultFormatParam == null || "".equals(resultFormatParam) || - !formatSymbols.containsKey(resultFormatParam) || + !rsFormats.containsKey( resultFormatParam ) || rdfResultFormatParam == null || "".equals(rdfResultFormatParam) || - !rdfFormatSymbols.keySet().contains(rdfResultFormatParam) ) { + !modelFormats.containsKey( rdfResultFormatParam ) ) { doHelp(request,response); return; } @@ -135,68 +140,128 @@ public class SparqlQueryServlet extends BaseEditController { String resultFormatParam, String rdfResultFormatParam, String queryParam, - RDFService rdfService ) throws IOException { - - ResultSetFormat rsf = null; + RDFService rdfService ) throws IOException { /* BJL23 2008-11-06 * modified to support CSV output. * Unfortunately, ARQ doesn't make it easy to * do this by implementing a new ResultSetFormat, because * ResultSetFormatter is hardwired with expected values. - * This slightly ugly approach will have to do for now. - */ - if ( !("vitro:csv").equals(resultFormatParam) ) { - rsf = formatSymbols.get(resultFormatParam); - } - String mimeType = mimeTypes.get(resultFormatParam); + * This slightly ugly approach will have to do for now. + */ +// if ( !("vitro:csv").equals(resultFormatParam) ) { +// rsf = selectFormatSymbols.get(resultFormatParam); +// } +// String mimeType = rdfFormatToMimeType.get(resultFormatParam); try{ Query query = SparqlQueryUtils.create(queryParam); if( query.isSelectType() ){ - ResultSet results = null; - results = ResultSetFactory.fromJSON(rdfService.sparqlSelectQuery( - queryParam, RDFService.ResultFormat.JSON)); - response.setContentType(mimeType); - if (rsf != null) { - OutputStream out = response.getOutputStream(); - ResultSetFormatter.output(out, results, rsf); - } else { - Writer out = response.getWriter(); - toCsv(out, results); - } - } else { - Model resultModel = null; - if( query.isConstructType() ){ - resultModel = RDFServiceUtils.parseModel( - rdfService.sparqlConstructQuery( - queryParam, - RDFService.ModelSerializationFormat.N3), - RDFService.ModelSerializationFormat.N3); - }else if ( query.isDescribeType() ){ - resultModel = RDFServiceUtils.parseModel( - rdfService.sparqlDescribeQuery( - queryParam, - RDFService.ModelSerializationFormat.N3), - RDFService.ModelSerializationFormat.N3); - }else if(query.isAskType()){ - // Irrespective of the ResultFormatParam, - // this always prints a boolean to the default OutputStream. - String result = (rdfService.sparqlAskQuery(queryParam) == true) - ? "true" - : "false"; - PrintWriter p = response.getWriter(); - p.write(result); - return; - } - response.setContentType(rdfFormatSymbols.get(rdfResultFormatParam)); - OutputStream out = response.getOutputStream(); - resultModel.write(out, rdfResultFormatParam); + doSelectQuery( queryParam, rdfService, resultFormatParam, response); + } else if(query.isAskType()){ + // Irrespective of the ResultFormatParam, + // this always prints a boolean to the default OutputStream. + String result = (rdfService.sparqlAskQuery(queryParam) == true) + ? "true" + : "false"; + PrintWriter p = response.getWriter(); + p.write(result); + return; + } else { + doModelResultQuery( query, rdfService, rdfResultFormatParam, response); } } catch (RDFServiceException e) { throw new RuntimeException(e); } } + /** + * Execute the query and send the result to out. Attempt to + * send the RDFService the same format as the rdfResultFormatParam + * so that the results from the RDFService can be directly piped to the client. + * @param rdfService + * @throws IOException + * @throws RDFServiceException + */ + private void doSelectQuery( String queryParam, + RDFService rdfService, String resultFormatParam, + HttpServletResponse response) throws IOException, RDFServiceException{ + RSFormatConfig config = rsFormats.get( resultFormatParam ); + + if( ! config.converstionFromWireFormat ){ + response.setContentType( config.responseMimeType ); + InputStream results = rdfService.sparqlSelectQuery(queryParam, config.wireFormat ); + pipe( results, response.getOutputStream() ); + }else{ + //always use JSON when conversion is needed. + InputStream results = rdfService.sparqlSelectQuery(queryParam, ResultFormat.JSON ); + + response.setContentType( config.responseMimeType ); + + ResultSet rs = ResultSetFactory.fromJSON( results ); + OutputStream out = response.getOutputStream(); + ResultSetFormatter.output(out, rs, config.jenaResponseFormat); + + // } else { + // Writer out = response.getWriter(); + // toCsv(out, results); + //} + } + } + + /** + * Execute the query and send the result to out. Attempt to + * send the RDFService the same format as the rdfResultFormatParam + * so that the results from the RDFService can be directly piped to the client. + * @param rdfService + * @throws IOException + * @throws RDFServiceException + * @throws + */ + private void doModelResultQuery( Query query, + RDFService rdfService, String rdfResultFormatParam, + HttpServletResponse response) throws IOException, RDFServiceException{ + + //config drives what formats and conversions to use + ModelFormatConfig config = modelFormats.get( rdfResultFormatParam ); + + InputStream rawResult = null; + if( query.isConstructType() ){ + rawResult= rdfService.sparqlConstructQuery( query.toString(), config.wireFormat ); + }else if ( query.isDescribeType() ){ + rawResult = rdfService.sparqlDescribeQuery( query.toString(), config.wireFormat ); + } + + response.setContentType( config.responseMimeType ); + + if( config.converstionFromWireFormat ){ + Model resultModel = RDFServiceUtils.parseModel( rawResult, config.wireFormat ); + if( "JSON-LD".equals( config.jenaResponseFormat )){ + //since jena 2.6.4 doesn't support JSON-LD we do it + try { + JenaRDFParser parser = new JenaRDFParser(); + Object json = JSONLD.fromRDF(resultModel, parser); + JSONUtils.write(response.getWriter(), json); + } catch (JSONLDProcessingError e) { + throw new RDFServiceException("Could not convert from Jena model to JSON-LD", e); + } + }else{ + OutputStream out = response.getOutputStream(); + resultModel.write(out, config.jenaResponseFormat ); + } + }else{ + OutputStream out = response.getOutputStream(); + pipe( rawResult, out ); + } + } + + private void pipe( InputStream in, OutputStream out) throws IOException{ + int size; + byte[] buffer = new byte[4096]; + while( (size = in.read(buffer)) > -1 ) { + out.write(buffer,0,size); + } + } + private void doNoModelInContext(HttpServletResponse res){ try { res.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); @@ -290,8 +355,6 @@ public class SparqlQueryServlet extends BaseEditController { req.setAttribute("prefixList", prefixList); - // nac26: 2009-09-25 - this was causing problems in safari on localhost installations because the href did not include the context. The edit.css is not being used here anyway (or anywhere else for that matter) - // req.setAttribute("css", ""); req.setAttribute("title","SPARQL Query"); req.setAttribute("bodyJsp", "/admin/sparqlquery/sparqlForm.jsp"); @@ -299,4 +362,55 @@ public class SparqlQueryServlet extends BaseEditController { rd.forward(req,res); } + + public static class ModelFormatConfig{ + public String valueFromForm; + public boolean converstionFromWireFormat; + public RDFService.ModelSerializationFormat wireFormat; + public String jenaResponseFormat; + public String responseMimeType; + + public ModelFormatConfig( String valueFromForm, + boolean converstionFromWireFormat, + RDFService.ModelSerializationFormat wireFormat, + String jenaResponseFormat, + String responseMimeType){ + this.valueFromForm = valueFromForm; + this.converstionFromWireFormat = converstionFromWireFormat; + this.wireFormat = wireFormat; + this.jenaResponseFormat = jenaResponseFormat; + this.responseMimeType = responseMimeType; + } + } + + public static class RSFormatConfig{ + public String valueFromForm; + public boolean converstionFromWireFormat; + public ResultFormat wireFormat; + public ResultSetFormat jenaResponseFormat; + public String responseMimeType; + + public RSFormatConfig( String valueFromForm, + boolean converstionFromWireFormat, + ResultFormat wireFormat, + ResultSetFormat jenaResponseFormat, + String responseMimeType ){ + this.valueFromForm = valueFromForm; + this.converstionFromWireFormat = converstionFromWireFormat; + this.wireFormat = wireFormat; + this.jenaResponseFormat = jenaResponseFormat; + this.responseMimeType = responseMimeType; + } + } + + static{ + /* move the lists of configs into maps for easy lookup */ + for( RSFormatConfig rsfc : rsfs ){ + rsFormats.put( rsfc.valueFromForm, rsfc ); + } + for( ModelFormatConfig mfc : fmts ){ + modelFormats.put( mfc.valueFromForm, mfc); + } + } + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java index dd25369b9..df1e29eee 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java @@ -45,6 +45,7 @@ public class VitroHttpServlet extends HttpServlet { public final static String HTML_MIMETYPE = "text/html"; public final static String RDFXML_MIMETYPE = "application/rdf+xml"; + public final static String JSON_MIMETYPE = "application/json"; public final static String N3_MIMETYPE = "text/n3"; // unofficial and unregistered public final static String TTL_MIMETYPE = "text/turtle"; // unofficial and unregistered diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroRequest.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroRequest.java index 854c09ac7..6d1e10cc1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroRequest.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroRequest.java @@ -95,6 +95,14 @@ public class VitroRequest extends HttpServletRequestWrapper { setAttribute("dataset", dataset); } + public Dataset getUnfilteredDataset() { + return (Dataset) getAttribute("unfilteredDataset"); + } + + public void setUnfilteredDataset(Dataset dataset) { + setAttribute("unfilteredDataset", dataset); + } + //Method that retrieves write model, returns special model in case of write model public OntModel getWriteModel() { //if special write model doesn't exist use get ont model @@ -171,6 +179,18 @@ public class VitroRequest extends HttpServletRequestWrapper { return getWebappDaoFactory().getApplicationDao().getApplicationBean(); } + /** + * Gets the the ip of the client. + * This will be X-forwarded-for header or, if that header is not + * set, getRemoteAddr(). This still may not be the client's address + * as they may be using a proxy. + * + */ + public String getClientAddr(){ + String xff = getHeader("x-forwarded-for"); + return ( xff == null || xff.trim().isEmpty() ) ? getRemoteAddr() : xff; + } + @SuppressWarnings("unchecked") @Override public Map getParameterMap() { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java index 7ae86c744..c48ba8691 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java @@ -11,7 +11,7 @@ import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; -import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader; +import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; @@ -200,7 +200,7 @@ public class UserAccountsFirstTimeExternalPage extends UserAccountsPage { u.setLoginCount(0); u.setStatus(Status.ACTIVE); u.setPermissionSetUris(Collections - .singleton(PermissionSetsLoader.URI_SELF_EDITOR)); + .singleton(PermissionSets.URI_SELF_EDITOR)); userAccountsDao.insertUserAccount(u); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/ajax/VitroAjaxController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/ajax/VitroAjaxController.java index 955a8e2a8..ab848e895 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/ajax/VitroAjaxController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/ajax/VitroAjaxController.java @@ -17,7 +17,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfigurationLoader; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import freemarker.template.Configuration; import freemarker.template.Template; @@ -73,8 +73,8 @@ public abstract class VitroAjaxController extends HttpServlet { * Process data through a Freemarker template and output the result. */ protected void writeTemplate(String templateName, Map map, - VitroRequest vreq, HttpServletResponse response) { - Configuration config = FreemarkerConfigurationLoader.getConfig(vreq); + HttpServletRequest req, HttpServletResponse response) { + Configuration config = FreemarkerConfiguration.getConfig(req); try { Template template = config.getTemplate(templateName); PrintWriter out = response.getWriter(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java index c11f66c96..236c2c38f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ClassgroupRetryController.java @@ -65,13 +65,10 @@ public class ClassgroupRetryController extends BaseEditController { } catch (NullPointerException e) { log.error("Need to implement 'record not found' error message."); } - if (vclassGroupForEditing == null) { - try { - String uriToFind = new String(request.getParameter("uri").getBytes("ISO-8859-1"),"UTF-8"); - vclassGroupForEditing = (VClassGroup)cgDao.getGroupByURI(uriToFind); - } catch (java.io.UnsupportedEncodingException uee) { - // forget it - } + if (vclassGroupForEditing == null) { + //UTF-8 expected due to URIEncoding on Connector in server.xml + String uriToFind = new String(request.getParameter("uri")); + vclassGroupForEditing = (VClassGroup)cgDao.getGroupByURI(uriToFind); } } else { vclassGroupForEditing = new VClassGroup(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java index 362457344..0eeb5dc28 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyGroupRetryController.java @@ -65,12 +65,9 @@ public class PropertyGroupRetryController extends BaseEditController { log.error("Need to implement 'record not found' error message."); } if (propertyGroupForEditing == null) { - try { - String uriToFind = new String(request.getParameter("uri").getBytes("ISO-8859-1"),"UTF-8"); - propertyGroupForEditing = (PropertyGroup)pgDao.getGroupByURI(uriToFind); - } catch (java.io.UnsupportedEncodingException uee) { - // forget it - } + // UTF-8 expected due to URIEncoding on Connector element in server.xml + String uriToFind = new String(request.getParameter("uri")); + propertyGroupForEditing = (PropertyGroup)pgDao.getGroupByURI(uriToFind); } } else { propertyGroupForEditing = new PropertyGroup(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerComponentGenerator.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerComponentGenerator.java index d71dd7366..e01b14cd9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerComponentGenerator.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerComponentGenerator.java @@ -61,6 +61,8 @@ public class FreemarkerComponentGenerator extends FreemarkerHttpServlet { return get(templateName, root, request); } + // JB Because this is pretending to be a servlet, but the init method has not been called, providing the context. + // Do that in the constructor, and we should be fine. VIVO-251 // RY We need the servlet context in getConfig(). For some reason using the method inherited from // GenericServlet bombs. @Override diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java deleted file mode 100644 index b358b87b6..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfiguration.java +++ /dev/null @@ -1,379 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.controller.freemarker; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean; -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; -import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route; -import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants; -import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel; -import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter; -import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetterUtils; -import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective; -import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective; -import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective; -import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualLocalNameMethod; -import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualPlaceholderImageUrlMethod; -import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualProfileUrlMethod; -import freemarker.cache.ClassTemplateLoader; -import freemarker.cache.FileTemplateLoader; -import freemarker.cache.MultiTemplateLoader; -import freemarker.cache.TemplateLoader; -import freemarker.core.Environment; -import freemarker.ext.beans.BeansWrapper; -import freemarker.template.Configuration; -import freemarker.template.DefaultObjectWrapper; -import freemarker.template.ObjectWrapper; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import freemarker.template.TemplateModelException; -import freemarker.template.utility.DeepUnwrap; - -public class FreemarkerConfiguration extends Configuration { - - private static final Log log = LogFactory.getLog(FreemarkerConfiguration.class); - - private static final String PROPERTY_DEVELOPER_DEFEAT_CACHE = "developer.defeatFreemarkerCache"; - private static final String PROPERTY_DEVELOPER_INSERT_DELIMITERS = "developer.insertFreemarkerDelimiters"; - - private final String themeDir; - private final ServletContext context; - private final ApplicationBean appBean; - private final ConfigurationProperties props; - - FreemarkerConfiguration(String themeDir, ApplicationBean appBean, ServletContext context) { - - this.themeDir = themeDir; - this.context = context; - this.appBean = appBean; - this.props = ConfigurationProperties.getBean(context); - - String flag = props.getProperty(PROPERTY_DEVELOPER_DEFEAT_CACHE, "false"); - if (Boolean.valueOf(flag.trim())) { - log.debug("Disabling Freemarker template caching in development build."); - setTemplateUpdateDelay(0); // no template caching in development - } else { - int delay = 60; - log.debug("Setting Freemarker template cache update delay to " + delay + "."); - setTemplateUpdateDelay(delay); // in seconds; Freemarker default is 5 - } - - // Specify how templates will see the data model. - // The Freemarker default wrapper exposes set methods and get methods that take - // arguments. We block exposure to these methods by default. - BeansWrapper wrapper = new DefaultObjectWrapper(); - wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY); - setObjectWrapper(wrapper); - - // Set some formatting defaults. These can be overridden at the template - // or environment (template-processing) level, or for an individual - // token by using built-ins. - setLocale(java.util.Locale.US); - - String dateFormat = "M/d/yyyy"; - setDateFormat(dateFormat); - String timeFormat = "h:mm a"; - setTimeFormat(timeFormat); - setDateTimeFormat(dateFormat + " " + timeFormat); - - //config.setNumberFormat("#,##0.##"); - - try { - setSetting("url_escaping_charset", "ISO-8859-1"); - } catch (TemplateException e) { - log.error("Error setting value for url_escaping_charset."); - } - - setTemplateLoader(createTemplateLoader()); - - setSharedVariables(); - - } - - /** - * These are values that are accessible to all - * templates loaded by the Configuration's TemplateLoader. They - * should be application- rather than request-specific. - */ - private void setSharedVariables() { - - Map sharedVariables = new HashMap(); - - sharedVariables.put("siteName", appBean.getApplicationName()); - sharedVariables.put("version", getRevisionInfo()); - sharedVariables.put("urls", getSiteUrls()); - sharedVariables.put("themeDir", themeDir); - sharedVariables.put("currentTheme", themeDir.substring(themeDir.lastIndexOf('/')+1)); - - sharedVariables.putAll(getDirectives()); - sharedVariables.putAll(getMethods()); - sharedVariables.put("siteTagline", appBean.getShortHand()); - - //Put in edit configuration constants - useful for freemarker templates/editing - sharedVariables.put("editConfigurationConstants", EditConfigurationConstants.exportConstants()); - - for ( Map.Entry variable : sharedVariables.entrySet() ) { - try { - setSharedVariable(variable.getKey(), variable.getValue()); - } catch (TemplateModelException e) { - log.error("Could not set shared variable '" + variable.getKey() + "' in Freemarker configuration"); - } - } - } - - private final Map getRevisionInfo() { - Map map = new HashMap(); - map.put("label", RevisionInfoBean.getBean(context) - .getReleaseLabel()); - map.put("moreInfoUrl", UrlBuilder.getUrl("/revisionInfo")); - return map; - } - - private final Map getSiteUrls() { - Map urls = new HashMap(); - - // Templates use this to construct urls. - urls.put("base", context.getContextPath()); - - urls.put("home", UrlBuilder.getHomeUrl()); - urls.put("about", UrlBuilder.getUrl(Route.ABOUT)); - urls.put("search", UrlBuilder.getUrl(Route.SEARCH)); - urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE)); - urls.put("login", UrlBuilder.getLoginUrl()); - urls.put("logout", UrlBuilder.getLogoutUrl()); - urls.put("siteAdmin", UrlBuilder.getUrl(Route.SITE_ADMIN)); - urls.put("themeImages", UrlBuilder.getUrl(themeDir + "/images")); - urls.put("images", UrlBuilder.getUrl("/images")); - urls.put("theme", UrlBuilder.getUrl(themeDir)); - urls.put("index", UrlBuilder.getUrl("/browse")); - - return urls; - } - - private static Map getDirectives() { - Map map = new HashMap(); - map.put("dump", new freemarker.ext.dump.DumpDirective()); - map.put("dumpAll", new freemarker.ext.dump.DumpAllDirective()); - map.put("help", new freemarker.ext.dump.HelpDirective()); - map.put("shortView", new IndividualShortViewDirective()); - map.put("url", new UrlDirective()); - map.put("widget", new WidgetDirective()); - - - return map; - } - - private static Map getMethods() { - Map map = new HashMap(); - map.put("profileUrl", new IndividualProfileUrlMethod()); - map.put("localName", new IndividualLocalNameMethod()); - map.put("placeholderImageUrl", new IndividualPlaceholderImageUrlMethod()); - map.put("i18n", new I18nMethodModel()); - return map; - } - - // Define template locations. Template loader will look first in the theme-specific - // location, then in the vitro location. - protected final TemplateLoader createTemplateLoader() { - - List loaders = new ArrayList(); - MultiTemplateLoader mtl = null; - try { - // Theme template loader - String themeTemplatePath = context.getRealPath(themeDir) + "/templates"; - File themeTemplateDir = new File(themeTemplatePath); - // Handle the case where there's no theme template directory gracefully - if (themeTemplateDir.exists()) { - FileTemplateLoader themeFtl = new FileTemplateLoader(themeTemplateDir); - loaders.add(themeFtl); - } - - // Vitro template loader - String vitroTemplatePath = context.getRealPath("/templates/freemarker"); - loaders.add(new FlatteningTemplateLoader(new File(vitroTemplatePath))); - - loaders.add(new ClassTemplateLoader(getClass(), "")); - - TemplateLoader[] loaderArray = loaders.toArray(new TemplateLoader[loaders.size()]); - mtl = new MultiTemplateLoader(loaderArray); - - } catch (IOException e) { - log.error("Error creating template loaders"); - } - - // Add the ability to add delimiters to the templates, based on - // settings. - if (Boolean.valueOf(props.getProperty(PROPERTY_DEVELOPER_INSERT_DELIMITERS))) { - return new DelimitingTemplateLoader(mtl); - } else { - return mtl; - } - } - - /** - * Override getTemplate(), so we can apply DataGetters to all included - * templates. - * - * This won't work for top-level Templates, since the Environment hasn't - * been created yet. When TemplateProcessingHelper creates the Environment, - * it must call retrieveAndRunDataGetters() for the top-level Template. - */ - @Override - public Template getTemplate(String name, Locale locale, String encoding, - boolean parse) throws IOException { - Template template = super.getTemplate(name, locale, encoding, parse); - - if (template == null) { - log.debug("Template '" + name + "' not found for locale '" + locale + "'."); - return template; - } - - Environment env = getEnvironment(); - if (env == null) { - log.debug("Not fetching data getters for template '" + template.getName() + "'. No environment."); - return template; - } - - retrieveAndRunDataGetters(env, template.getName()); - return template; - } - - - /** - * Find the DataGetters for this template, and apply them to the Freemarker - * environment. - */ - public static void retrieveAndRunDataGetters(Environment env, String templateName) { - HttpServletRequest req = (HttpServletRequest) env.getCustomAttribute("request"); - VitroRequest vreq = new VitroRequest(req); - - if (dataGettersAlreadyApplied(env, templateName)) { - log.debug("DataGetters for '" + templateName+"' have already been applied"); - return; - } - - try { - List dgList = DataGetterUtils.getDataGettersForTemplate( - vreq, vreq.getDisplayModel(), templateName); - log.debug("Retrieved " + dgList.size() + " data getters for template '" + templateName + "'"); - - @SuppressWarnings("unchecked") - Map dataMap = (Map) DeepUnwrap.permissiveUnwrap(env.getDataModel()); - for (DataGetter dg : dgList) { - applyDataGetter(dg, env, dataMap); - } - } catch (Exception e) { - log.warn(e, e); - } - } - - /** - * Have the DataGetters for this template already been applied to this environment? - * If not, record that they are being applied now. - */ - @SuppressWarnings("unchecked") - private static boolean dataGettersAlreadyApplied(Environment env, String templateName) { - Set names; - Object o = env.getCustomAttribute("dataGettersApplied"); - if (o instanceof Set) { - names = (Set) o; - } else { - names = new HashSet(); - } - - boolean added = names.add(templateName); - if (added) { - env.setCustomAttribute("dataGettersApplied", names); - return false; - } else { - return true; - } - } - - /** - * Get the data from a DataGetter, and store it in global variables in the - * Freemarker environment. - */ - private static void applyDataGetter(DataGetter dg, Environment env, - Map dataMap) throws TemplateModelException { - Map moreData = dg.getData(dataMap); - ObjectWrapper wrapper = env.getObjectWrapper(); - if (moreData != null) { - for (String key : moreData.keySet()) { - Object value = moreData.get(key); - env.setGlobalVariable(key, wrapper.wrap(value)); - log.debug("Stored in environment: '" + key + "' = '" + value + "'"); - } - } - } - - // ---------------------------------------------------------------------- - // Request info and overrides - // ---------------------------------------------------------------------- - - private ThreadLocal reqInfo = new ThreadLocal<>(); - - void setRequestInfo(HttpServletRequest req) { - reqInfo.set(new FreemarkerRequestInfo(req)); - } - - @Override - public Object getCustomAttribute(String name) { - if ("request".equals(name)) { - return reqInfo.get().getRequest(); - } else { - return super.getCustomAttribute(name); - } - } - - @Override - public String[] getCustomAttributeNames() { - String[] nameArray = super.getCustomAttributeNames(); - Set nameSet = new HashSet(Arrays.asList(nameArray)); - nameSet.add("request"); - return nameSet.toArray(new String[nameSet.size()]); - } - - @Override - public Locale getLocale() { - return reqInfo.get().getLocale(); - } - - - - public static class FreemarkerRequestInfo { - private final HttpServletRequest req; - - public FreemarkerRequestInfo(HttpServletRequest req) { - this.req = req; - } - - public HttpServletRequest getRequest() { - return req; - } - - public Locale getLocale() { - return req.getLocale(); - } - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfigurationLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfigurationLoader.java deleted file mode 100644 index 340686d19..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerConfigurationLoader.java +++ /dev/null @@ -1,66 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.controller.freemarker; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; - -public class FreemarkerConfigurationLoader { - private static final Log log = LogFactory - .getLog(FreemarkerConfigurationLoader.class); - - private static final Map themeToConfigMap = new HashMap(); - - public static FreemarkerConfiguration getConfig(VitroRequest vreq) { - String themeDir = getThemeDir(vreq.getAppBean()); - FreemarkerConfiguration config = getConfigForTheme(themeDir, vreq.getAppBean(), vreq.getSession().getServletContext()); - config.setRequestInfo(vreq); - return config; - } - - private static String getThemeDir(ApplicationBean appBean) { - if (appBean == null) { - log.error("Cannot get themeDir from null application bean"); - return null; - } - - String themeDir = appBean.getThemeDir(); - if (themeDir == null) { - log.error("themeDir is null"); - return null; - } - - return themeDir.replaceAll("/$", ""); - } - - /** - * The Configuration is theme-specific because: - * - * 1. The template loader is theme-specific, since it specifies a theme - * directory to load templates from. - * - * 2. Some shared variables are theme-specific. - */ - private static FreemarkerConfiguration getConfigForTheme(String themeDir, - ApplicationBean appBean, ServletContext context) { - synchronized (themeToConfigMap) { - if (themeToConfigMap.containsKey(themeDir)) { - return themeToConfigMap.get(themeDir); - } else { - FreemarkerConfiguration config = new FreemarkerConfiguration( - themeDir, appBean, context); - themeToConfigMap.put(themeDir, config); - return config; - } - } - } - -} \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java index 3cf0faeb0..1530aaeee 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java @@ -2,7 +2,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker; -import static javax.mail.Message.RecipientType.TO; +import static javax.mail.Message.RecipientType.*; import java.io.IOException; import java.io.PrintWriter; @@ -20,6 +20,11 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.github.jsonldjava.core.JSONLD; +import com.github.jsonldjava.core.JSONLDProcessingError; +import com.github.jsonldjava.impl.JenaRDFParser; +import com.github.jsonldjava.utils.JSONUtils; + import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; @@ -37,16 +42,19 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.Res import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory; import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.Tags; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.User; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.menu.MainMenu; import freemarker.ext.beans.BeansWrapper; +import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import freemarker.template.utility.DeepUnwrap; -public class FreemarkerHttpServlet extends VitroHttpServlet { +public class FreemarkerHttpServlet extends VitroHttpServlet { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(FreemarkerHttpServlet.class); @@ -201,7 +209,8 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { * NB This method can't be static, because then the superclass method gets called rather than * the subclass method. For the same reason, it can't refer to a static or instance field * REQUIRED_ACTIONS which is overridden in the subclass. - */ + * + */ protected Actions requiredActions(VitroRequest vreq) { return Actions.AUTHORIZED; } @@ -304,17 +313,29 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { String mediaType = values.getContentType().getMediaType(); response.setContentType(mediaType); - - String format = ""; - if ( RDFXML_MIMETYPE.equals(mediaType)) { - format = "RDF/XML"; - } else if( N3_MIMETYPE.equals(mediaType)) { - format = "N3"; - } else if ( TTL_MIMETYPE.equals(mediaType)) { - format ="TTL"; + + if ( JSON_MIMETYPE.equals(mediaType)){ + //json-ld is not supported by jena v2.6.4 + try { + JenaRDFParser parser = new JenaRDFParser(); + Object json = JSONLD.fromRDF( values.getModel() , parser); + JSONUtils.write(response.getWriter(), json); + } catch (JSONLDProcessingError e) { + throw new IOException("Could not convert from Jena model to JSON-LD", e); + } + }else{ + String format = ""; + if ( RDFXML_MIMETYPE.equals(mediaType)) { + format = "RDF/XML"; + } else if( N3_MIMETYPE.equals(mediaType)) { + format = "N3"; + } else if ( TTL_MIMETYPE.equals(mediaType)) { + format ="TTL"; + } + values.getModel().write( response.getOutputStream(), format ); } - values.getModel().write( response.getOutputStream(), format ); + } protected void doException(VitroRequest vreq, HttpServletResponse response, @@ -336,7 +357,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet { private Map buildRequestUrls(VitroRequest vreq) { Map requestUrls = new HashMap(); - FreemarkerConfiguration config = FreemarkerConfigurationLoader.getConfig(vreq); + Configuration config = FreemarkerConfiguration.getConfig(vreq); TemplateModel urlModel = config.getSharedVariable("urls"); try { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java index 10ff18f7c..785bb031b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,6 +18,10 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.SimpleRequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequiresActions; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; @@ -41,6 +46,79 @@ public class PageController extends FreemarkerHttpServlet{ protected static final String DATA_GETTER_MAP = "pageTypeToDataGetterMap"; + /** + * Get the required actions for all the data getters then + * AND them together. + */ + @Override + protected Actions requiredActions(VitroRequest vreq) { + try { + Actions pageActs = getActionsForPage( vreq ); + Actions dgActs = getActionsForDataGetters( vreq ); + + if( pageActs == null && dgActs == null){ + return Actions.AUTHORIZED; + }else if( pageActs == null && dgActs != null ){ + return dgActs; + }else{ + return pageActs; + } + + } catch (Exception e) { + // TODO Auto-generated catch block + log.debug(e); + return Actions.UNAUTHORIZED; + } + } + + /** + * Get all the required actions directly required for the page. + */ + private Actions getActionsForPage( VitroRequest vreq ) throws Exception{ + List simplePremUris = vreq.getWebappDaoFactory().getPageDao() + .getRequiredActions( getPageUri(vreq) ); + + List actions = new ArrayList(); + + for( String uri : simplePremUris ){ + actions.add( new SimpleRequestedAction(uri) ); + } + + return new Actions( actions ); + } + /** + * Get Actions object for the data getters for the page. + */ + private Actions getActionsForDataGetters(VitroRequest vreq ){ + try { + Actions dgActs = null; + + List dgList = + DataGetterUtils.getDataGettersForPage( + vreq, vreq.getDisplayModel(), getPageUri(vreq)); + + for( DataGetter dg : dgList){ + if( dg instanceof RequiresActions ){ + RequiresActions ra = (RequiresActions) dg; + Actions newActions = ra.requiredActions(vreq); + if( newActions != null ){ + if( dgActs != null ){ + dgActs = dgActs.and( newActions ); + }else{ + dgActs = newActions; + } + } + } + } + + return dgActs; + } catch (Exception e) { + // TODO Auto-generated catch block + log.debug(e); + return Actions.UNAUTHORIZED; + } + } + @Override protected ResponseValues processRequest(VitroRequest vreq) throws Exception { @@ -65,8 +143,7 @@ public class PageController extends FreemarkerHttpServlet{ return doNotFound(vreq); } - //executePageDataGetters( pageUri, vreq, getServletContext(), mapForTemplate ); - //these should all be data getters now + //these should all be DataGetters now, not PageDataGetters executeDataGetters( pageUri, vreq, mapForTemplate); mapForTemplate.putAll( getPageControllerValues( pageUri, vreq, getServletContext(), mapForTemplate)); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java index 828b09555..08b89c33c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/TemplateProcessingHelper.java @@ -13,7 +13,8 @@ import javax.servlet.http.HttpServletRequest; 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.freemarker.config.FreemarkerConfiguration; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfigurationImpl; import freemarker.core.Environment; import freemarker.template.Configuration; import freemarker.template.Template; @@ -26,7 +27,7 @@ public class TemplateProcessingHelper { private Configuration config = null; public TemplateProcessingHelper(HttpServletRequest request, ServletContext context) { - this.config = FreemarkerConfigurationLoader.getConfig(new VitroRequest(request)); + this.config = FreemarkerConfiguration.getConfig(request); } public StringWriter processTemplate(String templateName, Map map) @@ -50,7 +51,8 @@ public class TemplateProcessingHelper { } // Apply any data-getters that are associated with this template. - FreemarkerConfiguration.retrieveAndRunDataGetters(env, template.getName()); + // TODO clean this up VIVO-249 + FreemarkerConfigurationImpl.retrieveAndRunDataGetters(env, template.getName()); // Now process it. env.process(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java index 80994386c..ed3a6430a 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilder.java @@ -242,23 +242,12 @@ public class UrlBuilder { } public static String getIndividualProfileUrl(Individual individual, VitroRequest vreq) { - return getIndividualProfileUrl(individual, individual.getURI(),vreq); - } - - public static String getIndividualProfileUrl(String individualUri, VitroRequest vreq) { - Individual individual = new IndividualImpl(individualUri); - return getIndividualProfileUrl(individual, individualUri, vreq); - } - - private static String getIndividualProfileUrl(Individual individual, String individualUri, VitroRequest vreq) { WebappDaoFactory wadf = vreq.getWebappDaoFactory(); String profileUrl = null; try { - URI uri = new URIImpl(individualUri); // throws exception if individualUri is invalid - String namespace = uri.getNamespace(); - String defaultNamespace = wadf.getDefaultNamespace(); - String localName = individual.getLocalName(); + String namespace = individual.getNamespace(); + String defaultNamespace = wadf.getDefaultNamespace(); if (defaultNamespace.equals(namespace)) { profileUrl = getUrl(Route.INDIVIDUAL.path() + "/" + localName); @@ -267,7 +256,7 @@ public class UrlBuilder { log.debug("Found externally linked namespace " + namespace); profileUrl = namespace + localName; } else { - ParamMap params = new ParamMap("uri", individualUri); + ParamMap params = new ParamMap("uri", individual.getURI()); profileUrl = getUrl("/individual", params); } } @@ -276,25 +265,56 @@ public class UrlBuilder { return null; } - if (profileUrl != null) { - LinkedHashMap specialParams = getModelParams(vreq); - if(specialParams.size() != 0) { - profileUrl = addParams(profileUrl, new ParamMap(specialParams)); - } - } - - return profileUrl; + if (profileUrl != null) { + LinkedHashMap specialParams = getModelParams(vreq); + if(specialParams.size() != 0) { + profileUrl = addParams(profileUrl, new ParamMap(specialParams)); + } + } + + return profileUrl; } + /** + * If you already have an Individual object around, + * call getIndividualProfileUrl(Individual, VitroRequest) + * instead of this method. + */ + public static String getIndividualProfileUrl(String individualUri, VitroRequest vreq) { + return getIndividualProfileUrl(new IndividualImpl(individualUri), vreq); + } + + protected static String getIndividualProfileUrl( + String individualUri, + String namespace, String localName, + String defaultNamespace){ + String profileUrl = ""; + try{ + if ( isUriInDefaultNamespace( individualUri, defaultNamespace) ) { + profileUrl = getUrl(Route.INDIVIDUAL.path() + "/" + localName); + } else { + ParamMap params = new ParamMap("uri", individualUri); + profileUrl = getUrl("/individual", params); + } + } catch (Exception e) { + log.warn(e); + return null; + } + return profileUrl; + } + public static boolean isUriInDefaultNamespace(String individualUri, VitroRequest vreq) { return isUriInDefaultNamespace(individualUri, vreq.getWebappDaoFactory()); } - public static boolean isUriInDefaultNamespace(String individualUri, WebappDaoFactory wadf) { + public static boolean isUriInDefaultNamespace(String individualUri, WebappDaoFactory wadf) { + return isUriInDefaultNamespace( individualUri, wadf.getDefaultNamespace()); + } + + public static boolean isUriInDefaultNamespace(String individualUri, String defaultNamespace){ try { - URI uri = new URIImpl(individualUri); // throws exception if individualUri is invalid - String namespace = uri.getNamespace(); - String defaultNamespace = wadf.getDefaultNamespace(); + Individual ind = new IndividualImpl(individualUri); + String namespace = ind.getNamespace(); return defaultNamespace.equals(namespace); } catch (Exception e) { log.warn(e); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java index 2e632159f..8c10623b2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java @@ -39,6 +39,8 @@ public class IndividualController extends FreemarkerHttpServlet { map.put(HTML_MIMETYPE, 0.5f); map.put(XHTML_MIMETYPE, 0.5f); map.put("application/xml", 0.5f); + map.put(JSON_MIMETYPE, 1.0f); + map.put(RDFXML_MIMETYPE, 1.0f); map.put(RDFXML_MIMETYPE, 1.0f); map.put(N3_MIMETYPE, 1.0f); map.put(TTL_MIMETYPE, 1.0f); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualRequestAnalyzer.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualRequestAnalyzer.java index 3b3c6d091..d89766164 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualRequestAnalyzer.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualRequestAnalyzer.java @@ -5,6 +5,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.individual; import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.N3_MIMETYPE; import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.RDFXML_MIMETYPE; import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.TTL_MIMETYPE; +import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.JSON_MIMETYPE; import java.util.Map; import java.util.regex.Matcher; @@ -117,7 +118,9 @@ public class IndividualRequestAnalyzer { return "/individual/" + m.group(1) + "/" + m.group(1) + ".n3"; } else if (TTL_MIMETYPE.equals(mediaType)) { return "/individual/" + m.group(1) + "/" + m.group(1) + ".ttl"; - } + } else if (JSON_MIMETYPE.equals(mediaType)){ + return "/individual/" + m.group(1) + "/" + m.group(1) + ".jsonld"; + } } // or redirect to the canonical URL for HTML representation. return "/display/" + m.group(1); @@ -244,7 +247,10 @@ public class IndividualRequestAnalyzer { if (formatParam.contains("ttl")) { return ContentType.TURTLE; } - + if (formatParam.contains("jsonld") || formatParam.contains("json")){ + return ContentType.JSON; + } + /* * Check for parts of URL that indicate request for RDF. Examples: * http://vivo.cornell.edu/individual/n23/n23.rdf diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java index e63791df5..0fd8ae2f4 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java @@ -7,6 +7,8 @@ import java.lang.Integer; import java.lang.String; import java.sql.SQLException; import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.logging.Log; @@ -31,6 +33,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils; import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +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; @@ -108,6 +111,8 @@ class IndividualResponseBuilder { */ // body.put("individual", wrap(itm, BeansWrapper.EXPOSE_SAFE)); body.put("labelCount", getLabelCount(itm.getUri(), vreq)); + //We also need to know the number of available locales + body.put("localesCount", SelectedLocale.getSelectableLocales(vreq).size()); body.put("profileType", getProfileType(itm.getUri(), vreq)); body.put("individual", wrap(itm, new ReadOnlyBeansWrapper())); @@ -279,7 +284,9 @@ class IndividualResponseBuilder { log.debug("queryStr = " + queryStr); int theCount = 0; try { - ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); + //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(); String countStr = soln.get("labelCount").toString(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java index 4a832d784..a3f0fdab3 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java @@ -48,6 +48,7 @@ public class DisplayVocabulary { public static final String ITEM_TO_PAGE = NS + "toPage"; public static final String HAS_ELEMENT = NS + "hasElement"; public static final String USES_DATAGETTER_CLASS = NS + "usesDataGetterClass"; + public static final String REQUIRED_ACTIONS = NS + "requiredAction"; /**Data Getter object properties **/ public static final String HAS_DATA_GETTER = NS + "hasDataGetter"; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/PageDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/PageDao.java index b08f559d1..3004dddd7 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/PageDao.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/PageDao.java @@ -29,4 +29,10 @@ public interface PageDao { List getDataGetterClass(String pageUri); + /** + * Gets the required actions directly associated with a page. + * Does not get required actions for any data getters that are + * related to the page. + */ + List getRequiredActions(String pageUri); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java index a899edea0..4ca5fe172 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyDaoJena.java @@ -874,7 +874,7 @@ public class ObjectPropertyDaoJena extends PropertyDaoJena implements ObjectProp */ protected static final List EXCLUDED_NAMESPACES = Arrays.asList( - "http://www.w3.org/2002/07/owl#" + //"http://www.w3.org/2002/07/owl#" ); /* * This is a hack to throw out properties in the vitro, rdf, rdfs, and owl namespaces. diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJena.java index 9b626d22d..45810e361 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJena.java @@ -2,6 +2,7 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -10,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,6 +36,7 @@ import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.Lock; import com.hp.hpl.jena.util.iterator.ClosableIterator; +import com.hp.hpl.jena.vocabulary.OWL; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty; @@ -353,7 +356,7 @@ public class ObjectPropertyStatementDaoJena extends JenaBaseDao implements Objec return null; } - Model constructedModel = ModelFactory.createDefaultModel(); + Model constructedModel = ModelFactory.createDefaultModel(); for (String queryString : constructQueries) { @@ -408,13 +411,24 @@ public class ObjectPropertyStatementDaoJena extends JenaBaseDao implements Objec w.close(); } } else { - constructedModel.read( - rdfService.sparqlConstructQuery( - queryString, RDFService.ModelSerializationFormat.N3), null, "N3"); - + String parseFormat = "N3"; + RDFService.ModelSerializationFormat resultFormat = RDFService.ModelSerializationFormat.N3; + + /* If the test ObjectPropertyStatementDaoJenaTest.testN3WithSameAs() fails + * this code can be removed: */ + if( OWL.sameAs.getURI().equals( propertyUri )){ + // VIVO-111: owl:sameAs can be represented as = in n3 but Jena's parser does not recognize it. + // Switch to rdf/xml only for sameAs since it seems like n3 would be faster the rest of the time. + parseFormat = "RDF/XML"; + resultFormat = RDFService.ModelSerializationFormat.RDFXML; + } + /* end of removal */ + + InputStream is = rdfService.sparqlConstructQuery(queryString, resultFormat); + constructedModel.read( is, null, parseFormat); } - } catch (Exception e) { - log.error("Error getting constructed model for subject " + subjectUri + " and property " + propertyUri); + } catch (Exception e) { + log.error("Error getting constructed model for subject " + subjectUri + " and property " + propertyUri, e); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java index e8ac4392e..e723e0a2b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -77,7 +78,14 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { " ?pageUri display:hasDataGetter ?dg .\n"+ " ?dg rdf:type ?dataGetterType . \n" + "} \n" ; - + + //Get the required actions directly associated with a page + static final protected String requiredActionsQuery = + prefixes + "\n" + + "SELECT ?requiredAction WHERE{\n" + + " ?pageUri <" + DisplayVocabulary.REQUIRED_ACTIONS + "> ?requiredAction .\n"+ + "}"; + //Get data getter URIs static final protected String dataGetterURIsQueryString = prefixes + "\n" + @@ -519,10 +527,54 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { return dataGetterClasses; } - + + /** + * Gets the requiredActions directly associated with page. + */ + public List getRequiredActions(String pageUri){ + QuerySolutionMap initialBindings = new QuerySolutionMap(); + initialBindings.add("pageUri", ResourceFactory.createResource(pageUri)); + List actions = new ArrayList(); + + Model dModel = getOntModelSelector().getDisplayModel(); + try{ + QueryExecution qe = + QueryExecutionFactory.create( requiredActionsQuery, dModel, initialBindings); + actions = executeQueryToList( qe ); + qe.close(); + }finally{ + dModel.enterCriticalSection(false); + } + return actions; + } /* *************************** Utility methods ********************************* */ - + + /** + * Assumes single bound variable in solution. + */ + protected List executeQueryToList(QueryExecution qex){ + List rv = new LinkedList(); + ResultSet results = qex.execSelect(); + while (results.hasNext()) { + rv.add(querySolutionToString( results.nextSolution() )); + } + return rv; + } + + /** + * Assumes single bound variable in solution. + */ + protected String querySolutionToString( QuerySolution soln ){ + Iterator varNames = soln.varNames(); + if(varNames.hasNext()){ + String name = varNames.next(); + return nodeToString( soln.get(name) ); + }else{ + return ""; + } + } + /** * Converts a sparql query that returns a multiple rows to a list of maps. * The maps will have column names as keys to the values. @@ -548,7 +600,9 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { } return map; } - + + + static protected Object nodeToObject( RDFNode node ){ if( node == null ){ return ""; @@ -582,11 +636,6 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { return ""; } } - protected Map resultsToMap(){ - return null; - } - - } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJena.java index e03271273..19cbdffeb 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PropertyInstanceDaoJena.java @@ -12,6 +12,7 @@ import java.util.List; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntProperty; +import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.ResourceFactory; @@ -39,7 +40,8 @@ public class PropertyInstanceDaoJena extends PropertyDaoJena implements deleteObjectPropertyStatement(subjectURI, propertyURI, objectURI, getOntModelSelector()); } - public void deleteObjectPropertyStatement(String subjectURI, String propertyURI, String objectURI, OntModelSelector ontModelSelector) { + public void deleteObjectPropertyStatement(String subjectURI, String propertyURI, String objectURI, + OntModelSelector ontModelSelector) { OntModel ontModel = ontModelSelector.getABoxModel(); OntModel tboxModel = ontModelSelector.getTBoxModel(); ontModel.enterCriticalSection(Lock.WRITE); @@ -51,29 +53,31 @@ public class PropertyInstanceDaoJena extends PropertyDaoJena implements invPred = pred.as(OntProperty.class).getInverse(); } Resource objRes = ontModel.getResource(objectURI); + Model baseModel = getOntModel().getBaseModel(); + String userUri = getWebappDaoFactory().getUserURI(); if ( (subjRes != null) && (pred != null) && (objRes != null) ) { - getOntModel().getBaseModel().notifyEvent(new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),true,subjectURI)); + baseModel.notifyEvent(new IndividualUpdateEvent(userUri,true,subjectURI)); try { - ontModel.remove(subjRes,pred,objRes); - - updatePropertyDateTimeValue(subjRes,MODTIME,Calendar.getInstance().getTime(),getOntModel()); + ontModel.remove(subjRes,pred,objRes); + updatePropertyDateTimeValue(subjRes,MODTIME,Calendar.getInstance().getTime(),ontModel); } finally { - getOntModel().getBaseModel().notifyEvent(new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),false,subjectURI)); + baseModel.notifyEvent(new IndividualUpdateEvent(userUri,false,subjectURI)); } try{ - getOntModel().getBaseModel().notifyEvent(new IndividualDeletionEvent(getWebappDaoFactory().getUserURI(),true,objectURI)); - List depResStmts = DependentResourceDeleteJena.getDependentResourceDeleteList(ResourceFactory.createStatement(subjRes, pred, objRes),ontModel); - getOntModel().remove(depResStmts); + baseModel.notifyEvent(new IndividualDeletionEvent(userUri,true,objectURI)); + List depResStmts = DependentResourceDeleteJena + .getDependentResourceDeleteList(ResourceFactory.createStatement(subjRes, pred, objRes),ontModel); + ontModel.remove(depResStmts); } finally { - getOntModel().getBaseModel().notifyEvent(new IndividualDeletionEvent(getWebappDaoFactory().getUserURI(),false,objectURI)); + baseModel.notifyEvent(new IndividualDeletionEvent(userUri,false,objectURI)); } if (invPred != null) { - getOntModel().getBaseModel().notifyEvent(new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),true,objectURI)); + baseModel.notifyEvent(new IndividualUpdateEvent(userUri,true,objectURI)); try { ontModel.remove(objRes,invPred,subjRes); - updatePropertyDateTimeValue(objRes,MODTIME,Calendar.getInstance().getTime(),getOntModel()); + updatePropertyDateTimeValue(objRes,MODTIME,Calendar.getInstance().getTime(),ontModel); } finally { - getOntModel().getBaseModel().notifyEvent(new IndividualUpdateEvent(getWebappDaoFactory().getUserURI(),false,subjectURI)); + baseModel.notifyEvent(new IndividualUpdateEvent(userUri,false,subjectURI)); } } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/QueryUtils.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/QueryUtils.java index 04b03aed5..e2c1df90b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/QueryUtils.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/QueryUtils.java @@ -115,9 +115,16 @@ public class QueryUtils { } public static ResultSet getQueryResults(String queryStr, VitroRequest vreq) { - - Dataset dataset = vreq.getDataset(); - dataset.getLock().enterCriticalSection(Lock.READ); + return getQueryResults(queryStr, vreq.getDataset()); + } + + public static ResultSet getLanguageNeutralQueryResults(String queryStr, VitroRequest vreq) { + return getQueryResults(queryStr, vreq.getUnfilteredDataset()); + } + + /** Already have the dataset, so process the query and return the results. */ + private static ResultSet getQueryResults(String queryStr, Dataset dataset) { + dataset.getLock().enterCriticalSection(Lock.READ); QueryExecution qexec = null; ResultSet results = null; try { @@ -133,6 +140,6 @@ public class QueryUtils { } return results; - } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java index 534bce2a4..3ecb8c289 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/EditConfigurationUtils.java @@ -23,11 +23,11 @@ import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfigurationLoader; import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; 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 freemarker.template.Configuration; public class EditConfigurationUtils { @@ -282,7 +282,7 @@ public class EditConfigurationUtils { //Generate HTML for a specific field name given public static String generateHTMLForElement(VitroRequest vreq, String fieldName, EditConfigurationVTwo editConfig) { String html = ""; - Configuration fmConfig = FreemarkerConfigurationLoader.getConfig(vreq); + Configuration fmConfig = FreemarkerConfiguration.getConfig(vreq); FieldVTwo field = editConfig == null ? null : editConfig.getField(fieldName); MultiValueEditSubmission editSub = EditSubmissionUtils.getEditSubmissionFromSession(vreq.getSession(), editConfig); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java new file mode 100644 index 000000000..d34c1c5da --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java @@ -0,0 +1,576 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.shared.Lock; +import com.hp.hpl.jena.sparql.resultset.ResultSetMem; +import com.hp.hpl.jena.vocabulary.RDFS; +import com.hp.hpl.jena.vocabulary.XSD; + +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestActionConstants; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddDataPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.AddObjectPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.beans.DataProperty; +import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.VClass; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; +import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationUtils; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +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.LocaleSelectionDataGetter; +import edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectorUtilities; +import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DataPropertyStatementTemplateModel; + +/** + *This allows the page to show all the labels for a particular individual and sets up code + *enabling the addition of a new label. Links on the page will allow for removal or editing of a given label. + */ +public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGenerator implements EditConfigurationGenerator { + public static Log log = LogFactory.getLog(ManageLabelsForIndividualGenerator.class); + private static String template = "manageLabelsForIndividual.ftl"; + private HashMap> labelsSortedByLanguage = null; + private List existingLabelLiterals = null; + //list of language names sorted alphabetically + private List existingSortedLanguageNameList = null; + //This would be for the full list and can be used for the existing labels list as well + + private HashMap fullLanguageNameToCodeMap = null; + private static String predicateUri = RDFS.label.getURI(); + @Override + public EditConfigurationVTwo getEditConfiguration(VitroRequest vreq, HttpSession session) { + + EditConfigurationVTwo config = new EditConfigurationVTwo(); + config.setTemplate(this.getTemplate()); + + initBasics(config, vreq); + initPropertyParameters(vreq, session, config); + + //This form is technically not an object property form in the true sense + //or a data property form because it is used to list the various labels + //and allow for adding new labels + //URL to return to is the same page once addition is complete + this.setUrlToReturnTo(config, vreq); + + config.setSubjectUri(EditConfigurationUtils.getSubjectUri(vreq)); + + setVarNames(config); + config.setDatapropKey( EditConfigurationUtils.getDataHash(vreq) ); + //Add n3, fields, etc. in the case where the user wants to add a label + //N3 required should be empty since the addition of a label is optional in this case + config.setN3Required(this.generateN3Required(vreq)); + config.setN3Optional(this.generateN3Optional(vreq)); + this.setUrisAndLiteralsOnForm(config, vreq); + setUrisAndLiteralsInScope(config); + this.setFields(config, vreq, EditConfigurationUtils + .getPredicateUri(vreq)); + + //Get existing labels + //this.initExistingLabels(config, vreq); + + //Add form specific data used to populate template + addFormSpecificData(config, vreq); + //This preprocessor handles getting the correct label language and putting the attribute on the label + config.addEditSubmissionPreprocessor( + new ManageLabelsForIndividualPreprocessor(config)); + //This will handle generating the label from the first name and last name and also make sure to associate + //a language with that label + config.addModelChangePreprocessor(new FoafNameToRdfsLabelPreprocessor()); + + prepare(vreq, config); + return config; + } + + private void setUrlToReturnTo(EditConfigurationVTwo editConfiguration, VitroRequest vreq) { + editConfiguration.setUrlPatternToReturnTo(EditConfigurationUtils.getFormUrlWithoutContext(vreq)); + } + + private void setVarNames(EditConfigurationVTwo editConfiguration) { + editConfiguration.setVarNameForSubject("subject"); + editConfiguration.setVarNameForPredicate("predicate"); + + } + + + private List generateN3Required(VitroRequest vreq) { + List n3Required = new ArrayList(); + return n3Required; + } + + private List generateN3Optional(VitroRequest vreq) { + List n3Optional = new ArrayList(); + String predicateUri = EditConfigurationUtils.getPredicateUri(vreq); + String n3 = "?subject <" + predicateUri + "> ?label "; + //n3 used if the subject is a person + String personN3 = this.N3_PREFIX + "?subject foaf:firstName ?firstName ; foaf:lastName ?lastName ."; + n3Optional.add(n3); + n3Optional.add(personN3); + return n3Optional; + } + + + + private void setFields(EditConfigurationVTwo editConfiguration, VitroRequest vreq, String predicateUri) { + Map fields = new HashMap(); + editConfiguration.setFields(fields); + editConfiguration.addField(new FieldVTwo(). + setName("label"). + setValidators(getLabelValidators(vreq, editConfiguration))); + editConfiguration.addField(new FieldVTwo( + ).setName("newLabelLanguage")); + //no validators since all of this is optional + //there should be error-checking client side though + editConfiguration.addField(new FieldVTwo(). + setName("firstName"). + setValidators(getFirstNameValidators(vreq, editConfiguration))); + + editConfiguration.addField(new FieldVTwo(). + setName("lastName"). + setValidators(getLastNameValidators(vreq, editConfiguration))); + } + + //first and last name have validators if is person is true + private List getFirstNameValidators(VitroRequest vreq, EditConfigurationVTwo config) { + List validators = new ArrayList(); + if(isPersonType(vreq, config)) { + validators.add("nonempty"); + } + return validators; + } + + private List getLastNameValidators(VitroRequest vreq, EditConfigurationVTwo config) { + List validators = new ArrayList(); + if(isPersonType(vreq, config)) { + validators.add("nonempty"); + } + return validators; + } + + //validate label if person is not true + private List getLabelValidators(VitroRequest vreq, EditConfigurationVTwo config) { + List validators = new ArrayList(); + if(!isPersonType(vreq, config)) { + validators.add("nonempty"); + } + return validators; + } + + + + private void setUrisAndLiteralsOnForm(EditConfigurationVTwo config, + VitroRequest vreq) { + List literalsOnForm = new ArrayList(); + literalsOnForm.add("label"); + literalsOnForm.add("newLabelLanguage"); + //optional for person + literalsOnForm.add("firstName"); + literalsOnForm.add("lastName"); + config.setLiteralsOnForm(literalsOnForm); + + } + + + private void setUrisAndLiteralsInScope(EditConfigurationVTwo editConfiguration) { + HashMap> urisInScope = new HashMap>(); + //note that at this point the subject, predicate, and object var parameters have already been processed + urisInScope.put(editConfiguration.getVarNameForSubject(), + Arrays.asList(new String[]{editConfiguration.getSubjectUri()})); + urisInScope.put(editConfiguration.getVarNameForPredicate(), + Arrays.asList(new String[]{editConfiguration.getPredicateUri()})); + editConfiguration.setUrisInScope(urisInScope); + //Uris in scope include subject, predicate, and object var + + editConfiguration.setLiteralsInScope(new HashMap>()); + } + + private void initExistingLabels(EditConfigurationVTwo config, + VitroRequest vreq) { + this.existingLabelLiterals = this.getExistingLabels(config.getSubjectUri(), vreq); + // this.labelsSortedByLanguage = this.getLabelsSortedByLanguage(config,vreq); + //language names sorted for the existing languages + // this.existingSortedLanguageNameList = getExistingSortedLanguageNamesList(); + + //Generate a label to language code hash map + //TODO: + + //HashMap labelToLanguageCode = new HashMap(); + + //this.labels = getExistingLabels(config.getSubjectUri(), vreq); + //this.labelsSortedByLanguage = getLabelsSortedByLanguage(config.getSubjectUri(), vreq); + + } + + + private List getExistingSortedLanguageNamesList() { + HashSet existingLanguages = new HashSet(); + for(Literal l: this.existingLabelLiterals) { + String language = l.getLanguage(); + if(!existingLanguages.contains(language)) { + existingLanguages.add(language); + } + } + List sortedNames = new ArrayList(existingLanguages); + //sort alphabetically + Collections.sort(sortedNames); + return sortedNames; + } + + + 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); + //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); + 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); + //Get available locales for the drop down for adding a new label, also sorted by language name + HashSet existingLanguageNames = new HashSet(existingLabelsByLanguageName.keySet()); + List> availableLocalesForAdd = getAvailableLocales(locales, existingLanguageNames); + + + //Save all locales + config.addFormSpecificData("selectLocaleFullList", locales); + //Save labels sorted by language name, untyped have "untyped" as the language name value + config.addFormSpecificData("labelsSortedByLanguageName", existingLabelsByLanguageName); + config.addFormSpecificData("selectLocale",availableLocalesForAdd); + config.addFormSpecificData("displayRemoveLink", (numberExistingLabels > 1)); + + + //How do we edit? Will need to see + config.addFormSpecificData("deleteWebpageUrl", "/edit/primitiveDelete"); + + + Individual subject = vreq.getWebappDaoFactory().getIndividualDao().getIndividualByURI(config.getSubjectUri()); + if( subject != null && subject.getName() != null ){ + config.addFormSpecificData("subjectName", subject.getName()); + }else{ + config.addFormSpecificData("subjectName", null); + } + + //Put in whether or not person type + if(isPersonType(vreq, config)) { + //Doing this b/c unsure how freemarker will handle boolean value from JAVA + config.addFormSpecificData("isPersonType", "true"); + } else { + config.addFormSpecificData("isPersonType", "false"); + + } + + //Include whether or not editable to enable edit/remove links and add to show up + config.addFormSpecificData("editable", isEditable(vreq, config)); + } + + + + //Based on what locales have already been selected for labels, return a list of + //locales for which new labels can be added and have these sorted by the name of the language + private List> getAvailableLocales(List> allLocales, + HashSet existingLabelsLanguageNames) { + List> availableLocales = new ArrayList>(); + for(HashMap localeInfo: allLocales) { + String languageName = (String) localeInfo.get("label"); + //If this language label is NOT in the labels sorted by language, then available + //for selection when creating a new label + //The assumption here is we don't want to allow the user to add a new label when a label + //already exists in that language + if(languageName != "untyped" && !existingLabelsLanguageNames.contains(languageName)) { + availableLocales.add(localeInfo); + } + } + //Sort list by language label and return + Collections.sort(availableLocales, new Comparator>() { + public int compare(HashMap h1, HashMap h2) { + String languageName1 = (String) h1.get("label"); + String languageName2 = (String) h2.get("label"); + return languageName1.compareTo(languageName2); + } + }); + + return availableLocales; + } + + + private Object isEditable(VitroRequest vreq, EditConfigurationVTwo config) { + Individual individual = EditConfigurationUtils.getIndividual(vreq, config.getSubjectUri()); + AddDataPropertyStatement adps = new AddDataPropertyStatement( + vreq.getJenaOntModel(), individual.getURI(), + RequestActionConstants.SOME_URI); + AddObjectPropertyStatement aops = new AddObjectPropertyStatement( + vreq.getJenaOntModel(), individual.getURI(), + RequestActionConstants.SOME_URI, + RequestActionConstants.SOME_URI); + return PolicyHelper.isAuthorizedForActions(vreq, new Actions(adps).or(aops)); + } + + + //Copied from NewIndividualFormGenerator + //TODO: Refactor so common code can be used by both generators + public String getFOAFPersonClassURI() { + return "http://xmlns.com/foaf/0.1/Person"; + } + + public boolean isPersonType(VitroRequest vreq, EditConfigurationVTwo config) { + WebappDaoFactory wdf = vreq.getWebappDaoFactory(); + Boolean isPersonType = Boolean.FALSE; + String foafPersonType = getFOAFPersonClassURI(); + List vclasses = this.getVClasses(config, vreq); + if( vclasses != null ){ + for( VClass v: vclasses){ + String typeUri = v.getURI(); + if( foafPersonType.equals(typeUri)) { + isPersonType = Boolean.TRUE; + break; + } + } + } + return isPersonType; + } + + //how to get the type of the individual in question + public List getVClasses(EditConfigurationVTwo config, VitroRequest vreq) { + Individual subject = EditConfigurationUtils.getIndividual(vreq, config.getSubjectUri()); + //Get the vclasses appropriate for this subject + return subject.getVClasses(); + } + + //Languages sorted by language name + private HashMap> getLabelsSortedByLanguageName(List labels, Map localeCodeToNameMap, EditConfigurationVTwo config, + VitroRequest vreq) { + String subjectUri = config.getSubjectUri(); + String propertyUri = config.getPredicateUri(); + + + //Iterate through the labels and create a hashmap + HashMap> labelsHash= new HashMap>(); + + for(Literal l: labels) { + String languageTag = l.getLanguage(); + String languageName = ""; + if(languageTag == "") { + languageName = "untyped"; + } + else if(localeCodeToNameMap.containsKey(languageTag)) { + languageName = localeCodeToNameMap.get(languageTag); + } else { + log.warn("This language tag " + languageTag + " does not have corresponding name in the system and was not processed"); + } + + if(languageName != "") { + if(!labelsHash.containsKey(languageName)) { + labelsHash.put(languageName, new ArrayList()); + } + ArrayList labelsList = (ArrayList)labelsHash.get(languageName); + //This should put the label in the list + //Create label information instance with the required information + //To generate link + DataPropertyStatementTemplateModel dpstm = new DataPropertyStatementTemplateModel(subjectUri, propertyUri, l, + template, vreq); + labelsList.add(new LabelInformation( + l, dpstm.getEditUrl(), dpstm.getDeleteUrl(), languageTag, languageName)); + } + } + + //Sort each label list + LabelInformationComparator lic = new LabelInformationComparator(); + for(String languageName: labelsHash.keySet()) { + List labelInfo = labelsHash.get(languageName); + Collections.sort(labelInfo, lic); + } + return labelsHash; + + } + + + public static class LabelInformationComparator implements Comparator { + + public int compare(LabelInformation l1, LabelInformation l2) { + return l1.getLabelStringValue().compareTo(l2.getLabelStringValue()); + } + } + + + private static String LABEL_QUERY = "" + + "PREFIX rdfs: \n" + + "SELECT DISTINCT ?label WHERE { \n" + + " ?subject rdfs:label ?label \n" + + "} ORDER BY ?label"; + + + private ArrayList getExistingLabels(String subjectUri, VitroRequest vreq) { + String queryStr = QueryUtils.subUriForQueryVar(LABEL_QUERY, "subject", subjectUri); + log.debug("queryStr = " + queryStr); + + 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); + while (results.hasNext()) { + QuerySolution soln = results.nextSolution(); + Literal nodeLiteral = soln.get("label").asLiteral(); + labels.add(nodeLiteral); + + + } + } catch (Exception e) { + log.error(e, e); + } + return labels; +} + + + + //Putting this into a method allows overriding it in subclasses + protected String getEditForm() { + return null; + //return AddEditWebpageFormGenerator.class.getName(); + } + + + protected String getTemplate() { + return template; + } + + + + //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; + } + + + + public HashMap getFullCodeToLanguageNameMap(List> localesList) { + HashMap codeToLanguageMap = new HashMap(); + for(Map locale: localesList) { + String code = (String) locale.get("code"); + String label = (String) locale.get("label"); + if(!codeToLanguageMap.containsKey(code)) { + codeToLanguageMap.put(code, label); + } + else { + log.warn("Language code " + code + " for " + label + " was not associated in map becayse label already exists"); + } + } + return codeToLanguageMap; + } + + public List getFullLanguagesNamesSortedList(List> localesList) { + HashSet languageNamesSet = new HashSet(); + for(Map locale: localesList) { + String label = (String) locale.get("label"); + if(!languageNamesSet.contains(label)) { + languageNamesSet.add(label); + } + + } + List languageNames = new ArrayList(languageNamesSet); + Collections.sort(languageNames); + return languageNames; + } + + //copied from locale selection data getter but don't need all this information + private HashMap buildLocaleMap(Locale locale, + Locale currentLocale) throws FileNotFoundException { + HashMap map = new HashMap(); + //Replacing the underscore with a hyphen because that is what is represented in the actual literals + map.put("code", locale.toString().replace("_", "-")); + map.put("label", locale.getDisplayName(currentLocale)); + return map; + } + + //Class used to store the information needed for the template, such as the labels, their languages, their edit links + public class LabelInformation { + private Literal labelLiteral = null; + private String editLinkURL; + private String deleteLinkURL; + private String languageCode; //languageCode + private String languageName; + public LabelInformation(Literal inputLiteral, String inputEditLinkURL, String inputDeleteLinkURL, String inputLanguageCode, String inputLanguageName) { + this.labelLiteral = inputLiteral; + this.editLinkURL = inputEditLinkURL; + this.deleteLinkURL = inputDeleteLinkURL; + this.languageCode = inputLanguageCode; + this.languageName = inputLanguageName; + } + + + public Literal getLabelLiteral() { + return this.labelLiteral; + } + + public String getLabelStringValue() { + return this.labelLiteral.getString(); + } + + public String getEditLinkURL() { + return this.editLinkURL; + } + + public String getDeleteLinkURL() { + return this.deleteLinkURL; + } + public String getLanguageCode() { + return this.languageCode; + } + + public String getLanguageName() { + return this.languageName; + } + } + + private String N3_PREFIX = "@prefix foaf: .\n"; + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/FoafNameToRdfsLabelPreprocessor.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/FoafNameToRdfsLabelPreprocessor.java index 49e979740..bbb280f2a 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/FoafNameToRdfsLabelPreprocessor.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/FoafNameToRdfsLabelPreprocessor.java @@ -4,6 +4,7 @@ package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocess import javax.servlet.http.HttpServletRequest; +import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.ResIterator; @@ -30,7 +31,21 @@ public class FoafNameToRdfsLabelPreprocessor implements ModelChangePreprocessor Statement fname = sub.getProperty( firstNameP ); Statement lname = sub.getProperty( lastNameP ); if( fname != null && lname != null && fname.getString() != null && lname.getString() != null ){ - additionsModel.add(sub, rdfsLabelP, lname.getString() + ", " + fname.getString() ); + //Check if there are languages associated with first name and last name and add the language + //attribute to the label + //This preprocessor is used in multiple places, including for managing labels + Literal firstNameLiteral = fname.getLiteral(); + Literal lastNameLiteral = lname.getLiteral(); + String firstNameLanguage = firstNameLiteral.getLanguage(); + String lastNameLanguage = lastNameLiteral.getLanguage(); + String newLabel = lname.getString() + ", " + fname.getString(); + if(firstNameLanguage != null && lastNameLanguage != null && firstNameLanguage.equals(lastNameLanguage)) { + //create a literal with the appropriate value and the language + Literal labelWithLanguage = additionsModel.createLiteral(newLabel, firstNameLanguage); + additionsModel.add(sub, rdfsLabelP, labelWithLanguage); + } else { + additionsModel.add(sub, rdfsLabelP, newLabel ); + } } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/ManageLabelsForIndividualPreprocessor.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/ManageLabelsForIndividualPreprocessor.java new file mode 100644 index 000000000..bb7967d64 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/ManageLabelsForIndividualPreprocessor.java @@ -0,0 +1,111 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.ResIterator; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; + +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +/* + * This preprocessor is used to set the language attribute on the label based on the user selection + * on the manage labels page when adding a new label. + */ +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.BaseEditSubmissionPreprocessorVTwo; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.MultiValueEditSubmission; + +public class ManageLabelsForIndividualPreprocessor extends BaseEditSubmissionPreprocessorVTwo { + + + + + public ManageLabelsForIndividualPreprocessor(EditConfigurationVTwo editConfig) { + super(editConfig); + + } + + @Override + public void preprocess(MultiValueEditSubmission inputSubmission) { + //Check and see if a language was selected by the user, and this is the regular label submission + //TODO: Check if firstname and lastname should be changed here or elsewhere + if(inputSubmission.hasLiteralValue("label") && inputSubmission.hasLiteralValue("newLabelLanguage")) { + Map> literalsFromForm = inputSubmission.getLiteralsFromForm(); + List newLabelLanguages = literalsFromForm.get("newLabelLanguage"); + List labels = literalsFromForm.get("label"); + + //Expecting only one language + if(labels.size() > 0 && newLabelLanguages.size() > 0) { + Literal newLabelLanguage = newLabelLanguages.get(0); + Literal labelLiteral = labels.get(0); + //Get the string + String lang = this.getLanguage(newLabelLanguage.getString()); + String label = labelLiteral.getString(); + //Now add the language category to the literal + Literal labelWithLanguage = inputSubmission.createLiteral(label, + newLabelLanguage.getDatatypeURI(), + lang); + labels = new ArrayList(); + labels.add(labelWithLanguage); + //replace the label with one with language, again assuming only one label being returned + literalsFromForm.put("label", labels); + inputSubmission.setLiteralsFromForm(literalsFromForm); + } + } + //First name and last name would also have a language selected so make sure those literals are also + //correctly typed + if(inputSubmission.hasLiteralValue("firstName") && inputSubmission.hasLiteralValue("lastName") && inputSubmission.hasLiteralValue("newLabelLanguage")) { + Map> literalsFromForm = inputSubmission.getLiteralsFromForm(); + List newLabelLanguages = literalsFromForm.get("newLabelLanguage"); + List firstNames = literalsFromForm.get("firstName"); + List lastNames = literalsFromForm.get("lastName"); + + //Expecting only one language + if(firstNames.size() > 0 && lastNames.size() > 0 && newLabelLanguages.size() > 0) { + Literal newLabelLanguage = newLabelLanguages.get(0); + Literal firstNameLiteral = firstNames.get(0); + Literal lastNameLiteral = lastNames.get(0); + //Get the string + String lang = this.getLanguage(newLabelLanguage.getString()); + String firstNameValue = firstNameLiteral.getString(); + String lastNameValue = lastNameLiteral.getString(); + //Now add the language category to the literal + Literal firstNameWithLanguage = inputSubmission.createLiteral(firstNameValue, + null, + lang); + Literal lastNameWithLanguage = inputSubmission.createLiteral(lastNameValue, + null, + lang); + firstNames = new ArrayList(); + lastNames = new ArrayList(); + firstNames.add(firstNameWithLanguage); + lastNames.add(lastNameWithLanguage); + //replace the label with one with language, again assuming only one label being returned + literalsFromForm.put("firstName", firstNames); + literalsFromForm.put("lastName", lastNames); + inputSubmission.setLiteralsFromForm(literalsFromForm); + } + } + + } + + //The language code returned from JAVA locales has an underscore whereas we need a hyphen + private String getLanguage(String inputLanguageCode) { + if(inputLanguageCode.contains("_")) { + return inputLanguageCode.replace("_", "-"); + } + return inputLanguageCode; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailFactory.java index 84f0c1fcd..3f9d3d852 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailFactory.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailFactory.java @@ -25,9 +25,9 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfiguration; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfigurationLoader; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; +import freemarker.template.Configuration; /** * A factory that creates Freemarker-based email messages. @@ -59,8 +59,7 @@ public class FreemarkerEmailFactory { } FreemarkerEmailFactory factory = getFactory(vreq); - FreemarkerConfiguration fConfig = FreemarkerConfigurationLoader - .getConfig(vreq); + Configuration fConfig = FreemarkerConfiguration.getConfig(vreq); return new FreemarkerEmailMessage(vreq, fConfig, factory.getEmailSession(), factory.getReplyToAddress()); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailMessage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailMessage.java index 1b3cd9a53..710242dd9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailMessage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/email/FreemarkerEmailMessage.java @@ -28,8 +28,8 @@ 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.controller.freemarker.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.web.directives.EmailDirective; +import freemarker.template.Configuration; import freemarker.template.TemplateException; /** @@ -49,7 +49,7 @@ public class FreemarkerEmailMessage { private final VitroRequest vreq; private final Session mailSession; - private final FreemarkerConfiguration config; + private final Configuration config; private final List recipients = new ArrayList(); private final InternetAddress replyToAddress; @@ -64,7 +64,7 @@ public class FreemarkerEmailMessage { /** * Package access - should only be created by the factory. */ - FreemarkerEmailMessage(VitroRequest vreq, FreemarkerConfiguration fConfig, + FreemarkerEmailMessage(VitroRequest vreq, Configuration fConfig, Session mailSession, InternetAddress replyToAddress) { this.vreq = vreq; this.mailSession = mailSession; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filters/RequestModelsPrep.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filters/RequestModelsPrep.java index 4d53fa058..983742ab3 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/filters/RequestModelsPrep.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filters/RequestModelsPrep.java @@ -131,6 +131,7 @@ public class RequestModelsPrep implements Filter { VitroRequest vreq = new VitroRequest(req); vreq.setUnfilteredRDFService(rawRdfService); + vreq.setUnfilteredDataset(new RDFServiceDataset(rawRdfService)); List langs = getPreferredLanguages(req); RDFService rdfService = addLanguageAwareness(langs, rawRdfService); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java new file mode 100644 index 000000000..17ad1fd0b --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java @@ -0,0 +1,293 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.freemarker.config; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.DelimitingTemplateLoader; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FlatteningTemplateLoader; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants; +import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; +import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective; +import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective; +import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective; +import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualLocalNameMethod; +import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualPlaceholderImageUrlMethod; +import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualProfileUrlMethod; +import freemarker.cache.ClassTemplateLoader; +import freemarker.cache.FileTemplateLoader; +import freemarker.cache.MultiTemplateLoader; +import freemarker.cache.TemplateLoader; +import freemarker.ext.beans.BeansWrapper; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.TemplateException; +import freemarker.template.TemplateModelException; + +/** + * Access point for a singleton Configuration instance. + * + * The instance is created at system startup, so we can fail early if there are + * any problems. + * + * The Configuration is slightly extended to hold request-based information in a + * ThreadLocal. The net result is although there is only one configuration (and + * hence only one template cache), each request gets a customization with its + * own locale, etc. + * + * Each time a request asks for the configuration, check to see whether the + * cache is still valid, and whether the theme has changed (needs a new + * TemplateLoader). Store the request info to the ThreadLocal. + */ +public abstract class FreemarkerConfiguration { + private static final Log log = LogFactory + .getLog(FreemarkerConfiguration.class); + + private static final String PROPERTY_DEFEAT_CACHE = "developer.defeatFreemarkerCache"; + private static final String PROPERTY_INSERT_DELIMITERS = "developer.insertFreemarkerDelimiters"; + + private static volatile FreemarkerConfigurationImpl instance; + private static volatile String previousThemeDir; + + public static Configuration getConfig(HttpServletRequest req) { + confirmInstanceIsSet(); + + synchronized (instance) { + clearTemplateCacheIfRequested(req); + keepTemplateLoaderCurrentWithThemeDirectory(req); + setThreadLocalsForRequest(req); + return instance; + } + } + + private static void confirmInstanceIsSet() { + if (instance == null) { + throw new IllegalStateException( + "VitroFreemarkerConfiguration has not been set."); + } + } + + private static void clearTemplateCacheIfRequested(HttpServletRequest req) { + if (isTemplateCacheInvalid(req)) { + instance.clearTemplateCache(); + } + } + + private static boolean isTemplateCacheInvalid(HttpServletRequest req) { + ConfigurationProperties props = ConfigurationProperties.getBean(req); + + // If the developer doesn't want the cache, it's invalid. + if (Boolean.valueOf(props.getProperty(PROPERTY_DEFEAT_CACHE))) { + return true; + } + + return false; + } + + /** + * Keep track of the theme directory. If it changes, create an appropriate + * new TemplateLoader. + * + * Note that setting a new TemplateLoader on the context Configuration also + * creates a new, empty TemplateCache. + */ + private static void keepTemplateLoaderCurrentWithThemeDirectory( + HttpServletRequest req) { + String themeDir = getThemeDirectory(req); + if (hasThemeDirectoryChanged(themeDir)) { + TemplateLoader tl = createTemplateLoader(req, themeDir); + instance.setTemplateLoader(tl); + } + } + + private static String getThemeDirectory(HttpServletRequest req) { + return new VitroRequest(req).getAppBean().getThemeDir(); + } + + private static boolean hasThemeDirectoryChanged(String themeDir) { + synchronized (instance) { + if (StringUtils.equals(themeDir, previousThemeDir)) { + return false; + } else { + previousThemeDir = themeDir; + return true; + } + } + } + + private static TemplateLoader createTemplateLoader(HttpServletRequest req, + String themeDir) { + ServletContext ctx = req.getSession().getServletContext(); + ConfigurationProperties props = ConfigurationProperties.getBean(ctx); + + List loaders = new ArrayList(); + + // Theme template loader + String themeTemplatePath = ctx.getRealPath(themeDir) + "/templates"; + File themeTemplateDir = new File(themeTemplatePath); + // A theme need not contain a template directory. + if (themeTemplateDir.exists()) { + try { + FileTemplateLoader themeFtl = new FileTemplateLoader( + themeTemplateDir); + loaders.add(themeFtl); + } catch (IOException e) { + log.error("Error creating theme template loader", e); + } + } + + // Vitro template loader + String vitroTemplatePath = ctx.getRealPath("/templates/freemarker"); + loaders.add(new FlatteningTemplateLoader(new File(vitroTemplatePath))); + + // TODO VIVO-243 Why is this here? + loaders.add(new ClassTemplateLoader(FreemarkerConfiguration.class, "")); + + TemplateLoader[] loaderArray = loaders + .toArray(new TemplateLoader[loaders.size()]); + MultiTemplateLoader mtl = new MultiTemplateLoader(loaderArray); + + // If requested, add delimiters to the templates. + if (Boolean.valueOf(props.getProperty(PROPERTY_INSERT_DELIMITERS))) { + return new DelimitingTemplateLoader(mtl); + } else { + return mtl; + } + } + + private static void setThreadLocalsForRequest(HttpServletRequest req) { + instance.setRequestInfo(req); + } + + // ---------------------------------------------------------------------- + // Setup class + // ---------------------------------------------------------------------- + + public static class Setup implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + StartupStatus ss = StartupStatus.getBean(ctx); + try { + instance = createConfiguration(ctx); + ss.info(this, "Initialized the Freemarker configuration."); + } catch (Exception e) { + ss.fatal(this, + "Failed to initialize the Freemarker configuration.", e); + } + } + + private FreemarkerConfigurationImpl createConfiguration( + ServletContext ctx) throws TemplateModelException { + FreemarkerConfigurationImpl c = new FreemarkerConfigurationImpl(); + + setMiscellaneousProperties(c); + setSharedVariables(c, ctx); + addDirectives(c); + addMethods(c); + + return c; + } + + private void setMiscellaneousProperties(FreemarkerConfigurationImpl c) { + /* + * Lengthen the cache time. + */ + c.setTemplateUpdateDelay(60); // increase from the 5-second default + + /* + * On most template models, hide the getters and setters that take + * arguments. + */ + BeansWrapper wrapper = new DefaultObjectWrapper(); + wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY); + c.setObjectWrapper(wrapper); + + /* + * Set a default Locale, but expect it to be overridden by the + * request. + */ + c.setLocale(java.util.Locale.US); + + /* + * This is how we like our date and time strings to look. + */ + String dateFormat = "M/d/yyyy"; + c.setDateFormat(dateFormat); + String timeFormat = "h:mm a"; + c.setTimeFormat(timeFormat); + c.setDateTimeFormat(dateFormat + " " + timeFormat); + + /* + * What character set is used when escaping special characters in a + * URL? + */ + try { + c.setSetting("url_escaping_charset", "UTF-8"); + } catch (TemplateException e) { + log.error("Error setting value for url_escaping_charset."); + } + } + + private void setSharedVariables(FreemarkerConfigurationImpl c, + ServletContext ctx) throws TemplateModelException { + c.setSharedVariable("version", getRevisionInfo(ctx)); + + /* + * Put in edit configuration constants - useful for freemarker + * templates/editing + */ + c.setSharedVariable("editConfigurationConstants", + EditConfigurationConstants.exportConstants()); + } + + private void addDirectives(FreemarkerConfigurationImpl c) { + c.setSharedVariable("dump", new freemarker.ext.dump.DumpDirective()); + c.setSharedVariable("dumpAll", + new freemarker.ext.dump.DumpAllDirective()); + c.setSharedVariable("help", new freemarker.ext.dump.HelpDirective()); + c.setSharedVariable("shortView", new IndividualShortViewDirective()); + c.setSharedVariable("url", new UrlDirective()); + c.setSharedVariable("widget", new WidgetDirective()); + } + + private void addMethods(FreemarkerConfigurationImpl c) { + c.setSharedVariable("profileUrl", new IndividualProfileUrlMethod()); + c.setSharedVariable("localName", new IndividualLocalNameMethod()); + c.setSharedVariable("placeholderImageUrl", + new IndividualPlaceholderImageUrlMethod()); + c.setSharedVariable("i18n", new I18nMethodModel()); + } + + private Map getRevisionInfo(ServletContext ctx) { + Map map = new HashMap(); + map.put("label", RevisionInfoBean.getBean(ctx).getReleaseLabel()); + map.put("moreInfoUrl", UrlBuilder.getUrl("/revisionInfo")); + return map; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + instance = null; + } + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java new file mode 100644 index 000000000..b4318c97f --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java @@ -0,0 +1,309 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.freemarker.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter; +import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetterUtils; +import freemarker.core.Environment; +import freemarker.template.Configuration; +import freemarker.template.ObjectWrapper; +import freemarker.template.SimpleScalar; +import freemarker.template.Template; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import freemarker.template.utility.DeepUnwrap; + +/** + * Extend the Freemarker Configuration class to include some information that is + * particular to the current request. + * + * Takes advantage of the fact that each servlet request runs in a separate + * thread. Stores the request-based information in a ThreadLocal. Override any + * methods that should return that information instead of (or in addition to) + * the common info. + * + * Only the getters are overridden, not the setters. So if you call + * setAllSharedVariables(), for example, it will have no effect on the + * request-based information. + */ +public class FreemarkerConfigurationImpl extends Configuration { + private static final Log log = LogFactory + .getLog(FreemarkerConfigurationImpl.class); + + private final ThreadLocal rbiRef = new ThreadLocal<>(); + + void setRequestInfo(HttpServletRequest req) { + rbiRef.set(new RequestBasedInformation(req, this)); + } + + @Override + public Object getCustomAttribute(String name) { + Map attribs = rbiRef.get().getCustomAttributes(); + if (attribs.containsKey(name)) { + return attribs.get(name); + } else { + return super.getCustomAttribute(name); + } + } + + @Override + public String[] getCustomAttributeNames() { + Set rbiNames = rbiRef.get().getCustomAttributes().keySet(); + return joinNames(rbiNames, super.getCustomAttributeNames()); + } + + @Override + public TemplateModel getSharedVariable(String name) { + Map vars = rbiRef.get().getSharedVariables(); + if (vars.containsKey(name)) { + return vars.get(name); + } else { + return super.getSharedVariable(name); + } + } + + @Override + public Set getSharedVariableNames() { + Set rbiNames = rbiRef.get().getSharedVariables().keySet(); + + @SuppressWarnings("unchecked") + Set superNames = super.getSharedVariableNames(); + + Set allNames = new HashSet<>(superNames); + allNames.addAll(rbiNames); + return allNames; + } + + @Override + public Locale getLocale() { + return rbiRef.get().getReq().getLocale(); + } + + private String[] joinNames(Set nameSet, String[] nameArray) { + Set allNames = new HashSet<>(nameSet); + for (String n : nameArray) { + allNames.add(n); + } + return (String[]) allNames.toArray(); + } + + // ---------------------------------------------------------------------- + // Apply DataGetters to templates when loading. + // + // TODO Clean this up VIVO-249 + // ---------------------------------------------------------------------- + + /** + * Override getTemplate(), so we can apply DataGetters to all included + * templates. + * + * This won't work for top-level Templates, since the Environment hasn't + * been created yet. When TemplateProcessingHelper creates the Environment, + * it must call retrieveAndRunDataGetters() for the top-level Template. + */ + @Override + public Template getTemplate(String name, Locale locale, String encoding, + boolean parse) throws IOException { + Template template = super.getTemplate(name, locale, encoding, parse); + + if (template == null) { + log.debug("Template '" + name + "' not found for locale '" + locale + + "'."); + return template; + } + + Environment env = getEnvironment(); + if (env == null) { + log.debug("Not fetching data getters for template '" + + template.getName() + "'. No environment."); + return template; + } + + retrieveAndRunDataGetters(env, template.getName()); + return template; + } + + /** + * Find the DataGetters for this template, and apply them to the Freemarker + * environment. + */ + public static void retrieveAndRunDataGetters(Environment env, + String templateName) { + HttpServletRequest req = (HttpServletRequest) env + .getCustomAttribute("request"); + VitroRequest vreq = new VitroRequest(req); + + if (dataGettersAlreadyApplied(env, templateName)) { + log.debug("DataGetters for '" + templateName + + "' have already been applied"); + return; + } + + try { + List dgList = DataGetterUtils + .getDataGettersForTemplate(vreq, vreq.getDisplayModel(), + templateName); + log.debug("Retrieved " + dgList.size() + + " data getters for template '" + templateName + "'"); + + @SuppressWarnings("unchecked") + Map dataMap = (Map) DeepUnwrap + .permissiveUnwrap(env.getDataModel()); + for (DataGetter dg : dgList) { + applyDataGetter(dg, env, dataMap); + } + } catch (Exception e) { + log.warn(e, e); + } + } + + /** + * Have the DataGetters for this template already been applied to this + * environment? If not, record that they are being applied now. + */ + @SuppressWarnings("unchecked") + private static boolean dataGettersAlreadyApplied(Environment env, + String templateName) { + Set names; + Object o = env.getCustomAttribute("dataGettersApplied"); + if (o instanceof Set) { + names = (Set) o; + } else { + names = new HashSet(); + } + + boolean added = names.add(templateName); + if (added) { + env.setCustomAttribute("dataGettersApplied", names); + return false; + } else { + return true; + } + } + + /** + * Get the data from a DataGetter, and store it in global variables in the + * Freemarker environment. + */ + private static void applyDataGetter(DataGetter dg, Environment env, + Map dataMap) throws TemplateModelException { + Map moreData = dg.getData(dataMap); + ObjectWrapper wrapper = env.getObjectWrapper(); + if (moreData != null) { + for (String key : moreData.keySet()) { + Object value = moreData.get(key); + env.setGlobalVariable(key, wrapper.wrap(value)); + log.debug("Stored in environment: '" + key + "' = '" + value + + "'"); + } + } + } + + // ---------------------------------------------------------------------- + // Helper class + // ---------------------------------------------------------------------- + + /** + * Holds the request-based information. Currently, it's shared variables, a + * custom attribute, and the locale. In the future, it could be more. + */ + private static class RequestBasedInformation { + private final HttpServletRequest req; + private final Configuration c; + private final Map customAttributes = new HashMap<>(); + private final Map sharedVariables = new HashMap<>(); + + public RequestBasedInformation(HttpServletRequest req, Configuration c) { + this.req = req; + this.c = c; + + setSharedVariables(req); + setCustomAttributes(req); + } + + public HttpServletRequest getReq() { + return req; + } + + public Map getCustomAttributes() { + return customAttributes; + } + + public Map getSharedVariables() { + return sharedVariables; + } + + private void setSharedVariables(HttpServletRequest req) { + ServletContext ctx = req.getSession().getServletContext(); + VitroRequest vreq = new VitroRequest(req); + ApplicationBean appBean = vreq.getAppBean(); + String siteName = appBean.getApplicationName(); + String tagLine = appBean.getShortHand(); + String themeDir = appBean.getThemeDir().replaceAll("/$", ""); + String currentTheme = themeDir + .substring(themeDir.lastIndexOf('/') + 1); + Map siteUrls = getSiteUrls(ctx, themeDir); + + sharedVariables.put("siteName", wrap(siteName)); + sharedVariables.put("themeDir", wrap(themeDir)); + sharedVariables.put("currentTheme", wrap(currentTheme)); + sharedVariables.put("siteTagline", wrap(tagLine)); + sharedVariables.put("urls", wrap(siteUrls)); + } + + private Map getSiteUrls(ServletContext ctx, + String themeDir) { + Map urls = new HashMap(); + + // Templates use this to construct urls. + urls.put("base", ctx.getContextPath()); + urls.put("home", UrlBuilder.getHomeUrl()); + urls.put("about", UrlBuilder.getUrl(Route.ABOUT)); + urls.put("search", UrlBuilder.getUrl(Route.SEARCH)); + urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE)); + urls.put("login", UrlBuilder.getLoginUrl()); + urls.put("logout", UrlBuilder.getLogoutUrl()); + urls.put("siteAdmin", UrlBuilder.getUrl(Route.SITE_ADMIN)); + + urls.put("themeImages", UrlBuilder.getUrl(themeDir + "/images")); + urls.put("images", UrlBuilder.getUrl("/images")); + urls.put("theme", UrlBuilder.getUrl(themeDir)); + urls.put("index", UrlBuilder.getUrl("/browse")); + + return urls; + } + + private TemplateModel wrap(Object o) { + try { + return c.getObjectWrapper().wrap(o); + } catch (TemplateModelException e) { + log.error("Failed to wrap this " + + "for the Freemarker configuration: " + o, e); + return new SimpleScalar(String.valueOf(o)); + } + } + + private void setCustomAttributes(HttpServletRequest req) { + customAttributes.put("request", req); + } + + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java index 627ad04ca..9bd5107f2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/i18n/selection/LocaleSelectionDataGetter.java @@ -60,11 +60,11 @@ public class LocaleSelectionDataGetter implements DataGetter { return Collections.emptyMap(); } - Map result = new HashMap(); + Map result = new HashMap<>(); result.put("selectLocaleUrl", UrlBuilder.getUrl("/selectLocale")); result.put("locales", buildLocalesList(selectables)); - Map bodyMap = new HashMap(); + Map bodyMap = new HashMap<>(); bodyMap.put("selectLocale", result); log.debug("Sending these values: " + bodyMap); return bodyMap; @@ -72,7 +72,7 @@ public class LocaleSelectionDataGetter implements DataGetter { private List> buildLocalesList(List selectables) { Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); - List> list = new ArrayList>(); + List> list = new ArrayList<>(); for (Locale locale : selectables) { try { list.add(buildLocaleMap(locale, currentLocale)); @@ -86,10 +86,12 @@ public class LocaleSelectionDataGetter implements DataGetter { private Map buildLocaleMap(Locale locale, Locale currentLocale) throws FileNotFoundException { - Map map = new HashMap(); + Map map = new HashMap<>(); map.put("code", locale.toString()); map.put("label", locale.getDisplayName(currentLocale)); map.put("imageUrl", LocaleSelectorUtilities.getImageUrl(vreq, locale)); + map.put("selected", currentLocale.equals(locale)); return map; } + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java index 1ce5169e5..b0cb19d29 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java @@ -63,8 +63,11 @@ public class SimpleReasoner extends StatementListener { private OntModel fullModel; // contains at least the // asserted and inferred ABox - private static final String mostSpecificTypePropertyURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#mostSpecificType"; - private static final AnnotationProperty mostSpecificType = (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)).createAnnotationProperty(mostSpecificTypePropertyURI); + private static final String mostSpecificTypePropertyURI = + "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#mostSpecificType"; + private static final AnnotationProperty mostSpecificType = + (ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)) + .createAnnotationProperty(mostSpecificTypePropertyURI); // DeltaComputer private CumulativeDeltaModeler aBoxDeltaModeler1 = null; @@ -77,13 +80,18 @@ public class SimpleReasoner extends StatementListener { private boolean stopRequested = false; private List pluginList = new CopyOnWriteArrayList(); + + private boolean doSameAs = true; /** * @param tboxModel - input. This model contains both asserted and inferred TBox axioms * @param aboxModel - input. This model contains asserted ABox statements - * @param inferenceModel - output. This is the model in which inferred (materialized) ABox statements are maintained (added or retracted). - * @param inferenceRebuildModel - output. This the model is temporarily used when the whole ABox inference model is rebuilt - * @param inferenceScratchpadModel - output. This the model is temporarily used when the whole ABox inference model is rebuilt + * @param inferenceModel - output. This is the model in which inferred (materialized) + * ABox statements are maintained (added or retracted). + * @param inferenceRebuildModel - output. This the model is temporarily used when the + * whole ABox inference model is rebuilt + * @param inferenceScratchpadModel - output. This the model is temporarily used when + * the whole ABox inference model is rebuilt */ public SimpleReasoner(OntModel tboxModel, RDFService rdfService, @@ -125,7 +133,8 @@ public class SimpleReasoner extends StatementListener { * * @param tboxModel - input. This model contains both asserted and inferred TBox axioms * @param aboxModel - input. This model contains asserted ABox statements - * @param inferenceModel - output. This is the model in which inferred (materialized) ABox statements are maintained (added or retracted). + * @param inferenceModel - output. This is the model in which inferred (materialized) + * ABox statements are maintained (added or retracted). */ public SimpleReasoner(OntModel tboxModel, OntModel aboxModel, Model inferenceModel) { this.tboxModel = tboxModel; @@ -148,7 +157,11 @@ public class SimpleReasoner extends StatementListener { public List getPluginList() { return this.pluginList; } - + + public void setSameAsEnabled( boolean tf){ + this.doSameAs = tf; + } + /* * Performs incremental ABox reasoning based * on the addition of a new statement @@ -158,9 +171,9 @@ public class SimpleReasoner extends StatementListener { public void addedStatement(Statement stmt) { try { if (stmt.getPredicate().equals(RDF.type)) { - addedABoxTypeAssertion(stmt, inferenceModel, new HashSet()); + addedABoxTypeAssertion(stmt, inferenceModel, new HashSet()); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet()); - } else if (stmt.getPredicate().equals(OWL.sameAs)) { + } else if ( doSameAs && stmt.getPredicate().equals(OWL.sameAs)) { addedABoxSameAsAssertion(stmt, inferenceModel); } else { addedABoxAssertion(stmt, inferenceModel); @@ -168,8 +181,7 @@ public class SimpleReasoner extends StatementListener { doPlugins(ModelUpdate.Operation.ADD,stmt); - } catch (Exception e) { - // don't stop the edit if there's an exception + } catch (Exception e) { // don't stop the edit if there's an exception log.error("Exception while computing inferences: " + e.getMessage()); } } @@ -180,20 +192,16 @@ public class SimpleReasoner extends StatementListener { * from the ABox. */ @Override - public void removedStatement(Statement stmt) { - + public void removedStatement(Statement stmt) { try { - handleRemovedStatement(stmt); - - } catch (Exception e) { - // don't stop the edit if there's an exception + handleRemovedStatement(stmt); + } catch (Exception e) { // don't stop the edit if there's an exception log.error("Exception while retracting inferences: ", e); } } /* - * Synchronized part of removedStatement. Interacts - * with DeltaComputer. + * Synchronized part of removedStatement. Interacts with DeltaComputer. */ protected synchronized void handleRemovedStatement(Statement stmt) { if (batchMode == 1) { @@ -204,7 +212,7 @@ public class SimpleReasoner extends StatementListener { if (stmt.getPredicate().equals(RDF.type)) { removedABoxTypeAssertion(stmt, inferenceModel); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet()); - } else if (stmt.getPredicate().equals(OWL.sameAs)) { + } else if ( doSameAs && stmt.getPredicate().equals(OWL.sameAs)) { removedABoxSameAsAssertion(stmt, inferenceModel); } else { removedABoxAssertion(stmt, inferenceModel); @@ -212,16 +220,21 @@ public class SimpleReasoner extends StatementListener { doPlugins(ModelUpdate.Operation.RETRACT,stmt); } } - - /* + + /** * Performs incremental ABox reasoning based * on changes to the class hierarchy. - * - * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf - */ - public void addedTBoxStatement(Statement stmt) { - try { - if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) { + * + * addedTBoxStatement and removedTBoxStatement use the + * same tests so the are merged into this method. + * + * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf + */ + protected void changedTBoxStatement( Statement stmt , boolean add){ + try { + if (!(stmt.getPredicate().equals(RDFS.subClassOf) + || stmt.getPredicate().equals(OWL.equivalentClass) + || stmt.getPredicate().equals(OWL.inverseOf))) { return; } @@ -230,7 +243,9 @@ public class SimpleReasoner extends StatementListener { return; } - if (stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass)) { + if (stmt.getPredicate().equals(RDFS.subClassOf) + || stmt.getPredicate().equals(OWL.equivalentClass)) { + // ignore anonymous classes if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) { return; @@ -238,22 +253,33 @@ public class SimpleReasoner extends StatementListener { OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); if (subject == null) { - log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); + log.debug("didn't find subject class in the tbox: " + + (stmt.getSubject()).getURI()); return; } OntClass object = tboxModel.getOntClass(((Resource)stmt.getObject()).getURI()); if (object == null) { - log.debug("didn't find object class in the tbox: " + ((Resource)stmt.getObject()).getURI()); + log.debug("didn't find object class in the tbox: " + + ((Resource)stmt.getObject()).getURI()); return; } if (stmt.getPredicate().equals(RDFS.subClassOf)) { + if( add ){ addedSubClass(subject,object,inferenceModel); + }else{ + removedSubClass( subject,object,inferenceModel); + } } else { // equivalent class is the same as subclass in both directions - addedSubClass(subject,object,inferenceModel); - addedSubClass(object,subject,inferenceModel); + if(add){ + addedSubClass(subject,object,inferenceModel); + addedSubClass(object,subject,inferenceModel); + }else{ + removedSubClass( subject,object,inferenceModel); + removedSubClass(object,subject,inferenceModel); + } } } else { if ( stmt.getObject().asResource().getURI() == null ) { @@ -268,99 +294,50 @@ public class SimpleReasoner extends StatementListener { OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI()); if (prop1 == null) { - log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI()); + log.debug("didn't find subject property in the tbox: " + + (stmt.getSubject()).getURI()); return; } OntProperty prop2 = tboxModel.getOntProperty(((Resource)stmt.getObject()).getURI()); if (prop2 == null) { - log.debug("didn't find object property in the tbox: " + ((Resource)stmt.getObject()).getURI()); + log.debug("didn't find object property in the tbox: " + + ((Resource)stmt.getObject()).getURI()); return; } - addedInverseProperty(prop1, prop2, inferenceModel); + if( add ){ + addedInverseProperty(prop1, prop2, inferenceModel); + } else { + removedInverseProperty(prop1,prop2,inferenceModel); + } } - } catch (Exception e) { - // don't stop the edit if there's an exception - log.error("Exception while adding inference(s)",e); - } + } catch (Exception e) { // don't stop the edit if there's an exception + log.error("Exception while " + (add?"adding":"removing") + " inference(s)",e); + } + } + + /** + * Performs incremental ABox reasoning based + * on changes to the class hierarchy. + * + * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf + */ + public void addedTBoxStatement(Statement stmt) { + changedTBoxStatement( stmt, true); } - /* + /** * Performs incremental ABox reasoning based * on changes to the class hierarchy. * * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf */ public void removedTBoxStatement(Statement stmt) { - try { - if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) { - return; - } - - if (!stmt.getObject().isResource()) { - log.warn("The object of this assertion is not a resource: " + stmtString(stmt)); - return; - } - - if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) { - - // ignore anonymous classes - if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) { - return; - } - - OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); - if (subject == null) { - log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); - return; - } - - OntClass object = tboxModel.getOntClass(((Resource)stmt.getObject()).getURI()); - if (object == null) { - log.debug("didn't find object class in the tbox: " + ((Resource)stmt.getObject()).getURI()); - return; - } - - if (stmt.getPredicate().equals(RDFS.subClassOf)) { - removedSubClass(subject,object,inferenceModel); - } else { - // equivalent class is the same as subclass in both directions - removedSubClass(subject,object,inferenceModel); - removedSubClass(object,subject,inferenceModel); - } - } else { - if ( stmt.getObject().asResource().getURI() == null ) { - log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); - return; - } - - if ( stmt.getSubject().getURI() == null ) { - log.warn("The subject of this assertion has a null URI: " + stmtString(stmt)); - return; - } - - OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI()); - if (prop1 == null) { - log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI()); - return; - } - - OntProperty prop2 = tboxModel.getOntProperty(((Resource)stmt.getObject()).getURI()); - if (prop2 == null) { - log.debug("didn't find object property in the tbox: " + ((Resource)stmt.getObject()).getURI()); - return; - } - - removedInverseProperty(prop1, prop2, inferenceModel); - } - } catch (Exception e) { - // don't stop the edit if there's an exception - log.error("Exception while removing inference(s)",e); - } + changedTBoxStatement(stmt, false); } - /* + /** * Performs incremental reasoning based on a new type assertion * added to the ABox (assertion that an individual is of a certain * type). @@ -368,7 +345,9 @@ public class SimpleReasoner extends StatementListener { * If it is added that B is of type A, then for each superclass of * A assert that B is of that type. */ - protected void addedABoxTypeAssertion(Statement stmt, Model inferenceModel, HashSet unknownTypes) { + protected void addedABoxTypeAssertion(Statement stmt, + Model inferenceModel, + HashSet unknownTypes) { tboxModel.enterCriticalSection(Lock.READ); try { @@ -391,7 +370,9 @@ public class SimpleReasoner extends StatementListener { // of classes not individuals. if (parentClass.isAnon()) continue; - Statement infStmt = ResourceFactory.createStatement(stmt.getSubject(), RDF.type, parentClass); + Statement infStmt = + ResourceFactory.createStatement(stmt.getSubject(), + RDF.type, parentClass); addInference(infStmt,inferenceModel,true); } } @@ -399,13 +380,17 @@ public class SimpleReasoner extends StatementListener { if ( !(stmt.getObject().asResource().getNameSpace()).equals(OWL.NS)) { if (!unknownTypes.contains(stmt.getObject().asResource().getURI())) { unknownTypes.add(stmt.getObject().asResource().getURI()); - log.warn("Didn't find the target class (the object of an added rdf:type statement) in the TBox: " + - (stmt.getObject().asResource()).getURI() + ". No class subsumption reasoning will be done based on type assertions of this type."); + log.warn("Didn't find the target class (the object of an added " + + "rdf:type statement) in the TBox: " + + (stmt.getObject().asResource()).getURI() + + ". No class subsumption reasoning will be done " + + "based on type assertions of this type."); } } } } else { - log.debug("The object of this rdf:type assertion has a null URI, no reasoning will be done based on this assertion: " + stmtString(stmt)); + log.debug("The object of this rdf:type assertion has a null URI, no reasoning" + + " will be done based on this assertion: " + stmtString(stmt)); return; } } finally { @@ -422,7 +407,7 @@ public class SimpleReasoner extends StatementListener { } } - /* + /** * If it is removed that B is of type A, then for each superclass of A remove * the inferred statement that B is of that type UNLESS it is otherwise entailed * that B is of that type. @@ -432,7 +417,7 @@ public class SimpleReasoner extends StatementListener { removedABoxTypeAssertion(stmt, inferenceModel, null); } - /* + /** * If it is removed that B is of type A, then for each superclass of A remove * the inferred statement that B is of that type UNLESS it is otherwise entailed * that B is of that type. @@ -442,7 +427,9 @@ public class SimpleReasoner extends StatementListener { * improvement when this method is called repeatedly for the same subject. * */ - protected void removedABoxTypeAssertion(Statement stmt, Model inferenceModel, List remainingTypeURIs) { + protected void removedABoxTypeAssertion(Statement stmt, + Model inferenceModel, + List remainingTypeURIs) { tboxModel.enterCriticalSection(Lock.READ); try { Resource cls = null; @@ -469,22 +456,27 @@ public class SimpleReasoner extends StatementListener { // of classes not individuals. if (parentClass.isAnon()) continue; - List typeURIs = (remainingTypeURIs == null) ? getRemainingAssertedTypeURIs(stmt.getSubject()) : remainingTypeURIs; + List typeURIs = (remainingTypeURIs == null) + ? getRemainingAssertedTypeURIs(stmt.getSubject()) : remainingTypeURIs; if (entailedType(stmt.getSubject(),parentClass, typeURIs)) { continue; // if a type is still entailed without the } - // removed statement, then don't remove it - // from the inferences + // removed statement, then don't remove it + // from the inferences - Statement infStmt = ResourceFactory.createStatement(stmt.getSubject(), RDF.type, parentClass); + Statement infStmt = + ResourceFactory.createStatement(stmt.getSubject(), RDF.type, parentClass); removeInference(infStmt,inferenceModel,true,false); } } else { - log.warn("Didn't find target class (the object of the removed rdf:type statement) in the TBox: " - + ((Resource)stmt.getObject()).getURI() + ". No class subsumption reasoning will be performed based on the removal of this assertion."); + log.warn("Didn't find target class (the object of the removed rdf:type" + + "statement) in the TBox: " + + ((Resource)stmt.getObject()).getURI() + ". No class subsumption" + +" reasoning will be performed based on the removal of this assertion."); } } else { - log.warn("The object of this rdf:type assertion has a null URI: " + stmtString(stmt)); + log.warn("The object of this rdf:type assertion has a null URI: " + + stmtString(stmt)); } } catch (Exception e) { log.warn("exception while removing abox type assertions: " + e.getMessage()); @@ -493,7 +485,206 @@ public class SimpleReasoner extends StatementListener { } } - /* + /** + * Performs incremental property-based reasoning. + * + * Retracts inferences based on the owl:inverseOf relationship. + * + * If it is removed that x prop1 y, and prop2 is an inverseOf prop1 + * then remove y prop2 x from the inference graph, unless it is + * otherwise entailed by the assertions graph independently of + * this removed statement. + */ + protected void removedABoxAssertion(Statement stmt, Model inferenceModel) { + + if (!stmt.getObject().isLiteral()) { + List inverseProperties = getInverseProperties(stmt); + Iterator inverseIter = inverseProperties.iterator(); + + while (inverseIter.hasNext()) { + OntProperty inverseProp = inverseIter.next(); + Statement infStmt = ResourceFactory.createStatement( + stmt.getObject().asResource(), inverseProp, stmt.getSubject()); + removeInference(infStmt,inferenceModel); + } + } + + if( doSameAs ) + doSameAsForRemovedABoxAssertion( stmt, inferenceModel ); + + // if a statement has been removed that is otherwise entailed, + // add it to the inference graph. + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (entailedStatement(stmt) && !inferenceModel.contains(stmt)) { + inferenceModel.add(stmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } + + + /** + * If it is added that B is a subClass of A, then for each + * individual that is typed as B, either in the ABox or in the + * inferred model, infer that it is of type A. + */ + protected void addedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) { + //log.debug("subClass = " + subClass.getURI() + " superClass = " + superClass.getURI()); + OntModel unionModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + unionModel.addSubModel(aboxModel); + unionModel.addSubModel(inferenceModel); + List subjectList = new ArrayList(); + aboxModel.enterCriticalSection(Lock.READ); + try { + StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass); + while (iter.hasNext()) { + Statement stmt = iter.next(); + subjectList.add(stmt.getSubject()); + } + } finally { + aboxModel.leaveCriticalSection(); + } + + for (Resource subject : subjectList) { + Statement infStmt = ResourceFactory.createStatement(subject, RDF.type, superClass); + addInference(infStmt, inferenceModel, true); + setMostSpecificTypes(infStmt.getSubject(), inferenceModel, new HashSet()); + } + } + + /** + * If removed that B is a subclass of A, then for each individual + * that is of type B, either inferred or in the ABox, remove the + * assertion that it is of type A from the inferred model, + * UNLESS the individual is of some type C that is a subClass + * of A (including A itself) + */ + protected void removedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) { + OntModel unionModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + unionModel.addSubModel(aboxModel); + unionModel.addSubModel(inferenceModel); + List subjectList = new ArrayList(); + aboxModel.enterCriticalSection(Lock.READ); + try { + StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass); + while (iter.hasNext()) { + Statement stmt = iter.next(); + subjectList.add(stmt.getSubject()); + } + } finally { + aboxModel.leaveCriticalSection(); + } + + for (Resource ind : subjectList) { + if (entailedType(ind,superClass)) { + continue; + } + Statement infStmt = ResourceFactory.createStatement(ind, RDF.type, superClass); + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (inferenceModel.contains(infStmt)) { + inferenceModel.remove(infStmt); + } + + } finally { + inferenceModel.leaveCriticalSection(); + } + + setMostSpecificTypes(ind, inferenceModel, new HashSet()); + } + } + + /** + * If it is added that P is an inverse of Q, then: + * 1. For each statement involving predicate P in + * the assertions model add the inverse statement + * to the inference model if that inverse is + * in the assertions model. + * + * 2. Repeat the same for predicate Q. + * + */ + protected void addedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { + + if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { + log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); + return; + } + + Model inferences = ModelFactory.createDefaultModel(); + inferences.add(generateInverseInferences(prop1, prop2)); + inferences.add(generateInverseInferences(prop2, prop1)); + + if (inferences.isEmpty()) return; + + StmtIterator iter = inferences.listStatements(); + + while (iter.hasNext()) { + Statement infStmt = iter.next(); + addInference(infStmt, inferenceModel, true); + } + } + + /** + * If it is removed that P is an inverse of Q, then: + * 1. For each statement involving predicate P in + * the abox assertions model remove the inverse + * statement from the inference model unless + * that statement is otherwise entailed. + * + * 2. Repeat the same for predicate Q. + */ + protected void removedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { + + if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { + log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); + return; + } + + Model inferences = ModelFactory.createDefaultModel(); + inferences.add(generateInverseInferences(prop1, prop2)); + inferences.add(generateInverseInferences(prop2, prop1)); + + if (inferences.isEmpty()) return; + + StmtIterator iter = inferences.listStatements(); + + while (iter.hasNext()) { + Statement infStmt = iter.next(); + + if (entailedStatement(infStmt)) { + continue; + } + + removeInference(infStmt,inferenceModel); + } + } + + /** + * Get a list of individuals the same as the given individual + */ + protected List getSameIndividuals(Resource ind, Model inferenceModel) { + ArrayList sameIndividuals = new ArrayList(); + fullModel.enterCriticalSection(Lock.READ); + try { + Iterator iter = fullModel.listStatements(ind, OWL.sameAs, (RDFNode) null); + while (iter.hasNext()) { + Statement stmt = iter.next(); + if (stmt.getObject() != null + && stmt.getObject().isResource() + && stmt.getObject().asResource().getURI() != null) { + sameIndividuals.add(stmt.getObject().asResource()); + } + } + } finally { + fullModel.leaveCriticalSection(); + } + return sameIndividuals; + } + + /** * Materializes inferences based on the owl:sameAs relationship. * * If it is added that x owl:sameAs y, then all asserted and inferred @@ -542,12 +733,13 @@ public class SimpleReasoner extends StatementListener { generateSameAsInferences(object, subject, inferenceModel); } - /* + /** * Materializes inferences based on the owl:sameAs relationship. * * If it is removed that x is sameAs y, then remove y sameAs x from * the inference graph and then recompute the inferences for x and - * y based on their respective assertions. that x owl:sameAs y, then all asserted and inferred + * y based on their respective assertions. + * that x owl:sameAs y, then all asserted and inferred */ protected void removedABoxSameAsAssertion(Statement stmt, Model inferenceModel) { Resource subject = null; @@ -588,9 +780,30 @@ public class SimpleReasoner extends StatementListener { computeInferencesForIndividual(sIter2.next(), inferenceModel); } } + protected void doSameAsForAddedABoxAssertion(Statement stmt, Model inferenceModel){ + List sameIndividuals = + getSameIndividuals(stmt.getSubject().asResource(), inferenceModel); - /* - * + Iterator sameIter = sameIndividuals.iterator(); + while (sameIter.hasNext()) { + Resource subject = sameIter.next(); + Statement sameStmt = + ResourceFactory.createStatement(subject,stmt.getPredicate(),stmt.getObject()); + addInference(sameStmt,inferenceModel,false); + } + + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (inferenceModel.contains(stmt)) { + inferenceModel.remove(stmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } + + + /** * Materializes inferences based on the owl:inverseOf relationship. * and owl:sameAs * @@ -612,223 +825,22 @@ public class SimpleReasoner extends StatementListener { addInference(infStmt,inferenceModel,true); } } - - List sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(), inferenceModel); - Iterator sameIter = sameIndividuals.iterator(); - while (sameIter.hasNext()) { - Resource subject = sameIter.next(); - Statement sameStmt = ResourceFactory.createStatement(subject,stmt.getPredicate(),stmt.getObject()); - addInference(sameStmt,inferenceModel,false); - } - inferenceModel.enterCriticalSection(Lock.WRITE); - try { - if (inferenceModel.contains(stmt)) { - inferenceModel.remove(stmt); - } - } finally { - inferenceModel.leaveCriticalSection(); - } + doSameAsForAddedABoxAssertion( stmt, inferenceModel); } - - /* - * Performs incremental property-based reasoning. - * - * Retracts inferences based on the owl:inverseOf relationship. - * - * If it is removed that x prop1 y, and prop2 is an inverseOf prop1 - * then remove y prop2 x from the inference graph, unless it is - * otherwise entailed by the assertions graph independently of - * this removed statement. - */ - protected void removedABoxAssertion(Statement stmt, Model inferenceModel) { - - if (!stmt.getObject().isLiteral()) { - List inverseProperties = getInverseProperties(stmt); - Iterator inverseIter = inverseProperties.iterator(); - - while (inverseIter.hasNext()) { - OntProperty inverseProp = inverseIter.next(); - Statement infStmt = ResourceFactory.createStatement( - stmt.getObject().asResource(), inverseProp, stmt.getSubject()); - removeInference(infStmt,inferenceModel); - } - } - - List sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(), inferenceModel); + + void doSameAsForRemovedABoxAssertion(Statement stmt, Model inferenceModel){ + List sameIndividuals = + getSameIndividuals(stmt.getSubject().asResource(), inferenceModel); Iterator sameIter = sameIndividuals.iterator(); while (sameIter.hasNext()) { - Statement stmtSame = ResourceFactory.createStatement(sameIter.next(), stmt.getPredicate(), stmt.getObject()); + Statement stmtSame = + ResourceFactory.createStatement(sameIter.next(), + stmt.getPredicate(), + stmt.getObject()); removeInference(stmtSame,inferenceModel,false,true); - } - - // if a statement has been removed that is otherwise entailed, - // add it to the inference graph. - inferenceModel.enterCriticalSection(Lock.WRITE); - try { - if (entailedStatement(stmt) && !inferenceModel.contains(stmt)) { - inferenceModel.add(stmt); - } - } finally { - inferenceModel.leaveCriticalSection(); - } - } - - /* - * If it is added that B is a subClass of A, then for each - * individual that is typed as B, either in the ABox or in the - * inferred model, infer that it is of type A. - */ - protected void addedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) { - //log.debug("subClass = " + subClass.getURI() + " superClass = " + superClass.getURI()); - OntModel unionModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - unionModel.addSubModel(aboxModel); - unionModel.addSubModel(inferenceModel); - List subjectList = new ArrayList(); - aboxModel.enterCriticalSection(Lock.READ); - try { - StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass); - while (iter.hasNext()) { - Statement stmt = iter.next(); - subjectList.add(stmt.getSubject()); - } - } finally { - aboxModel.leaveCriticalSection(); - } - - for (Resource subject : subjectList) { - Statement infStmt = ResourceFactory.createStatement(subject, RDF.type, superClass); - addInference(infStmt, inferenceModel, true); - setMostSpecificTypes(infStmt.getSubject(), inferenceModel, new HashSet()); - } - } - - /* - * If removed that B is a subclass of A, then for each individual - * that is of type B, either inferred or in the ABox, remove the - * assertion that it is of type A from the inferred model, - * UNLESS the individual is of some type C that is a subClass - * of A (including A itself) - */ - protected void removedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) { - OntModel unionModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - unionModel.addSubModel(aboxModel); - unionModel.addSubModel(inferenceModel); - List subjectList = new ArrayList(); - aboxModel.enterCriticalSection(Lock.READ); - try { - StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass); - while (iter.hasNext()) { - Statement stmt = iter.next(); - subjectList.add(stmt.getSubject()); - } - } finally { - aboxModel.leaveCriticalSection(); - } - - for (Resource ind : subjectList) { - if (entailedType(ind,superClass)) { - continue; - } - Statement infStmt = ResourceFactory.createStatement(ind, RDF.type, superClass); - inferenceModel.enterCriticalSection(Lock.WRITE); - try { - if (inferenceModel.contains(infStmt)) { - inferenceModel.remove(infStmt); - } - - } finally { - inferenceModel.leaveCriticalSection(); - } - - setMostSpecificTypes(ind, inferenceModel, new HashSet()); - } - } - - /* - * If it is added that P is an inverse of Q, then: - * 1. For each statement involving predicate P in - * the assertions model add the inverse statement - * to the inference model if that inverse is - * in the assertions model. - * - * 2. Repeat the same for predicate Q. - * - */ - protected void addedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { - - if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { - log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); - return; - } - - Model inferences = ModelFactory.createDefaultModel(); - inferences.add(generateInverseInferences(prop1, prop2)); - inferences.add(generateInverseInferences(prop2, prop1)); - - if (inferences.isEmpty()) return; - - StmtIterator iter = inferences.listStatements(); - - while (iter.hasNext()) { - Statement infStmt = iter.next(); - addInference(infStmt, inferenceModel, true); - } - } - - /* - * If it is removed that P is an inverse of Q, then: - * 1. For each statement involving predicate P in - * the abox assertions model remove the inverse - * statement from the inference model unless - * that statement is otherwise entailed. - * - * 2. Repeat the same for predicate Q. - */ - protected void removedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { - - if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { - log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); - return; - } - - Model inferences = ModelFactory.createDefaultModel(); - inferences.add(generateInverseInferences(prop1, prop2)); - inferences.add(generateInverseInferences(prop2, prop1)); - - if (inferences.isEmpty()) return; - - StmtIterator iter = inferences.listStatements(); - - while (iter.hasNext()) { - Statement infStmt = iter.next(); - - if (entailedStatement(infStmt)) { - continue; - } - - removeInference(infStmt,inferenceModel); - } - } - - /* - * Get a list of individuals the same as the given individual - */ - protected List getSameIndividuals(Resource ind, Model inferenceModel) { - ArrayList sameIndividuals = new ArrayList(); - fullModel.enterCriticalSection(Lock.READ); - try { - Iterator iter = fullModel.listStatements(ind, OWL.sameAs, (RDFNode) null); - while (iter.hasNext()) { - Statement stmt = iter.next(); - if (stmt.getObject() == null || !stmt.getObject().isResource() || stmt.getObject().asResource().getURI() == null) continue; - sameIndividuals.add(stmt.getObject().asResource()); - } - } finally { - fullModel.leaveCriticalSection(); - } - return sameIndividuals; - } + } + } protected void generateSameAsInferences(Resource ind1, Resource ind2, Model inferenceModel) { @@ -838,11 +850,13 @@ public class SimpleReasoner extends StatementListener { aboxModel.enterCriticalSection(Lock.READ); try { - Iterator iter = unionModel.listStatements(ind1, (Property) null, (RDFNode) null); + Iterator iter = + unionModel.listStatements(ind1, (Property) null, (RDFNode) null); while (iter.hasNext()) { Statement stmt = iter.next(); if (stmt.getObject() == null) continue; - Statement infStmt = ResourceFactory.createStatement(ind2,stmt.getPredicate(),stmt.getObject()); + Statement infStmt = + ResourceFactory.createStatement(ind2,stmt.getPredicate(),stmt.getObject()); addInference(infStmt, inferenceModel,true); } } finally { @@ -852,7 +866,7 @@ public class SimpleReasoner extends StatementListener { return; } - /* + /** * Remove inferences for individual */ protected void removeInferencesForIndividual(Resource ind, Model inferenceModel) { @@ -861,7 +875,8 @@ public class SimpleReasoner extends StatementListener { inferenceModel.enterCriticalSection(Lock.READ); try { - Iterator iter = inferenceModel.listStatements(ind, (Property) null, (RDFNode) null); + Iterator iter = + inferenceModel.listStatements(ind, (Property) null, (RDFNode) null); while (iter.hasNext()) { individualInferences.add(iter.next()); @@ -880,7 +895,7 @@ public class SimpleReasoner extends StatementListener { return; } - /* + /** * compute inferences for individual */ protected void computeInferencesForIndividual(Resource ind, Model inferenceModel) { @@ -901,17 +916,21 @@ public class SimpleReasoner extends StatementListener { return; } - // Returns true if it is entailed by class subsumption that - // subject is of type cls; otherwise returns false. + /** + * Returns true if it is entailed by class subsumption that + * subject is of type cls; otherwise returns false. + */ protected boolean entailedType(Resource subject, Resource cls) { return entailedType(subject, cls, null); } - // Returns true if it is entailed by class subsumption that - // subject is of type cls; otherwise returns false. - // remainingTypeURIs is an optional list of asserted type URIs for the subject - // resource, and may be null. Supplying a precompiled list can yield performance - // improvement when this method is called repeatedly for the same subject. + /** + * Returns true if it is entailed by class subsumption that + * subject is of type cls; otherwise returns false. + * remainingTypeURIs is an optional list of asserted type URIs for the subject + * resource, and may be null. Supplying a precompiled list can yield performance + * improvement when this method is called repeatedly for the same subject. + */ protected boolean entailedType(Resource subject, Resource cls, List remainingTypeURIs) { List subClasses = getSubClasses(cls); @@ -922,7 +941,8 @@ public class SimpleReasoner extends StatementListener { } } - List typeURIs = (remainingTypeURIs == null) ? getRemainingAssertedTypeURIs(subject) : remainingTypeURIs; + List typeURIs = (remainingTypeURIs == null) ? + getRemainingAssertedTypeURIs(subject) : remainingTypeURIs; for (String typeURI : typeURIs) { if (!typeURI.equals(cls.getURI()) && subClassURIs.contains(typeURI)) { @@ -966,10 +986,12 @@ public class SimpleReasoner extends StatementListener { List subClasses = new ArrayList(); tboxModel.enterCriticalSection(Lock.READ); try { - Iterator iter = tboxModel.listStatements((Resource) null, RDFS.subClassOf, cls); + Iterator iter = + tboxModel.listStatements((Resource) null, RDFS.subClassOf, cls); while (iter.hasNext()) { Statement stmt = iter.next(); - if (stmt.getSubject() == null || stmt.getSubject().asResource().getURI() == null) continue; + if (stmt.getSubject() == null + || stmt.getSubject().asResource().getURI() == null) continue; if (!subClasses.contains(stmt.getSubject())) { subClasses.add(stmt.getSubject()); } @@ -1011,7 +1033,8 @@ public class SimpleReasoner extends StatementListener { iter = tboxModel.listStatements((Resource) null, OWL.equivalentClass, cls); while (iter.hasNext()) { Statement stmt = iter.next(); - if (stmt.getSubject() == null || stmt.getSubject().asResource().getURI() == null) continue; + if (stmt.getSubject() == null + || stmt.getSubject().asResource().getURI() == null) continue; if (!superClasses.contains(stmt.getSubject())) { superClasses.add(stmt.getSubject()); } @@ -1022,8 +1045,10 @@ public class SimpleReasoner extends StatementListener { } } - // Returns true if the triple is entailed by inverse property - // reasoning or sameAs reasoning; otherwise returns false. + /** + * Returns true if the triple is entailed by inverse property + * reasoning or sameAs reasoning; otherwise returns false. + */ protected boolean entailedStatement(Statement stmt) { //TODO think about checking class subsumption here (for convenience) @@ -1035,8 +1060,9 @@ public class SimpleReasoner extends StatementListener { try { while (iIter.hasNext()) { Property invProp = iIter.next(); - - Statement invStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), invProp, stmt.getSubject()); + Statement invStmt = + ResourceFactory.createStatement(stmt.getObject().asResource(), + invProp, stmt.getSubject()); if (aboxModel.contains(invStmt)) { return true; } @@ -1047,27 +1073,30 @@ public class SimpleReasoner extends StatementListener { } // individuals sameAs each other - List sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(),inferenceModel); - Iterator rIter = sameIndividuals.iterator(); - if (rIter.hasNext()) { - aboxModel.enterCriticalSection(Lock.READ); - try { - while (rIter.hasNext()) { - Resource subject = rIter.next(); + if( doSameAs ){ + List sameIndividuals = + getSameIndividuals(stmt.getSubject().asResource(),inferenceModel); + Iterator rIter = sameIndividuals.iterator(); + if (rIter.hasNext()) { + aboxModel.enterCriticalSection(Lock.READ); + try { + while (rIter.hasNext()) { + Resource subject = rIter.next(); - if (aboxModel.contains(subject, stmt.getPredicate(), stmt.getObject())) { - return true; - } - } - } finally { - aboxModel.leaveCriticalSection(); - } - } + if (aboxModel.contains(subject, stmt.getPredicate(), stmt.getObject())) { + return true; + } + } + } finally { + aboxModel.leaveCriticalSection(); + } + } + } return false; } - /* + /** * Returns a list of properties that are inverses of the property * in the given statement. */ @@ -1090,7 +1119,8 @@ public class SimpleReasoner extends StatementListener { } if (!stmt.getObject().isResource()) { - log.debug("The predicate of this statement is an object property, but the object is not a resource."); + log.debug("The predicate of this statement is an object property, " + +"but the object is not a resource."); return inverses; } @@ -1130,7 +1160,9 @@ public class SimpleReasoner extends StatementListener { while (iter.hasNext()) { Statement stmt = iter.next(); if (!stmt.getObject().isResource()) continue; - Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp, stmt.getSubject()); + Statement infStmt = + ResourceFactory.createStatement(stmt.getObject().asResource(), + inverseProp, stmt.getSubject()); inferences.add(infStmt); } } finally { @@ -1140,7 +1172,7 @@ public class SimpleReasoner extends StatementListener { return inferences; } - /* + /** * Add an inference from the inference model * * Adds the inference to the inference model if it is not already in @@ -1162,17 +1194,22 @@ public class SimpleReasoner extends StatementListener { } if (handleSameAs) { - List sameIndividuals = getSameIndividuals(infStmt.getSubject().asResource(), inferenceModel); + List sameIndividuals = + getSameIndividuals(infStmt.getSubject().asResource(), inferenceModel); Iterator sameIter = sameIndividuals.iterator(); while (sameIter.hasNext()) { Resource subject = sameIter.next(); - Statement sameStmt = ResourceFactory.createStatement(subject,infStmt.getPredicate(),infStmt.getObject()); - if (subject.equals(infStmt.getObject()) && OWL.sameAs.equals(infStmt.getPredicate())) { + Statement sameStmt = + ResourceFactory.createStatement(subject,infStmt.getPredicate(), + infStmt.getObject()); + if (subject.equals(infStmt.getObject()) + && OWL.sameAs.equals(infStmt.getPredicate())) { continue; } - if (!inferenceModel.contains(sameStmt) && !aboxModel.contains(sameStmt)) { + if (!inferenceModel.contains(sameStmt) + && !aboxModel.contains(sameStmt)) { inferenceModel.add(sameStmt); } } @@ -1185,7 +1222,7 @@ public class SimpleReasoner extends StatementListener { } } - /* + /** * Remove an inference from the inference model * * Removes the inference if it is not entailed by the abox model @@ -1202,7 +1239,9 @@ public class SimpleReasoner extends StatementListener { inferenceModel.enterCriticalSection(Lock.WRITE); try { - if ( (!checkEntailment || !entailedStatement(infStmt)) && inferenceModel.contains(infStmt)) { + if ( (!checkEntailment + || !entailedStatement(infStmt)) + && inferenceModel.contains(infStmt)) { inferenceModel.remove(infStmt); } } finally { @@ -1212,12 +1251,17 @@ public class SimpleReasoner extends StatementListener { if (handleSameAs) { inferenceModel.enterCriticalSection(Lock.WRITE); try { - List sameIndividuals = getSameIndividuals(infStmt.getSubject().asResource(), inferenceModel); + List sameIndividuals = + getSameIndividuals(infStmt.getSubject().asResource(), inferenceModel); Iterator sameIter = sameIndividuals.iterator(); while (sameIter.hasNext()) { - Statement infStmtSame = ResourceFactory.createStatement(sameIter.next(), infStmt.getPredicate(), infStmt.getObject()); - if ((!checkEntailment || !entailedStatement(infStmtSame)) && inferenceModel.contains(infStmtSame)) { + Statement infStmtSame = + ResourceFactory.createStatement(sameIter.next(), + infStmt.getPredicate(), infStmt.getObject()); + if ((!checkEntailment + || !entailedStatement(infStmtSame)) + && inferenceModel.contains(infStmtSame)) { inferenceModel.remove(infStmtSame); } } @@ -1227,7 +1271,8 @@ public class SimpleReasoner extends StatementListener { } } - /* + + /** * Find the most specific types (classes) of an individual and * infer them for the individual with the mostSpecificType * annotation. @@ -1480,18 +1525,10 @@ public class SimpleReasoner extends StatementListener { this.stopRequested = true; } - /* - * Utility method for logging - */ - public static String stmtString(Statement statement) { - return " [subject = " + statement.getSubject().getURI() + - "] [property = " + statement.getPredicate().getURI() + - "] [object = " + (statement.getObject().isLiteral() ? ((Literal)statement.getObject()).getLexicalForm() + " (Literal)" - : ((Resource)statement.getObject()).getURI() + " (Resource)") + "]"; - } + // DeltaComputer - /* + /** * Asynchronous reasoning mode (DeltaComputer) is used in the case of batch removals. */ public boolean isABoxReasoningAsynchronous() { @@ -1684,4 +1721,18 @@ public class SimpleReasoner extends StatementListener { log.info("ending DeltaComputer.run. batchMode = " + batchMode); } } + + + + /** + * Utility method for logging + */ + public static String stmtString(Statement statement) { + return " [subject = " + statement.getSubject().getURI() + + "] [property = " + statement.getPredicate().getURI() + + "] [object = " + (statement.getObject().isLiteral() + ? ((Literal)statement.getObject()).getLexicalForm() + " (Literal)" + : ((Resource)statement.getObject()).getURI() + " (Resource)") + "]"; + } + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/plugin/DisableSameAs.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/plugin/DisableSameAs.java new file mode 100644 index 000000000..009f0b409 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/plugin/DisableSameAs.java @@ -0,0 +1,58 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.reasoner.plugin; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.Statement; + +import edu.cornell.mannlib.vitro.webapp.reasoner.ReasonerPlugin; +import edu.cornell.mannlib.vitro.webapp.reasoner.SimpleReasoner; + +/** + * Disables sameAs in associated SimpleReasoner. + */ +public abstract class DisableSameAs implements ReasonerPlugin { + + private static final Log log = LogFactory.getLog(DisableSameAs.class); + + private SimpleReasoner simpleReasoner; + + public void setSimpleReasoner(SimpleReasoner simpleReasoner) { + this.simpleReasoner = simpleReasoner; + this.simpleReasoner.setSameAsEnabled( false ); + log.info("owl:sameAs disabled in SimpleReasoner."); + } + + public SimpleReasoner getSimpleReasoner() { + return this.simpleReasoner; + } + + public boolean isInterestedInAddedStatement(Statement stmt) { + return false; + } + + public boolean isInterestedInRemovedStatement(Statement stmt) { + return false; + } + + public void addedABoxStatement(Statement stmt, + Model aboxAssertionsModel, + Model aboxInferencesModel, + OntModel TBoxInferencesModel) { + return; + } + + + public void removedABoxStatement(Statement stmt, + Model aboxAssertionsModel, + Model aboxInferencesModel, + OntModel TBoxInferencesModel) { + return; + } + +} + diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/beans/FileBasedProhibitedFromSearch.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/beans/FileBasedProhibitedFromSearch.java index 45549f9f7..3f7502a64 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/beans/FileBasedProhibitedFromSearch.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/beans/FileBasedProhibitedFromSearch.java @@ -3,12 +3,10 @@ package edu.cornell.mannlib.vitro.webapp.search.beans; import java.io.File; -import java.io.FileInputStream; import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetupBase; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.RDFFilesLoader; public class FileBasedProhibitedFromSearch extends ProhibitedFromSearch { @@ -19,7 +17,7 @@ public class FileBasedProhibitedFromSearch extends ProhibitedFromSearch { * @param dir to find N3 files in. */ public FileBasedProhibitedFromSearch(String uri, File dir){ - super( uri, JenaDataSourceSetupBase.getModelFromDir(dir)); + super( uri, RDFFilesLoader.getModelFromDir(dir)); } public FileBasedProhibitedFromSearch(String URI, OntModel model) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SearchServiceController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SearchServiceController.java index 1d82546a6..b2dd876ab 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SearchServiceController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SearchServiceController.java @@ -43,7 +43,7 @@ public class SearchServiceController extends FreemarkerHttpServlet { * userAccount associated with the email. */ @Override - protected Actions requiredActions(VitroRequest vreq) { + public Actions requiredActions(VitroRequest vreq) { try{ // Works by side effect: parse the multi-part request and stash FileItems in request FileUploadServletRequest.parseRequest(vreq, 0); @@ -52,42 +52,21 @@ public class SearchServiceController extends FreemarkerHttpServlet { String pw = vreq.getParameter("password"); String email = vreq.getParameter("email"); - log.debug(String.format("email: '%s' password: '%s' ",email,pw)); - if( pw == null || email == null || pw.isEmpty() || email.isEmpty()){ return SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS; } - Authenticator basicAuth = new BasicAuthenticator(vreq); - UserAccount user = basicAuth.getAccountForInternalAuth( email ); - log.debug("userAccount is " + user==null?"null":user.getUri() ); - - if( ! basicAuth.isCurrentPassword( user, pw ) ){ - log.debug(String.format("UNAUTHORIZED, password not accepted for %s, account URI: %s", - user.getEmailAddress(), user.getUri())); - return Actions.UNAUTHORIZED; - }else{ - log.debug(String.format("password accepted for %s, account URI: %s", - user.getEmailAddress(), user.getUri() )); - } - - //then figure out if that account can manage the search index. - IdentifierBundle ids = - ActiveIdentifierBundleFactories.getUserIdentifierBundle(vreq,user); - PolicyIface policy = ServletPolicyList.getPolicies(vreq); - boolean canManageSearchIndex = - PolicyHelper.isAuthorizedForActions( ids, policy, - SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS ); - if( canManageSearchIndex ){ + if( PolicyHelper.isAuthorizedForActions(vreq, email, pw, + SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS ) ){ return Actions.AUTHORIZED; }else{ - log.debug(String.format("userAccount is unauthorized to" + - " manage the search index.",user.getUri())); + log.debug(email + " is unauthorized to manage the search index. " + + "client IP "+vreq.getClientAddr()); return Actions.UNAUTHORIZED; } }catch(Exception ex){ - log.error("Error while attempting to log in " + + log.error("Error while client IP "+ vreq.getClientAddr() + " attempting to log in " + "to SearchServiceController: " + ex.getMessage()); return Actions.UNAUTHORIZED; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/indexing/IndexBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/indexing/IndexBuilder.java index ab8e4750d..497caab35 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/indexing/IndexBuilder.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/indexing/IndexBuilder.java @@ -9,8 +9,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ConcurrentSkipListSet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -61,7 +59,10 @@ public class IndexBuilder extends VitroBackgroundThread { /** Indicates that a stop of the indexing objects has been requested. */ private volatile boolean stopRequested = false; - + + /** Indicates that new updates should not be started. */ + private boolean deferNewUpdates = false; + /** Length of time to wait before looking for work (if not wakened sooner). */ public static final long MAX_IDLE_INTERVAL = 1000 * 60 /* msec */ ; @@ -163,7 +164,7 @@ public class IndexBuilder extends VitroBackgroundThread { * This will re-index Individuals were added with addToChanged(). */ public synchronized void doUpdateIndex() { - log.debug("callto doUpdateIndex()"); + log.debug("call to doUpdateIndex()"); //wake up thread and it will attempt to index anything in changedUris this.notifyAll(); } @@ -184,12 +185,33 @@ public class IndexBuilder extends VitroBackgroundThread { this.notifyAll(); this.interrupt(); } - + + /** + * Calling this will cause the IndexBuider to no start a new index update + * until unpuase is called. This is intended to allow a large change + * without slowing it down with incremental search index updates. + */ + public synchronized void pause(){ + this.deferNewUpdates = true; + } + + public synchronized void unpause(){ + if( deferNewUpdates == true ){ + this.deferNewUpdates = false; + this.notifyAll(); + this.interrupt(); + } + } + @Override public void run() { while(! stopRequested ){ try{ - if( reindexRequested ){ + if ( deferNewUpdates ){ + log.debug("there is no indexing working to do, waiting for work"); + synchronized (this) { this.wait(MAX_IDLE_INTERVAL); } + } + else if ( reindexRequested ){ setWorkLevel(WorkLevel.WORKING, FLAG_REBUILDING); log.debug("full re-index requested"); @@ -198,7 +220,8 @@ public class IndexBuilder extends VitroBackgroundThread { notifyListeners( IndexingEventListener.EventTypes.FINISH_FULL_REBUILD ); setWorkLevel(WorkLevel.IDLE); - }else{ + } + else{ boolean workToDo = false; synchronized (changedStmts ){ workToDo = !changedStmts.isEmpty(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/freemarker/FreemarkerProcessingServiceImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/freemarker/FreemarkerProcessingServiceImpl.java index 2f53a8459..b6ccfd536 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/services/freemarker/FreemarkerProcessingServiceImpl.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/services/freemarker/FreemarkerProcessingServiceImpl.java @@ -12,8 +12,7 @@ import javax.servlet.http.HttpServletRequest; 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.controller.freemarker.FreemarkerConfigurationLoader; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.utils.log.LogUtils; import freemarker.core.ParseException; import freemarker.template.Configuration; @@ -51,8 +50,7 @@ public class FreemarkerProcessingServiceImpl implements throws TemplateProcessingException { Template template = null; try { - Configuration config = FreemarkerConfigurationLoader - .getConfig(new VitroRequest(req)); + Configuration config = FreemarkerConfiguration.getConfig(req); template = config.getTemplate(templateName); } catch (ParseException e) { log.warn("Failed to parse the template at '" + templateName + "'" diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ApplicationModelSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ApplicationModelSetup.java index 366cbc5d6..7bb26c752 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ApplicationModelSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ApplicationModelSetup.java @@ -1,9 +1,6 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ package edu.cornell.mannlib.vitro.webapp.servlet.setup; -import java.io.File; -import java.io.FileOutputStream; - import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -11,14 +8,8 @@ import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.joda.time.DateTime; -import org.joda.time.format.ISODateTimeFormat; import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.query.Query; -import com.hp.hpl.jena.query.QueryExecution; -import com.hp.hpl.jena.query.QueryExecutionFactory; -import com.hp.hpl.jena.query.QueryFactory; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.ModelFactory; @@ -58,16 +49,15 @@ implements ServletContextListener { try { Model displayDbModel = makeDBModel(bds, JENA_DISPLAY_METADATA_MODEL, DB_ONT_MODEL_SPEC, ctx); - if (displayDbModel.size() == 0) { - readOntologyFilesInPathSet(APPPATH, ctx,displayDbModel); - } + RDFFilesLoader.loadFirstTimeFiles(ctx, "display", displayDbModel, displayDbModel.isEmpty()); + OntModel displayModel = ModelFactory.createOntologyModel(MEM_ONT_MODEL_SPEC); displayModel.add(displayDbModel); displayModel.getBaseModel().register(new ModelSynchronizer(displayDbModel)); ModelAccess.on(ctx).setDisplayModel(displayModel); - //at each startup load all RDF files from directory to sub-models of display model - initializeDisplayLoadedAtStartup(ctx, displayModel); + //at each startup load all RDF files from directory to sub-models of display model + RDFFilesLoader.loadEveryTimeFiles(ctx, "display", displayModel); } catch (Throwable t) { log.error("Unable to load user application configuration model", t); ss.fatal(this, "Unable to load user application configuration model", t); @@ -78,158 +68,35 @@ implements ServletContextListener { Model displayTboxModel = makeDBModel(bds, JENA_DISPLAY_TBOX_MODEL, DB_ONT_MODEL_SPEC, ctx); - //Reading in single file every time, needs to be cleared/removed every time - readOntologyFileFromPath(APPPATH_LOAD + "displayTBOX.n3", displayTboxModel, ctx); - OntModel appTBOXModel = ModelFactory.createOntologyModel( - MEM_ONT_MODEL_SPEC); + OntModel appTBOXModel = ModelFactory.createOntologyModel(MEM_ONT_MODEL_SPEC); appTBOXModel.add(displayTboxModel); appTBOXModel.getBaseModel().register(new ModelSynchronizer(displayTboxModel)); ModelAccess.on(ctx).setOntModel(ModelID.DISPLAY_TBOX, appTBOXModel); - log.debug("Loaded file " + APPPATH_LOAD + "displayTBOX.n3 into display tbox model"); + + //Reading in every time, needs to be cleared/removed every time + RDFFilesLoader.loadEveryTimeFiles(ctx, "displayTbox", appTBOXModel); } catch (Throwable t) { log.error("Unable to load user application configuration model TBOX", t); ss.fatal(this, "Unable to load user application configuration model TBOX", t); } - //Display Display model, currently empty, create if doesn't exist but no files to load + //Display Display model, currently reading in every time try { Model displayDisplayModel = makeDBModel(bds, JENA_DISPLAY_DISPLAY_MODEL, DB_ONT_MODEL_SPEC, ctx); - //Reading in single file every time, needs to be cleared/removed every - readOntologyFileFromPath(APPPATH_LOAD + "displayDisplay.n3", displayDisplayModel, ctx); - OntModel appDisplayDisplayModel = ModelFactory.createOntologyModel( - MEM_ONT_MODEL_SPEC); + OntModel appDisplayDisplayModel = ModelFactory.createOntologyModel(MEM_ONT_MODEL_SPEC); appDisplayDisplayModel.add(displayDisplayModel); appDisplayDisplayModel.getBaseModel().register(new ModelSynchronizer(displayDisplayModel)); ModelAccess.on(ctx).setOntModel(ModelID.DISPLAY_DISPLAY, appDisplayDisplayModel); - log.debug("Loaded file " + APPPATH_LOAD + "displayDisplay.n3 into display display model"); + + //Reading in every time, needs to be cleared/removed every time + RDFFilesLoader.loadEveryTimeFiles(ctx, "displayDisplay", appDisplayDisplayModel); } catch (Throwable t) { log.error("Unable to load user application configuration model Display Model", t); ss.fatal(this, "Unable to load user application configuration model Display Model", t); } } - - /** - * Load the RDF found in the directory DISPLAY_MODEL_LOAD_AT_STARTUP_DIR - * a sub-models of displayModel. The RDF from thes files will not be saved - * in the database and it will be reloaded each time the system starts up. - */ - private void initializeDisplayLoadedAtStartup(ServletContext ctx, OntModel displayModel){ - log.info("loading display model from files in " + ctx.getRealPath(DISPLAY_MODEL_LOAD_AT_STARTUP_DIR) ); - Model displayLoadAtStartup = readInDisplayModelLoadAtStartup(ctx); - - if( log.isDebugEnabled() ){ - log.debug("loaded display model from files in " + ctx.getRealPath(DISPLAY_MODEL_LOAD_AT_STARTUP_DIR) ); - displayLoadAtStartup.write(System.out, "N3-PP"); - } - - checkForOldListViews(ctx,displayModel,displayLoadAtStartup); - displayModel.addSubModel( displayLoadAtStartup ); - } - - protected Model readInDisplayModelLoadAtStartup( ServletContext ctx ){ - return getModelFromDir( new File( ctx.getRealPath( DISPLAY_MODEL_LOAD_AT_STARTUP_DIR ))); - } - - /** - * All of the list views should now reside in files in DISPLAY_MODEL_LOAD_AT_STARTUP_DIR. - * This will check for custom list view annotation statements in the displayModel, check - * if they exist in the files in DISPLAY_MODEL_LOAD_AT_STARTUP_DIR, and write any that don't - * exist there to a file in DISPLAY_MODEL_LOAD_AT_STARTUP_DIR. After that the statements - * will be removed from the displayDBModel. - * - * returns true if there were old list view statements in the DB, returns false - * if there were none. displayLoadAlways should be reloaded from the file system - * if this returns true as this method may have changed the files. - * - * displayLoadAtStartup and displayModel may be modified. - */ - private void checkForOldListViews( ServletContext ctx, OntModel displayModel, Model displayLoadAtStartup){ - // run construct for old custom list view statements from displayModel - Model oldListViewModel = getOldListViewStatements( displayModel ); - if( log.isDebugEnabled() ){ - log.debug("Printing the old list view statements from the display model to System.out."); - oldListViewModel.write(System.out,"N3-PP"); - } - - // find statements in old stmts that are not in loadedAtStartup and - // save them in a new file in DISPLAY_MODEL_LOAD_AT_STARTUP_DIR - // so that in the future they will be in loadedAtStartup - Model stmtsInOldAndFiles = displayLoadAtStartup.intersection( displayModel ); - Model unhandledOldListViewStmts = oldListViewModel.difference( stmtsInOldAndFiles ); - - boolean saved = false; - boolean neededSave = false; - - if( unhandledOldListViewStmts != null && !unhandledOldListViewStmts.isEmpty() ){ - log.debug("need to deal with old list view statements from the display model"); - neededSave = true; - try{ - //create a file for the old statements in the loadAtStartup directory - String newFileName = ctx.getRealPath( - DISPLAY_MODEL_LOAD_AT_STARTUP_DIR + File.separator - + new DateTime().toString(ISODateTimeFormat.basicDateTime()) + ".n3" ); - File file = new File( newFileName ); - file.createNewFile(); - - log.info("Relocating " + unhandledOldListViewStmts.size() + " custom list view statements from DB and saving to " - + file.getAbsolutePath()+ File.separator + file.getName() - + ". These will be loaded from this file when the system starts up."); - - FileOutputStream fileOut = new FileOutputStream(file); - unhandledOldListViewStmts.write(fileOut, "N3-PP"); - fileOut.close(); - saved = true; - }catch(Throwable th){ - log.warn("Could not save old list view statements. Leaving them in the DB",th); - } - - //need to reload displayLoadAlways because DISPLAY_MODEL_LOAD_AT_STARTUP_DIR may have changed - displayLoadAtStartup.removeAll().add(readInDisplayModelLoadAtStartup(ctx)); - } - - - if( oldListViewModel != null && ! oldListViewModel.isEmpty() ){ - //At this point, there are old list view statements in the DB but they - //should are all redundant with ones in DISPLAY_MODEL_LOAD_AT_STARTUP_DIR - if( (neededSave && saved) || (!neededSave) ){ - //if there was nothing to save, just remove the old stuff - //if there was stuff to save, only remove if it was saved. - log.debug("removing old statements from displayModel"); - displayModel.remove(oldListViewModel); - } - } - - } - - private Model getOldListViewStatements(OntModel displayModel) { - //run a construct on displayModel to get all list view statements - Query query = QueryFactory.create ( listViewQuery ); - QueryExecution qexec = QueryExecutionFactory.create(query, displayModel) ; - Model oldModel = null; - - try { - oldModel = qexec.execConstruct(); - } catch( Throwable th ){ - log.error("could not check for old custom list views, query exception",th); - }finally { - qexec.close() ; - } - - if( oldModel != null) - return oldModel; - else - return ModelFactory.createDefaultModel(); - } - - private static final String listViewQuery = "" + - "PREFIX d: \n" + - "CONSTRUCT { \n" + - " ?a d:listViewConfigFile ?b . \n" + - "} WHERE {\n" + - " ?a d:listViewConfigFile ?b . \n" + - "} "; @Override public void contextDestroyed(ServletContextEvent arg0) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java index 454d5474c..3ad2addca 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ContentModelSetup.java @@ -24,11 +24,13 @@ import com.hp.hpl.jena.rdf.model.ResIterator; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.shared.Lock; import com.hp.hpl.jena.util.ResourceUtils; +import com.hp.hpl.jena.util.iterator.ClosableIterator; import com.hp.hpl.jena.vocabulary.RDF; import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.FactoryID; import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID; +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.dao.jena.ModelSynchronizer; @@ -40,7 +42,6 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; -import edu.cornell.mannlib.vitro.webapp.utils.jena.InitialJenaModelUtils; /** * Sets up the content models, OntModelSelectors and webapp DAO factories. @@ -79,9 +80,13 @@ public class ContentModelSetup extends JenaDataSourceSetupBase OntModel unionABoxModel = createCombinedBulkUpdatingModel(baseABoxModel, inferenceABoxModel); OntModel unionTBoxModel = createCombinedBulkUpdatingModel(baseTBoxModel, inferenceTBoxModel); + if (isFirstStartup()) { - loadInitialApplicationMetadataModel(applicationMetadataModel, ctx); - loadDataFromFilesystem(baseABoxModel, baseTBoxModel, applicationMetadataModel, ctx); + initializeApplicationMetadata(ctx, applicationMetadataModel); + RDFFilesLoader.loadFirstTimeFiles(ctx, "abox", baseABoxModel, true); + RDFFilesLoader.loadFirstTimeFiles(ctx, "tbox", baseTBoxModel, true); + } else { + checkForNamespaceMismatch( applicationMetadataModel, ctx ); } log.info("Setting up full models"); @@ -101,7 +106,6 @@ public class ContentModelSetup extends JenaDataSourceSetupBase models.setOntModel(ModelID.UNION_TBOX, unionTBoxModel); models.setOntModel(ModelID.UNION_FULL, unionFullModel); - checkForNamespaceMismatch( applicationMetadataModel, ctx ); log.info("Setting up DAO factories"); @@ -159,35 +163,54 @@ public class ContentModelSetup extends JenaDataSourceSetupBase return ModelFactory.createOntologyModel(MEM_ONT_MODEL_SPEC, unionModel); } - private void loadInitialApplicationMetadataModel(OntModel applicationMetadataModel, - ServletContext ctx) { - try { - applicationMetadataModel.add( - InitialJenaModelUtils.loadInitialModel(ctx, getDefaultNamespace(ctx))); - } catch (Throwable e) { - throw new RuntimeException("Unable to load application metadata model cache from DB", e); - } - } - - private void loadDataFromFilesystem(OntModel baseABoxModel, OntModel baseTBoxModel, OntModel applicationMetadataModel, - ServletContext ctx) { - Long startTime = System.currentTimeMillis(); - log.info("Initializing models from RDF files"); - - readOntologyFilesInPathSet(USER_ABOX_PATH, ctx, baseABoxModel); - readOntologyFilesInPathSet(USER_TBOX_PATH, ctx, baseTBoxModel); - readOntologyFilesInPathSet(USER_APPMETA_PATH, ctx, applicationMetadataModel); - - log.debug(((System.currentTimeMillis() - startTime) / 1000) - + " seconds to read RDF files "); - } - private long secondsSince(long startTime) { return (System.currentTimeMillis() - startTime) / 1000; } /* ===================================================================== */ + /** + * We need to read the RDF files and change the Portal from a blank node to + * one with a URI in the default namespace. + * + * Do this before adding the data to the RDFService-backed model, to avoid + * warnings about editing a blank node. + */ + private void initializeApplicationMetadata(ServletContext ctx, + OntModel applicationMetadataModel) { + OntModel temporaryAMModel = ModelFactory.createOntologyModel(MEM_ONT_MODEL_SPEC); + RDFFilesLoader.loadFirstTimeFiles(ctx, "applicationMetadata", temporaryAMModel, true); + setPortalUriOnFirstTime(temporaryAMModel, ctx); + applicationMetadataModel.add(temporaryAMModel); + } + + /** + * If we are loading the application metadata for the first time, set the + * URI of the Portal based on the default namespace. + */ + private void setPortalUriOnFirstTime(OntModel model, ServletContext ctx) { + // Only a single portal is permitted in the initialization data + Resource portalResource = null; + ClosableIterator portalResIt = model + .listSubjectsWithProperty(RDF.type, + model.getResource(VitroVocabulary.PORTAL)); + try { + if (portalResIt.hasNext()) { + Resource portalRes = portalResIt.next(); + if (portalRes.isAnon()) { + portalResource = portalRes; + } + } + } finally { + portalResIt.close(); + } + + if (portalResource != null) { + ResourceUtils.renameResource(portalResource, getDefaultNamespace(ctx) + "portal1"); + } + } + + /** * If we find a "portal1" portal (and we should), its URI should use the * default namespace. diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java index f734f2054..5e039e808 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java @@ -2,13 +2,17 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +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.Iterator; import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,23 +30,36 @@ import com.hp.hpl.jena.query.Dataset; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.ModelFactory; +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; import edu.cornell.mannlib.vitro.webapp.dao.jena.OntModelSelector; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; // This ContextListener must run after the JenaDataSourceSetup ContextListener public class FileGraphSetup implements ServletContextListener { + private static final Log log = LogFactory.getLog(FileGraphSetup.class); + private static final String RDF = "rdf"; private static final String ABOX = "abox"; private static final String TBOX = "tbox"; - private static final String PATH_ROOT = "/WEB-INF/filegraph/"; + private static final String FILEGRAPH = "filegraph"; + private static final String PROPERTY_VITRO_HOME = "vitro.home"; + public static final String FILEGRAPH_URI_ROOT = "http://vitro.mannlib.cornell.edu/filegraph/"; + + /** Ignore hidden files when looking for filegraph RDF. */ + private static final DirectoryStream.Filter REJECT_HIDDEN_FILES = new DirectoryStream.Filter() { + @Override + public boolean accept(Path entry) throws IOException { + return !Files.isHidden(entry); + } + }; - private static final Log log = LogFactory.getLog(FileGraphSetup.class); - - public void contextInitialized(ServletContextEvent sce) { + @Override + public void contextInitialized(ServletContextEvent sce) { boolean aboxChanged = false; // indicates whether any ABox file graph model has changed boolean tboxChanged = false; // indicates whether any TBox file graph model has changed @@ -57,24 +74,20 @@ public class FileGraphSetup implements ServletContextListener { RDFServiceModelMaker maker = new RDFServiceModelMaker(RDFServiceUtils.getRDFServiceFactory(ctx)); // ABox files - Set pathSet = ctx.getResourcePaths(PATH_ROOT + ABOX); + Set paths = getFilegraphPaths(ctx, RDF, ABOX, FILEGRAPH); - cleanupDB(dataset, pathToURI(pathSet, ABOX), ABOX); + cleanupDB(dataset, pathsToURIs(paths, ABOX), ABOX); - if (pathSet != null) { - OntModel aboxBaseModel = baseOms.getABoxModel(); - aboxChanged = readGraphs(sce, pathSet, maker, ABOX, aboxBaseModel); - } + OntModel aboxBaseModel = baseOms.getABoxModel(); + aboxChanged = readGraphs(paths, maker, ABOX, aboxBaseModel); // TBox files - pathSet = ctx.getResourcePaths(PATH_ROOT + TBOX); + paths = getFilegraphPaths(ctx, RDF, TBOX, FILEGRAPH); - cleanupDB(dataset, pathToURI(pathSet, TBOX),TBOX); + cleanupDB(dataset, pathsToURIs(paths, TBOX),TBOX); - if (pathSet != null) { - OntModel tboxBaseModel = baseOms.getTBoxModel(); - tboxChanged = readGraphs(sce, pathSet, maker, TBOX, tboxBaseModel); - } + OntModel tboxBaseModel = baseOms.getTBoxModel(); + tboxChanged = readGraphs(paths, maker, TBOX, tboxBaseModel); } catch (ClassCastException cce) { String errMsg = "Unable to cast servlet context attribute to the appropriate type " + cce.getLocalizedMessage(); log.error(errMsg); @@ -99,7 +112,35 @@ public class FileGraphSetup implements ServletContextListener { } } - /* + private Set getFilegraphPaths(ServletContext ctx, String... strings) { + StartupStatus ss = StartupStatus.getBean(ctx); + + ConfigurationProperties props = ConfigurationProperties.getBean(ctx); + String homeDirProperty = props.getProperty(PROPERTY_VITRO_HOME); + Path filegraphDir = Paths.get(homeDirProperty, strings); + + Set paths = new TreeSet<>(); + if (Files.isDirectory(filegraphDir)) { + try (DirectoryStream stream = Files.newDirectoryStream( + filegraphDir, REJECT_HIDDEN_FILES)) { + for (Path p : stream) { + paths.add(p); + } + ss.info(this, "Read " + paths.size() + " RDF files from '" + + filegraphDir + "'"); + } catch (IOException e) { + ss.fatal(this, "Failed to read filegraph RDF from '" + + filegraphDir + "' directory.", e); + } + } else { + ss.info(this, "Filegraph directory '" + filegraphDir + + "' doesn't exist."); + } + log.debug("Paths from '" + filegraphDir + "': " + paths); + return paths; + } + + /* * Reads the graphs stored as files in sub-directories of * 1. updates the SDB store to reflect the current contents of the graph. * 2. adds the graph as an in-memory submodel of the base in-memory graph @@ -107,7 +148,7 @@ public class FileGraphSetup implements ServletContextListener { * Note: no connection needs to be maintained between the in-memory copy of the * graph and the DB copy. */ - public boolean readGraphs(ServletContextEvent sce, Set pathSet, RDFServiceModelMaker dataset, String type, OntModel baseModel) { + public boolean readGraphs(Set pathSet, RDFServiceModelMaker dataset, String type, OntModel baseModel) { int count = 0; @@ -115,18 +156,17 @@ public class FileGraphSetup implements ServletContextListener { // For each file graph in the target directory update or add that graph to // the Jena SDB, and attach the graph as a submodel of the base model - for ( String p : pathSet ) { + for ( Path p : pathSet ) { count++; // note this will count the empty files too - File file = new File(sce.getServletContext().getRealPath(p)); - try { - FileInputStream fis = new FileInputStream( file ); + FileInputStream fis = new FileInputStream( p.toFile() ); try { OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - if ( p.endsWith(".n3") || p.endsWith(".N3") || p.endsWith(".ttl") || p.endsWith(".TTL") ) { + String fn = p.getFileName().toString().toLowerCase(); + if ( fn.endsWith(".n3") || fn.endsWith(".ttl") ) { model.read( fis, null, "N3" ); - } else if ( p.endsWith(".owl") || p.endsWith(".OWL") || p.endsWith(".rdf") || p.endsWith(".RDF") || p.endsWith(".xml") || p.endsWith(".XML") ) { + } else if ( fn.endsWith(".owl") || fn.endsWith(".rdf") || fn.endsWith(".xml") ) { model.read( fis, null, "RDF/XML" ); } else { log.warn("Ignoring " + type + " file graph " + p + " because the file extension is unrecognized."); @@ -134,7 +174,7 @@ public class FileGraphSetup implements ServletContextListener { if ( !model.isEmpty() ) { baseModel.addSubModel(model); - log.info("Attached file graph as " + type + " submodel " + p); + log.info("Attached file graph as " + type + " submodel " + pathToURI(p, type)); } modelChanged = modelChanged | updateGraphInDB(dataset, model, type, p); @@ -155,8 +195,8 @@ public class FileGraphSetup implements ServletContextListener { log.warn("Exception while trying to close file graph file: " + p,ioe); } } // end - for - - System.out.println("Read " + count + " " + type + " file graph" + ((count == 1) ? "" : "s") + " from " + PATH_ROOT + type); + + log.info("Read " + count + " " + type + " file graph" + ((count == 1) ? "" : "s")); return modelChanged; } @@ -171,7 +211,7 @@ public class FileGraphSetup implements ServletContextListener { * Otherwise, if a graph with the given name is in the DB and is isomorphic with * the graph that was read from the files system, then do nothing. */ - public boolean updateGraphInDB(RDFServiceModelMaker dataset, Model fileModel, String type, String path) { + public boolean updateGraphInDB(RDFServiceModelMaker dataset, Model fileModel, String type, Path path) { String graphURI = pathToURI(path,type); Model dbModel = dataset.getModel(graphURI); boolean modelChanged = false; @@ -224,46 +264,34 @@ public class FileGraphSetup implements ServletContextListener { return; } + /* + * Takes a set of paths for file graphs and returns a set containing a graph + * uri for each path. + */ + private Set pathsToURIs(Set paths, String type) { + HashSet uriSet = new HashSet(); + for (Path path : paths) { + uriSet.add(pathToURI(path, type)); + } + log.debug("uriSet = " + uriSet); + return uriSet; + } + /* - * Takes a set of path names for file graphs and returns a set containing - * a graph uri for each path name in the input set. If pathSet is null - * returns an empty set. - */ - public Set pathToURI (Set pathSet, String type) { - - HashSet uriSet = new HashSet(); - - if (pathSet != null) { - for ( String path : pathSet ) { - uriSet.add(pathToURI(path,type)); - } - } - - return uriSet; - } - - /* - * Takes a path name for a file graph and returns the corresponding SDB URI + * Takes a path for a file graph and returns the corresponding SDB URI * for the graph. The correspondence is by defined convention. */ - public String pathToURI(String path, String type) { - - String uri = null; - - if (path != null) { - File file = new File(path); - uri = FILEGRAPH_URI_ROOT + type + "/" + file.getName(); - } - - return uri; + private String pathToURI(Path path, String type) { + return FILEGRAPH_URI_ROOT + type + "/" + path.getFileName(); } - - public void contextDestroyed( ServletContextEvent sce ) { + + @Override + public void contextDestroyed( ServletContextEvent sce ) { // nothing to do } private static boolean isUpdateRequired(ServletContext ctx) { return (ctx.getAttribute(UpdateKnowledgeBase.KBM_REQURIED_AT_STARTUP) != null); } - + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaDataSourceSetupBase.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaDataSourceSetupBase.java index f56d75a3d..b79b71fa2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaDataSourceSetupBase.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaDataSourceSetupBase.java @@ -3,11 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup; import java.beans.PropertyVetoException; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.sql.SQLException; -import java.util.Set; import javax.servlet.ServletContext; import javax.sql.DataSource; @@ -69,21 +65,6 @@ public class JenaDataSourceSetupBase extends JenaBaseDaoCon { protected final static boolean DEFAULT_TESTONBORROW = true, DEFAULT_TESTONRETURN = true, DEFAULT_TESTWHILEIDLE = true; - protected static String BASE = "/WEB-INF/ontologies/"; - protected static String USERPATH = BASE+"user/"; - protected static String USER_ABOX_PATH = BASE+"user/abox"; - protected static String USER_TBOX_PATH = BASE+"user/tbox"; - protected static String USER_APPMETA_PATH = BASE+"user/applicationMetadata"; - protected static String SYSTEMPATH = BASE+"system/"; - public static String APPPATH = BASE+"app/"; - //these files are loaded everytime the system starts up - public static String APPPATH_LOAD = APPPATH + "menuload/"; - - //All files in this directory will be reloaded every startup - //and attached as sub-models to the displayOntModel. - static final String DISPLAY_MODEL_LOAD_AT_STARTUP_DIR = - APPPATH + "loadedAtStartup"; - protected static boolean firstStartup = false; String DB_USER = "jenatest"; // database user id @@ -395,45 +376,6 @@ public class JenaDataSourceSetupBase extends JenaBaseDaoCon { return dbModel; } - public static void readOntologyFilesInPathSet(String path, - ServletContext ctx, Model model) { - log.debug("Reading ontology files from '" + path + "'"); - Set paths = ctx.getResourcePaths(path); - if (paths != null) { - for (String p : paths) { - readOntologyFileFromPath(p, model, ctx); - } - } - } - - public static void readOntologyFileFromPath(String p, - Model model, - ServletContext ctx) { - //Check that this is a file and not a directory - File f = new File(ctx.getRealPath(p)); - if(f.exists() && f.isFile()){ - String format = getRdfFormat(p); - log.info("Loading ontology file at " + p + - " as format " + format); - InputStream ontologyInputStream = ctx.getResourceAsStream(p); - try { - model.read(ontologyInputStream, null, format); - log.debug("...successful"); - } catch (Throwable t) { - log.error("Failed to load ontology file at '" + p + - "' as format " + format, t); - } - } else { - if(!f.exists()) { - log.info("File for path " + p + " does not exist"); - } - else if(f.isDirectory()) { - log.info("Path " + p + - " corresponds to directory and not file so was not read in"); - } - } - } - private static String getRdfFormat(String filename){ String defaultformat = "RDF/XML"; if( filename == null ) @@ -489,42 +431,6 @@ public class JenaDataSourceSetupBase extends JenaBaseDaoCon { } - /** - * Read all the files in the directory as RDF files - * and return a model build from all RDF data found in those files. - * This will attempt to load formats supported by getRdfFormat(). - */ - public static OntModel getModelFromDir( File dir){ - if( dir == null ) - throw new IllegalStateException("Must pass a File to getModelFromDir()"); - if( !dir.isDirectory() ) - throw new IllegalStateException( - "Directory must be a File object for a directory"); - if( !dir.canRead() ) - throw new IllegalStateException("getModelFromDir(): Directory " + - " must be readable, check premissions on " - + dir.getAbsolutePath()); - - OntModel model = ModelFactory.createOntologyModel(); - for( File file : dir.listFiles()){ - if( file.isFile() - && file.canRead() - && file.getName() != null ){ - String format = getRdfFormat( file.getName() ); - try{ - model.read( new FileInputStream(file), null, format); - log.info("read in file " + file.getCanonicalPath() ); - }catch( Throwable th){ - log.warn("Could not load file " + - file.getAbsolutePath() + file.separator + file.getName() + - " check that it contains valid " + format + " data.", - th); - } - } - } - return model; - } - public static void setVitroJenaModelMaker(VitroJenaModelMaker vjmm, ServletContext ctx){ ctx.setAttribute(rdbModelMaker, vjmm); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java new file mode 100644 index 000000000..50838e472 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFFilesLoader.java @@ -0,0 +1,183 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.TreeSet; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.ontology.OntModelSpec; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; + +/** + * Help to load RDF files on first time and on every startup. + */ +public class RDFFilesLoader { + private static final Log log = LogFactory.getLog(RDFFilesLoader.class); + + private static final String PROPERTY_VITRO_HOME = "vitro.home"; + private static final String DEFAULT_RDF_FORMAT = "RDF/XML"; + private static final String RDF = "rdf"; + private static final String FIRST_TIME = "firsttime"; + private static final String EVERY_TIME = "everytime"; + + /** Path filter that ignores sub-directories and hidden files. */ + private static final DirectoryStream.Filter RDF_FILE_FILTER = new DirectoryStream.Filter() { + @Override + public boolean accept(Path p) throws IOException { + if (Files.isHidden(p)) { + return false; + } + if (Files.isDirectory(p)) { + log.warn("RDF files in subdirectories are not loaded. Directory '" + + p + "' ignored."); + return false; + } + return true; + } + }; + + /** + * Load the "first time" files if we say it is the first time. + * + * The location is based on the home directory and the model path: "abox", + * "display", etc. + * + * The files from the directory are added to the model. + */ + public static void loadFirstTimeFiles(ServletContext ctx, String modelPath, + Model model, boolean firstTime) { + if (firstTime) { + Set paths = getPaths(locateHomeDirectory(ctx), RDF, + modelPath, FIRST_TIME); + for (Path p : paths) { + readOntologyFileIntoModel(p, model); + } + } else { + log.debug("Not loading first time files on '" + modelPath + + "', firstTime=false"); + } + } + + /** + * Load the "every time" files. + * + * The location is based on the home directory and the model path: "abox", + * "display", etc. + * + * The files from the directory become a sub-model of the model. + */ + public static void loadEveryTimeFiles(ServletContext ctx, String modelPath, + OntModel model) { + OntModel everytimeModel = ModelFactory + .createOntologyModel(OntModelSpec.OWL_MEM); + Set paths = getPaths(locateHomeDirectory(ctx), RDF, modelPath, + EVERY_TIME); + for (Path p : paths) { + readOntologyFileIntoModel(p, everytimeModel); + } + model.addSubModel(everytimeModel); + } + + /** + * 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 and hidden + * files are ignored. + */ + private static Set getPaths(String parentDir, String... strings) { + Path dir = Paths.get(parentDir, strings); + + Set paths = new TreeSet<>(); + if (Files.isDirectory(dir)) { + try (DirectoryStream stream = Files.newDirectoryStream(dir, + RDF_FILE_FILTER)) { + for (Path p : stream) { + paths.add(p); + } + } catch (IOException e) { + log.warn("Failed to read directory '" + dir + "'", e); + } + } else { + log.debug("Directory '" + dir + "' doesn't exist."); + } + log.debug("Paths from '" + dir + "': " + paths); + return paths; + } + + private static void readOntologyFileIntoModel(Path p, Model model) { + String format = getRdfFormat(p); + log.info("Loading file at " + p + " as " + format); + try (InputStream stream = new FileInputStream(p.toFile())) { + model.read(stream, null, format); + log.debug("...successful"); + } catch (Exception e) { + log.warn("Could not load file '" + p + "' as " + format + + ". Check that it contains valid data.", e); + } + } + + private static String getRdfFormat(Path p) { + String filename = p.getFileName().toString().toLowerCase(); + if (filename.endsWith("n3")) + return "N3"; + else if (filename.endsWith("ttl")) + return "TURTLE"; + else + return DEFAULT_RDF_FORMAT; + } + + private static String locateHomeDirectory(ServletContext ctx) { + return ConfigurationProperties.getBean(ctx).getProperty( + PROPERTY_VITRO_HOME); + } + + /** + * No need to create an instance -- all methods are static. + */ + private RDFFilesLoader() { + // Nothing to initialize. + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UserModelSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UserModelSetup.java index 47b51a5a3..efbae4a80 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UserModelSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UserModelSetup.java @@ -17,59 +17,60 @@ import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; import edu.cornell.mannlib.vitro.webapp.dao.jena.ModelSynchronizer; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; - -/** - * Setup the user account model. If it does not exist in the - * database, create and populate it. +/** + * Setup the user account model. If it does not exist in the database, create + * and populate it. */ -public class UserModelSetup extends JenaDataSourceSetupBase - implements ServletContextListener { - protected static String AUTHPATH = BASE + "auth/"; - - private static final Log log = LogFactory.getLog( - UserModelSetup.class.getName()); +public class UserModelSetup extends JenaDataSourceSetupBase implements + ServletContextListener { + private static final Log log = LogFactory.getLog(UserModelSetup.class + .getName()); - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - StartupStatus ss = StartupStatus.getBean(ctx); - - DataSource bds = getApplicationDataSource(ctx); - if( bds == null ){ - ss.fatal(this, "A DataSource must be setup before ModelSetup "+ - "is run. Make sure that JenaPersistentDataSourceSetup runs before "+ - "ModelSetup."); - return; - } - - setupUserAccountModel(bds, ctx, ss); - } - - @Override - public void contextDestroyed(ServletContextEvent arg0) { - // Does nothing. - } + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + StartupStatus ss = StartupStatus.getBean(ctx); - private void setupUserAccountModel (DataSource bds, ServletContext ctx ,StartupStatus ss){ - try { - Model userAccountsDbModel = makeDBModel(bds, - JENA_USER_ACCOUNTS_MODEL, DB_ONT_MODEL_SPEC, ctx); - OntModel userAccountsModel = - ModelFactory.createOntologyModel( MEM_ONT_MODEL_SPEC); - - // This is used in Selenium testing, when we load user accounts from a file. - if (userAccountsDbModel.isEmpty()) { - readOntologyFilesInPathSet(AUTHPATH, ctx, userAccountsDbModel); - } + DataSource bds = getApplicationDataSource(ctx); + if (bds == null) { + ss.fatal( + this, + "A DataSource must be setup before ModelSetup " + + "is run. Make sure that JenaPersistentDataSourceSetup runs before " + + "ModelSetup."); + return; + } - userAccountsModel.add(userAccountsDbModel); - userAccountsModel.getBaseModel().register( - new ModelSynchronizer(userAccountsDbModel)); - ModelAccess.on(ctx).setUserAccountsModel(userAccountsModel); - - } catch (Throwable t) { - log.error("Unable to load user accounts model from DB", t); - ss.fatal(this, "Unable to load user accounts model from DB", t); - } - } + setupUserAccountModel(bds, ctx, ss); + } + + @Override + public void contextDestroyed(ServletContextEvent arg0) { + // Does nothing. + } + + private void setupUserAccountModel(DataSource bds, ServletContext ctx, + StartupStatus ss) { + try { + Model userAccountsDbModel = makeDBModel(bds, + JENA_USER_ACCOUNTS_MODEL, DB_ONT_MODEL_SPEC, ctx); + OntModel userAccountsModel = ModelFactory + .createOntologyModel(MEM_ONT_MODEL_SPEC); + + userAccountsModel.add(userAccountsDbModel); + userAccountsModel.getBaseModel().register( + new ModelSynchronizer(userAccountsDbModel)); + + // This is used in Selenium testing, to load accounts from a file. + RDFFilesLoader.loadFirstTimeFiles(ctx, "auth", userAccountsModel, + userAccountsDbModel.isEmpty()); + // This gets the permissions configuration. + RDFFilesLoader.loadEveryTimeFiles(ctx, "auth", userAccountsModel); + + ModelAccess.on(ctx).setUserAccountsModel(userAccountsModel); + } catch (Throwable t) { + log.error("Unable to load user accounts model from DB", t); + ss.fatal(this, "Unable to load user accounts model from DB", t); + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java index 46a8f3b68..008a3870f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/DataGetterUtils.java @@ -55,7 +55,12 @@ import edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache; public class DataGetterUtils { final static Log log = LogFactory.getLog(DataGetterUtils.class); - + + /** + * Attribute name in request for DataGetters + */ + public final static String DATA_GETTERS_FOR_PAGE = "data_getters_for_page"; + /** * Get a list of DataGetter objects that are associated with a page. * This should not return PageDataGetters and should not throw an @@ -63,10 +68,16 @@ public class DataGetterUtils { */ public static List getDataGettersForPage(VitroRequest vreq, Model displayModel, String pageURI) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException { - List dgUris = getDataGetterURIsForAssociatedURI(displayModel, pageURI); - List dgList = dataGettersForURIs(vreq, displayModel, dgUris); - log.debug("getDataGettersForPage: " + dgList); - return dgList; + + if( vreq.getAttribute(DATA_GETTERS_FOR_PAGE) != null){ + return (List) vreq.getAttribute(DATA_GETTERS_FOR_PAGE); + }else{ + List dgUris = getDataGetterURIsForAssociatedURI(displayModel, pageURI); + List dgList = dataGettersForURIs(vreq, displayModel, dgUris); + log.debug("getDataGettersForPage: " + dgList); + vreq.setAttribute( DATA_GETTERS_FOR_PAGE , dgList ); + return dgList; + } } /** diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java new file mode 100644 index 000000000..7bb33194b --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java @@ -0,0 +1,96 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.utils.dataGetter; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.update.GraphStore; +import com.hp.hpl.jena.update.GraphStoreFactory; +import com.hp.hpl.jena.update.UpdateAction; + +import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequiresActions; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; +import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder; + +/** + * Handle a SPARQL Update request. This uses Jena ARQ and the RDFServiceDataset to + * evaluate a SPARQL Update with the RDFService. + * + * The reason to make this a DataGettere was to allow configuration in RDF of this + * service. + */ +public class SparqlUpdate implements DataGetter, RequiresActions{ + + private static final Log log = LogFactory.getLog(SparqlUpdate.class); + + VitroRequest vreq; + ServletContext context; + + public SparqlUpdate( + VitroRequest vreq, Model displayModel, String dataGetterURI ) { + if( vreq == null ) + throw new IllegalArgumentException("VitroRequest may not be null."); + this.vreq = vreq; + this.context = vreq.getSession().getServletContext(); + } + + + /** + * Gets the update from the request and then executes it on + * the RDFService. + */ + @Override + public Map getData( Map valueMap ) { + HashMap data = new HashMap(); + + String update = vreq.getParameter("update"); + + if( update != null && !update.trim().isEmpty()){ + try{ + IndexBuilder.getBuilder(context).pause(); + Dataset ds = new RDFServiceDataset( vreq.getUnfilteredRDFService() ); + GraphStore graphStore = GraphStoreFactory.create(ds); + log.warn("The SPARQL update is '"+vreq.getParameter("update")+"'"); + UpdateAction.parseExecute( vreq.getParameter("update") , graphStore ); + }finally{ + IndexBuilder.getBuilder(context).unpause(); + } + + } + + data.put("bodyTemplate", "page-sparqlUpdateTest.ftl"); + return data; + } + + + /** + * Check if this request is authorized by the email/password. + * If not do normal authorization. + */ + @Override + public Actions requiredActions(VitroRequest vreq) { + String email = vreq.getParameter("email"); + String password = vreq.getParameter("password"); + + boolean isAuth = PolicyHelper.isAuthorizedForActions(vreq, + email, password, SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS); + + if( isAuth ) + return Actions.AUTHORIZED; + else + return SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/jena/InitialJenaModelUtils.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/jena/InitialJenaModelUtils.java deleted file mode 100644 index 06bb59872..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/jena/InitialJenaModelUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.utils.jena; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.ontology.OntModelSpec; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.util.ResourceUtils; -import com.hp.hpl.jena.util.iterator.ClosableIterator; -import com.hp.hpl.jena.vocabulary.RDF; - -import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; -import edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetupBase; - -public class InitialJenaModelUtils { - - private static final String INIT_DATA_DIRECTORY = "/WEB-INF/init-data"; - - private static final Log log = LogFactory.getLog(InitialJenaModelUtils.class); - - public static Model loadInitialModel(ServletContext ctx, String defaultNamespace) { - - OntModel initialModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - - try { - JenaDataSourceSetupBase.readOntologyFilesInPathSet(INIT_DATA_DIRECTORY, ctx, initialModel); - } catch (Throwable t) { - log.warn("Unable to read initial model data from " + INIT_DATA_DIRECTORY, t); - } - - if (initialModel.size() == 0) { - return initialModel; - } - - //find and rename portal object - //currently, only a single portal is permitted in the initialization data - Resource portalResource = null; - ClosableIterator portalResIt = initialModel.listSubjectsWithProperty(RDF.type, initialModel.getResource(VitroVocabulary.PORTAL)); - try { - if (portalResIt.hasNext()) { - Resource portalRes = portalResIt.next(); - if (portalRes.isAnon()) { - portalResource = portalRes; - } - } - } finally { - portalResIt.close(); - } - if (portalResource != null) { - ResourceUtils.renameResource(portalResource, defaultNamespace + "portal1"); - } - - return initialModel; - - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyTemplateModel.java index 49c4c4d60..621c35998 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyTemplateModel.java @@ -5,8 +5,6 @@ package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; @@ -21,13 +19,12 @@ import edu.cornell.mannlib.vitro.webapp.beans.DataProperty; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.Property; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfigurationLoader; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route; import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyStatementDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; -import edu.cornell.mannlib.vitro.webapp.web.templatemodels.customlistview.InvalidConfigurationException; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.customlistview.DataPropertyListConfig; import freemarker.cache.TemplateLoader; @@ -136,7 +133,7 @@ public class DataPropertyTemplateModel extends PropertyTemplateModel { } protected TemplateLoader getFreemarkerTemplateLoader() { - return FreemarkerConfigurationLoader.getConfig(vreq).getTemplateLoader(); + return FreemarkerConfiguration.getConfig(vreq).getTemplateLoader(); } @Override diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/ObjectPropertyTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/ObjectPropertyTemplateModel.java index 482c1cd27..5257c3dbb 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/ObjectPropertyTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/ObjectPropertyTemplateModel.java @@ -22,12 +22,12 @@ import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty; import edu.cornell.mannlib.vitro.webapp.beans.Property; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerConfigurationLoader; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route; import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.customlistview.InvalidConfigurationException; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.customlistview.PropertyListConfig; import freemarker.cache.TemplateLoader; @@ -155,7 +155,7 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel * This will do for now. */ protected TemplateLoader getFreemarkerTemplateLoader() { - return FreemarkerConfigurationLoader.getConfig(vreq).getTemplateLoader(); + return FreemarkerConfiguration.getConfig(vreq).getTemplateLoader(); } protected List> getStatementData() { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyGroupTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyGroupTemplateModel.java index 71e3fa65e..10b5ce4d9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyGroupTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyGroupTemplateModel.java @@ -22,10 +22,13 @@ public class PropertyGroupTemplateModel extends BaseTemplateModel { private final String name; private final List properties; - + + + PropertyGroupTemplateModel(VitroRequest vreq, PropertyGroup group, Individual subject, boolean editing, - List populatedDataPropertyList, List populatedObjectPropertyList) { + List populatedDataPropertyList, + List populatedObjectPropertyList) { this.name = group.getName(); @@ -54,6 +57,17 @@ public class PropertyGroupTemplateModel extends BaseTemplateModel { properties.remove(ptm); } + + public String toString(){ + String ptmStr =""; + for( int i=0; i < properties.size() ; i ++ ){ + PropertyTemplateModel ptm = properties.get(i); + String spacer = "\n "; + if( ptm != null ) + ptmStr = ptmStr + spacer + ptm.toString(); + } + return String.format("\nPropertyGroupTemplateModel %s[%s] ",name, ptmStr ); + } /* Accessor methods for templates */ // Add this so it's included in dumps for debugging. The templates will want to display @@ -63,18 +77,14 @@ public class PropertyGroupTemplateModel extends BaseTemplateModel { } public String getName(String otherGroupName) { - String displayName = name; - if (displayName == null) { - displayName = ""; - } else if (displayName.isEmpty()) { - displayName = otherGroupName; - } - return displayName; + if (name == null || name.isEmpty()) { + return otherGroupName; + } else { + return name; + } } public List getProperties() { return properties; - } - - + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyTemplateModel.java index 2815cdc7e..43240e66f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/PropertyTemplateModel.java @@ -39,13 +39,14 @@ public abstract class PropertyTemplateModel extends BaseTemplateModel { private String name; + + PropertyTemplateModel(Property property, Individual subject, VitroRequest vreq) { this.vreq = vreq; subjectUri = subject.getURI(); this.property = property; propertyUri = property.getURI(); - localName = property.getLocalName(); - log.debug("Local name for property " + propertyUri + ": " + localName); + localName = property.getLocalName(); setVerboseDisplayValues(property); addUrl = ""; @@ -57,14 +58,18 @@ public abstract class PropertyTemplateModel extends BaseTemplateModel { protected void setVerboseDisplayValues(Property property) { // No verbose display for vitro and vitro public properties. - // This models previous behavior. In theory the verbose display can be provided, but we may not want - // to give anyone access to these properties, since the application is dependent on them. + // This models previous behavior. In theory the verbose display can be provided, + // but we may not want to give anyone access to these properties, since the + // application is dependent on them. String namespace = property.getNamespace(); - if (VitroVocabulary.vitroURI.equals(namespace) || VitroVocabulary.VITRO_PUBLIC.equals(namespace)) { + if (VitroVocabulary.vitroURI.equals(namespace) + || VitroVocabulary.VITRO_PUBLIC.equals(namespace)) { return; } - Boolean verboseDisplayValue = (Boolean) vreq.getSession().getAttribute("verbosePropertyDisplay"); + Boolean verboseDisplayValue = + (Boolean) vreq.getSession().getAttribute("verbosePropertyDisplay"); + if ( ! Boolean.TRUE.equals(verboseDisplayValue)) { return; } @@ -98,6 +103,12 @@ public abstract class PropertyTemplateModel extends BaseTemplateModel { this.name = name; } + public String toString() { + return String.format("%s on %s", + propertyUri != null ? propertyUri : "null Prop URI", + subjectUri != null ? subjectUri : "null Sub URI" ); + } + /* Template properties */ public abstract String getType(); @@ -125,6 +136,5 @@ public abstract class PropertyTemplateModel extends BaseTemplateModel { public Map getVerboseDisplay() { return verboseDisplay; - } - + } } diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyListTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyListTest.java new file mode 100644 index 000000000..4b056db87 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyListTest.java @@ -0,0 +1,156 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AllRequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AnyRequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.UnauthorizedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +public class PolicyListTest { + + @Test + public void basicPolicyListTest() { + + List polis = new ArrayList(); + polis.add( new SimplePolicy() ); + PolicyIface policy = new PolicyList( polis ); + PolicyDecision decision = policy.isAuthorized(null, new UnauthorizedAction()); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + decision = policy.isAuthorized(null, new AuthorizedAction()); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + } + + /** + * Tests the handling of the AnyRequestedAction by the PolicyList. + */ + @Test + public void anyActionTest(){ + List polis = new ArrayList(); + polis.add( new SimplePolicy() ); + PolicyIface policy = new PolicyList( polis ); + + AnyRequestedAction act = new AnyRequestedAction( new UnauthorizedAction() ); + PolicyDecision decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new UnauthorizedAction() , new UnauthorizedAction()); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new UnauthorizedAction(),new UnauthorizedAction(),new UnauthorizedAction()); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new AuthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new AuthorizedAction(),new UnauthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new UnauthorizedAction(),new AuthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new UnauthorizedAction(),new UnauthorizedAction(),new AuthorizedAction()); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new UnauthorizedAction(),new AuthorizedAction(),new AuthorizedAction()); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AnyRequestedAction( new AuthorizedAction(),new AuthorizedAction(),new AuthorizedAction()); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + } + + /** + * Tests the handling of the AllRequestedAction by the PolicyList. + */ + @Test + public void andActionTest(){ + List polis = new ArrayList(); + polis.add( new SimplePolicy() ); + PolicyIface policy = new PolicyList( polis ); + + AllRequestedAction act = new AllRequestedAction( new UnauthorizedAction(), new UnauthorizedAction(), new UnauthorizedAction()); + PolicyDecision decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new UnauthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new UnauthorizedAction() , new AuthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new AuthorizedAction() , new UnauthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new AuthorizedAction() , new AuthorizedAction() ,new UnauthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.UNAUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new AuthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + + act = new AllRequestedAction( new AuthorizedAction() , new AuthorizedAction(), new AuthorizedAction() ); + decision = policy.isAuthorized(null, act); + Assert.assertNotNull( decision ); + Assert.assertEquals(Authorization.AUTHORIZED, decision.getAuthorized() ); + } + + + /** + * policy that only responds to Unauthorized and Authorized actions. + */ + public class SimplePolicy implements PolicyIface { + + @Override + public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, + RequestedAction whatToAuth) { + if( whatToAuth instanceof UnauthorizedAction ) + return new BasicPolicyDecision( Authorization.UNAUTHORIZED, "SimplePolicy unauthorized"); + if( whatToAuth instanceof AuthorizedAction ) + return new BasicPolicyDecision( Authorization.AUTHORIZED, "SimplePolicy authorized"); + else + return new BasicPolicyDecision(Authorization.INCONCLUSIVE, "SimplePolicy INCONCLUSIVE"); + } + + + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServletTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServletTest.java new file mode 100644 index 000000000..a03d3b9a7 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/SparqlQueryServletTest.java @@ -0,0 +1,76 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import com.github.jsonldjava.core.JSONLD; +import com.github.jsonldjava.core.JSONLDProcessingError; +import com.github.jsonldjava.impl.JenaRDFParser; +import com.github.jsonldjava.utils.JSONUtils; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; + +public class SparqlQueryServletTest { + + @Test + public void testJSONLD() throws JSONLDProcessingError { + //just check if we can use JSONLD-JAVA + + final String turtle = "@prefix const: .\n" + + "@prefix xsd: .\n" + + " const:code \"123\" .\n" + + " const:code \"ABC\"^^xsd:string .\n"; + + final List> expected = new ArrayList>() { + { + add(new LinkedHashMap() { + { + put("@id", "http://localhost:8080/foo1"); + put("http://foo.com/code", new ArrayList() { + { + add(new LinkedHashMap() { + { + put("@value", "123"); + } + }); + } + }); + } + }); + add(new LinkedHashMap() { + { + put("@id", "http://localhost:8080/foo2"); + put("http://foo.com/code", new ArrayList() { + { + add(new LinkedHashMap() { + { + put("@value", "ABC"); + } + }); + } + }); + } + }); + } + }; + + final Model modelResult = ModelFactory.createDefaultModel().read( + new ByteArrayInputStream(turtle.getBytes()), "", "TURTLE"); + final JenaRDFParser parser = new JenaRDFParser(); + final Object json = JSONLD.fromRDF(modelResult, parser); + + assertTrue(JSONUtils.equals(json, expected)); + + + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java index 8f071ee3c..3bcfee593 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java @@ -27,7 +27,7 @@ import stubs.javax.servlet.http.HttpServletResponseStub; import stubs.javax.servlet.http.HttpSessionStub; import edu.cornell.mannlib.vedit.beans.LoginStatusBean; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; -import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader; +import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; /** @@ -97,7 +97,7 @@ public class ProgramLoginTest extends AbstractTestClass { user.setEmailAddress(name); user.setUri(uri); user.setPermissionSetUris(Collections - .singleton(PermissionSetsLoader.URI_DBA)); + .singleton(PermissionSets.URI_DBA)); user.setMd5Password(Authenticator.applyMd5Encoding(password)); user.setLoginCount(loginCount); user.setPasswordChangeRequired(loginCount == 0); diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java index 2e98b283d..9c5dcfbb6 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/edit/AuthenticateTest.java @@ -2,8 +2,8 @@ package edu.cornell.mannlib.vitro.webapp.controller.edit; -import static edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader.URI_DBA; -import static edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader.URI_SELF_EDITOR; +import static edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets.URI_DBA; +import static edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets.URI_SELF_EDITOR; import static edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State.FORCED_PASSWORD_CHANGE; import static edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State.LOGGING_IN; import static org.junit.Assert.assertEquals; diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java index 9f3e87004..7cc7b9508 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java @@ -3,12 +3,21 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker; +import static org.easymock.EasyMock.*; + +import javax.servlet.http.HttpServletRequest; + import junit.framework.Assert; import org.junit.Test; +import stubs.edu.cornell.mannlib.vitro.webapp.dao.ApplicationDaoStub; +import stubs.edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactoryStub; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.web.URLEncoder; public class UrlBuilderTest extends AbstractTestClass { @@ -76,4 +85,81 @@ public class UrlBuilderTest extends AbstractTestClass { String vClassUriEncoded = "http%3A%2F%2Fvivoweb.org%2Fontology%2Fcore%23FacultyMember%E2%98%85"; Assert.assertEquals(vClassUri, UrlBuilder.urlDecode(vClassUriEncoded)); } + + + @Test + public void testGetIndividualProfileURI(){ + VitroRequest vreq = makeMockVitroRequest( "http://example.com/individual/"); + UrlBuilder.contextPath = "http://example.com"; + + String uri = "http://example.com/individual/n2343"; + String url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + Assert.assertEquals("http://example.com/individual/n2343", url); + + uri = "http://example.com/individual/bob"; + url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + Assert.assertEquals("http://example.com/individual/bob",url); + + uri = "http://nondefaultNS.com/individual/n2343"; + url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + Assert.assertEquals("http://example.com/individual?uri=" + URLEncoder.encode(uri), url); + + uri = "http://example.com/individual#n2343"; + url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + Assert.assertEquals("http://example.com/individual?uri=" + URLEncoder.encode(uri), url); + + uri = "http://example.com/individual/5LNCannotStartWithNumber"; + url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + Assert.assertEquals("http://example.com/individual?uri=" + URLEncoder.encode(uri), url); + } + + protected VitroRequest makeMockVitroRequest( final String defaultNS){ + HttpServletRequest req = createMock( HttpServletRequest.class ); + return new VitroRequest(req){ + + @Override + public String getParameter(String key){ return null; } + + @Override + public WebappDaoFactory getWebappDaoFactory(){ + return makeMockWDF(defaultNS); + } + }; + } + protected WebappDaoFactoryStub makeMockWDF( String defaultNS){ + WebappDaoFactoryStub wdf = new WebappDaoFactoryStub(); + wdf.setDefaultNamespace("http://example.com/individual/"); + ApplicationDaoStub aDao = new ApplicationDaoStub(){ + @Override + public boolean isExternallyLinkedNamespace(String ns){ + return false; + } + }; + wdf.setApplicationDao( aDao ); + return wdf; + } + + @Test + public void testIsUriInDefaultNamespace(){ + String[][] examples = { + { "http://example.com/individual/n3234", "http://example.com/individual/"}, + { "http://example.com/individual#n3234", "http://example.com/individual#"}, + { "http://example.com:8080/individual/n3234", "http://example.com:8080/individual/"}, + { "http://example.com:8080/individual#n3234", "http://example.com:8080/individual#"} + }; + + for( String[] example : examples ){ + Assert.assertTrue("expected '"+ example[0] + "' to be in the default NS of '"+example[1]+"'", + UrlBuilder.isUriInDefaultNamespace(example[0], example[1])); + } + + String[][] counterExamples = { + { "http://example.com/individual/5LNCannotStartWithNumber", "http://example.com/individual/" } + }; + for( String[] example : counterExamples ){ + Assert.assertFalse("expected '"+ example[0] + "' to NOT be in the default NS of '"+example[1]+"'", + UrlBuilder.isUriInDefaultNamespace(example[0], example[1])); + } + } + } diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java new file mode 100644 index 000000000..9493c3691 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/ObjectPropertyStatementDaoJenaTest.java @@ -0,0 +1,32 @@ +package edu.cornell.mannlib.vitro.webapp.dao.jena; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; + +public class ObjectPropertyStatementDaoJenaTest { + + /** + * Test if jena lib can parse N3 that it generates. + * owl:sameAs has been a problem when it is represetned + * in N3 with the character = + */ + @Test + public void testN3WithSameAs() { + + String n3WithSameAs = " = ."; + + try{ + Model m = ModelFactory.createDefaultModel(); + m.read(n3WithSameAs, null, "N3"); + fail( "If this test fails it means that jena now correctly parses = when reading N3."); + }catch(Exception ex ){ + + + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerInversePropertyTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerInversePropertyTest.java index de3b0ff0b..e0f81bb74 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerInversePropertyTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerInversePropertyTest.java @@ -31,6 +31,16 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { setLoggerLevel(ABoxRecomputer.class, Level.OFF); } + @Test + public void addABoxAssertion1Test(){ + addABoxAssertion1(true); + } + + @Test + public void addABoxAssertion1NoSameAsTest(){ + addABoxAssertion1(false); + } + /* * basic scenarios around adding abox data * @@ -40,8 +50,7 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { * Add a statement c Q d and verify that d Q c * is inferred. */ - @Test - public void addABoxAssertion1() { + public void addABoxAssertion1(boolean sameAs ) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -57,7 +66,11 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // create an abox and register the SimpleReasoner listener with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + SimpleReasoner sr = new SimpleReasoner(tBox,aBox,inf); + sr.setSameAsEnabled( sameAs ); + + aBox.register( sr ); // add assertions to the abox and verify inferences Resource a = aBox.createResource("http://test.vivo/a"); @@ -68,15 +81,24 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { aBox.add(a,P,b); Assert.assertTrue(inf.contains(b,Q,a)); aBox.add(c,Q,d); - Assert.assertTrue(inf.contains(d,P,c)); + Assert.assertTrue(inf.contains(d,P,c)); } - + + @Test + public void addABoxAssertion2Test() { + addABoxAssertion2(true); + } + + @Test + public void addABoxAssertion2NoSameAsTest() { + addABoxAssertion2(false); + } + /* * don't infer statements already in the abox * (never infer because it's in the abox already) */ - @Test - public void addABoxAssertion2() { + public void addABoxAssertion2(boolean sameAs ) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -107,12 +129,20 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { Assert.assertFalse(inf.contains(b,Q,a)); } + @Test + public void addABoxAssertion3Test() { + addABoxAssertion3(true); + } + @Test + public void addABoxAssertion3NoSameAsTest(){ + addABoxAssertion3(false); + } + /* * don't infer statements already in the abox * (remove the inference when it is asserted) */ - @Test - public void addABoxAssertion3() { + public void addABoxAssertion3(boolean sameAs) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -127,7 +157,11 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // create abox and register SimpleReasoner OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( sameAs ); + + aBox.register( sr ); // add statements to the abox and verify inferences Resource a = aBox.createResource("http://test.vivo/a"); @@ -138,13 +172,21 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { aBox.add(b,Q,a); // this should cause the inference to be removed Assert.assertFalse(inf.contains(b,Q,a)); } - + + @Test + public void addABoxAssertion4Test() { + addABoxAssertion4(true); + } + @Test + public void addABoxAssertion4NoSameAsTest() { + addABoxAssertion4(false); + } + /* * adding abox data where the property has an inverse and * and equivalent property. */ - @Test - public void addABoxAssertion4() { + public void addABoxAssertion4( boolean sameAs ) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -163,7 +205,10 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // create an ABox and register the SimpleReasoner with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( sameAs ); + aBox.register( sr ); // add abox statements and verify inferences Resource a = aBox.createResource("http://test.vivo/a"); @@ -179,13 +224,22 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { Assert.assertTrue(inf.contains(d,R,c)); } + @Test + public void removedABoxAssertion1Test(){ + removedABoxAssertion1(true); + } + + @Test + public void removedABoxAssertion1NoSameAsTest(){ + removedABoxAssertion1(false); + } + /* * basic scenarios around removing abox data * don't remove an inference if it's still * entailed by something else in the abox. */ - @Test - public void removedABoxAssertion1() { + public void removedABoxAssertion1(boolean sameAs) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -203,7 +257,9 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // create an ABox and register the SimpleReasoner with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( sameAs ); + aBox.register( sr ); // add statements to the abox and verify inferences Resource a = aBox.createResource("http://test.vivo/a"); @@ -226,12 +282,21 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { Assert.assertTrue(inf.contains(d,P,c)); // still inferred from c T d } + @Test + public void removedABoxAssertion2Test(){ + removedABoxAssertion2(true); + } + + @Test + public void removedABoxAssertion2NoSameAsTest(){ + removedABoxAssertion2(false); + } + /* * removing abox data with equivalent and inverse properties * don't remove inference if it's still inferred. */ - @Test - public void removedABoxAssertion2() { + public void removedABoxAssertion2(boolean sameAs) { // set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -253,7 +318,9 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // create an abox and register the SimpleReasoner listener with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( sameAs ); + aBox.register( sr ); // add abox data and verify inferences Resource a = aBox.createResource("http://test.vivo/a"); @@ -270,11 +337,20 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { Assert.assertTrue(inf.contains(b,Q,a)); } + @Test + public void removedABoxAssertion3Test(){ + removedABoxAssertion3(true); + } + + @Test + public void removedABoxAssertion3NoSameAsTest(){ + removedABoxAssertion3(false); + } + /* * removing abox data with equivalent and inverse properties */ - @Test - public void removedABoxAssertion3() { + public void removedABoxAssertion3(boolean sameAs) { //set up the tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -297,7 +373,9 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { aBox.add(a,P,b); // register the SimpleReasoner - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( sameAs ); + aBox.register( sr ); // add abox statements and verify inferences aBox.add(b,Q,a); @@ -309,11 +387,20 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // when it's removed from the abox } + @Test + public void addTBoxInverseAssertion1Test() throws InterruptedException { + addTBoxInverseAssertion1(true); + } + + @Test + public void addTBoxInverseAssertion1NoSameAsTest() throws InterruptedException { + addTBoxInverseAssertion1(false); + } + /* * adding an inverseOf assertion to the tbox */ - @Test - public void addTBoxInverseAssertion1() throws InterruptedException { + public void addTBoxInverseAssertion1(boolean sameAs) throws InterruptedException { // Set up the TBox. OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -330,7 +417,8 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // set up SimpleReasoner and register it with abox. register // SimpleReasonerTBoxListener with the tbox. - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf ); + simpleReasoner.setSameAsEnabled( sameAs ); aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -361,12 +449,21 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } - + + @Test + public void removeTBoxInverseAssertion1Test() throws InterruptedException { + removeTBoxInverseAssertion1(true); + } + + @Test + public void removeTBoxInverseAssertion1NoSameAsTest() throws InterruptedException { + removeTBoxInverseAssertion1(false); + } + /* * removing an inverseOf assertion from the tbox */ - @Test - public void removeTBoxInverseAssertion1() throws InterruptedException { + public void removeTBoxInverseAssertion1(boolean sameAs) throws InterruptedException { // set up the tbox. OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -385,7 +482,8 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { // set up SimpleReasoner and SimpleReasonerTBox listener, // register them with abox and tbox - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + simpleReasoner.setSameAsEnabled( sameAs ); aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -411,11 +509,20 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } + @Test + public void recomputeABox1Test() throws InterruptedException { + recomputeABox1(true); + } + + @Test + public void recomputeABox1NoSameAsTest() throws InterruptedException { + recomputeABox1(false); + } + /* * Basic scenario around recomputing the ABox inferences */ - @Test - public void recomputeABox1() throws InterruptedException { + public void recomputeABox1(boolean sameAs) throws InterruptedException { // set up tbox OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); @@ -437,6 +544,7 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + simpleReasoner.setSameAsEnabled( sameAs ); aBox.register(simpleReasoner); // abox statements @@ -487,4 +595,4 @@ public class SimpleReasonerInversePropertyTest extends AbstractTestClass { ontModel.writeAll(System.out,"N3",null); } -} \ No newline at end of file +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerSameAsTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerSameAsTest.java index 493a7d60a..c3b485803 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerSameAsTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerSameAsTest.java @@ -91,6 +91,37 @@ public class SimpleReasonerSameAsTest extends AbstractTestClass { Assert.assertFalse(aBox.contains(b,S,literal1)); Assert.assertFalse(aBox.contains(a,Q,d)); Assert.assertFalse(aBox.contains(a,T,literal2)); + + //run same test with sameAs = false + inf = ModelFactory.createDefaultModel(); + aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + SimpleReasoner sres = new SimpleReasoner(tBox,aBox,inf); + sres.setSameAsEnabled( false ); + aBox.register(sres); + + a = aBox.createResource("http://test.vivo/a"); + b = aBox.createResource("http://test.vivo/b"); + c = aBox.createResource("http://test.vivo/c"); + d = aBox.createResource("http://test.vivo/d"); + + aBox.add(a,P,c); + aBox.add(a,S,literal1); + aBox.add(b,Q,d); + aBox.add(b,T,literal2); + aBox.add(a,OWL.sameAs,b); + + //these are now false since sameAs is off + Assert.assertFalse(inf.contains(b,OWL.sameAs,a)); + Assert.assertFalse(inf.contains(b,P,c)); + Assert.assertFalse(inf.contains(b,S,literal1)); + Assert.assertFalse(inf.contains(a,Q,d)); + Assert.assertFalse(inf.contains(a,T,literal2)); + //these still shouldn't be in the abox + Assert.assertFalse(aBox.contains(b,OWL.sameAs,a)); + Assert.assertFalse(aBox.contains(b,P,c)); + Assert.assertFalse(aBox.contains(b,S,literal1)); + Assert.assertFalse(aBox.contains(a,Q,d)); + Assert.assertFalse(aBox.contains(a,T,literal2)); } /* @@ -246,7 +277,93 @@ public class SimpleReasonerSameAsTest extends AbstractTestClass { Assert.assertFalse(inf.contains(f,Q,d)); Assert.assertFalse(inf.contains(f,T,literal2)); } - + + /** + * test of enableSameAs( false ) + */ + @Test + public void disabledSameAs() { + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createObjectProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + + OntProperty Q = tBox.createObjectProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + + OntProperty S = tBox.createDatatypeProperty("http://test.vivo/"); + S.setLabel("property S", "en-US"); + + OntProperty T = tBox.createDatatypeProperty("http://test.vivo/"); + T.setLabel("property T", "en-US"); + + Literal literal1 = tBox.createLiteral("Literal value 1"); + Literal literal2 = tBox.createLiteral("Literal value 2"); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + aBox.add(a,P,c); + aBox.add(a,S,literal1); + aBox.add(b,Q,d); + aBox.add(b,T,literal2); + aBox.add(a,OWL.sameAs,b); + + Assert.assertTrue(inf.contains(b,OWL.sameAs,a)); + Assert.assertTrue(inf.contains(b,P,c)); + Assert.assertTrue(inf.contains(b,S,literal1)); + Assert.assertTrue(inf.contains(a,Q,d)); + Assert.assertTrue(inf.contains(a,T,literal2)); + + Assert.assertFalse(aBox.contains(b,OWL.sameAs,a)); + Assert.assertFalse(aBox.contains(b,P,c)); + Assert.assertFalse(aBox.contains(b,S,literal1)); + Assert.assertFalse(aBox.contains(a,Q,d)); + Assert.assertFalse(aBox.contains(a,T,literal2)); + + //run same test with sameAs = false + inf = ModelFactory.createDefaultModel(); + aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + SimpleReasoner sres = new SimpleReasoner(tBox,aBox,inf); + sres.setSameAsEnabled( false ); + aBox.register(sres); + + a = aBox.createResource("http://test.vivo/a"); + b = aBox.createResource("http://test.vivo/b"); + c = aBox.createResource("http://test.vivo/c"); + d = aBox.createResource("http://test.vivo/d"); + + aBox.add(a,P,c); + aBox.add(a,S,literal1); + aBox.add(b,Q,d); + aBox.add(b,T,literal2); + aBox.add(a,OWL.sameAs,b); + + //these are now false since sameAs is off + Assert.assertFalse(inf.contains(b,OWL.sameAs,a)); + Assert.assertFalse(inf.contains(b,P,c)); + Assert.assertFalse(inf.contains(b,S,literal1)); + Assert.assertFalse(inf.contains(a,Q,d)); + Assert.assertFalse(inf.contains(a,T,literal2)); + //these still shouldn't be in the abox + Assert.assertFalse(aBox.contains(b,OWL.sameAs,a)); + Assert.assertFalse(aBox.contains(b,P,c)); + Assert.assertFalse(aBox.contains(b,S,literal1)); + Assert.assertFalse(aBox.contains(a,Q,d)); + Assert.assertFalse(aBox.contains(a,T,literal2)); + } + /* * sameAs with datatype properties */ @@ -723,4 +840,4 @@ public class SimpleReasonerSameAsTest extends AbstractTestClass { ontModel.writeAll(System.out,"N3",null); } -} \ No newline at end of file +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerTest.java index 0c6427377..7b17d1303 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerTest.java @@ -2,10 +2,10 @@ package edu.cornell.mannlib.vitro.webapp.reasoner; - import org.apache.log4j.Level; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mindswap.pellet.jena.PelletReasonerFactory; @@ -38,12 +38,20 @@ public class SimpleReasonerTest extends AbstractTestClass { setLoggerLevel(SimpleReasonerTBoxListener.class, Level.OFF); } + @Test + public void addABoxTypeAssertion1Test(){ + addABoxTypeAssertion1(true); + } + + @Test + public void addABoxTypeAssertion1NoSameAsTest(){ + addABoxTypeAssertion1(false); + } /* * Test that when an individual is asserted to be of a type, * its asserted type is not added to the inference graph */ - @Test - public void addABoxTypeAssertion1(){ + public void addABoxTypeAssertion1( boolean sameAsEnabled ){ // Create a Tbox with a simple class hierarchy. B is a subclass of A. // Pellet will compute TBox inferences @@ -62,7 +70,9 @@ public class SimpleReasonerTest extends AbstractTestClass { // create an ABox and register the SimpleReasoner listener with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + simpleReasoner.setSameAsEnabled( sameAsEnabled ); + aBox.register(simpleReasoner); // Individual x Resource ind_x = aBox.createResource("http://test.vivo/x"); @@ -75,13 +85,20 @@ public class SimpleReasonerTest extends AbstractTestClass { Assert.assertFalse(inf.contains(xisb)); } + @Test + public void addABoxTypeAssertion2Test(){ + addABoxTypeAssertion2(true); + } + @Test + public void addABoxTypeAssertion2NoSameAs(){ + addABoxTypeAssertion2(false); + } /* * Test that when an individual is asserted have a type, * that inferences are materialized that it has the types * of its superclasses */ - @Test - public void addABoxTypeAssertion2(){ + public void addABoxTypeAssertion2(boolean enableSameAs){ // Create a Tbox with a simple class hierarchy. D and E are subclasses // of C. B and C are subclasses of A. Pellet will compute TBox inferences. @@ -113,7 +130,9 @@ public class SimpleReasonerTest extends AbstractTestClass { // create an Abox and register the SimpleReasoner listener with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + aBox.register( sr ); // add a statement to the ABox that individual x is of type E. Resource ind_x = aBox.createResource("http://test.vivo/x"); @@ -128,11 +147,18 @@ public class SimpleReasonerTest extends AbstractTestClass { Assert.assertTrue(inf.contains(xisa)); } + @Test + public void addABoxTypeAssertion3Test() throws InterruptedException{ + addABoxTypeAssertion3(true); + } + @Test + public void addABoxTypeAssertion3NoSameAs() throws InterruptedException{ + addABoxTypeAssertion3(false); + } /* * Test inference based on class equivalence */ - @Test - public void addABoxTypeAssertion3() throws InterruptedException { + public void addABoxTypeAssertion3(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox @@ -142,7 +168,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -182,11 +210,18 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested();; } + @Test + public void addABoxTypeAssertion4Test()throws InterruptedException{ + addABoxTypeAssertion4(true); + } + @Test + public void addABoxTypeAssertion4NoSameAs()throws InterruptedException{ + addABoxTypeAssertion4(false); + } /* * Test inference based on class equivalence */ - @Test - public void addABoxTypeAssertion4() throws InterruptedException { + public void addABoxTypeAssertion4(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox @@ -196,7 +231,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -228,11 +265,18 @@ public class SimpleReasonerTest extends AbstractTestClass { } + @Test + public void addABoxTypeAssertion5Test()throws InterruptedException{ + addABoxTypeAssertion5(true); + } + @Test + public void addABoxTypeAssertion5NoSameAs()throws InterruptedException{ + addABoxTypeAssertion5(false); + } /* * Test inference based on class equivalence */ - @Test - public void addABoxTypeAssertion5() throws InterruptedException { + public void addABoxTypeAssertion5(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox @@ -242,7 +286,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -279,6 +325,14 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } + @Test + public void removeABoxTypeAssertion1Test()throws InterruptedException{ + removeABoxTypeAssertion1(true); + } + @Test + public void removeABoxTypeAssertion1NoSameAs()throws InterruptedException{ + removeABoxTypeAssertion1(false); + } /* * Test that when it is retracted that an individual is of a type, * that the inferences that it is of the type of all superclasses @@ -287,8 +341,7 @@ public class SimpleReasonerTest extends AbstractTestClass { * TBox, ABox and inference graph minus the retracted type statement) * should not be retracted. */ - @Test - public void removeABoxTypeAssertion1(){ + public void removeABoxTypeAssertion1(boolean enableSameAs){ // Create a Tbox with a simple class hierarchy. C is a subclass of B // and B is a subclass of A. Pellet will compute TBox inferences. @@ -312,7 +365,9 @@ public class SimpleReasonerTest extends AbstractTestClass { // create an Abox and register the SimpleReasoner listener with it OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - aBox.register(new SimpleReasoner(tBox, aBox, inf)); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + aBox.register( sr ); // add a statement to the ABox that individual x is of type C. Resource ind_x = aBox.createResource("http://test.vivo/x"); @@ -335,6 +390,14 @@ public class SimpleReasonerTest extends AbstractTestClass { } + @Test + public void addTBoxSubClassAssertion1Test()throws InterruptedException{ + addTBoxSubClassAssertion1(true); + } + @Test + public void addTBoxSubClassAssertion1NoSameAs()throws InterruptedException{ + addTBoxSubClassAssertion1(false); + } /* * Test the addition of a subClassOf statement to * the TBox. The instance data that is the basis @@ -351,8 +414,7 @@ public class SimpleReasonerTest extends AbstractTestClass { * rdfs:subClassOf statement, this test serves * as a test of equivalentClass statements also. */ - @Test - public void addTBoxSubClassAssertion1() throws InterruptedException { + public void addTBoxSubClassAssertion1(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox @@ -362,7 +424,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -408,6 +472,14 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } + @Test + public void addTBoxSubClassAssertion2Test()throws InterruptedException{ + addTBoxSubClassAssertion2(true); + } + @Test + public void addTBoxSubClassAssertion2NoSameAs()throws InterruptedException{ + addTBoxSubClassAssertion2(false); + } /* * Test the addition of a subClassOf statement to * the TBox. The instance data that is the basis @@ -424,8 +496,7 @@ public class SimpleReasonerTest extends AbstractTestClass { * as some test of equivalentClass statements also. * */ - @Test - public void addTBoxSubClassAssertion2() throws InterruptedException { + public void addTBoxSubClassAssertion2(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox @@ -435,7 +506,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -480,6 +553,14 @@ public class SimpleReasonerTest extends AbstractTestClass { } + @Test + public void removeTBoxSubClassAssertion1Test()throws InterruptedException{ + removeTBoxSubClassAssertion1(true); + } + @Test + public void removeTBoxSubClassAssertion1NoSameAs()throws InterruptedException{ + removeTBoxSubClassAssertion1(false); + } /* * Test the removal of a subClassOf statement from * the TBox. The instance data that is the basis @@ -487,8 +568,7 @@ public class SimpleReasonerTest extends AbstractTestClass { * inference graph. * */ - @Test - public void removeTBoxSubClassAssertion1() throws InterruptedException { + public void removeTBoxSubClassAssertion1(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox // Pellet will compute TBox inferences @@ -497,7 +577,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -582,6 +664,17 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } + @Ignore(" needs PelletListener infrastructure which is not in this suite.") + @Test + public void bcdTest()throws InterruptedException{ + bcd(true); + } + + @Ignore(" needs PelletListener infrastructure which is not in this suite.") + @Test + public void bcdNoSameAsTest()throws InterruptedException{ + bcd(false); + } /* * Test the removal of a subClassOf statement from * the TBox. The instance data that is the basis @@ -589,12 +682,12 @@ public class SimpleReasonerTest extends AbstractTestClass { * inference graph. * */ - //@Test - this test would need PelletListener infrastructure, which we're not + // this test would need PelletListener infrastructure, which we're not // testing in this suite. The reason it doesn't work as it is because // the SimpleReasonerTBoxListener is not listening to the tBox inference // model as Pellet is updating it. I could simulate it by adding to the // tBox assertions what we can count on Pellet to infer. - public void bcdTest() throws InterruptedException { + public void bcd(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox // Pellet will compute TBox inferences @@ -604,6 +697,7 @@ public class SimpleReasonerTest extends AbstractTestClass { Model inf = ModelFactory.createDefaultModel(); SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + simpleReasoner.setSameAsEnabled( enableSameAs ); aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -654,12 +748,19 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } + @Test + public void mstTest1Test()throws InterruptedException{ + mstTest1(true); + } + @Test + public void mstTest1NoSameAs()throws InterruptedException{ + mstTest1(false); + } /* * Test computation of mostSpecificType annotations in response * to an added/removed ABox type assertion. */ - @Test - public void mstTest1() throws InterruptedException { + public void mstTest1(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox // Pellet will compute TBox inferences @@ -668,7 +769,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -732,12 +835,19 @@ public class SimpleReasonerTest extends AbstractTestClass { } + @Test + public void mstTest2Test()throws InterruptedException{ + mstTest2(true); + } + @Test + public void mstTest2NoSameAs()throws InterruptedException{ + mstTest2(false); + } /* * Test computation of mostSpecificType annotations in response * to an added ABox type assertion. */ - @Test - public void mstTest2() throws InterruptedException { + public void mstTest2(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox // Pellet will compute TBox inferences @@ -746,7 +856,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -785,13 +897,20 @@ public class SimpleReasonerTest extends AbstractTestClass { simpleReasonerTBoxListener.setStopRequested(); } - + + @Test + public void mstTest3Test()throws InterruptedException{ + mstTest3(true); + } + @Test + public void mstTest3NoSameAs()throws InterruptedException{ + mstTest3(false); + } /* * Test computation of mostSpecificType annotations in response * to an added/removed TBox assertions. */ - @Test - public void mstTest3() throws InterruptedException { + public void mstTest3(boolean enableSameAs) throws InterruptedException { // Create TBox, ABox and Inference models and register // the ABox reasoner listeners with the ABox and TBox // Pellet will compute TBox inferences @@ -800,7 +919,9 @@ public class SimpleReasonerTest extends AbstractTestClass { OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); Model inf = ModelFactory.createDefaultModel(); - SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + SimpleReasoner sr = new SimpleReasoner(tBox, aBox, inf); + sr.setSameAsEnabled( enableSameAs ); + SimpleReasoner simpleReasoner = sr ; aBox.register(simpleReasoner); SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); tBox.register(simpleReasonerTBoxListener); @@ -906,4 +1027,4 @@ public class SimpleReasonerTest extends AbstractTestClass { ontModel.writeAll(System.out,"N3",null); } -} \ No newline at end of file +} diff --git a/webapp/web/WEB-INF/ontologies/app/menuload/displayTBOX.n3 b/webapp/web/WEB-INF/ontologies/app/menuload/displayTBOX.n3 deleted file mode 100644 index 5d3fe4ccb..000000000 --- a/webapp/web/WEB-INF/ontologies/app/menuload/displayTBOX.n3 +++ /dev/null @@ -1,218 +0,0 @@ -# $This file is distributed under the terms of the license in /doc/license.txt$ - -@prefix rdfs: . -@prefix xsd: . -@prefix owl: . -@prefix rdf: . - - -#########Classes######### -###Basic -owl:Class - a owl:Class . - -owl:Ontology - a owl:Class . - -owl:AnnotationProperty - a owl:Class . - -owl:DatatypeProperty - a owl:Class . - -owl:ObjectProperty - a owl:Class . - -###Display Model - - a owl:Class ; - - "individual-menu.ftl"^^xsd:string . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - - - a owl:Class . - -##Adding a data getter type that is Solr Class search, i.e. get individuals for VClass - - a owl:Class . - - -########Data Properties######### - -###Basic - -rdfs:comment - a owl:DatatypeProperty . - -rdfs:label - a owl:DatatypeProperty . - - -owl:versionInfo - a owl:DatatypeProperty . - -###Vitro model - - - a owl:DatatypeProperty . - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - -###Display model - - - a owl:DatatypeProperty . - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty ; - - "1"^^xsd:int . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty . - - - a owl:DatatypeProperty. - - - a owl:DatatypeProperty. - - - a owl:DatatypeProperty. - - - a owl:DatatypeProperty. - -######### Object Properties######### -###Basic -rdfs:range - a owl:ObjectProperty . -rdfs:domain - a owl:ObjectProperty . -owl:topObjectProperty - a owl:ObjectProperty . - -##Adding object property defining class for solr data getter - - a owl:ObjectProperty. - - -###Vitro properties without which individual templates throw errors as are required - - - a owl:ObjectProperty ; - rdfs:range ; - rdfs:subPropertyOf , owl:topObjectProperty . - - - a owl:ObjectProperty ; - rdfs:label "Primary Link"@en-US ; - rdfs:range ; - rdfs:subPropertyOf , owl:topObjectProperty ; - - "defaultLinkForm.jsp"^^xsd:string ; - - "true"^^xsd:boolean ; - - "true"^^xsd:boolean ; - - "false"^^xsd:boolean ; - - "true"^^xsd:boolean . - -rdf:type - a owl:ObjectProperty ; - rdfs:label "RDF Type"@en-US ; - rdfs:range owl:Class ; - - "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RdfTypeGenerator"^^xsd:string ; - - "true"^^xsd:boolean ; - - "true"^^xsd:boolean . - - - - a owl:ObjectProperty ; - rdfs:label "Additional Link"@en-US ; - rdfs:range ; - rdfs:subPropertyOf , owl:topObjectProperty ; - - "defaultLinkForm.jsp"^^xsd:string ; - - "true"^^xsd:boolean ; - - "true"^^xsd:boolean ; - - "false"^^xsd:boolean ; - - "true"^^xsd:boolean . - -###Display model - -###Adding menu management customform annotation - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - - - a owl:ObjectProperty . - diff --git a/webapp/web/WEB-INF/ontologies/app/permissions.n3 b/webapp/web/WEB-INF/ontologies/app/permissions.n3 new file mode 100644 index 000000000..b11bb9d98 --- /dev/null +++ b/webapp/web/WEB-INF/ontologies/app/permissions.n3 @@ -0,0 +1,159 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix owl: . +@prefix display: . +@prefix rdf: . +@prefix rdfs: . +@prefix action: . + +# These are the reqired action objects from the SimplePermission.java. + +action:AccessSpecialDataModels + a display:RequiredAction ; + rdfs:label "ACCESS_SPECIAL_DATA_MODELS" . + + +action:DoBackEndEditing + a display:RequiredAction ; + rdfs:label "DO_BACK_END_EDITING" . + + +action:DoFrontEndEditing + a display:RequiredAction ; + rdfs:label "DO_FRONT_END_EDITING" . + + +action:EditOntology + a display:RequiredAction ; + rdfs:label "EDIT_ONTOLOGY" . + + +action:EditOwnAccount + a display:RequiredAction ; + rdfs:label "EDIT_OWN_ACCOUNT" . + + +action:EditSiteInformation + a display:RequiredAction ; + rdfs:label "EDIT_SITE_INFORMATION" . + + +action:LoginDuringMaintenance + a display:RequiredAction ; + rdfs:label "LOGIN_DURING_MAINTENANCE" . + + +action:ManageMenus + a display:RequiredAction ; + rdfs:label "MANAGE_MENUS" . + + +action:ManageOwnProxies + a display:RequiredAction ; + rdfs:label "MANAGE_OWN_PROXIES" . + + +action:ManagePortals + a display:RequiredAction ; + rdfs:label "MANAGE_PORTALS" . + + +action:ManageProxies + a display:RequiredAction ; + rdfs:label "MANAGE_PROXIES" . + + +action:ManageSearchIndex + a display:RequiredAction ; + rdfs:label "MANAGE_SEARCH_INDEX" . + + +action:ManageTabs + a display:RequiredAction ; + rdfs:label "MANAGE_TABS" . + + +action:ManageUserAccounts + a display:RequiredAction ; + rdfs:label "MANAGE_USER_ACCOUNTS" . + + +action:QueryFullModel + a display:RequiredAction ; + rdfs:label "QUERY_FULL_MODEL" . + + +action:QueryUserAccountsModel + a display:RequiredAction ; + rdfs:label "QUERY_USER_ACCOUNTS_MODEL" . + + +action:RebuildVClassGroupCache + a display:RequiredAction ; + rdfs:label "REBUILD_VCLASS_GROUP_CACHE" . + + +action:RefreshVisualizationCache + a display:RequiredAction ; + rdfs:label "REFRESH_VISUALIZATION_CACHE" . + + +action:SeeIndividualEditingPanel + a display:RequiredAction ; + rdfs:label "SEE_INDVIDUAL_EDITING_PANEL" . + + +action:SeeRevisionInfo + a display:RequiredAction ; + rdfs:label "SEE_REVISION_INFO" . + + +action:SeeSiteAdminPage + a display:RequiredAction ; + rdfs:label "SEE_SITE_ADMIN_PAGE" . + + +action:SeeStartupStatus + a display:RequiredAction ; + rdfs:label "SEE_STARTUP_STATUS" . + + +action:SeeVerbosePropertyInformation + a display:RequiredAction ; + rdfs:label "SEE_VERBOSE_PROPERTY_INFORMATION" . + + +action:UseAdvancedDataToolsPages + a display:RequiredAction ; + rdfs:label "USE_ADVANCED_DATA_TOOLS_PAGES" . + + +action:UseSparqlQueryPage + a display:RequiredAction ; + rdfs:label "USE_SPARQL_QUERY_PAGE" . + + +action:UseBasicAjaxControllers + a display:RequiredAction ; + rdfs:label "USE_BASIC_AJAX_CONTROLLERS" . + + +action:UseMiscellaneousAdminPages + a display:RequiredAction ; + rdfs:label "USE_MISCELLANEOUS_ADMIN_PAGES" . + + +action:UseMiscellaneousCuratorPages + a display:RequiredAction ; + rdfs:label "USE_MISCELLANEOUS_CURATOR_PAGES" . + + +action:UseMiscellaneousEditorPages + a display:RequiredAction ; + rdfs:label "USE_MISCELLANEOUS_EDITOR_PAGES" . + + +action:UseMiscellaneousPages + a display:RequiredAction ; + rdfs:label "USE_MISCELLANEOUS_PAGES" . + diff --git a/webapp/web/WEB-INF/ontologies/app/sparqlTestMenu.n3 b/webapp/web/WEB-INF/ontologies/app/sparqlTestMenu.n3 new file mode 100644 index 000000000..d3a8e017d --- /dev/null +++ b/webapp/web/WEB-INF/ontologies/app/sparqlTestMenu.n3 @@ -0,0 +1,23 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix owl: . +@prefix display: . +@prefix rdf: . +@prefix rdfs: . + +### page for SPARQL UPDATE ### +display:SparqlUpdateMenuItem + a display:NavigationElement ; + display:linkText "SPARQL Update"; + display:toPage display:SparqlUpdatePage . + +display:sparqlUpdateDataGetter + a . + +display:SparqlUpdatePage + a display:Page ; + display:title "SPARQL Update" ; + display:urlMapping "/sparql" ; + display:hasDataGetter display:sparqlUpdateDataGetter ; + display:requiredAction . + diff --git a/webapp/web/WEB-INF/resources/shortview_config.n3 b/webapp/web/WEB-INF/resources/shortview_config.n3 index ace0ff7ad..c4c2abfe4 100644 --- a/webapp/web/WEB-INF/resources/shortview_config.n3 +++ b/webapp/web/WEB-INF/resources/shortview_config.n3 @@ -9,5 +9,5 @@ # Ontology is complete. # # Find out how to use this file at -# https://sourceforge.net/apps/mediawiki/vivo/index.php?title=Using_Short_Views_in_Release_1.5 +# https://wiki.duraspace.org/display/VIVO/Using+Short+Views+in+Release+1.5 # diff --git a/webapp/web/WEB-INF/resources/startup_listeners.txt b/webapp/web/WEB-INF/resources/startup_listeners.txt index d7e47e9d9..98a3fac21 100644 --- a/webapp/web/WEB-INF/resources/startup_listeners.txt +++ b/webapp/web/WEB-INF/resources/startup_listeners.txt @@ -43,7 +43,7 @@ edu.cornell.mannlib.vitro.webapp.servlet.setup.ThemeInfoSetup edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionRegistry$Setup -edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader +edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsSmokeTest edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper$Setup @@ -65,6 +65,7 @@ edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionSetup edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerSetup +edu.cornell.mannlib.vitro.webapp.freemarker.config.FreemarkerConfiguration$Setup # On shutdown, this will kill the background thread started by Apache Commons File Upload org.apache.commons.fileupload.servlet.FileCleanerCleanup diff --git a/webapp/web/admin/sparqlquery/sparqlForm.jsp b/webapp/web/admin/sparqlquery/sparqlForm.jsp index 37968697c..35fb67127 100644 --- a/webapp/web/admin/sparqlquery/sparqlForm.jsp +++ b/webapp/web/admin/sparqlquery/sparqlForm.jsp @@ -52,6 +52,7 @@ LIMIT 20

Format for SELECT query results:

+ @@ -66,23 +67,11 @@ LIMIT 20 +
-<%-- -

Notes

-

CONSTRUCT and DESCRIBE queries always return RDF XML

-

The parameter 'resultFormat' must not be null or zero length

-

The parameter 'resultFormat' must be one of the following:

    -
  • RS_XML
  • -
  • RS_TEXT
  • -
  • RS_RDF/N3
  • -
  • RS_JSON
  • -
  • RS_RDF
  • -
-

---%> diff --git a/webapp/web/i18n/all.properties b/webapp/web/i18n/all.properties index 6fb58986b..a2ff550d5 100644 --- a/webapp/web/i18n/all.properties +++ b/webapp/web/i18n/all.properties @@ -675,7 +675,11 @@ photo = Photo no_image = no image placeholder_image = placeholder image manage_labels = manage labels - +manage_list_of_labels = manage list of labels +view_list_of_labels = view list of labels +view = view +add_label = Add Label +add_label_for_language = Language unsupported_ie_version = This form is not supported in versions of Internet Explorer below version 8. Please upgrade your browser, or switch to another browser, such as FireFox. # @@ -703,9 +707,10 @@ delete_entry_capitalized = Delete this entry? add_new_of_type = Add a new item of this type create_new_entry = Please create a new entry. no_appropriate_entry = If you don't find the appropriate entry on the selection list above - +return_to_individual = Return to Individual Page remove_menu_item = Remove menu item confirm_menu_item_delete = Are you sure you want to remove +remove_capitalized = Remove pretty_url = Pretty URL start_with_leading_slash = Must begin with a leading forward slash: / (e.g., /people) @@ -857,6 +862,6 @@ subproperty = subproperty manage_labels_for = Manage Labels for manage_labels_capitalized = Manage Labels -manage_labels_intro = Multiple labels exist for this profile but there should only be one. Select the label you want displayed on the profile page, and the others will be deleted. +manage_labels_intro = In the case where multiple labels exist in the same language, please use the remove link to delete the labels you do not want displayed on the profile page for a given language. processing_icon = processing selection_in_process = Your selection is being processed. diff --git a/webapp/web/images/individual/manage-icon.png b/webapp/web/images/individual/manage-icon.png new file mode 100644 index 000000000..18ab57568 Binary files /dev/null and b/webapp/web/images/individual/manage-icon.png differ diff --git a/webapp/web/js/individual/manageLabelsForIndividual.js b/webapp/web/js/individual/manageLabelsForIndividual.js index cd91e9033..f80513fa9 100644 --- a/webapp/web/js/individual/manageLabelsForIndividual.js +++ b/webapp/web/js/individual/manageLabelsForIndividual.js @@ -6,9 +6,10 @@ var manageLabels = { onLoad: function() { - this.mixIn(); + this.mixIn(); + this.initObjects(); this.initPage(); - + var selectedRadio; }, @@ -18,49 +19,135 @@ var manageLabels = { $.extend(this, customFormData); $.extend(this, i18nStrings); }, + + initObjects:function() { + this.addLabelForm = $('#addLabelForm'); + this.showFormButtonWrapper = $('#showAddForm'); + this.showFormButton = $("#showAddFormButton"); + this.addLabelCancel = this.addLabelForm.find(".cancel"); + this.submit = this.addLabelForm.find('input#submit'); + this.labelLanguage = this.addLabelForm.find("#newLabelLanguage"); + this.existingLabelsList = $("#existingLabelsList"); + }, // 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(); + //If the number of available locales is zero, then hide the ability to show the form as well + if(this.numberAvailableLocales == 0) { + manageLabels.showFormButtonWrapper.hide(); + } + + } else { + //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; + } - $('input#submit').attr('disabled', 'disabled'); - $('input#submit').addClass('disabledSubmit'); + } + + if(disableSubmit) { + //disable submit until user selects a language + this.submit.attr('disabled', 'disabled'); + this.submit.addClass('disabledSubmit'); + } + this.bindEventListeners(); }, bindEventListeners: function() { - $('input:radio').click( function() { - manageLabels.selectedRadio = $(this); - $('input#submit').attr('disabled', ''); - $('input#submit').removeClass('disabledSubmit'); + this.labelLanguage.change( function() { + //if language selected, allow submission, otherwise disallow + var selectedLanguage = manageLabels.labelLanguage.val(); + if(selectedLanguage != "") { + manageLabels.submit.attr('disabled', ''); + 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 + manageLabels.clearAddForm(); + manageLabels.onShowAddForm(); + }); + + //Check for clicking on existing labels list remove links + //Note addition will refresh the page and removing will remove the item so adding event listeners + //to remove links should keep remove link events in synch with page + + this.existingLabelsList.find("a.remove").click(function(event) { + var message = "Are you sure you wish to delete this label?" + if (!confirm(message)) { + return false; + } + + //First check with confirmation whether or not they want to delete + manageLabels.processLabelDeletion(this); + return false; + }); + + + this.addLabelForm.find("a.cancel").click(function(event){ + event.preventDefault(); + //clear the add form + manageLabels.clearAddForm(); + //hide the add form + manageLabels.onHideAddForm(); + return false; }); - $('input#submit').click( function() { - manageLabels.processLabel(manageLabels.selectedRadio); - $('span.or').hide(); - $('a.cancel').hide(); - $('span#indicator').removeClass('hidden'); - $('input.submit').addClass('disabledSubmit'); - $('input.submit').attr('disabled', 'disabled'); - }); }, - - processLabel: function(selectedRadio) { - + 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(); + manageLabels.showFormButtonWrapper.hide(); + manageLabels.addLabelCancel.click(function(){ + //Canceling the add label form will hide the form + manageLabels.addLabelForm.hide(); + manageLabels.showFormButtonWrapper.show(); + }); + }, + + onHideAddForm:function() { + manageLabels.addLabelForm.hide(); + manageLabels.showFormButtonWrapper.show(); + //manageLabels.addLabelCancel.unbind("click"); + }, + //Remove label + processLabelDeletion: function(selectedLink) { + // PrimitiveDelete only handles one statement, so we have to use PrimitiveRdfEdit to handle multiple // retractions if they exist. But PrimitiveRdfEdit also handles assertions, so pass an empty string // for "additions" var add = ""; - var retract = ""; - - $('input:radio').each( function() { - if ( !$(this).is(':checked') ) { - retract += " <" + manageLabels.individualUri + "> " - + "\"" + $(this).attr('id') + "\"" + $(this).attr('tagOrType') + " ." ; - } - }); + var labelValue = $(selectedLink).attr('labelValue'); + var tagOrTypeValue = $(selectedLink).attr('tagOrType'); + if(tagOrTypeValue == "untyped") { + tagOrTypeValue = ""; + } + var retract = "<" + manageLabels.individualUri + "> " + + "\"" + $(selectedLink).attr('labelValue') + "\"" + $(selectedLink).attr('tagOrType') + " ." ; + + retract = retract.substring(0,retract.length -1); @@ -72,28 +159,116 @@ var manageLabels = { retractions: retract }, dataType: 'json', - context: selectedRadio, // context for callback + context: selectedLink, // context for callback complete: function(request, status) { if (status == 'success') { - $('span.or').show(); - $('a.cancel').show(); - $('span#indicator').addClass('hidden'); - window.location = $('a.cancel').attr('href'); + //Remove the label from the list + manageLabels.removeLabelFromList(selectedLink); + manageLabels.updateLocaleSelection(); } else { + //Instead of alert, write error to template alert(manageLabels.errorProcessingLabels); - selectedRadio.removeAttr('checked'); - $('span.or').show(); - $('a.cancel').show(); - $('span#indicator').addClass('hidden'); - $('input.submit').removeClass('disabledSubmit'); - $('input.submit').attr('disabled', ''); + } } }); }, + removeLabelFromList:function(selectedLink) { + var languageName = $(selectedLink).attr("languageName"); + $(selectedLink).parent().remove(); + //See if there are any other remove link + if(languageName != "untyped") { + //find if there are any other remove links for the same language + var allRemoveLinks = manageLabels.existingLabelsList.find("a.remove"); + var removeLinks = manageLabels.existingLabelsList.find("a.remove[languageName='" + languageName + "']"); + if(removeLinks.length == 0) { + //if there aren't any other labels for this language, also remove the heading + manageLabels.existingLabelsList.find("h3[languageName='" + languageName + "']").remove(); + + } + //Check to see if there is only one label left on the page, if so remove or hide the remove link + if(allRemoveLinks.length == 1) { + allRemoveLinks.remove(); + //These will be removed instead of hidden because currently add will reload the page + //whereas remove executes an ajax query and the page isn't reloaded + } + } + + + }, + //Determine if there are new locales that can be added to the options once a delete has occurred + updateLocaleSelection:function() { + //Check what languages remain + var existingLanguages = {}; + //Look at which languages are currently represented + manageLabels.existingLabelsList.find("a.remove").each(function(){ + var languageCode = $(this).attr("languageCode"); + if(!(languageCode in existingLanguages)) { + existingLanguages[languageCode] = true; + } + }); + + //Now check against full list, if any in full list not represented, will need to include in dropdown + //This is a list of + var availableLocalesList = []; + var listLen = selectLocalesFullList.length; + var i; + 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); + availableLocalesList.push(possibleLanguageInfo); + } + } + + //Now sort this list by the label property on the object + availableLocalesList.sort(function(a, b) { + var compA = a["label"]; + 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")) { + manageLabels.showFormButtonWrapper.show(); + } + + //Now replace dropdown with this new list + manageLabels.generateLocalesDropdown(availableLocalesList); + + }, + generateLocalesDropdown:function(availableLocalesList) { + //First check if there are any available locales left, if not then hide the entire add form + //technically, this first part should never be invoked client side because + //this can only happen on ADD not remove, and add will refresh the page + //On the other hand the show add button etc. can be displayed + if(availableLocalesList.length == 0) { + //Hide the add form if there are no locales left that can be added + manageLabels.addLabelForm.hide(); + manageLabels.showFormButtonWrapper.hide(); + } else { + //There are some locales so generate the dropdown accordingly, removing all elements but the first + $("#newLabelLanguage option:gt(0)").remove(); + var i; + var len = availableLocalesList.length; + for(i = 0; i < len; i++) { + var localeInfo = availableLocalesList[i]; + manageLabels.addLocaleInfo(localeInfo); + } + //Put some value in that shows whether neither add button nor add form were shown + //because the form thought there were no available locales + } + }, + + addLocaleInfo:function(localeInfo) { + //Add code to dropdown + //Would we need to regenerate alphabetically? Argh. + manageLabels.labelLanguage.append(""); + } }; diff --git a/webapp/web/templates/freemarker/body/individual/individual-vitro.ftl b/webapp/web/templates/freemarker/body/individual/individual-vitro.ftl index db57984b6..742d8470d 100644 --- a/webapp/web/templates/freemarker/body/individual/individual-vitro.ftl +++ b/webapp/web/templates/freemarker/body/individual/individual-vitro.ftl @@ -2,6 +2,9 @@ <#if !labelCount??> <#assign labelCount = 0 > +<#if !localesCount??> + <#assign localesCount = 1> + <#-- Default individual profile page template --> <#--@dumpAll /--> @@ -32,7 +35,7 @@ <#else>

<#-- Label --> - <@p.label individual editable labelCount /> + <@p.label individual editable labelCount localesCount/> <#-- Most-specific types --> <@p.mostSpecificTypes individual /> diff --git a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividual.ftl b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividual.ftl index bf5d84337..63f520a79 100644 --- a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividual.ftl +++ b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividual.ftl @@ -1,62 +1,101 @@ <#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> - +<#include "manageLabelsForIndividualTerms.ftl" > <#-- Custom form for managing labels for individuals --> +<#import "manageLabelsForIndividualMacros.ftl" as m > +<#assign requiredHint = " *" /> +<#assign subjectUri = editConfiguration.subjectUri/> <#assign labelStr = "" > <#assign languageTag = "" > <#assign labelSeq = [] > -<#if subjectName?? > -

${i18n().manage_labels_for} ${subjectName}

+<#assign submissionErrorsExist = "false"/> +<#assign selectLocalesFullList = {} /> +<#assign editable = false/> +<#if editConfiguration.pageData.editable?has_content> + <#assign editable = editConfiguration.pageData.editable /> + +<#assign displayRemoveLink = true/> +<#if editConfiguration.pageData.displayRemoveLink?has_content> + <#assign displayRemoveLink = editConfiguration.pageData.displayRemoveLink/> + +<#if editSubmission?has_content && editSubmission.submissionExists = true && editSubmission.validationErrors?has_content> + <#assign submissionErrors = editSubmission.validationErrors/> + <#assign submissionErrorsExist = "true" /> + +<#assign availableLocalesNumber = 0/> +<#if editConfiguration.pageData.selectLocale?has_content> + <#assign availableLocalesNumber = editConfiguration.pageData.selectLocale?size /> + +<#if editConfiguration.pageData.subjectName?? > +

${i18n().manage_labels_for} ${editConfiguration.pageData.subjectName}

<#else>

${i18n().manage_labels_capitalized}

+ + +

${i18n().manage_labels_intro}

+
-
    - <#list labels as label> - <#-- the query will return labels with their language tag or datatype, if any. So strip those out --> - <#if label?? && ( label?index_of("@") > -1 ) > - <#assign labelStr = label?substring(0, label?index_of("@")) > - <#assign tagOrTypeStr = label?substring(label?index_of("@")) > - <#elseif label?? && ( label?index_of("^^") > -1 ) > - <#assign labelStr = label?substring(0, label?index_of("^^")) > - <#assign tagOrTypeStr = label?substring(label?index_of("^^")) > - <#assign tagOrTypeStr = tagOrTypeStr?replace("^^http","^^ - <#assign tagOrTypeStr = tagOrTypeStr?replace("#string","#string>") > - <#else> - <#assign labelStr = label > - <#assign tagOrTypeStr = "" > - -
  • - - -
  • - <#assign labelSeq = labelSeq + [labelStr]> - + + + +
      + <#if editConfiguration.pageData.labelsSortedByLanguageName?has_content> + <#--List of labelInformation objects as value where key = language name --> + <#assign labelsSorted = editConfiguration.pageData.labelsSortedByLanguageName /> + <#--Keys would be the actual names of languages--> + <#assign labelLanguages = labelsSorted?keys?sort /> + <#assign editGenerator = "editForm=edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RDFSLabelGenerator" /> + + <#--What we need here is printing out the labels by the language Name and not language code, starting with untyped first--> + <@m.displayExistingLabelsForLanguage "untyped" labelsSorted editable editGenerator/> + <@m.displayExistingTypedLabels labelLanguages labelsSorted editable editGenerator/> + +

    -

    - - or - ${i18n().cancel_link} - -

    -
+

+ + <#if editable> + <#include "manageLabelsForIndividualSubmissionErrors.ftl"> +

+ ${i18n().or} + ${returnText} +
+ + <#include "manageLabelsForIndividualAddForm.ftl" > + + +

+ + diff --git a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl new file mode 100644 index 000000000..e6d6714c3 --- /dev/null +++ b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualAddForm.ftl @@ -0,0 +1,29 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> +<#--The form for adding a new label--> +
+

${i18n().add_label}

+

+ + +

+ + + + + + +${i18n().or} +${i18n().cancel_link} + +
\ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl new file mode 100644 index 000000000..b8258891d --- /dev/null +++ b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualMacros.ftl @@ -0,0 +1,83 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> +<#--LabelsSorted is a hash keyed by language name where the value is a list of LabelInformation class objects--> +<#macro displayExistingLabelsForLanguage lang labelsSorted editable editGenerator displayRemoveLink=true> + <#--get label information for this language--> + <#assign labelList = labelsSorted[lang] /> + <#--Reset for every language--> + <#assign labelSeq = []/> + + <#list labelList as labelObject> + <#assign labelLiteral = labelObject.labelLiteral /> + <#assign labelStringValue = labelObject.labelStringValue /> + <#--Try label as label literal--> + <#assign label = labelLiteral /> + <#assign labelLang = labelObject.languageName /> + <#assign languageCode = labelObject.languageCode /> + <#assign labelEditLink = labelObject.editLinkURL /> + <#if label?? && ( label?index_of("@") > -1 ) > + <#assign labelStr = label?substring(0, label?index_of("@")) > + <#assign tagOrTypeStr = label?substring(label?index_of("@")) > + <#elseif label?? && ( label?index_of("^^") > -1 ) > + <#assign labelStr = label?substring(0, label?index_of("^^")) > + <#assign tagOrTypeStr = label?substring(label?index_of("^^")) > + <#assign tagOrTypeStr = tagOrTypeStr?replace("^^http","^^ + <#assign tagOrTypeStr = tagOrTypeStr?replace("#string","#string>") > + <#else> + <#assign labelStr = label > + <#assign tagOrTypeStr = "" > + + <@displayLabel labelSeq labelStr languageCode labelEditLink tagOrTypeStr editGenerator editable displayRemoveLink/> + + <#assign labelSeq = labelSeq + [labelStr]> + + + +<#--ignore 'untyped' and display everything--> +<#macro displayExistingTypedLabels langList labelsSorted editable editGenerator displayRemoveLink=true> + + + <#list langList as lang> + <#if lang != "untyped"> +

${lang}

+ <#--get label information for this language--> + <#assign labelList = labelsSorted[lang] /> + <#--Reset for every language--> + <#assign labelSeq = []/> + <#list labelList as labelObject> + <#assign labelLiteral = labelObject.labelLiteral /> + <#assign labelStringValue = labelObject.labelStringValue /> + <#--Try label as label literal--> + <#assign label = labelLiteral /> + <#assign labelLang = labelObject.languageName /> + <#assign languageCode = labelObject.languageCode /> + <#assign labelEditLink = labelObject.editLinkURL /> + <#if label?? && ( label?index_of("@") > -1 ) > + <#assign labelStr = label?substring(0, label?index_of("@")) > + <#assign tagOrTypeStr = label?substring(label?index_of("@")) > + <#elseif label?? && ( label?index_of("^^") > -1 ) > + <#assign labelStr = label?substring(0, label?index_of("^^")) > + <#assign tagOrTypeStr = label?substring(label?index_of("^^")) > + <#assign tagOrTypeStr = tagOrTypeStr?replace("^^http","^^ + <#assign tagOrTypeStr = tagOrTypeStr?replace("#string","#string>") > + <#else> + <#assign labelStr = label > + <#assign tagOrTypeStr = "" > + + <@displayLabel labelSeq labelStr languageCode labelEditLink tagOrTypeStr editGenerator editable displayRemoveLink/> + <#assign labelSeq = labelSeq + [labelStr]> + + + + + +<#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 displayRemoveLink = true> + ${i18n().remove_capitalized} + + + +
  • + \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualSubmissionErrors.ftl b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualSubmissionErrors.ftl new file mode 100644 index 000000000..ef4f8fe95 --- /dev/null +++ b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualSubmissionErrors.ftl @@ -0,0 +1,14 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> +<#if submissionErrors?has_content > + + \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualTerms.ftl b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualTerms.ftl new file mode 100644 index 000000000..913826af4 --- /dev/null +++ b/webapp/web/templates/freemarker/body/individual/manageLabelsForIndividualTerms.ftl @@ -0,0 +1,4 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#--Some values for labels etc. may be different between vitro and other systems --> +<#assign returnText = "${i18n().return_to_individual}" /> \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl b/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl new file mode 100644 index 000000000..17274f829 --- /dev/null +++ b/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl @@ -0,0 +1,12 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +

    SPARQL Update Test

    + +

    This is an expermental SPARQL update service.

    + +
    +

    + + +

    +
    \ No newline at end of file diff --git a/webapp/web/templates/freemarker/lib/lib-home-page.ftl b/webapp/web/templates/freemarker/lib/lib-home-page.ftl index 1715cf108..d4383b3a7 100644 --- a/webapp/web/templates/freemarker/lib/lib-home-page.ftl +++ b/webapp/web/templates/freemarker/lib/lib-home-page.ftl @@ -69,7 +69,7 @@

    ${i18n().you_can} ${i18n().add_content_manage_site}${i18n().from_site_admin_page}

    <#else> -

    ${i18n().please} ${i18n().log_in} ${i18n().to_manage_content.}

    +

    ${i18n().please} ${i18n().log_in} ${i18n().to_manage_content}.

    diff --git a/webapp/web/templates/freemarker/lib/lib-properties.ftl b/webapp/web/templates/freemarker/lib/lib-properties.ftl index 7d360496a..ee72732c0 100644 --- a/webapp/web/templates/freemarker/lib/lib-properties.ftl +++ b/webapp/web/templates/freemarker/lib/lib-properties.ftl @@ -208,17 +208,34 @@ name will be used as the label. --> <#-- Label --> -<#macro label individual editable labelCount> +<#macro label individual editable labelCount localesCount=1> + <#assign labelPropertyUri = ("http://www.w3.org/2000/01/rdf-schema#label"?url) /> + <#assign useEditLink = false /> + <#--edit link used if in edit mode and only one label and one language--> + <#if labelCount = 1 && editable && localeCount = 1 > + <#assign useEditLink = true/> + <#local label = individual.nameStatement> ${label.value} - <#if (labelCount > 1) && editable > + <#if useEditLink> + <@editingLinks "label" label editable /> + <#elseif editable || (labelCount > 1)> + <#--We display the link even when the user is not logged in case of multiple labels--> + <#if editable> + <#assign imageAlt = "${i18n().manage}" /> + <#assign linkTitle = "${i18n().manage_list_of_labels}"> + <#else> + <#assign linkTitle = "${i18n().view_list_of_labels}"> + <#assign imageAlt = "${i18n().view}" /> + + <#-- Manage labels now goes to generator --> + <#assign individualUri = individual.uri!""/> + <#assign individualUri = (individualUri?url)/> - - ${i18n().manage_labels} - + + ${imageAlt} - <#else> - <@editingLinks "label" label editable /> diff --git a/webapp/web/templates/freemarker/page/partials/languageSelector.ftl b/webapp/web/templates/freemarker/page/partials/languageSelector.ftl index 71561b5e1..d3e178a0f 100644 --- a/webapp/web/templates/freemarker/page/partials/languageSelector.ftl +++ b/webapp/web/templates/freemarker/page/partials/languageSelector.ftl @@ -25,4 +25,5 @@ * -- code * -- label (tooltip relative to the current Locale) * -- imageUrl + * -- selected (boolean) --> \ No newline at end of file