Compare commits
41 commits
del_indivi
...
texts
Author | SHA1 | Date | |
---|---|---|---|
234a849a19 | |||
75557ca4aa | |||
45f303c1ae | |||
f16eef642a | |||
![]() |
0df4231b41 | ||
![]() |
48297bba56 | ||
32f817e73a | |||
![]() |
a9a89d220b | ||
1dbbe6ed4c | |||
4ab94d59af | |||
c2429a6768 | |||
![]() |
64cd06fbbb | ||
![]() |
0bdfe65d42 | ||
8e3a98cc56 | |||
e2d37bbb75 | |||
caa1bc8412 | |||
63f15a90ef | |||
cb35b5f647 | |||
2f8fa040db | |||
75841b9fb2 | |||
1590d58830 | |||
a6fa88bd1f | |||
d6eb01de9c | |||
af9820b7f3 | |||
![]() |
2e0b3f0f05 | ||
4fdbb71fee | |||
2a56ec8c30 | |||
566ca88d6f | |||
9d48755893 | |||
f6fbe83c90 | |||
3e5e60e788 | |||
084801bf5e | |||
df8c386663 | |||
98398a58b6 | |||
37579c5581 | |||
ee16a2863d | |||
38e8d326a6 | |||
d2a3a73d3c | |||
06e1e104df | |||
f36881f24c | |||
d5a9a9bca6 |
37 changed files with 1263 additions and 64 deletions
|
@ -7,17 +7,11 @@ import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information about the account of a user. URI, email, password, etc.
|
* Information about the account of a user. URI, email, password, etc.
|
||||||
*
|
*
|
||||||
* The "password link expires hash" is just a string that is derived from the
|
|
||||||
* value in the passwordLinkExpires field. It doesn't have to be a hash, and
|
|
||||||
* there is no need for it to be cryptographic, but it seems embarrassing to
|
|
||||||
* just send the value as a clear string. There is no real need for security
|
|
||||||
* here, except that a brute force attack would allow someone to change the
|
|
||||||
* password on an account that they know has a password change pending.
|
|
||||||
*/
|
*/
|
||||||
public class UserAccount {
|
public class UserAccount {
|
||||||
public static final int MIN_PASSWORD_LENGTH = 6;
|
public static final int MIN_PASSWORD_LENGTH = 6;
|
||||||
|
@ -52,6 +46,7 @@ public class UserAccount {
|
||||||
private String md5Password = ""; // Never null.
|
private String md5Password = ""; // Never null.
|
||||||
private String oldPassword = ""; // Never null.
|
private String oldPassword = ""; // Never null.
|
||||||
private long passwordLinkExpires = 0L; // Never negative.
|
private long passwordLinkExpires = 0L; // Never negative.
|
||||||
|
private String emailKey = "";
|
||||||
private boolean passwordChangeRequired = false;
|
private boolean passwordChangeRequired = false;
|
||||||
|
|
||||||
private int loginCount = 0; // Never negative.
|
private int loginCount = 0; // Never negative.
|
||||||
|
@ -133,15 +128,27 @@ public class UserAccount {
|
||||||
return passwordLinkExpires;
|
return passwordLinkExpires;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPasswordLinkExpiresHash() {
|
|
||||||
return limitStringLength(8, Authenticator.applyArgon2iEncoding(String
|
|
||||||
.valueOf(passwordLinkExpires)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPasswordLinkExpires(long passwordLinkExpires) {
|
public void setPasswordLinkExpires(long passwordLinkExpires) {
|
||||||
this.passwordLinkExpires = Math.max(0, passwordLinkExpires);
|
this.passwordLinkExpires = Math.max(0, passwordLinkExpires);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void generateEmailKey() {
|
||||||
|
boolean useLetters = true;
|
||||||
|
boolean useNumbers = true;
|
||||||
|
int length = 64;
|
||||||
|
emailKey = RandomStringUtils.random(length, useLetters, useNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailKey(String emailKey) {
|
||||||
|
if (emailKey != null) {
|
||||||
|
this.emailKey = emailKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailKey() {
|
||||||
|
return emailKey;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPasswordChangeRequired() {
|
public boolean isPasswordChangeRequired() {
|
||||||
return passwordChangeRequired;
|
return passwordChangeRequired;
|
||||||
}
|
}
|
||||||
|
@ -247,6 +254,7 @@ public class UserAccount {
|
||||||
+ (", oldPassword=" + oldPassword)
|
+ (", oldPassword=" + oldPassword)
|
||||||
+ (", argon2password=" + argon2Password)
|
+ (", argon2password=" + argon2Password)
|
||||||
+ (", passwordLinkExpires=" + passwordLinkExpires)
|
+ (", passwordLinkExpires=" + passwordLinkExpires)
|
||||||
|
+ (", emailKey =" + emailKey)
|
||||||
+ (", passwordChangeRequired=" + passwordChangeRequired)
|
+ (", passwordChangeRequired=" + passwordChangeRequired)
|
||||||
+ (", externalAuthOnly=" + externalAuthOnly)
|
+ (", externalAuthOnly=" + externalAuthOnly)
|
||||||
+ (", loginCount=" + loginCount) + (", status=" + status)
|
+ (", loginCount=" + loginCount) + (", status=" + status)
|
||||||
|
|
|
@ -249,6 +249,7 @@ public class UserAccountsSelector {
|
||||||
user.setMd5Password(ifLiteralPresent(solution, "md5pwd", ""));
|
user.setMd5Password(ifLiteralPresent(solution, "md5pwd", ""));
|
||||||
user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", ""));
|
user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", ""));
|
||||||
user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L));
|
user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L));
|
||||||
|
user.setEmailKey(ifLiteralPresent(solution, "emailKey", ""));
|
||||||
user.setLoginCount(ifIntPresent(solution, "count", 0));
|
user.setLoginCount(ifIntPresent(solution, "count", 0));
|
||||||
user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0));
|
user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0));
|
||||||
user.setStatus(parseStatus(solution, "status", null));
|
user.setStatus(parseStatus(solution, "status", null));
|
||||||
|
|
|
@ -156,6 +156,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
|
||||||
u.setOldPassword("");
|
u.setOldPassword("");
|
||||||
u.setPasswordChangeRequired(false);
|
u.setPasswordChangeRequired(false);
|
||||||
u.setPasswordLinkExpires(0);
|
u.setPasswordLinkExpires(0);
|
||||||
|
u.setEmailKey("");
|
||||||
u.setLoginCount(0);
|
u.setLoginCount(0);
|
||||||
u.setLastLoginTime(0L);
|
u.setLastLoginTime(0L);
|
||||||
u.setStatus(Status.INACTIVE);
|
u.setStatus(Status.INACTIVE);
|
||||||
|
|
|
@ -84,6 +84,7 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
|
||||||
u.setStatus(Status.ACTIVE);
|
u.setStatus(Status.ACTIVE);
|
||||||
} else {
|
} else {
|
||||||
u.setPasswordLinkExpires(figureExpirationDate().getTime());
|
u.setPasswordLinkExpires(figureExpirationDate().getTime());
|
||||||
|
u.generateEmailKey();
|
||||||
u.setStatus(Status.INACTIVE);
|
u.setStatus(Status.INACTIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,10 +120,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
|
||||||
private String buildCreatePasswordLink() {
|
private String buildCreatePasswordLink() {
|
||||||
try {
|
try {
|
||||||
String email = page.getAddedAccount().getEmailAddress();
|
String email = page.getAddedAccount().getEmailAddress();
|
||||||
String hash = page.getAddedAccount()
|
String key = page.getAddedAccount().getEmailKey();
|
||||||
.getPasswordLinkExpiresHash();
|
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL, "user", email, "key", key);
|
||||||
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL,
|
|
||||||
"user", email, "key", hash);
|
|
||||||
|
|
||||||
URL context = new URL(vreq.getRequestURL().toString());
|
URL context = new URL(vreq.getRequestURL().toString());
|
||||||
URL url = new URL(context, relativeUrl);
|
URL url = new URL(context, relativeUrl);
|
||||||
|
|
|
@ -274,6 +274,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
|
||||||
userAccount.setOldPassword("");
|
userAccount.setOldPassword("");
|
||||||
userAccount.setPasswordChangeRequired(false);
|
userAccount.setPasswordChangeRequired(false);
|
||||||
userAccount.setPasswordLinkExpires(0L);
|
userAccount.setPasswordLinkExpires(0L);
|
||||||
|
userAccount.setEmailKey("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRootUser()) {
|
if (isRootUser()) {
|
||||||
|
|
|
@ -82,6 +82,7 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
|
||||||
protected void setAdditionalProperties(UserAccount u) {
|
protected void setAdditionalProperties(UserAccount u) {
|
||||||
if (resetPassword && !page.isExternalAuthOnly()) {
|
if (resetPassword && !page.isExternalAuthOnly()) {
|
||||||
u.setPasswordLinkExpires(figureExpirationDate().getTime());
|
u.setPasswordLinkExpires(figureExpirationDate().getTime());
|
||||||
|
u.generateEmailKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +122,8 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
|
||||||
private String buildResetPasswordLink() {
|
private String buildResetPasswordLink() {
|
||||||
try {
|
try {
|
||||||
String email = page.getUpdatedAccount().getEmailAddress();
|
String email = page.getUpdatedAccount().getEmailAddress();
|
||||||
String hash = page.getUpdatedAccount()
|
String key = page.getUpdatedAccount().getEmailKey();
|
||||||
.getPasswordLinkExpiresHash();
|
String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL, "user", email, "key", key);
|
||||||
String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL,
|
|
||||||
"user", email, "key", hash);
|
|
||||||
|
|
||||||
URL context = new URL(vreq.getRequestURL().toString());
|
URL context = new URL(vreq.getRequestURL().toString());
|
||||||
URL url = new URL(context, relativeUrl);
|
URL url = new URL(context, relativeUrl);
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class UserAccountsCreatePasswordPage extends
|
||||||
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
||||||
userAccount.setMd5Password("");
|
userAccount.setMd5Password("");
|
||||||
userAccount.setPasswordLinkExpires(0L);
|
userAccount.setPasswordLinkExpires(0L);
|
||||||
|
userAccount.setEmailKey("");
|
||||||
userAccount.setPasswordChangeRequired(false);
|
userAccount.setPasswordChangeRequired(false);
|
||||||
userAccount.setStatus(Status.ACTIVE);
|
userAccount.setStatus(Status.ACTIVE);
|
||||||
userAccountsDao.updateUserAccount(userAccount);
|
userAccountsDao.updateUserAccount(userAccount);
|
||||||
|
@ -53,6 +54,11 @@ public class UserAccountsCreatePasswordPage extends
|
||||||
protected String passwordChangeNotPendingMessage() {
|
protected String passwordChangeNotPendingMessage() {
|
||||||
return i18n.text("account_already_activated", userEmail);
|
return i18n.text("account_already_activated", userEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String passwordChangeInavlidKeyMessage() {
|
||||||
|
return i18n.text("password_change_invalid_key", userEmail);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String templateName() {
|
protected String templateName() {
|
||||||
|
|
|
@ -195,6 +195,7 @@ public class UserAccountsFirstTimeExternalPage extends UserAccountsPage {
|
||||||
u.setExternalAuthId(externalAuthId);
|
u.setExternalAuthId(externalAuthId);
|
||||||
u.setPasswordChangeRequired(false);
|
u.setPasswordChangeRequired(false);
|
||||||
u.setPasswordLinkExpires(0);
|
u.setPasswordLinkExpires(0);
|
||||||
|
u.setEmailKey("");
|
||||||
u.setExternalAuthOnly(true);
|
u.setExternalAuthOnly(true);
|
||||||
u.setLoginCount(0);
|
u.setLoginCount(0);
|
||||||
u.setStatus(Status.ACTIVE);
|
u.setStatus(Status.ACTIVE);
|
||||||
|
|
|
@ -159,6 +159,7 @@ public abstract class UserAccountsMyAccountPageStrategy extends
|
||||||
userAccount.setMd5Password("");
|
userAccount.setMd5Password("");
|
||||||
userAccount.setPasswordChangeRequired(false);
|
userAccount.setPasswordChangeRequired(false);
|
||||||
userAccount.setPasswordLinkExpires(0L);
|
userAccount.setPasswordLinkExpires(0L);
|
||||||
|
userAccount.setEmailKey("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,12 +103,12 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String expectedKey = userAccount.getPasswordLinkExpiresHash();
|
String expectedKey = userAccount.getEmailKey();
|
||||||
if (!key.equals(expectedKey)) {
|
if (key.isEmpty() || !key.equals(expectedKey)) {
|
||||||
log.warn("Password request for '" + userEmail + "' is bogus: key ("
|
log.warn("Password request for '" + userEmail + "' is bogus: key ("
|
||||||
+ key + ") doesn't match expected key (" + expectedKey
|
+ key + ") doesn't match expected key (" + expectedKey
|
||||||
+ ")");
|
+ ")");
|
||||||
bogusMessage = passwordChangeNotPendingMessage();
|
bogusMessage = passwordChangeInavlidKeyMessage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
|
||||||
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
|
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
|
||||||
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
|
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
|
||||||
body.put("userAccount", userAccount);
|
body.put("userAccount", userAccount);
|
||||||
body.put("key", userAccount.getPasswordLinkExpiresHash());
|
body.put("key", userAccount.getEmailKey());
|
||||||
body.put("newPassword", newPassword);
|
body.put("newPassword", newPassword);
|
||||||
body.put("confirmPassword", confirmPassword);
|
body.put("confirmPassword", confirmPassword);
|
||||||
body.put("formUrls", buildUrlsMap());
|
body.put("formUrls", buildUrlsMap());
|
||||||
|
@ -176,6 +176,8 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
|
||||||
protected abstract String alreadyLoggedInMessage(String currentUserEmail);
|
protected abstract String alreadyLoggedInMessage(String currentUserEmail);
|
||||||
|
|
||||||
protected abstract String passwordChangeNotPendingMessage();
|
protected abstract String passwordChangeNotPendingMessage();
|
||||||
|
|
||||||
|
protected abstract String passwordChangeInavlidKeyMessage();
|
||||||
|
|
||||||
protected abstract String templateName();
|
protected abstract String templateName();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,11 @@ public class UserAccountsResetPasswordPage extends UserAccountsPasswordBasePage
|
||||||
protected String passwordChangeNotPendingMessage() {
|
protected String passwordChangeNotPendingMessage() {
|
||||||
return i18n.text("password_change_not_pending", userEmail);
|
return i18n.text("password_change_not_pending", userEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String passwordChangeInavlidKeyMessage() {
|
||||||
|
return i18n.text("password_change_invalid_key", userEmail);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String templateName() {
|
protected String templateName() {
|
||||||
|
|
|
@ -134,6 +134,7 @@ public class BasicAuthenticator extends Authenticator {
|
||||||
userAccount.setMd5Password("");
|
userAccount.setMd5Password("");
|
||||||
userAccount.setPasswordChangeRequired(false);
|
userAccount.setPasswordChangeRequired(false);
|
||||||
userAccount.setPasswordLinkExpires(0L);
|
userAccount.setPasswordLinkExpires(0L);
|
||||||
|
userAccount.setEmailKey("");
|
||||||
getUserAccountsDao().updateUserAccount(userAccount);
|
getUserAccountsDao().updateUserAccount(userAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.jena.query.Query;
|
||||||
|
import org.apache.jena.query.QueryExecution;
|
||||||
|
import org.apache.jena.query.QueryExecutionFactory;
|
||||||
|
import org.apache.jena.query.QueryFactory;
|
||||||
|
import org.apache.jena.query.QuerySolution;
|
||||||
|
import org.apache.jena.query.QuerySolutionMap;
|
||||||
|
import org.apache.jena.query.ResultSet;
|
||||||
|
import org.apache.jena.rdf.model.Model;
|
||||||
|
import org.apache.jena.rdf.model.ResourceFactory;
|
||||||
|
import org.apache.jena.shared.Lock;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.DisplayVocabulary;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.event.BulkUpdateEvent;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
|
||||||
|
|
||||||
|
@WebServlet(name="DeleteIndividualController",urlPatterns="/deleteIndividualController")
|
||||||
|
public class DeleteIndividualController extends FreemarkerHttpServlet{
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(DeleteIndividualController.class);
|
||||||
|
private static final boolean BEGIN = true;
|
||||||
|
private static final boolean END = !BEGIN;
|
||||||
|
|
||||||
|
private static String TYPE_QUERY_START = ""
|
||||||
|
+ "PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>"
|
||||||
|
+ "SELECT ?type "
|
||||||
|
+ "WHERE"
|
||||||
|
+ "{ <";
|
||||||
|
private static String TYPE_QUERY_END = "> vitro:mostSpecificType ?type ."
|
||||||
|
+ "}";
|
||||||
|
private static String queryForDeleteQuery =
|
||||||
|
"PREFIX display: <" + DisplayVocabulary.DISPLAY_NS +"> \n" +
|
||||||
|
"SELECT ?deleteQueryText WHERE { ?associatedURI display:hasDeleteQuery ?deleteQueryText }";
|
||||||
|
|
||||||
|
private static final String DEFAULT_DELETE_QUERY_TEXT = "DESCRIBE ?individualURI";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
|
||||||
|
return SimplePermission.DO_FRONT_END_EDITING.ACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ResponseValues processRequest(VitroRequest vreq) {
|
||||||
|
String errorMessage = handleErrors(vreq);
|
||||||
|
if (!errorMessage.isEmpty()) {
|
||||||
|
return prepareErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
|
String individualUri = vreq.getParameter("individualUri");
|
||||||
|
String type = getObjectMostSpecificType(individualUri, vreq);
|
||||||
|
Model displayModel = vreq.getDisplayModel();
|
||||||
|
|
||||||
|
String delteQueryText = getDeleteQueryForType(type, displayModel);
|
||||||
|
byte[] toRemove = getIndividualsToDelete(individualUri, delteQueryText, vreq);
|
||||||
|
if (toRemove.length > 0) {
|
||||||
|
deleteIndividuals(toRemove,vreq);
|
||||||
|
}
|
||||||
|
String redirectUrl = getRedirectUrl(vreq);
|
||||||
|
|
||||||
|
return new RedirectResponseValues(redirectUrl, HttpServletResponse.SC_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRedirectUrl(VitroRequest vreq) {
|
||||||
|
String redirectUrl = vreq.getParameter("redirectUrl");
|
||||||
|
if (redirectUrl != null) {
|
||||||
|
return redirectUrl;
|
||||||
|
}
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private TemplateResponseValues prepareErrorMessage(String errorMessage) {
|
||||||
|
HashMap<String,Object> map = new HashMap<String,Object>();
|
||||||
|
map.put("errorMessage", errorMessage);
|
||||||
|
return new TemplateResponseValues("error-message.ftl", map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String handleErrors(VitroRequest vreq) {
|
||||||
|
String uri = vreq.getParameter("individualUri");
|
||||||
|
if ( uri == null) {
|
||||||
|
return "Individual uri is null. No object to delete.";
|
||||||
|
}
|
||||||
|
if (uri.contains(">")) {
|
||||||
|
return "Individual uri shouldn't contain >";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDeleteQueryForType(String typeURI,Model displayModel) {
|
||||||
|
|
||||||
|
String deleteQueryText = DEFAULT_DELETE_QUERY_TEXT;
|
||||||
|
|
||||||
|
Query queryForTypeSpecificDeleteQuery = QueryFactory.create(queryForDeleteQuery);
|
||||||
|
|
||||||
|
QuerySolutionMap initialBindings = new QuerySolutionMap();
|
||||||
|
initialBindings.add("associatedURI", ResourceFactory.createResource( typeURI ));
|
||||||
|
|
||||||
|
displayModel.enterCriticalSection(Lock.READ);
|
||||||
|
try{
|
||||||
|
QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery,displayModel,initialBindings );
|
||||||
|
try{
|
||||||
|
ResultSet results = qexec.execSelect();
|
||||||
|
while (results.hasNext()) {
|
||||||
|
QuerySolution solution = results.nextSolution();
|
||||||
|
deleteQueryText = solution.get("deleteQueryText").toString();
|
||||||
|
}
|
||||||
|
}finally{ qexec.close(); }
|
||||||
|
}finally{ displayModel.leaveCriticalSection(); }
|
||||||
|
|
||||||
|
if (!deleteQueryText.equals(DEFAULT_DELETE_QUERY_TEXT)) {
|
||||||
|
log.debug("For " + typeURI + " found delete query \n" + deleteQueryText);
|
||||||
|
} else {
|
||||||
|
log.debug("For " + typeURI + " delete query not found. Using defalut query \n" + deleteQueryText);
|
||||||
|
}
|
||||||
|
return deleteQueryText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getObjectMostSpecificType(String individualURI, VitroRequest vreq) {
|
||||||
|
String type = "";
|
||||||
|
try {
|
||||||
|
ResultSet results = QueryUtils.getLanguageNeutralQueryResults(makeTypeQuery(individualURI), vreq);
|
||||||
|
while (results.hasNext()) {
|
||||||
|
QuerySolution solution = results.nextSolution();
|
||||||
|
type = solution.get("type").toString();
|
||||||
|
log.debug(type);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to get type for individual URI " + individualURI);
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getIndividualsToDelete(String targetIndividual, String deleteQuery,VitroRequest vreq) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
Query queryForTypeSpecificDeleteQuery = QueryFactory.create(deleteQuery);
|
||||||
|
QuerySolutionMap initialBindings = new QuerySolutionMap();
|
||||||
|
initialBindings.add("individualURI", ResourceFactory.createResource( targetIndividual ));
|
||||||
|
Model ontModel = vreq.getJenaOntModel();
|
||||||
|
QueryExecution qexec = QueryExecutionFactory.create(queryForTypeSpecificDeleteQuery,ontModel,initialBindings );
|
||||||
|
Model results = qexec.execDescribe();
|
||||||
|
results.write(out,"N3");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Query raised an error \n" + deleteQuery);
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeTypeQuery(String objectURI) {
|
||||||
|
return TYPE_QUERY_START + objectURI + TYPE_QUERY_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteIndividuals(byte[] toRemove, VitroRequest vreq) {
|
||||||
|
String removingString = new String(toRemove, StandardCharsets.UTF_8);
|
||||||
|
RDFService rdfService = vreq.getRDFService();
|
||||||
|
ChangeSet cs = makeChangeSet(rdfService);
|
||||||
|
InputStream in = new ByteArrayInputStream(toRemove);
|
||||||
|
cs.addRemoval(in, RDFServiceUtils.getSerializationFormatFromJenaString("N3"), ModelNames.ABOX_ASSERTIONS);
|
||||||
|
try {
|
||||||
|
rdfService.changeSetUpdate(cs);
|
||||||
|
} catch (RDFServiceException e) {
|
||||||
|
log.error("Got error while removing\n" + removingString);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeSet makeChangeSet(RDFService rdfService) {
|
||||||
|
ChangeSet cs = rdfService.manufactureChangeSet();
|
||||||
|
cs.addPreChangeEvent(new BulkUpdateEvent(null, BEGIN));
|
||||||
|
cs.addPostChangeEvent(new BulkUpdateEvent(null, END));
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ public class UrlBuilder {
|
||||||
LOGIN("/login"),
|
LOGIN("/login"),
|
||||||
LOGOUT("/logout"),
|
LOGOUT("/logout"),
|
||||||
OBJECT_PROPERTY_EDIT("/propertyEdit"),
|
OBJECT_PROPERTY_EDIT("/propertyEdit"),
|
||||||
|
CUSTOMSEARCH("/customsearch"),
|
||||||
SEARCH("/search"),
|
SEARCH("/search"),
|
||||||
SITE_ADMIN("/siteAdmin"),
|
SITE_ADMIN("/siteAdmin"),
|
||||||
TERMS_OF_USE("/termsOfUse"),
|
TERMS_OF_USE("/termsOfUse"),
|
||||||
|
|
|
@ -155,6 +155,7 @@ public class VitroVocabulary {
|
||||||
public static final String USERACCOUNT_LAST_LOGIN_TIME = VITRO_AUTH + "lastLoginTime";
|
public static final String USERACCOUNT_LAST_LOGIN_TIME = VITRO_AUTH + "lastLoginTime";
|
||||||
public static final String USERACCOUNT_STATUS = VITRO_AUTH + "status";
|
public static final String USERACCOUNT_STATUS = VITRO_AUTH + "status";
|
||||||
public static final String USERACCOUNT_PASSWORD_LINK_EXPIRES = VITRO_AUTH + "passwordLinkExpires";
|
public static final String USERACCOUNT_PASSWORD_LINK_EXPIRES = VITRO_AUTH + "passwordLinkExpires";
|
||||||
|
public static final String USERACCOUNT_EMAIL_KEY = VITRO_AUTH + "emailKey";
|
||||||
public static final String USERACCOUNT_PASSWORD_CHANGE_REQUIRED = VITRO_AUTH + "passwordChangeRequired";
|
public static final String USERACCOUNT_PASSWORD_CHANGE_REQUIRED = VITRO_AUTH + "passwordChangeRequired";
|
||||||
public static final String USERACCOUNT_EXTERNAL_AUTH_ID = VITRO_AUTH + "externalAuthId";
|
public static final String USERACCOUNT_EXTERNAL_AUTH_ID = VITRO_AUTH + "externalAuthId";
|
||||||
public static final String USERACCOUNT_EXTERNAL_AUTH_ONLY = VITRO_AUTH + "externalAuthOnly";
|
public static final String USERACCOUNT_EXTERNAL_AUTH_ONLY = VITRO_AUTH + "externalAuthOnly";
|
||||||
|
|
|
@ -121,6 +121,7 @@ public class JenaBaseDaoCon {
|
||||||
protected DatatypeProperty USERACCOUNT_LAST_LOGIN_TIME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_LOGIN_TIME);
|
protected DatatypeProperty USERACCOUNT_LAST_LOGIN_TIME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_LOGIN_TIME);
|
||||||
protected DatatypeProperty USERACCOUNT_STATUS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_STATUS);
|
protected DatatypeProperty USERACCOUNT_STATUS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_STATUS);
|
||||||
protected DatatypeProperty USERACCOUNT_PASSWORD_LINK_EXPIRES = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_LINK_EXPIRES);
|
protected DatatypeProperty USERACCOUNT_PASSWORD_LINK_EXPIRES = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_LINK_EXPIRES);
|
||||||
|
protected DatatypeProperty USERACCOUNT_EMAIL_KEY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EMAIL_KEY);
|
||||||
protected DatatypeProperty USERACCOUNT_PASSWORD_CHANGE_REQUIRED = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_CHANGE_REQUIRED);
|
protected DatatypeProperty USERACCOUNT_PASSWORD_CHANGE_REQUIRED = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_CHANGE_REQUIRED);
|
||||||
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ID = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ID);
|
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ID = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ID);
|
||||||
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ONLY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ONLY);
|
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ONLY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ONLY);
|
||||||
|
|
|
@ -12,10 +12,10 @@ import java.util.function.Supplier;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.apache.jena.graph.Capabilities;
|
import org.apache.jena.graph.Capabilities;
|
||||||
import org.apache.jena.graph.Graph;
|
import org.apache.jena.graph.Graph;
|
||||||
import org.apache.jena.graph.GraphEventManager;
|
import org.apache.jena.graph.GraphEventManager;
|
||||||
|
import org.apache.jena.graph.GraphListener;
|
||||||
import org.apache.jena.graph.GraphStatisticsHandler;
|
import org.apache.jena.graph.GraphStatisticsHandler;
|
||||||
import org.apache.jena.graph.Node;
|
import org.apache.jena.graph.Node;
|
||||||
import org.apache.jena.graph.TransactionHandler;
|
import org.apache.jena.graph.TransactionHandler;
|
||||||
|
@ -23,7 +23,6 @@ import org.apache.jena.graph.Triple;
|
||||||
import org.apache.jena.graph.impl.GraphWithPerform;
|
import org.apache.jena.graph.impl.GraphWithPerform;
|
||||||
import org.apache.jena.graph.impl.SimpleEventManager;
|
import org.apache.jena.graph.impl.SimpleEventManager;
|
||||||
import org.apache.jena.query.QuerySolution;
|
import org.apache.jena.query.QuerySolution;
|
||||||
import org.apache.jena.rdf.listeners.StatementListener;
|
|
||||||
import org.apache.jena.rdf.model.Model;
|
import org.apache.jena.rdf.model.Model;
|
||||||
import org.apache.jena.rdf.model.ModelFactory;
|
import org.apache.jena.rdf.model.ModelFactory;
|
||||||
import org.apache.jena.rdf.model.StmtIterator;
|
import org.apache.jena.rdf.model.StmtIterator;
|
||||||
|
@ -404,7 +403,18 @@ public class RDFServiceGraph implements GraphWithPerform {
|
||||||
@Override
|
@Override
|
||||||
public GraphEventManager getEventManager() {
|
public GraphEventManager getEventManager() {
|
||||||
if (eventManager == null) {
|
if (eventManager == null) {
|
||||||
eventManager = new SimpleEventManager(this);
|
eventManager = new SimpleEventManager() {
|
||||||
|
@Override
|
||||||
|
public void notifyEvent(Graph g, Object event) {
|
||||||
|
ChangeSet changeSet = rdfService.manufactureChangeSet();
|
||||||
|
changeSet.addPreChangeEvent(event);
|
||||||
|
try {
|
||||||
|
rdfService.changeSetUpdate(changeSet);
|
||||||
|
} catch (RDFServiceException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return eventManager;
|
return eventManager;
|
||||||
}
|
}
|
||||||
|
@ -590,21 +600,7 @@ public class RDFServiceGraph implements GraphWithPerform {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Model createRDFServiceModel(final RDFServiceGraph g) {
|
public static Model createRDFServiceModel(final RDFServiceGraph g) {
|
||||||
Model m = VitroModelFactory.createModelForGraph(g);
|
return VitroModelFactory.createModelForGraph(g);
|
||||||
m.register(new StatementListener() {
|
|
||||||
@Override
|
|
||||||
public void notifyEvent(Model m, Object event) {
|
|
||||||
ChangeSet changeSet = g.getRDFService().manufactureChangeSet();
|
|
||||||
changeSet.addPreChangeEvent(event);
|
|
||||||
try {
|
|
||||||
g.getRDFService().changeSetUpdate(changeSet);
|
|
||||||
} catch (RDFServiceException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,7 +4,6 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -98,6 +97,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
||||||
u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD));
|
u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD));
|
||||||
u.setPasswordLinkExpires(getPropertyLongValue(r,
|
u.setPasswordLinkExpires(getPropertyLongValue(r,
|
||||||
USERACCOUNT_PASSWORD_LINK_EXPIRES));
|
USERACCOUNT_PASSWORD_LINK_EXPIRES));
|
||||||
|
u.setEmailKey(getPropertyStringValue(r,USERACCOUNT_EMAIL_KEY));
|
||||||
|
|
||||||
u.setPasswordChangeRequired(getPropertyBooleanValue(r,
|
u.setPasswordChangeRequired(getPropertyBooleanValue(r,
|
||||||
USERACCOUNT_PASSWORD_CHANGE_REQUIRED));
|
USERACCOUNT_PASSWORD_CHANGE_REQUIRED));
|
||||||
u.setExternalAuthOnly(getPropertyBooleanValue(r,
|
u.setExternalAuthOnly(getPropertyBooleanValue(r,
|
||||||
|
@ -240,6 +241,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
||||||
userAccount.getLoginCount(), model);
|
userAccount.getLoginCount(), model);
|
||||||
addPropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
|
addPropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
|
||||||
userAccount.getLastLoginTime(), model);
|
userAccount.getLastLoginTime(), model);
|
||||||
|
addPropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
|
||||||
|
userAccount.getEmailKey(), model);
|
||||||
if (userAccount.getStatus() != null) {
|
if (userAccount.getStatus() != null) {
|
||||||
addPropertyStringValue(res, USERACCOUNT_STATUS, userAccount
|
addPropertyStringValue(res, USERACCOUNT_STATUS, userAccount
|
||||||
.getStatus().toString(), model);
|
.getStatus().toString(), model);
|
||||||
|
@ -306,6 +309,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
||||||
userAccount.getLoginCount(), model);
|
userAccount.getLoginCount(), model);
|
||||||
updatePropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
|
updatePropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
|
||||||
userAccount.getLastLoginTime(), model);
|
userAccount.getLastLoginTime(), model);
|
||||||
|
updatePropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
|
||||||
|
userAccount.getEmailKey(), model);
|
||||||
if (userAccount.getStatus() == null) {
|
if (userAccount.getStatus() == null) {
|
||||||
updatePropertyStringValue(res, USERACCOUNT_STATUS, null, model);
|
updatePropertyStringValue(res, USERACCOUNT_STATUS, null, model);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -61,6 +61,10 @@ public class EditConfigurationUtils {
|
||||||
return vreq.getParameter("rangeUri");
|
return vreq.getParameter("rangeUri");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getTypeOfNew(VitroRequest vreq) {
|
||||||
|
return vreq.getParameter("typeOfNew");
|
||||||
|
}
|
||||||
|
|
||||||
public static VClass getRangeVClass(VitroRequest vreq) {
|
public static VClass getRangeVClass(VitroRequest vreq) {
|
||||||
// This needs a WebappDaoFactory with no filtering/RDFService
|
// This needs a WebappDaoFactory with no filtering/RDFService
|
||||||
// funny business because it needs to be able to retrieve anonymous union
|
// funny business because it needs to be able to retrieve anonymous union
|
||||||
|
|
|
@ -28,7 +28,9 @@ public class DefaultDeleteGenerator extends BaseEditConfigurationGenerator imple
|
||||||
private Integer dataHash = 0;
|
private Integer dataHash = 0;
|
||||||
private DataPropertyStatement dps = null;
|
private DataPropertyStatement dps = null;
|
||||||
private String dataLiteral = null;
|
private String dataLiteral = null;
|
||||||
private String template = "confirmDeletePropertyForm.ftl";
|
private String propertyTemplate = "confirmDeletePropertyForm.ftl";
|
||||||
|
private String individualTemplate = "confirmDeleteIndividualForm.ftl";
|
||||||
|
|
||||||
|
|
||||||
//In this case, simply return the edit configuration currently saved in session
|
//In this case, simply return the edit configuration currently saved in session
|
||||||
//Since this is forwarding from another form, an edit configuration should already exist in session
|
//Since this is forwarding from another form, an edit configuration should already exist in session
|
||||||
|
@ -43,12 +45,32 @@ public class DefaultDeleteGenerator extends BaseEditConfigurationGenerator imple
|
||||||
if(editConfiguration == null) {
|
if(editConfiguration == null) {
|
||||||
editConfiguration = setupEditConfiguration(vreq, session);
|
editConfiguration = setupEditConfiguration(vreq, session);
|
||||||
}
|
}
|
||||||
editConfiguration.setTemplate(template);
|
|
||||||
//prepare update?
|
//prepare update?
|
||||||
prepare(vreq, editConfiguration);
|
prepare(vreq, editConfiguration);
|
||||||
|
if (editConfiguration.getPredicateUri() == null && editConfiguration.getSubjectUri() == null) {
|
||||||
|
editConfiguration.setTemplate(individualTemplate);
|
||||||
|
addDeleteParams(vreq, editConfiguration);
|
||||||
|
}else {
|
||||||
|
editConfiguration.setTemplate(propertyTemplate);
|
||||||
|
}
|
||||||
return editConfiguration;
|
return editConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addDeleteParams(VitroRequest vreq, EditConfigurationVTwo editConfiguration) {
|
||||||
|
String redirectUrl = vreq.getParameter("redirectUrl");
|
||||||
|
if (redirectUrl != null) {
|
||||||
|
editConfiguration.addFormSpecificData("redirectUrl", redirectUrl);
|
||||||
|
}
|
||||||
|
String individualName = vreq.getParameter("individualName");
|
||||||
|
if (redirectUrl != null) {
|
||||||
|
editConfiguration.addFormSpecificData("individualName", individualName);
|
||||||
|
}
|
||||||
|
String individualType = vreq.getParameter("individualType");
|
||||||
|
if (redirectUrl != null) {
|
||||||
|
editConfiguration.addFormSpecificData("individualType", individualType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private EditConfigurationVTwo setupEditConfiguration(VitroRequest vreq, HttpSession session) {
|
private EditConfigurationVTwo setupEditConfiguration(VitroRequest vreq, HttpSession session) {
|
||||||
EditConfigurationVTwo editConfiguration = new EditConfigurationVTwo();
|
EditConfigurationVTwo editConfiguration = new EditConfigurationVTwo();
|
||||||
initProcessParameters(vreq, session, editConfiguration);
|
initProcessParameters(vreq, session, editConfiguration);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import javax.servlet.annotation.WebServlet;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.jena.ontology.OntModel;
|
import org.apache.jena.ontology.OntModel;
|
||||||
|
@ -67,10 +68,20 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet {
|
||||||
//TODO: Create this generator
|
//TODO: Create this generator
|
||||||
final String RDFS_LABEL_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RDFSLabelGenerator";
|
final String RDFS_LABEL_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RDFSLabelGenerator";
|
||||||
final String DEFAULT_DELETE_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.DefaultDeleteGenerator";
|
final String DEFAULT_DELETE_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.DefaultDeleteGenerator";
|
||||||
|
final String MANAGE_MENUS_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.ManagePageGenerator";
|
||||||
@Override
|
|
||||||
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
|
@Override
|
||||||
//Check if this statement can be edited here and return unauthorized if not
|
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
|
||||||
|
// If request is for new individual, return simple do back end editing action permission
|
||||||
|
if (StringUtils.isNotEmpty(EditConfigurationUtils.getTypeOfNew(vreq))) {
|
||||||
|
return SimplePermission.DO_BACK_END_EDITING.ACTION;
|
||||||
|
} else if(MANAGE_MENUS_FORM.equals(vreq.getParameter("editForm"))) {
|
||||||
|
return SimplePermission.MANAGE_MENUS.ACTION;
|
||||||
|
}
|
||||||
|
if (isIndividualDeletion(vreq)) {
|
||||||
|
return SimplePermission.DO_BACK_END_EDITING.ACTION;
|
||||||
|
}
|
||||||
|
// Check if this statement can be edited here and return unauthorized if not
|
||||||
String subjectUri = EditConfigurationUtils.getSubjectUri(vreq);
|
String subjectUri = EditConfigurationUtils.getSubjectUri(vreq);
|
||||||
String predicateUri = EditConfigurationUtils.getPredicateUri(vreq);
|
String predicateUri = EditConfigurationUtils.getPredicateUri(vreq);
|
||||||
String objectUri = EditConfigurationUtils.getObjectUri(vreq);
|
String objectUri = EditConfigurationUtils.getObjectUri(vreq);
|
||||||
|
@ -98,6 +109,16 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet {
|
||||||
return isAuthorized? SimplePermission.DO_FRONT_END_EDITING.ACTION: AuthorizationRequest.UNAUTHORIZED;
|
return isAuthorized? SimplePermission.DO_FRONT_END_EDITING.ACTION: AuthorizationRequest.UNAUTHORIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isIndividualDeletion(VitroRequest vreq) {
|
||||||
|
String subjectUri = EditConfigurationUtils.getSubjectUri(vreq);
|
||||||
|
String predicateUri = EditConfigurationUtils.getPredicateUri(vreq);
|
||||||
|
String objectUri = EditConfigurationUtils.getObjectUri(vreq);
|
||||||
|
if (objectUri != null && subjectUri == null && predicateUri == null && isDeleteForm(vreq)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ResponseValues processRequest(VitroRequest vreq) {
|
protected ResponseValues processRequest(VitroRequest vreq) {
|
||||||
|
|
||||||
|
@ -355,7 +376,7 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet {
|
||||||
String predicateUri = EditConfigurationUtils.getPredicateUri(vreq);
|
String predicateUri = EditConfigurationUtils.getPredicateUri(vreq);
|
||||||
String formParam = getFormParam(vreq);
|
String formParam = getFormParam(vreq);
|
||||||
//if no form parameter, then predicate uri and subject uri must both be populated
|
//if no form parameter, then predicate uri and subject uri must both be populated
|
||||||
if (formParam == null || "".equals(formParam)) {
|
if ((formParam == null || "".equals(formParam)) && !isDeleteForm(vreq)) {
|
||||||
if ((predicateUri == null || predicateUri.trim().length() == 0)) {
|
if ((predicateUri == null || predicateUri.trim().length() == 0)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,6 +304,7 @@ public class FreemarkerConfigurationImpl extends Configuration {
|
||||||
urls.put("home", UrlBuilder.getHomeUrl());
|
urls.put("home", UrlBuilder.getHomeUrl());
|
||||||
urls.put("about", UrlBuilder.getUrl(Route.ABOUT));
|
urls.put("about", UrlBuilder.getUrl(Route.ABOUT));
|
||||||
urls.put("search", UrlBuilder.getUrl(Route.SEARCH));
|
urls.put("search", UrlBuilder.getUrl(Route.SEARCH));
|
||||||
|
urls.put("customsearch", UrlBuilder.getUrl(Route.CUSTOMSEARCH));
|
||||||
urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE));
|
urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE));
|
||||||
urls.put("login", UrlBuilder.getLoginUrl());
|
urls.put("login", UrlBuilder.getLoginUrl());
|
||||||
urls.put("logout", UrlBuilder.getLogoutUrl());
|
urls.put("logout", UrlBuilder.getLogoutUrl());
|
||||||
|
|
|
@ -0,0 +1,766 @@
|
||||||
|
/* $This file is distributed under the terms of the license in LICENSE$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.search.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupsForRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField.Count;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocument;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocumentList;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.LinkTemplateModel;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.searchresult.IndividualSearchResult;
|
||||||
|
import edu.ucsf.vitro.opensocial.OpenSocialManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paged search controller that uses the search engine
|
||||||
|
*/
|
||||||
|
|
||||||
|
@WebServlet(name = "CustomSearchController", urlPatterns = {"/customsearch","/customsearch.jsp","/customfedsearch","/customsearchcontroller"} )
|
||||||
|
public class CustomSearchController extends FreemarkerHttpServlet {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Log log = LogFactory.getLog(CustomSearchController.class);
|
||||||
|
|
||||||
|
protected static final int DEFAULT_HITS_PER_PAGE = 25;
|
||||||
|
protected static final int DEFAULT_MAX_HIT_COUNT = 1000;
|
||||||
|
|
||||||
|
private static final String PARAM_XML_REQUEST = "xml";
|
||||||
|
private static final String PARAM_CSV_REQUEST = "csv";
|
||||||
|
private static final String PARAM_START_INDEX = "startIndex";
|
||||||
|
private static final String PARAM_HITS_PER_PAGE = "hitsPerPage";
|
||||||
|
private static final String PARAM_CLASSGROUP = "classgroup";
|
||||||
|
private static final String PARAM_RDFTYPE = "type";
|
||||||
|
private static final String PARAM_QUERY_TEXT = "querytext";
|
||||||
|
|
||||||
|
protected static final Map<Format,Map<Result,String>> templateTable;
|
||||||
|
|
||||||
|
protected enum Format {
|
||||||
|
HTML, XML, CSV;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum Result {
|
||||||
|
PAGED, ERROR, BAD_QUERY
|
||||||
|
}
|
||||||
|
|
||||||
|
static{
|
||||||
|
templateTable = setupTemplateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overriding doGet from FreemarkerHttpController to do a page template (as
|
||||||
|
* opposed to body template) style output for XML requests.
|
||||||
|
*
|
||||||
|
* This follows the pattern in AutocompleteController.java.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
VitroRequest vreq = new VitroRequest(request);
|
||||||
|
boolean wasXmlRequested = isRequestedFormatXml(vreq);
|
||||||
|
boolean wasCSVRequested = isRequestedFormatCSV(vreq);
|
||||||
|
if( !wasXmlRequested && !wasCSVRequested){
|
||||||
|
super.doGet(vreq,response);
|
||||||
|
}else if (wasXmlRequested){
|
||||||
|
try {
|
||||||
|
ResponseValues rvalues = processRequest(vreq);
|
||||||
|
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/xml;charset=UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=search.xml");
|
||||||
|
writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
}else if (wasCSVRequested){
|
||||||
|
try {
|
||||||
|
ResponseValues rvalues = processRequest(vreq);
|
||||||
|
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/csv;charset=UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=search.csv");
|
||||||
|
writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
VitroRequest vreq = new VitroRequest(request);
|
||||||
|
boolean wasXmlRequested = isRequestedFormatXml(vreq);
|
||||||
|
boolean wasCSVRequested = isRequestedFormatCSV(vreq);
|
||||||
|
if( !wasXmlRequested && !wasCSVRequested){
|
||||||
|
super.doGet(vreq,response);
|
||||||
|
}else if (wasXmlRequested){
|
||||||
|
try {
|
||||||
|
ResponseValues rvalues = processRequest(vreq);
|
||||||
|
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/xml;charset=UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=search.xml");
|
||||||
|
writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
}else if (wasCSVRequested){
|
||||||
|
try {
|
||||||
|
ResponseValues rvalues = processRequest(vreq);
|
||||||
|
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/csv;charset=UTF-8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=search.csv");
|
||||||
|
writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ResponseValues processRequest(VitroRequest vreq) {
|
||||||
|
|
||||||
|
//There may be other non-html formats in the future
|
||||||
|
Format format = getFormat(vreq);
|
||||||
|
boolean wasXmlRequested = Format.XML == format;
|
||||||
|
boolean wasCSVRequested = Format.CSV == format;
|
||||||
|
log.debug("Requested format was " + (wasXmlRequested ? "xml" : "html"));
|
||||||
|
boolean wasHtmlRequested = ! (wasXmlRequested || wasCSVRequested);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//make sure an IndividualDao is available
|
||||||
|
if( vreq.getWebappDaoFactory() == null
|
||||||
|
|| vreq.getWebappDaoFactory().getIndividualDao() == null ){
|
||||||
|
log.error("Could not get webappDaoFactory or IndividualDao");
|
||||||
|
throw new Exception("Could not access model.");
|
||||||
|
}
|
||||||
|
IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao();
|
||||||
|
VClassGroupDao grpDao = vreq.getWebappDaoFactory().getVClassGroupDao();
|
||||||
|
VClassDao vclassDao = vreq.getWebappDaoFactory().getVClassDao();
|
||||||
|
|
||||||
|
ApplicationBean appBean = vreq.getAppBean();
|
||||||
|
|
||||||
|
log.debug("IndividualDao is " + iDao.toString() + " Public classes in the classgroup are " + grpDao.getPublicGroupsWithVClasses().toString());
|
||||||
|
log.debug("VClassDao is "+ vclassDao.toString() );
|
||||||
|
|
||||||
|
int startIndex = getStartIndex(vreq);
|
||||||
|
int hitsPerPage = getHitsPerPage( vreq );
|
||||||
|
String queryBuilderRules = getQueryBuilderRules(vreq);
|
||||||
|
|
||||||
|
|
||||||
|
String queryText = vreq.getParameter(PARAM_QUERY_TEXT);
|
||||||
|
log.debug("Query text is \""+ queryText + "\"");
|
||||||
|
|
||||||
|
|
||||||
|
String badQueryMsg = badQueryText( queryText, vreq );
|
||||||
|
if( badQueryMsg != null ){
|
||||||
|
return doFailedSearch(badQueryMsg, queryText, format, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchQuery query = getQuery(queryText, hitsPerPage, startIndex, vreq);
|
||||||
|
SearchEngine search = ApplicationUtils.instance().getSearchEngine();
|
||||||
|
SearchResponse response = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = search.query(query);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String msg = makeBadSearchMessage(queryText, ex.getMessage(), vreq);
|
||||||
|
log.error("could not run search query",ex);
|
||||||
|
return doFailedSearch(msg, queryText, format, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
log.error("Search response was null");
|
||||||
|
return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResultDocumentList docs = response.getResults();
|
||||||
|
if (docs == null) {
|
||||||
|
log.error("Document list for a search was null");
|
||||||
|
return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText,format, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
long hitCount = docs.getNumFound();
|
||||||
|
log.debug("Number of hits = " + hitCount);
|
||||||
|
if ( hitCount < 1 ) {
|
||||||
|
return doNoHits(queryText,format, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Individual> individuals = new ArrayList<Individual>(docs.size());
|
||||||
|
for (SearchResultDocument doc : docs) {
|
||||||
|
try {
|
||||||
|
String uri = doc.getStringValue(VitroSearchTermNames.URI);
|
||||||
|
Individual ind = iDao.getIndividualByURI(uri);
|
||||||
|
if (ind != null) {
|
||||||
|
ind.setSearchSnippet(getSnippet(doc, response));
|
||||||
|
individuals.add(ind);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Problem getting usable individuals from search hits. ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParamMap pagingLinkParams = new ParamMap();
|
||||||
|
pagingLinkParams.put(PARAM_QUERY_TEXT, queryText);
|
||||||
|
pagingLinkParams.put(PARAM_HITS_PER_PAGE, String.valueOf(hitsPerPage));
|
||||||
|
|
||||||
|
if( wasXmlRequested ){
|
||||||
|
pagingLinkParams.put(PARAM_XML_REQUEST,"1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compile the data for the templates */
|
||||||
|
|
||||||
|
Map<String, Object> body = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
String classGroupParam = vreq.getParameter(PARAM_CLASSGROUP);
|
||||||
|
log.debug("ClassGroupParam is \""+ classGroupParam + "\"");
|
||||||
|
boolean classGroupFilterRequested = false;
|
||||||
|
if (!StringUtils.isEmpty(classGroupParam)) {
|
||||||
|
VClassGroup grp = grpDao.getGroupByURI(classGroupParam);
|
||||||
|
classGroupFilterRequested = true;
|
||||||
|
if (grp != null && grp.getPublicName() != null)
|
||||||
|
body.put("classGroupName", grp.getPublicName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String typeParam = vreq.getParameter(PARAM_RDFTYPE);
|
||||||
|
boolean typeFilterRequested = false;
|
||||||
|
if (!StringUtils.isEmpty(typeParam)) {
|
||||||
|
VClass type = vclassDao.getVClassByURI(typeParam);
|
||||||
|
typeFilterRequested = true;
|
||||||
|
if (type != null && type.getName() != null)
|
||||||
|
body.put("typeName", type.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add ClassGroup and type refinement links to body */
|
||||||
|
if( wasHtmlRequested ){
|
||||||
|
if ( !classGroupFilterRequested && !typeFilterRequested ) {
|
||||||
|
// Search request includes no ClassGroup and no type, so add ClassGroup search refinement links.
|
||||||
|
body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText));
|
||||||
|
} else if ( classGroupFilterRequested && !typeFilterRequested ) {
|
||||||
|
// Search request is for a ClassGroup, so add rdf:type search refinement links
|
||||||
|
// but try to filter out classes that are subclasses
|
||||||
|
body.put("classLinks", getVClassLinks(vclassDao, docs, response, queryText));
|
||||||
|
pagingLinkParams.put(PARAM_CLASSGROUP, classGroupParam);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//search request is for a class so there are no more refinements
|
||||||
|
pagingLinkParams.put(PARAM_RDFTYPE, typeParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.put("individuals", IndividualSearchResult
|
||||||
|
.getIndividualTemplateModels(individuals, vreq));
|
||||||
|
|
||||||
|
body.put("querytext", queryText);
|
||||||
|
body.put("title", new StringBuilder().append(appBean.getApplicationName()).append(" - ").
|
||||||
|
append(I18n.text(vreq, "search_results_for")).append(" '").append(queryText).append("'").toString());
|
||||||
|
|
||||||
|
body.put("hitCount", hitCount);
|
||||||
|
body.put("startIndex", startIndex);
|
||||||
|
body.put(PARAM_HITS_PER_PAGE, hitsPerPage);
|
||||||
|
|
||||||
|
body.put("pagingLinks",
|
||||||
|
getPagingLinks(startIndex, hitsPerPage, hitCount,
|
||||||
|
vreq.getServletPath(),
|
||||||
|
pagingLinkParams, vreq));
|
||||||
|
|
||||||
|
if (startIndex != 0) {
|
||||||
|
body.put("prevPage", getPreviousPageLink(startIndex,
|
||||||
|
hitsPerPage, vreq.getServletPath(), pagingLinkParams));
|
||||||
|
}
|
||||||
|
if (startIndex < (hitCount - hitsPerPage)) {
|
||||||
|
body.put("nextPage", getNextPageLink(startIndex, hitsPerPage,
|
||||||
|
vreq.getServletPath(), pagingLinkParams));
|
||||||
|
}
|
||||||
|
if (queryBuilderRules != null) {
|
||||||
|
body.put("queryBuilderRules", queryBuilderRules);
|
||||||
|
}
|
||||||
|
body.put(PARAM_HITS_PER_PAGE, hitsPerPage);
|
||||||
|
|
||||||
|
// VIVO OpenSocial Extension by UCSF
|
||||||
|
try {
|
||||||
|
OpenSocialManager openSocialManager = new OpenSocialManager(vreq, "search");
|
||||||
|
// put list of people found onto pubsub channel
|
||||||
|
// only turn this on for a people only search
|
||||||
|
if ("http://vivoweb.org/ontology#vitroClassGrouppeople".equals(vreq.getParameter(PARAM_CLASSGROUP))) {
|
||||||
|
List<String> ids = OpenSocialManager.getOpenSocialId(individuals);
|
||||||
|
openSocialManager.setPubsubData(OpenSocialManager.JSON_PERSONID_CHANNEL,
|
||||||
|
OpenSocialManager.buildJSONPersonIds(ids, "" + ids.size() + " people found"));
|
||||||
|
}
|
||||||
|
// TODO put this in a better place to guarantee that it gets called at the proper time!
|
||||||
|
openSocialManager.removePubsubGadgetsWithoutData();
|
||||||
|
body.put("openSocial", openSocialManager);
|
||||||
|
if (openSocialManager.isVisible()) {
|
||||||
|
body.put("bodyOnload", "my.init();");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("IOException in doTemplate()", e);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("SQLException in doTemplate()", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String template = templateTable.get(format).get(Result.PAGED);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new TemplateResponseValues(template, body);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return doSearchError(e,format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getQueryBuilderRules(VitroRequest vreq) {
|
||||||
|
String rules = null;
|
||||||
|
try {
|
||||||
|
rules = vreq.getParameter("queryBuilderRules");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getHitsPerPage(VitroRequest vreq) {
|
||||||
|
int hitsPerPage = DEFAULT_HITS_PER_PAGE;
|
||||||
|
try{
|
||||||
|
hitsPerPage = Integer.parseInt(vreq.getParameter(PARAM_HITS_PER_PAGE));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
hitsPerPage = DEFAULT_HITS_PER_PAGE;
|
||||||
|
}
|
||||||
|
log.debug("hitsPerPage is " + hitsPerPage);
|
||||||
|
return hitsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStartIndex(VitroRequest vreq) {
|
||||||
|
int startIndex = 0;
|
||||||
|
try{
|
||||||
|
startIndex = Integer.parseInt(vreq.getParameter(PARAM_START_INDEX));
|
||||||
|
}catch (Throwable e) {
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
log.debug("startIndex is " + startIndex);
|
||||||
|
return startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String badQueryText(String qtxt, VitroRequest vreq) {
|
||||||
|
if( qtxt == null || "".equals( qtxt.trim() ) )
|
||||||
|
return I18n.text(vreq, "enter_search_term");
|
||||||
|
|
||||||
|
if( qtxt.equals("*:*") )
|
||||||
|
return I18n.text(vreq, "invalid_search_term") ;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class groups represented for the individuals in the documents.
|
||||||
|
*/
|
||||||
|
private List<VClassGroupSearchLink> getClassGroupsLinks(VitroRequest vreq, VClassGroupDao grpDao, SearchResultDocumentList docs, SearchResponse rsp, String qtxt) {
|
||||||
|
Map<String,Long> cgURItoCount = new HashMap<String,Long>();
|
||||||
|
|
||||||
|
List<VClassGroup> classgroups = new ArrayList<VClassGroup>( );
|
||||||
|
List<SearchFacetField> ffs = rsp.getFacetFields();
|
||||||
|
for(SearchFacetField ff : ffs){
|
||||||
|
if(VitroSearchTermNames.CLASSGROUP_URI.equals(ff.getName())){
|
||||||
|
List<Count> counts = ff.getValues();
|
||||||
|
for( Count ct: counts){
|
||||||
|
VClassGroup vcg = grpDao.getGroupByURI( ct.getName() );
|
||||||
|
if( vcg == null ){
|
||||||
|
log.debug("could not get classgroup for URI " + ct.getName());
|
||||||
|
}else{
|
||||||
|
classgroups.add(vcg);
|
||||||
|
cgURItoCount.put(vcg.getURI(), ct.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grpDao.sortGroupList(classgroups);
|
||||||
|
|
||||||
|
VClassGroupsForRequest vcgfr = VClassGroupCache.getVClassGroups(vreq);
|
||||||
|
List<VClassGroupSearchLink> classGroupLinks = new ArrayList<VClassGroupSearchLink>(classgroups.size());
|
||||||
|
for (VClassGroup vcg : classgroups) {
|
||||||
|
String groupURI = vcg.getURI();
|
||||||
|
VClassGroup localizedVcg = vcgfr.getGroup(groupURI);
|
||||||
|
long count = cgURItoCount.get( groupURI );
|
||||||
|
if (localizedVcg.getPublicName() != null && count > 0 ) {
|
||||||
|
classGroupLinks.add(new VClassGroupSearchLink(qtxt, localizedVcg, count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classGroupLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VClassSearchLink> getVClassLinks(VClassDao vclassDao, SearchResultDocumentList docs, SearchResponse rsp, String qtxt){
|
||||||
|
HashSet<String> typesInHits = getVClassUrisForHits(docs);
|
||||||
|
List<VClass> classes = new ArrayList<VClass>(typesInHits.size());
|
||||||
|
Map<String,Long> typeURItoCount = new HashMap<String,Long>();
|
||||||
|
|
||||||
|
List<SearchFacetField> ffs = rsp.getFacetFields();
|
||||||
|
for(SearchFacetField ff : ffs){
|
||||||
|
if(VitroSearchTermNames.RDFTYPE.equals(ff.getName())){
|
||||||
|
List<Count> counts = ff.getValues();
|
||||||
|
for( Count ct: counts){
|
||||||
|
String typeUri = ct.getName();
|
||||||
|
long count = ct.getCount();
|
||||||
|
try{
|
||||||
|
if( VitroVocabulary.OWL_THING.equals(typeUri) ||
|
||||||
|
count == 0 )
|
||||||
|
continue;
|
||||||
|
VClass type = vclassDao.getVClassByURI(typeUri);
|
||||||
|
if( type != null &&
|
||||||
|
! type.isAnonymous() &&
|
||||||
|
type.getName() != null && !"".equals(type.getName()) &&
|
||||||
|
type.getGroupURI() != null ){ //don't display classes that aren't in classgroups
|
||||||
|
typeURItoCount.put(typeUri,count);
|
||||||
|
classes.add(type);
|
||||||
|
}
|
||||||
|
}catch(Exception ex){
|
||||||
|
if( log.isDebugEnabled() )
|
||||||
|
log.debug("could not add type " + typeUri, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
classes.sort(new Comparator<VClass>() {
|
||||||
|
public int compare(VClass o1, VClass o2) {
|
||||||
|
return o1.compareTo(o2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<VClassSearchLink> vClassLinks = new ArrayList<VClassSearchLink>(classes.size());
|
||||||
|
for (VClass vc : classes) {
|
||||||
|
long count = typeURItoCount.get(vc.getURI());
|
||||||
|
vClassLinks.add(new VClassSearchLink(qtxt, vc, count ));
|
||||||
|
}
|
||||||
|
|
||||||
|
return vClassLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<String> getVClassUrisForHits(SearchResultDocumentList docs){
|
||||||
|
HashSet<String> typesInHits = new HashSet<String>();
|
||||||
|
for (SearchResultDocument doc : docs) {
|
||||||
|
try {
|
||||||
|
Collection<Object> types = doc.getFieldValues(VitroSearchTermNames.RDFTYPE);
|
||||||
|
if (types != null) {
|
||||||
|
for (Object o : types) {
|
||||||
|
String typeUri = o.toString();
|
||||||
|
typesInHits.add(typeUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("problems getting rdf:type for search hits",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typesInHits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSnippet(SearchResultDocument doc, SearchResponse response) {
|
||||||
|
String docId = doc.getStringValue(VitroSearchTermNames.DOCID);
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
Map<String, Map<String, List<String>>> highlights = response.getHighlighting();
|
||||||
|
if (highlights != null && highlights.get(docId) != null) {
|
||||||
|
List<String> snippets = highlights.get(docId).get(VitroSearchTermNames.ALLTEXT);
|
||||||
|
if (snippets != null && snippets.size() > 0) {
|
||||||
|
text.append("... ").append(snippets.get(0)).append(" ...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, VitroRequest vreq) {
|
||||||
|
// Lowercase the search term to support wildcard searches: The search engine applies no text
|
||||||
|
// processing to a wildcard search term.
|
||||||
|
SearchQuery query = ApplicationUtils.instance().getSearchEngine().createQuery(queryText);
|
||||||
|
|
||||||
|
query.setStart( startIndex )
|
||||||
|
.setRows(hitsPerPage);
|
||||||
|
|
||||||
|
// ClassGroup filtering param
|
||||||
|
String classgroupParam = vreq.getParameter(PARAM_CLASSGROUP);
|
||||||
|
|
||||||
|
// rdf:type filtering param
|
||||||
|
String typeParam = vreq.getParameter(PARAM_RDFTYPE);
|
||||||
|
|
||||||
|
if ( ! StringUtils.isBlank(classgroupParam) ) {
|
||||||
|
// ClassGroup filtering
|
||||||
|
log.debug("Firing classgroup query ");
|
||||||
|
log.debug("request.getParameter(classgroup) is "+ classgroupParam);
|
||||||
|
query.addFilterQuery(VitroSearchTermNames.CLASSGROUP_URI + ":\"" + classgroupParam + "\"");
|
||||||
|
|
||||||
|
//with ClassGroup filtering we want type facets
|
||||||
|
query.addFacetFields(VitroSearchTermNames.RDFTYPE).setFacetLimit(-1);
|
||||||
|
|
||||||
|
}else if ( ! StringUtils.isBlank(typeParam) ) {
|
||||||
|
// rdf:type filtering
|
||||||
|
log.debug("Firing type query ");
|
||||||
|
log.debug("request.getParameter(type) is "+ typeParam);
|
||||||
|
query.addFilterQuery(VitroSearchTermNames.RDFTYPE + ":\"" + typeParam + "\"");
|
||||||
|
//with type filtering we don't have facets.
|
||||||
|
}else{
|
||||||
|
//When no filtering is set, we want ClassGroup facets
|
||||||
|
query.addFacetFields(VitroSearchTermNames.CLASSGROUP_URI).setFacetLimit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Query = " + query.toString());
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VClassGroupSearchLink extends LinkTemplateModel {
|
||||||
|
long count = 0;
|
||||||
|
VClassGroupSearchLink(String querytext, VClassGroup classgroup, long count) {
|
||||||
|
super(classgroup.getPublicName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, classgroup.getURI());
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCount() { return Long.toString(count); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VClassSearchLink extends LinkTemplateModel {
|
||||||
|
long count = 0;
|
||||||
|
VClassSearchLink(String querytext, VClass type, long count) {
|
||||||
|
super(type.getName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI());
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCount() { return Long.toString(count); }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static List<PagingLink> getPagingLinks(int startIndex, int hitsPerPage, long hitCount, String baseUrl, ParamMap params, VitroRequest vreq) {
|
||||||
|
|
||||||
|
List<PagingLink> pagingLinks = new ArrayList<PagingLink>();
|
||||||
|
|
||||||
|
// No paging links if only one page of results
|
||||||
|
if (hitCount <= hitsPerPage) {
|
||||||
|
return pagingLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxHitCount = DEFAULT_MAX_HIT_COUNT ;
|
||||||
|
if( startIndex >= DEFAULT_MAX_HIT_COUNT - hitsPerPage )
|
||||||
|
maxHitCount = startIndex + DEFAULT_MAX_HIT_COUNT ;
|
||||||
|
|
||||||
|
for (int i = 0; i < hitCount; i += hitsPerPage) {
|
||||||
|
params.put(PARAM_START_INDEX, String.valueOf(i));
|
||||||
|
if ( i < maxHitCount - hitsPerPage) {
|
||||||
|
int pageNumber = i/hitsPerPage + 1;
|
||||||
|
boolean iIsCurrentPage = (i >= startIndex && i < (startIndex + hitsPerPage));
|
||||||
|
if ( iIsCurrentPage ) {
|
||||||
|
pagingLinks.add(new PagingLink(pageNumber));
|
||||||
|
} else {
|
||||||
|
pagingLinks.add(new PagingLink(pageNumber, baseUrl, params));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pagingLinks.add(new PagingLink(I18n.text(vreq, "paging_link_more"), baseUrl, params));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagingLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPreviousPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) {
|
||||||
|
params.put(PARAM_START_INDEX, String.valueOf(startIndex-hitsPerPage));
|
||||||
|
return UrlBuilder.getUrl(baseUrl, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNextPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) {
|
||||||
|
params.put(PARAM_START_INDEX, String.valueOf(startIndex+hitsPerPage));
|
||||||
|
return UrlBuilder.getUrl(baseUrl, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class PagingLink extends LinkTemplateModel {
|
||||||
|
|
||||||
|
PagingLink(int pageNumber, String baseUrl, ParamMap params) {
|
||||||
|
super(String.valueOf(pageNumber), baseUrl, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for current page item: not a link, so no url value.
|
||||||
|
PagingLink(int pageNumber) {
|
||||||
|
setText(String.valueOf(pageNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for "more..." item
|
||||||
|
PagingLink(String text, String baseUrl, ParamMap params) {
|
||||||
|
super(text, baseUrl, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExceptionResponseValues doSearchError(Throwable e, Format f) {
|
||||||
|
Map<String, Object> body = new HashMap<String, Object>();
|
||||||
|
body.put("message", "Search failed: " + e.getMessage());
|
||||||
|
return new ExceptionResponseValues(getTemplate(f,Result.ERROR), body, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplateResponseValues doFailedSearch(String message, String querytext, Format f, VitroRequest vreq) {
|
||||||
|
Map<String, Object> body = new HashMap<String, Object>();
|
||||||
|
body.put("title", I18n.text(vreq, "search_for", querytext));
|
||||||
|
if ( StringUtils.isEmpty(message) ) {
|
||||||
|
message = I18n.text(vreq, "search_failed");
|
||||||
|
}
|
||||||
|
body.put("message", message);
|
||||||
|
return new TemplateResponseValues(getTemplate(f,Result.ERROR), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplateResponseValues doNoHits(String querytext, Format f, VitroRequest vreq) {
|
||||||
|
Map<String, Object> body = new HashMap<String, Object>();
|
||||||
|
body.put("title", I18n.text(vreq, "search_for", querytext));
|
||||||
|
body.put("message", I18n.text(vreq, "no_matching_results"));
|
||||||
|
return new TemplateResponseValues(getTemplate(f,Result.ERROR), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a message to display to user for a bad search term.
|
||||||
|
*/
|
||||||
|
private String makeBadSearchMessage(String querytext, String exceptionMsg, VitroRequest vreq){
|
||||||
|
String rv = "";
|
||||||
|
try{
|
||||||
|
//try to get the column in the search term that is causing the problems
|
||||||
|
int coli = exceptionMsg.indexOf("column");
|
||||||
|
if( coli == -1) return "";
|
||||||
|
int numi = exceptionMsg.indexOf(".", coli+7);
|
||||||
|
if( numi == -1 ) return "";
|
||||||
|
String part = exceptionMsg.substring(coli+7,numi );
|
||||||
|
int i = Integer.parseInt(part) - 1;
|
||||||
|
|
||||||
|
// figure out where to cut preview and post-view
|
||||||
|
int errorWindow = 5;
|
||||||
|
int pre = i - errorWindow;
|
||||||
|
if (pre < 0)
|
||||||
|
pre = 0;
|
||||||
|
int post = i + errorWindow;
|
||||||
|
if (post > querytext.length())
|
||||||
|
post = querytext.length();
|
||||||
|
// log.warn("pre: " + pre + " post: " + post + " term len:
|
||||||
|
// " + term.length());
|
||||||
|
|
||||||
|
// get part of the search term before the error and after
|
||||||
|
String before = querytext.substring(pre, i);
|
||||||
|
String after = "";
|
||||||
|
if (post > i)
|
||||||
|
after = querytext.substring(i + 1, post);
|
||||||
|
|
||||||
|
rv = I18n.text(vreq, "search_term_error_near") +
|
||||||
|
" <span class='searchQuote'>"
|
||||||
|
+ before + "<span class='searchError'>" + querytext.charAt(i)
|
||||||
|
+ "</span>" + after + "</span>";
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int MAX_QUERY_LENGTH = 500;
|
||||||
|
|
||||||
|
protected boolean isRequestedFormatXml(VitroRequest req){
|
||||||
|
if( req != null ){
|
||||||
|
String param = req.getParameter(PARAM_XML_REQUEST);
|
||||||
|
return param != null && "1".equals(param);
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isRequestedFormatCSV(VitroRequest req){
|
||||||
|
if( req != null ){
|
||||||
|
String param = req.getParameter(PARAM_CSV_REQUEST);
|
||||||
|
return param != null && "1".equals(param);
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Format getFormat(VitroRequest req){
|
||||||
|
if( req != null && req.getParameter("xml") != null && "1".equals(req.getParameter("xml")))
|
||||||
|
return Format.XML;
|
||||||
|
else if ( req != null && req.getParameter("csv") != null && "1".equals(req.getParameter("csv")))
|
||||||
|
return Format.CSV;
|
||||||
|
else
|
||||||
|
return Format.HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getTemplate(Format format, Result result){
|
||||||
|
if( format != null && result != null)
|
||||||
|
return templateTable.get(format).get(result);
|
||||||
|
else{
|
||||||
|
log.error("getTemplate() must not have a null format or result.");
|
||||||
|
return templateTable.get(Format.HTML).get(Result.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Map<Format,Map<Result,String>> setupTemplateTable(){
|
||||||
|
Map<Format,Map<Result,String>> table = new HashMap<>();
|
||||||
|
|
||||||
|
HashMap<Result,String> resultsToTemplates = new HashMap<Result,String>();
|
||||||
|
|
||||||
|
// set up HTML format
|
||||||
|
resultsToTemplates.put(Result.PAGED, "search-pagedResults.ftl");
|
||||||
|
resultsToTemplates.put(Result.ERROR, "search-error.ftl");
|
||||||
|
// resultsToTemplates.put(Result.BAD_QUERY, "search-badQuery.ftl");
|
||||||
|
table.put(Format.HTML, Collections.unmodifiableMap(resultsToTemplates));
|
||||||
|
|
||||||
|
// set up XML format
|
||||||
|
resultsToTemplates = new HashMap<Result,String>();
|
||||||
|
resultsToTemplates.put(Result.PAGED, "search-xmlResults.ftl");
|
||||||
|
resultsToTemplates.put(Result.ERROR, "search-xmlError.ftl");
|
||||||
|
|
||||||
|
// resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl");
|
||||||
|
table.put(Format.XML, Collections.unmodifiableMap(resultsToTemplates));
|
||||||
|
|
||||||
|
|
||||||
|
// set up CSV format
|
||||||
|
resultsToTemplates = new HashMap<Result,String>();
|
||||||
|
resultsToTemplates.put(Result.PAGED, "search-csvResults.ftl");
|
||||||
|
resultsToTemplates.put(Result.ERROR, "search-csvError.ftl");
|
||||||
|
|
||||||
|
// resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl");
|
||||||
|
table.put(Format.CSV, Collections.unmodifiableMap(resultsToTemplates));
|
||||||
|
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(table);
|
||||||
|
}
|
||||||
|
}
|
|
@ -705,6 +705,10 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel {
|
||||||
public String getDeleteProcessingUrl() {
|
public String getDeleteProcessingUrl() {
|
||||||
return vreq.getContextPath() + "/deletePropertyController";
|
return vreq.getContextPath() + "/deletePropertyController";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeleteIndividualProcessingUrl() {
|
||||||
|
return vreq.getContextPath() + "/deleteIndividualController";
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Check if this logic is correct and delete prohibited does not expect a specific value
|
//TODO: Check if this logic is correct and delete prohibited does not expect a specific value
|
||||||
public boolean isDeleteProhibited() {
|
public boolean isDeleteProhibited() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import static edu.cornell.mannlib.vitro.webapp.auth.requestedAction.RequestedAct
|
||||||
import static edu.cornell.mannlib.vitro.webapp.auth.requestedAction.RequestedAction.SOME_URI;
|
import static edu.cornell.mannlib.vitro.webapp.auth.requestedAction.RequestedAction.SOME_URI;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.VClass;
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
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;
|
||||||
|
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.controller.freemarker.UrlBuilder.Route;
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao;
|
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao;
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
|
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
|
||||||
|
@ -37,6 +39,7 @@ import edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel;
|
||||||
public abstract class BaseIndividualTemplateModel extends BaseTemplateModel {
|
public abstract class BaseIndividualTemplateModel extends BaseTemplateModel {
|
||||||
|
|
||||||
private static final Log log = LogFactory.getLog(BaseIndividualTemplateModel.class);
|
private static final Log log = LogFactory.getLog(BaseIndividualTemplateModel.class);
|
||||||
|
private static final String EDIT_PATH = "editRequestDispatch";
|
||||||
|
|
||||||
protected final Individual individual;
|
protected final Individual individual;
|
||||||
protected final LoginStatusBean loginStatusBean;
|
protected final LoginStatusBean loginStatusBean;
|
||||||
|
@ -148,6 +151,22 @@ public abstract class BaseIndividualTemplateModel extends BaseTemplateModel {
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return individual.getName();
|
return individual.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeleteUrl() {
|
||||||
|
Collection<String> types = getMostSpecificTypes();
|
||||||
|
ParamMap params = new ParamMap(
|
||||||
|
"objectUri", individual.getURI(),
|
||||||
|
"cmd", "delete",
|
||||||
|
"individualName",getNameStatement().getValue()
|
||||||
|
);
|
||||||
|
Iterator<String> typesIterator = types.iterator();
|
||||||
|
if (types.iterator().hasNext()) {
|
||||||
|
String type = typesIterator.next();
|
||||||
|
params.put("individualType", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UrlBuilder.getUrl(EDIT_PATH, params);
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<String> getMostSpecificTypes() {
|
public Collection<String> getMostSpecificTypes() {
|
||||||
ObjectPropertyStatementDao opsDao = vreq.getWebappDaoFactory().getObjectPropertyStatementDao();
|
ObjectPropertyStatementDao opsDao = vreq.getWebappDaoFactory().getObjectPropertyStatementDao();
|
||||||
|
|
|
@ -106,8 +106,8 @@ http://www.w3.org/TR/html401/struct/global.html
|
||||||
<regexp name="angle" value="(-|\+)?([0-9]+(\.[0-9]+)?)(deg|grads|rad)"/>
|
<regexp name="angle" value="(-|\+)?([0-9]+(\.[0-9]+)?)(deg|grads|rad)"/>
|
||||||
<regexp name="time" value="([0-9]+(\.[0-9]+)?)(ms|s)"/>
|
<regexp name="time" value="([0-9]+(\.[0-9]+)?)(ms|s)"/>
|
||||||
<regexp name="frequency" value="([0-9]+(\.[0-9]+)?)(hz|khz)"/>
|
<regexp name="frequency" value="([0-9]+(\.[0-9]+)?)(hz|khz)"/>
|
||||||
<regexp name="length" value="((-|\+)?0|(-|\+)?([0-9]+(\.[0-9]+)?)(em|ex|px|in|cm|mm|pt|pc))"/>
|
<regexp name="length" value="((-|\+)?0|(-|\+)?([0-9]+(\.[0-9]+)?)(rem|em|ex|px|in|cm|mm|pt|pc))"/>
|
||||||
<regexp name="positiveLength" value="((\+)?0|(\+)?([0-9]+(\.[0-9]+)?)(em|ex|px|in|cm|mm|pt|pc))"/>
|
<regexp name="positiveLength" value="((\+)?0|(\+)?([0-9]+(\.[0-9]+)?)(rem|em|ex|px|in|cm|mm|pt|pc))"/>
|
||||||
<regexp name="percentage" value="(-|\+)?([0-9]+(\.[0-9]+)?)%"/>
|
<regexp name="percentage" value="(-|\+)?([0-9]+(\.[0-9]+)?)%"/>
|
||||||
<regexp name="positivePercentage" value="(\+)?([0-9]+(\.[0-9]+)?)%"/>
|
<regexp name="positivePercentage" value="(\+)?([0-9]+(\.[0-9]+)?)%"/>
|
||||||
|
|
||||||
|
@ -149,6 +149,14 @@ http://www.w3.org/TR/html401/struct/global.html
|
||||||
</regexp-list>
|
</regexp-list>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
|
||||||
|
<attribute name="dir" description="The dir attribute specifies the text direction of the element's content.">
|
||||||
|
<literal-list>
|
||||||
|
<literal value="ltr"/>
|
||||||
|
<literal value="rtl"/>
|
||||||
|
<literal value="auto"/>
|
||||||
|
</literal-list>
|
||||||
|
</attribute>
|
||||||
|
|
||||||
<attribute name="class" description="The 'class' of any HTML attribute is usually a single word, but it can also be a list of class names separated by spaces">
|
<attribute name="class" description="The 'class' of any HTML attribute is usually a single word, but it can also be a list of class names separated by spaces">
|
||||||
<regexp-list>
|
<regexp-list>
|
||||||
<regexp name="htmlClass"/>
|
<regexp name="htmlClass"/>
|
||||||
|
@ -549,12 +557,24 @@ http://www.w3.org/TR/html401/struct/global.html
|
||||||
|
|
||||||
<!-- All formatting tags -->
|
<!-- All formatting tags -->
|
||||||
|
|
||||||
<tag name="h1" action="validate"/>
|
<tag name="h1" action="validate">
|
||||||
<tag name="h2" action="validate"/>
|
<attribute name="dir"/>
|
||||||
<tag name="h3" action="validate"/>
|
</tag>
|
||||||
<tag name="h4" action="validate"/>
|
<tag name="h2" action="validate">
|
||||||
<tag name="h5" action="validate"/>
|
<attribute name="dir"/>
|
||||||
<tag name="h6" action="validate"/>
|
</tag>
|
||||||
|
<tag name="h3" action="validate">
|
||||||
|
<attribute name="dir"/>
|
||||||
|
</tag>
|
||||||
|
<tag name="h4" action="validate">
|
||||||
|
<attribute name="dir"/>
|
||||||
|
</tag>
|
||||||
|
<tag name="h5" action="validate">
|
||||||
|
<attribute name="dir"/>
|
||||||
|
</tag>
|
||||||
|
<tag name="h6" action="validate">
|
||||||
|
<attribute name="dir"/>
|
||||||
|
</tag>
|
||||||
|
|
||||||
<tag name="p" action="validate">
|
<tag name="p" action="validate">
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.dao.jena;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.apache.jena.rdf.model.Model;
|
||||||
|
import org.apache.jena.rdf.model.ModelFactory;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel;
|
||||||
|
|
||||||
|
|
||||||
|
public class RDFServiceGraphTest extends AbstractTestClass {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/**
|
||||||
|
* Test that creating a new model with the same underlying RDFServiceGraph
|
||||||
|
* does not result in a new listener registered on that graph. No matter
|
||||||
|
* how many models have been created using a given RDFServiceGraph, an event
|
||||||
|
* sent to the last-created model should be heard only once by the
|
||||||
|
* RDFService.
|
||||||
|
* @throws RDFServiceException
|
||||||
|
*/
|
||||||
|
public void testEventListening() throws RDFServiceException {
|
||||||
|
Model m = ModelFactory.createDefaultModel();
|
||||||
|
RDFService rdfService = new RDFServiceModel(m);
|
||||||
|
EventsCounter counter = new EventsCounter();
|
||||||
|
rdfService.registerListener(counter);
|
||||||
|
RDFServiceGraph g = new RDFServiceGraph(rdfService);
|
||||||
|
Model model = null;
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
model = RDFServiceGraph.createRDFServiceModel(g);
|
||||||
|
}
|
||||||
|
model.notifyEvent("event");
|
||||||
|
assertEquals(1, counter.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EventsCounter implements ChangeListener {
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyModelChange(ModelChange modelChange) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyEvent(String graphURI, Object event) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -128,6 +128,7 @@
|
||||||
<owl:ObjectProperty rdf:about="&display;restrictResultsByClass"/>
|
<owl:ObjectProperty rdf:about="&display;restrictResultsByClass"/>
|
||||||
<owl:ObjectProperty rdf:about="&display;getIndividualsForClass"/>
|
<owl:ObjectProperty rdf:about="&display;getIndividualsForClass"/>
|
||||||
<owl:ObjectProperty rdf:about="&display;hasDataGetter"/>
|
<owl:ObjectProperty rdf:about="&display;hasDataGetter"/>
|
||||||
|
<owl:DataProperty rdf:about="&display;hasDeleteQuery"/>
|
||||||
<owl:ObjectProperty rdf:about="&display;requiresAction">
|
<owl:ObjectProperty rdf:about="&display;requiresAction">
|
||||||
|
|
||||||
</owl:ObjectProperty>
|
</owl:ObjectProperty>
|
||||||
|
|
|
@ -204,6 +204,9 @@ vitro:additionalLink
|
||||||
display:hasElement
|
display:hasElement
|
||||||
a owl:ObjectProperty .
|
a owl:ObjectProperty .
|
||||||
|
|
||||||
|
display:hasDeleteQuery
|
||||||
|
a owl:DataProperty .
|
||||||
|
|
||||||
display:excludeClass
|
display:excludeClass
|
||||||
a owl:ObjectProperty .
|
a owl:ObjectProperty .
|
||||||
|
|
||||||
|
|
|
@ -720,6 +720,7 @@ there_are_no_entries_for_selection = There are no entries in the system from whi
|
||||||
the_range_class_does_not_exist= The range class for this property does not exist in the system.
|
the_range_class_does_not_exist= The range class for this property does not exist in the system.
|
||||||
editing_prohibited = This property is currently configured to prohibit editing.
|
editing_prohibited = This property is currently configured to prohibit editing.
|
||||||
confirm_entry_deletion_from = Are you sure you want to delete the following entry from
|
confirm_entry_deletion_from = Are you sure you want to delete the following entry from
|
||||||
|
confirm_individual_deletion = Are you sure you want to delete the following individual?
|
||||||
|
|
||||||
edit_date_time_value = Edit Date/Time Value
|
edit_date_time_value = Edit Date/Time Value
|
||||||
create_date_time_value = Create Date/Time Value
|
create_date_time_value = Create Date/Time Value
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<form method="POST" action="${formUrls.createPassword}" class="customForm" role="create password">
|
<form method="POST" action="${formUrls.createPassword}" class="customForm" role="create password">
|
||||||
<input type="hidden" name="user" value="${userAccount.emailAddress}" role="input" />
|
<input type="hidden" name="user" value="${userAccount.emailAddress}" role="input" />
|
||||||
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" role="input" />
|
<input type="hidden" name="key" value="${userAccount.emailKey}" role="input" />
|
||||||
|
|
||||||
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
|
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
|
||||||
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
|
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<section id="reset-password" role="region">
|
<section id="reset-password" role="region">
|
||||||
<form method="POST" action="${formUrls.resetPassword}" class="customForm" role="create password">
|
<form method="POST" action="${formUrls.resetPassword}" class="customForm" role="create password">
|
||||||
<input type="hidden" name="user" value="${userAccount.emailAddress}" />
|
<input type="hidden" name="user" value="${userAccount.emailAddress}" />
|
||||||
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" />
|
<input type="hidden" name="key" value="${userAccount.emailKey}" />
|
||||||
|
|
||||||
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
|
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
|
||||||
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
|
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<#-- $This file is distributed under the terms of the license in LICENSE$ -->
|
||||||
|
<#if editConfiguration.pageData.redirectUrl??>
|
||||||
|
<#assign redirectUrl = editConfiguration.pageData.redirectUrl />
|
||||||
|
<#else>
|
||||||
|
<#assign redirectUrl = "/" />
|
||||||
|
</#if>
|
||||||
|
<#if editConfiguration.pageData.individualName??>
|
||||||
|
<#assign individualName = editConfiguration.pageData.individualName />
|
||||||
|
</#if>
|
||||||
|
<#if editConfiguration.pageData.individualType??>
|
||||||
|
<#assign individualType = editConfiguration.pageData.individualType />
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<form action="${editConfiguration.deleteIndividualProcessingUrl}" method="get">
|
||||||
|
<h2>${i18n().confirm_individual_deletion} </h2>
|
||||||
|
|
||||||
|
<input type="hidden" name="individualUri" value="${editConfiguration.objectUri}" role="input" />
|
||||||
|
<input type="hidden" name="redirectUrl" value="${redirectUrl}" role="input" />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<#if individualType??>
|
||||||
|
${individualType}
|
||||||
|
</#if>
|
||||||
|
<#if individualName??>
|
||||||
|
${individualName}
|
||||||
|
</#if>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<p class="submit">
|
||||||
|
<input type="submit" id="submit" value="${i18n().delete_button}" role="button"/>
|
||||||
|
or
|
||||||
|
<a class="cancel" title="${i18n().cancel_title}" href="${editConfiguration.cancelUrl}">${i18n().cancel_link}</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
|
@ -39,7 +39,7 @@
|
||||||
theme_advanced_resizing : true,
|
theme_advanced_resizing : true,
|
||||||
height : "${height}",
|
height : "${height}",
|
||||||
width : "${width}",
|
width : "${width}",
|
||||||
valid_elements : "a[href|name|title],br,p,i,em,cite,strong/b,u,sub,sup,ul,ol,li",
|
valid_elements : "tr[*],td[*],tbody[*],table[*],a[href|name|title],br,p[style],i,em,cite,strong/b,u,sub,sup,ul,ol,li,h1[dir|style|id],h2[dir|style|id],h3[dir|style|id],h4,h5,h6,div[style|class],span[dir|style|class]",
|
||||||
fix_list_elements : true,
|
fix_list_elements : true,
|
||||||
fix_nesting : true,
|
fix_nesting : true,
|
||||||
cleanup_on_startup : true,
|
cleanup_on_startup : true,
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
paste_use_dialog : false,
|
paste_use_dialog : false,
|
||||||
paste_auto_cleanup_on_paste : true,
|
paste_auto_cleanup_on_paste : true,
|
||||||
paste_convert_headers_to_strong : true,
|
paste_convert_headers_to_strong : true,
|
||||||
paste_strip_class_attributes : "all",
|
paste_strip_class_attributes : "mso",
|
||||||
paste_remove_spans : true,
|
paste_remove_spans : true,
|
||||||
paste_remove_styles : true,
|
paste_remove_styles : true,
|
||||||
paste_retain_style_properties : ""
|
paste_retain_style_properties : ""
|
||||||
|
|
|
@ -203,6 +203,16 @@ name will be used as the label. -->
|
||||||
<a class="edit-${propertyLocalName}" href="${url}" title="${i18n().edit_entry}"><img class="edit-individual" data-range="${rangeUri}" src="${urls.images}/individual/editIcon.gif" alt="${i18n().edit_entry}" /></a>
|
<a class="edit-${propertyLocalName}" href="${url}" title="${i18n().edit_entry}"><img class="edit-individual" data-range="${rangeUri}" src="${urls.images}/individual/editIcon.gif" alt="${i18n().edit_entry}" /></a>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
|
<#macro deleteIndividualLink individual redirectUrl="/">
|
||||||
|
<#local url = individual.deleteUrl + "&redirectUrl=" + "${redirectUrl}">
|
||||||
|
<@showDeleteIndividualLink url />
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
|
||||||
|
<#macro showDeleteIndividualLink url>
|
||||||
|
<a class="delete-individual" href="${url}" title="${i18n().delete_entry}"><img class="delete-individual" src="${urls.images}/individual/deleteIcon.gif" alt="${i18n().delete_entry}" /></a>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
<#macro deleteLink propertyLocalName propertyName statement rangeUri="">
|
<#macro deleteLink propertyLocalName propertyName statement rangeUri="">
|
||||||
<#local url = statement.deleteUrl>
|
<#local url = statement.deleteUrl>
|
||||||
<#if url?has_content>
|
<#if url?has_content>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>${i18n().search_form}</legend>
|
<legend>${i18n().search_form}</legend>
|
||||||
|
|
||||||
<form id="search-form" action="${urls.search}" name="search" role="search" accept-charset="UTF-8" method="POST">
|
<form id="search-form" action="${urls.customsearch}" name="search" role="search" accept-charset="UTF-8" method="POST">
|
||||||
<div id="search-field">
|
<div id="search-field">
|
||||||
<input type="text" name="querytext" class="search-vitro" value="${querytext!}" autocapitalize="off" />
|
<input type="text" name="querytext" class="search-vitro" value="${querytext!}" autocapitalize="off" />
|
||||||
<input type="submit" value="${i18n().search_button}" class="submit">
|
<input type="submit" value="${i18n().search_button}" class="submit">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<#-- $This file is distributed under the terms of the license in LICENSE$ -->
|
<#-- $This file is distributed under the terms of the license in LICENSE$ -->
|
||||||
|
|
||||||
<div id="searchBlock">
|
<div id="searchBlock">
|
||||||
<form id="searchForm" action="${urls.search}" accept-charset="UTF-8" method="POST">
|
<form id="searchForm" action="${urls.customsearch}" accept-charset="UTF-8" method="POST">
|
||||||
<label for="search">${i18n().search_button}</label>
|
<label for="search">${i18n().search_button}</label>
|
||||||
<input type="text" name="querytext" id="search" class="search-form-item" value="${querytext!}" size="20" autocapitalize="off" />
|
<input type="text" name="querytext" id="search" class="search-form-item" value="${querytext!}" size="20" autocapitalize="off" />
|
||||||
<input class="search-form-submit" name="submit" type="submit" value="${i18n().search_button}" />
|
<input class="search-form-submit" name="submit" type="submit" value="${i18n().search_button}" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue