diff --git a/install/rulesets/ruleset.xml b/install/rulesets/ruleset.xml index 2d1d9d518..31ec9794c 100644 --- a/install/rulesets/ruleset.xml +++ b/install/rulesets/ruleset.xml @@ -3608,7 +3608,7 @@ Page page Página - area + logicalPageNumber _ucc_id physPageNumber @@ -3638,6 +3638,10 @@ logicalPageNumber physPageNumber + + BoundBook Gebundenes Buch diff --git a/pom.xml b/pom.xml index de8384526..401dddbbf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.goobi.workflow workflow-base - 24.04.30 + 24.05 workflow-core diff --git a/src/main/java/de/sub/goobi/config/ConfigurationHelper.java b/src/main/java/de/sub/goobi/config/ConfigurationHelper.java index 77d6b6a18..b3edfd840 100644 --- a/src/main/java/de/sub/goobi/config/ConfigurationHelper.java +++ b/src/main/java/de/sub/goobi/config/ConfigurationHelper.java @@ -1115,6 +1115,10 @@ public boolean isMetsEditorShowOCRButton() { return getLocalBoolean("showOcrButton", false); } + public boolean isShowNamedEntityEditor() { + return getLocalBoolean("showNamedEntityEditor", false); + } + public boolean isMetsEditorDisplayFileManipulation() { return getLocalBoolean("MetsEditorDisplayFileManipulation", false); } diff --git a/src/main/java/de/sub/goobi/metadaten/AltoSaver.java b/src/main/java/de/sub/goobi/metadaten/AltoSaver.java index f96d8e91e..0671c893e 100644 --- a/src/main/java/de/sub/goobi/metadaten/AltoSaver.java +++ b/src/main/java/de/sub/goobi/metadaten/AltoSaver.java @@ -28,7 +28,12 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.apache.commons.lang.StringUtils; import org.goobi.beans.AltoChange; import org.jdom2.Document; import org.jdom2.Element; @@ -43,7 +48,9 @@ import de.sub.goobi.helper.StorageProvider; import de.sub.goobi.helper.XmlTools; +import lombok.extern.log4j.Log4j2; +@Log4j2 public class AltoSaver { private static SAXBuilder sax = XmlTools.getSAXBuilder(); private static XPathFactory xFactory = XPathFactory.instance(); @@ -54,13 +61,50 @@ public static void saveAltoChanges(Path altoFile, AltoChange[] changes) throws J } Document doc = sax.build(altoFile.toFile()); Namespace namespace = Namespace.getNamespace("alto", doc.getRootElement().getNamespaceURI()); + boolean writeTags = Arrays.stream(changes).anyMatch(change -> "setNamedEntity".equals(change.getAction())); + //first remove all tags and tagrefs. Create new Tags element if necessary + XPathExpression tagsXPath = xFactory.compile("//alto:Tags", Filters.element(), null, namespace); + Element tagsElement = tagsXPath.evaluateFirst(doc); + if (writeTags) { + if (tagsElement == null) { + int layoutIndex = doc.getRootElement().indexOf(doc.getRootElement().getChild("Layout", namespace)); + tagsElement = new Element("Tags", doc.getRootElement().getNamespace()); + doc.getRootElement().addContent(layoutIndex, tagsElement); + } + tagsElement.removeChildren("NamedEntityTag", namespace); + XPathExpression stringsXPath = xFactory.compile("//alto:String", Filters.element(), null, namespace); + List stringElements = stringsXPath.evaluate(doc); + stringElements.forEach(string -> string.removeAttribute("TAGREFS")); + } + int tagCounter = 0; for (AltoChange change : changes) { - String xpath = String.format("//alto:String[@ID='%s']", change.getWordId()); - XPathExpression compXpath = xFactory.compile(xpath, Filters.element(), null, namespace); - Element stringEl = compXpath.evaluateFirst(doc); - if (stringEl != null) { - stringEl.setAttribute("CONTENT", change.getValue()); + Element stringEl = getWordById(doc, namespace, change.getWordId()); + if ("changeContent".equals(change.getAction())) { + if (stringEl != null) { + stringEl.setAttribute("CONTENT", change.getValue()); + } + } else if ("setNamedEntity".equals(change.getAction())) { + String tagId = String.format("Tag%d", ++tagCounter); + Element namedEntityTag = new Element("NamedEntityTag", doc.getRootElement().getNamespace()); + namedEntityTag.setAttribute("ID", tagId); + if (StringUtils.isNotBlank(change.getEntity().getLabel())) { + namedEntityTag.setAttribute("LABEL", change.getEntity().getLabel()); + } + if (StringUtils.isNotBlank(change.getEntity().getType())) { + namedEntityTag.setAttribute("TYPE", change.getEntity().getType()); + } + if (StringUtils.isNotBlank(change.getEntity().getUri())) { + namedEntityTag.setAttribute("URI", change.getEntity().getUri()); + } + tagsElement.addContent(namedEntityTag); + change.getWords().stream().map(id -> getWordById(doc, namespace, id)).filter(Objects::nonNull).forEach(ele -> { + String tagRefs = Optional.ofNullable(ele.getAttributeValue("TAGREFS")).orElse("").trim(); + tagRefs = (tagRefs + " " + tagId).trim(); + ele.setAttribute("TAGREFS", tagRefs); + }); + } else { + log.error("Cannot add alto change: Unknown action {}", change.getAction()); } } try (OutputStream out = StorageProvider.getInstance().newOutputStream(altoFile)) { @@ -68,4 +112,10 @@ public static void saveAltoChanges(Path altoFile, AltoChange[] changes) throws J xmlOut.output(doc, out); } } + + public static Element getWordById(Document doc, Namespace namespace, String id) { + String xpath = String.format("//alto:String[@ID='%s']", id); + XPathExpression compXpath = xFactory.compile(xpath, Filters.element(), null, namespace); + return compXpath.evaluateFirst(doc); + } } diff --git a/src/main/java/de/sub/goobi/metadaten/Metadaten.java b/src/main/java/de/sub/goobi/metadaten/Metadaten.java index bd6a57170..f201577f0 100644 --- a/src/main/java/de/sub/goobi/metadaten/Metadaten.java +++ b/src/main/java/de/sub/goobi/metadaten/Metadaten.java @@ -68,6 +68,7 @@ import org.goobi.api.display.helper.ConfigDisplayRules; import org.goobi.api.display.helper.NormDatabase; import org.goobi.beans.AltoChange; +import org.goobi.beans.AuthorityData; import org.goobi.beans.ImageComment; import org.goobi.beans.Process; import org.goobi.beans.Processproperty; @@ -1826,7 +1827,7 @@ public String XMLschreiben() { this.myProzess.setSortHelperMetadata(zaehlen.getNumberOfUghElements(this.logicalTopstruct, CountType.METADATA)); try { this.myProzess - .setSortHelperImages(StorageProvider.getInstance().getNumberOfFiles(Paths.get(this.myProzess.getImagesOrigDirectory(true)))); + .setSortHelperImages(StorageProvider.getInstance().getNumberOfFiles(Paths.get(this.myProzess.getImagesOrigDirectory(true)))); ProcessManager.saveProcess(this.myProzess); } catch (DAOException e) { Helper.setFehlerMeldung("fehlerNichtSpeicherbar", e); @@ -3681,6 +3682,10 @@ public void saveAlto() { } } + public boolean isShowNamedEntityEditor() { + return ConfigurationHelper.getInstance().isShowNamedEntityEditor(); + } + public String getOcrAddress() { int startseite = -1; int endseite = -1; @@ -5185,4 +5190,35 @@ private void readMetadataEditorExtensions() { extension = extensions.get(0); } } + + public String getAuthorityMetadataJSON() { + List persons = + getMyPersonen().stream().filter(p -> StringUtils.isNotBlank(p.getNormdataValue())).map(MetaPerson::getP).collect(Collectors.toList()); + List corporates = + getCorporates().stream() + .filter(p -> StringUtils.isNotBlank(p.getNormdataValue())) + .map(MetaCorporate::getCorporate) + .collect(Collectors.toList()); + List metadata = + getMyMetadaten().stream() + .filter(p -> StringUtils.isNotBlank(p.getNormdataValue())) + .map(MetadatumImpl::getMd) + .collect(Collectors.toList()); + + List authorityList = new ArrayList<>(); + for (Person person : persons) { + AuthorityData data = new AuthorityData(person.getDisplayname(), person.getAuthorityURI() + person.getAuthorityValue()); + authorityList.add(data); + } + for (Corporate corporate : corporates) { + AuthorityData data = new AuthorityData(corporate.getMainName(), corporate.getAuthorityURI() + corporate.getAuthorityValue()); + authorityList.add(data); + } + for (Metadata md : metadata) { + AuthorityData data = new AuthorityData(md.getValue(), md.getAuthorityURI() + md.getAuthorityValue()); + authorityList.add(data); + } + + return new Gson().toJson(authorityList); + } } diff --git a/src/main/java/io/goobi/workflow/xslt/XsltPreparatorDocket.java b/src/main/java/io/goobi/workflow/xslt/XsltPreparatorDocket.java index eb1b2e2ca..1e8f5d5aa 100644 --- a/src/main/java/io/goobi/workflow/xslt/XsltPreparatorDocket.java +++ b/src/main/java/io/goobi/workflow/xslt/XsltPreparatorDocket.java @@ -528,6 +528,7 @@ public Document createDocument(Process process, boolean addNamespace, boolean in if (s.getBearbeitungsbenutzer() != null && s.getBearbeitungsbenutzer().getNachVorname() != null) { Element user = new Element(ELEMENT_USER, namespace); user.setText(s.getBearbeitungsbenutzer().getNachVorname()); + user.setAttribute(ATTRIBUTE_LOCATION, s.getBearbeitungsbenutzer().getStandort()); stepElement.addContent(user); } Element editType = new Element(ELEMENT_EDITTYPE, namespace); diff --git a/src/main/java/org/goobi/api/rest/AuthorizationFilter.java b/src/main/java/org/goobi/api/rest/AuthorizationFilter.java index 181fd081e..d09d8500d 100644 --- a/src/main/java/org/goobi/api/rest/AuthorizationFilter.java +++ b/src/main/java/org/goobi/api/rest/AuthorizationFilter.java @@ -70,7 +70,7 @@ public class AuthorizationFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { - // first try to get basic authentication + // try to get basic authentication String authentication = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); // get token, decode it, check if token exists in db @@ -82,25 +82,17 @@ public void filter(ContainerRequestContext requestContext) throws IOException { String tokenHash = new Sha256Hash(keyName, ConfigurationHelper.getInstance().getApiTokenSalt(), 10000).toBase64(); AuthenticationToken token = UserManager.getAuthenticationToken(tokenHash); - if (token == null) { - // token does not exist, abort - requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) - .entity("API Token is invalid.") - .build()); - return; - } - //if token exists, check if token has the permission to access the current request - String methodType = requestContext.getMethod(); - String requestUri = req.getPathInfo(); - for (AuthenticationMethodDescription method : token.getMethods()) { - if (method.isSelected() && methodType.equalsIgnoreCase(method.getMethodType()) && Pattern.matches(method.getUrl(), requestUri)) { - return; + if (token != null) { + + //if token exists, check if token has the permission to access the current request + String methodType = requestContext.getMethod(); + String requestUri = req.getPathInfo(); + for (AuthenticationMethodDescription method : token.getMethods()) { + if (method.isSelected() && methodType.equalsIgnoreCase(method.getMethodType()) && Pattern.matches(method.getUrl(), requestUri)) { + return; + } } } - requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) - .entity("The API token has no access to the Goobi REST API for " + requestUri) - .build()); - return; } // get token @@ -151,7 +143,6 @@ public void filter(ContainerRequestContext requestContext) throws IOException { try { conf = RestConfig.getConfigForPath(pathInfo); } catch (ConfigurationException e) { - // TODO Auto-generated catch block log.error(e); } if (conf != null && !conf.getCorsMethods().isEmpty()) { diff --git a/src/main/java/org/goobi/api/rest/Login.java b/src/main/java/org/goobi/api/rest/Login.java index 23f40ec5e..e56cf625f 100644 --- a/src/main/java/org/goobi/api/rest/Login.java +++ b/src/main/java/org/goobi/api/rest/Login.java @@ -37,6 +37,7 @@ import javax.ws.rs.Path; import javax.ws.rs.core.Context; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; @@ -99,20 +100,25 @@ public void openIdLogin(@FormParam("error") String error, @FormParam("id_token") // get the user by the configured claim from the JWT String login = jwt.getClaim(config.getOIDCIdClaim()).asString(); - log.debug("logging in user " + login); - User user = UserManager.getUserBySsoId(login); - if (user == null) { - userBean.setSsoError("Could not find user in Goobi database. Please contact your admin to add your SSO ID to the database."); - servletResponse.sendRedirect("/goobi/uii/logout.xhtml"); - return; + if (StringUtils.isBlank(login)) { + log.error("The configured claim '{}' is not present in the response.", config.getOIDCIdClaim()); + } else { + log.debug("logging in user "); + User user = UserManager.getUserBySsoId(login); + if (user == null) { + userBean.setSsoError( + "Could not find user in Goobi database. Please contact your admin to add your SSO ID to the database."); + servletResponse.sendRedirect("/goobi/uii/logout.xhtml"); + return; + } + userBean.setSsoError(null); + user.lazyLoad(); + userBean.setMyBenutzer(user); + userBean.setRoles(user.getAllUserRoles()); + userBean.setMyBenutzer(user); + //add the user to the sessionform that holds information about all logged in users + sessionForm.updateSessionUserName(servletRequest.getSession(), user); } - userBean.setSsoError(null); - user.lazyLoad(); - userBean.setMyBenutzer(user); - userBean.setRoles(user.getAllUserRoles()); - userBean.setMyBenutzer(user); - //add the user to the sessionform that holds information about all logged in users - sessionForm.updateSessionUserName(servletRequest.getSession(), user); } else { if (!nonce.equals(jwt.getClaim("nonce").asString())) { log.error("nonce does not match. Not logging user in"); @@ -184,22 +190,26 @@ public void openIdLoginAuthFlow(@FormParam("error") String error, @FormParam("co // get the user by the configured claim from the JWT String login = jwt.getClaim(config.getOIDCIdClaim()).asString(); - log.debug("logging in user " + login); - User user = UserManager.getUserBySsoId(login); - System.out.println("Trying to log in user with SSOID; " + login); - if (user == null) { - userBean.setSsoError( - "Could not find user in Goobi database. Please contact your admin to add your SSO ID to the database."); - servletResponse.sendRedirect("/goobi/uii/logout.xhtml"); - return; + if (StringUtils.isBlank(login)) { + log.error("The configured claim '{}' is not present in the response.", config.getOIDCIdClaim()); + log.error("The following are available: {}", String.join(",", jwt.getClaims().keySet())); + } else { + log.debug("logging in user "); + User user = UserManager.getUserBySsoId(login); + if (user == null) { + userBean.setSsoError( + "Could not find user in Goobi database. Please contact your admin to add your SSO ID to the database."); + servletResponse.sendRedirect("/goobi/uii/logout.xhtml"); + return; + } + userBean.setSsoError(null); + user.lazyLoad(); + userBean.setMyBenutzer(user); + userBean.setRoles(user.getAllUserRoles()); + userBean.setMyBenutzer(user); + //add the user to the sessionform that holds information about all logged in users + sessionForm.updateSessionUserName(servletRequest.getSession(), user); } - userBean.setSsoError(null); - user.lazyLoad(); - userBean.setMyBenutzer(user); - userBean.setRoles(user.getAllUserRoles()); - userBean.setMyBenutzer(user); - //add the user to the sessionform that holds information about all logged in users - sessionForm.updateSessionUserName(servletRequest.getSession(), user); } else { if (!nonce.equals(jwt.getClaim("nonce").asString())) { log.error("nonce does not match. Not logging user in"); @@ -255,12 +265,12 @@ public String apacheHeaderLogin() throws IOException { LoginBean userBean = Helper.getLoginBeanFromSession(servletRequest.getSession()); User user = UserManager.getUserBySsoId(ssoId); if (user == null) { - log.debug(LoginBean.LOGIN_LOG_PREFIX + "There is no user with ssoId \"" + ssoId + "\"."); + log.debug(LoginBean.LOGIN_LOG_PREFIX + "There is no user with this ssoId."); userBean.setSsoError("Could not find user in Goobi database. Please contact your admin to add your SSO ID to the database."); servletResponse.sendRedirect("/goobi/uii/logout.xhtml"); return ""; } - log.debug(LoginBean.LOGIN_LOG_PREFIX + "User \"" + user.getLogin() + "\" can be logged in via SSO:"); + log.debug(LoginBean.LOGIN_LOG_PREFIX + "User can be logged in via SSO:"); userBean.setSsoError(null); user.lazyLoad(); userBean.setMyBenutzer(user); diff --git a/src/main/java/org/goobi/beans/AltoChange.java b/src/main/java/org/goobi/beans/AltoChange.java index a4e2e9ad9..9e00ce290 100644 --- a/src/main/java/org/goobi/beans/AltoChange.java +++ b/src/main/java/org/goobi/beans/AltoChange.java @@ -24,6 +24,8 @@ */ package org.goobi.beans; +import java.util.List; + import lombok.Data; @Data @@ -31,4 +33,6 @@ public class AltoChange { private String wordId; private String action; private String value; + private List words; + private NamedEntity entity; } diff --git a/src/main/java/org/goobi/beans/AuthorityData.java b/src/main/java/org/goobi/beans/AuthorityData.java new file mode 100644 index 000000000..5300a3a5d --- /dev/null +++ b/src/main/java/org/goobi/beans/AuthorityData.java @@ -0,0 +1,13 @@ +package org.goobi.beans; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class AuthorityData { + + private final String value; + private final String uri; + +} diff --git a/src/main/java/org/goobi/beans/NamedEntity.java b/src/main/java/org/goobi/beans/NamedEntity.java new file mode 100644 index 000000000..5abc1ce74 --- /dev/null +++ b/src/main/java/org/goobi/beans/NamedEntity.java @@ -0,0 +1,40 @@ +package org.goobi.beans; + +import java.util.Objects; + +import lombok.Data; + +@Data +public class NamedEntity { + private String id; + private String label; + private String type; + private String uri; + + public boolean equals(Object o) { + if (o != null && o.getClass().equals(this.getClass())) { + return Objects.equals(this.id, ((NamedEntity) o).id); + } else { + return false; + } + } + + @Override + public int hashCode() { + if (this.id != null) { + return this.id.hashCode(); + } else { + return 0; + } + } + + public NamedEntity() { + } + + public NamedEntity(String id, String label, String type, String uri) { + this.id = id; + this.label = label; + this.type = type; + this.uri = uri; + } +} diff --git a/src/main/java/org/goobi/beans/Process.java b/src/main/java/org/goobi/beans/Process.java index 5a340420f..86a802e74 100644 --- a/src/main/java/org/goobi/beans/Process.java +++ b/src/main/java/org/goobi/beans/Process.java @@ -1358,7 +1358,7 @@ public String getMethodFromName(String methodName) { return (String) o; } catch (SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException exception) { - log.error(exception); + // this exception is expected } try { diff --git a/src/main/java/org/goobi/beans/SimpleAlto.java b/src/main/java/org/goobi/beans/SimpleAlto.java index f61e9e16d..88dfb28ab 100644 --- a/src/main/java/org/goobi/beans/SimpleAlto.java +++ b/src/main/java/org/goobi/beans/SimpleAlto.java @@ -44,22 +44,30 @@ import de.sub.goobi.helper.StorageProvider; import de.sub.goobi.helper.XmlTools; import lombok.Data; +import lombok.extern.log4j.Log4j2; +import software.amazon.awssdk.utils.StringUtils; @Data +@Log4j2 public class SimpleAlto { private static Namespace altoNamespace = Namespace.getNamespace("alto", "http://www.loc.gov/standards/alto/ns-v2#"); private static Namespace altov4Namespace = Namespace.getNamespace("alto", "http://www.loc.gov/standards/alto/ns-v4#"); private static XPathFactory xFactory = XPathFactory.instance(); private static XPathExpression linesXpath = xFactory.compile("//alto:TextLine", Filters.element(), null, altoNamespace); private static XPathExpression wordXpath = xFactory.compile("./alto:String", Filters.element(), null, altoNamespace); + private static XPathExpression namedEntityXpath = xFactory.compile("//alto:NamedEntityTag", Filters.element(), null, altoNamespace); private static XPathExpression linesV4Xpath = xFactory.compile("//alto:TextLine", Filters.element(), null, altov4Namespace); private static XPathExpression wordV4Xpath = xFactory.compile("./alto:String", Filters.element(), null, altov4Namespace); + private static XPathExpression namedEntityV4Xpath = xFactory.compile("//alto:NamedEntityTag", Filters.element(), null, altov4Namespace); private List lines; private Map lineMap = new HashMap<>(); private Map wordMap = new HashMap<>(); + private List namedEntities = new ArrayList<>(); + private Map> namedEntityMap = new HashMap<>(); + public SimpleAlto() { super(); } @@ -96,6 +104,9 @@ public static SimpleAlto readAlto(Path altoPath) throws IOException, JDOMExcepti xmlLines = linesV4Xpath.evaluate(doc); useV4 = true; } + + createNamedEntities(doc, alto); + for (Element xmlLine : xmlLines) { SimpleAltoLine line = new SimpleAltoLine(); line.setId(xmlLine.getAttributeValue("ID")); @@ -119,6 +130,8 @@ public static SimpleAlto readAlto(Path altoPath) throws IOException, JDOMExcepti word.setWidth((int) Double.parseDouble(xmlWord.getAttributeValue("WIDTH", "0"))); word.setHeight((int) Double.parseDouble(xmlWord.getAttributeValue("HEIGHT", "0"))); + parseNamedEntityTagRefs(alto, xmlWord); + words.add(word); alto.wordMap.put(word.getId(), word); } @@ -130,4 +143,35 @@ public static SimpleAlto readAlto(Path altoPath) throws IOException, JDOMExcepti return alto; } + + public static void parseNamedEntityTagRefs(SimpleAlto alto, Element xmlWord) { + String tagrefString = xmlWord.getAttributeValue("TAGREFS", ""); + String[] tagrefs = tagrefString.split("[\\s,;]+"); + for (String tagref : tagrefs) { + if (StringUtils.isNotBlank(tagref)) { + List tagWordList = alto.namedEntityMap.getOrDefault((tagref), new ArrayList<>()); + tagWordList.add(xmlWord.getAttributeValue("ID")); + } + } + } + + public static void createNamedEntities(Document doc, SimpleAlto alto) { + List namedEntityElements = namedEntityXpath.evaluate(doc); + if (namedEntityElements.isEmpty()) { + namedEntityElements = namedEntityV4Xpath.evaluate(doc); + } + for (Element entityElement : namedEntityElements) { + String id = entityElement.getAttributeValue("ID"); + String label = entityElement.getAttributeValue("LABEL"); + String type = entityElement.getAttributeValue("TYPE"); + String uri = entityElement.getAttributeValue("URI"); + if (StringUtils.isNotBlank(id)) { + NamedEntity entity = new NamedEntity(id, label, type, uri); + alto.namedEntities.add(entity); + alto.namedEntityMap.put(id, new ArrayList<>()); + } else { + log.warn("Found named entity without id. Entity cannot be imported and will be ignored"); + } + } + } } diff --git a/src/main/java/org/goobi/beans/User.java b/src/main/java/org/goobi/beans/User.java index 54e6870ad..52b1b01b1 100644 --- a/src/main/java/org/goobi/beans/User.java +++ b/src/main/java/org/goobi/beans/User.java @@ -252,7 +252,7 @@ public class User extends AbstractJournal implements DatabaseObject, Serializabl @Getter @Setter - private String processListDefaultSortField = "titel"; + private String processListDefaultSortField = "prozesse.titel"; @Getter @Setter private String processListDefaultSortOrder = " asc"; @@ -302,7 +302,6 @@ public class User extends AbstractJournal implements DatabaseObject, Serializabl @Setter private AuthenticationToken token; - @Getter @Setter // any additional data is hold in a map and gets stored in an xml column, it is searchable using xpath @@ -403,13 +402,13 @@ public boolean istPasswortKorrekt(String inPasswort) { if (inPasswort == null || inPasswort.length() == 0) { return false; } else /* Verbindung zum LDAP-Server aufnehmen und Login prüfen, wenn LDAP genutzt wird */ - if (ldapGruppe.getAuthenticationTypeEnum() == AuthenticationType.LDAP) { - LdapAuthentication myldap = new LdapAuthentication(); - return myldap.isUserPasswordCorrect(this, inPasswort); - } else { - String hashedPasswordBase64 = new Sha256Hash(inPasswort, passwordSalt, 10000).toBase64(); - return this.encryptedPassword.equals(hashedPasswordBase64); - } + if (ldapGruppe.getAuthenticationTypeEnum() == AuthenticationType.LDAP) { + LdapAuthentication myldap = new LdapAuthentication(); + return myldap.isUserPasswordCorrect(this, inPasswort); + } else { + String hashedPasswordBase64 = new Sha256Hash(inPasswort, passwordSalt, 10000).toBase64(); + return this.encryptedPassword.equals(hashedPasswordBase64); + } } public String getPasswordHash(String plainTextPassword) { diff --git a/src/main/java/org/goobi/goobiScript/GoobiScriptAddStep.java b/src/main/java/org/goobi/goobiScript/GoobiScriptAddStep.java index d13c69137..e11f59e02 100644 --- a/src/main/java/org/goobi/goobiScript/GoobiScriptAddStep.java +++ b/src/main/java/org/goobi/goobiScript/GoobiScriptAddStep.java @@ -57,7 +57,7 @@ public String getSampleCall() { addNewActionToSampleCall(sb, "This GoobiScript allows to add a new workflow step into the workflow."); addParameterToSampleCall(sb, STEPTITLE, "Scanning", "Title of the workflow step to add"); addParameterToSampleCall(sb, NUMBER, "5", - "This number defines where in the workflow this new step is ordered into. Numerical values or the keyword 'end' are permitted in order to insert the step at a specific position or at the end"); + "This number defines where in the workflow this new step is ordered into. Numerical values or the keyword \"end\" are permitted in order to insert the step at a specific position or at the end"); return sb.toString(); } diff --git a/src/main/java/org/goobi/managedbeans/LoginBean.java b/src/main/java/org/goobi/managedbeans/LoginBean.java index 79d902ae2..1f4c139b4 100644 --- a/src/main/java/org/goobi/managedbeans/LoginBean.java +++ b/src/main/java/org/goobi/managedbeans/LoginBean.java @@ -217,7 +217,7 @@ public String Einloggen() { // Check the password if (!user.istPasswortKorrekt(this.passwort)) { Helper.setFehlerMeldung(HTML_LOGIN_FIELD_ID, "", Helper.getTranslation(WRONG_LOGIN)); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. Password of user with login " + this.login + " was not correct."); + log.debug(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. Password was not correct."); return ""; } log.debug(LoginBean.LOGIN_LOG_PREFIX + "Password was correct."); @@ -233,12 +233,6 @@ public String Einloggen() { this.myBenutzer.lazyLoad(); roles = myBenutzer.getAllUserRoles(); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Following user was logged in successfully:"); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Login name: " + user.getLogin()); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "First name: " + user.getVorname()); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Last name: " + user.getNachname()); - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Login type: " + user.getLdapGruppe().getAuthenticationType()); - String dashboard = user.getDashboardPlugin(); if (StringUtils.isBlank(dashboard) || "null".equals(dashboard)) { log.debug(LoginBean.LOGIN_LOG_PREFIX + "Loading start page..."); @@ -254,11 +248,11 @@ private static User findUserByLoginName(String login) { String userString = "login='" + MySQLHelper.escapeSql(login) + "'"; List users = UserManager.getUsers(null, userString, null, null, null); if (users != null && users.size() == 1 && users.get(0) != null) { - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Found user with login name " + login + " in database."); + log.debug(LoginBean.LOGIN_LOG_PREFIX + "Found user in database."); return users.get(0); } else { Helper.setFehlerMeldung(HTML_LOGIN_FIELD_ID, "", Helper.getTranslation(WRONG_LOGIN)); - log.error(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. User with login name " + login + " does not exist."); + log.error(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. User does not exist."); return null; } } catch (DAOException exception) { @@ -274,11 +268,11 @@ private static User findUserByMail(String email) { String userString = "email='" + MySQLHelper.escapeSql(email) + "'"; List users = UserManager.getUsers(null, userString, null, null, null); if (users != null && users.size() == 1 && users.get(0) != null) { - log.debug(LoginBean.LOGIN_LOG_PREFIX + "Found user with email " + email + " in database."); + log.debug(LoginBean.LOGIN_LOG_PREFIX + "Found user in database."); return users.get(0); } else { Helper.setFehlerMeldung(HTML_LOGIN_FIELD_ID, "", Helper.getTranslation(WRONG_LOGIN)); - log.error(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. User with email " + email + " does not exist."); + log.error(LoginBean.LOGIN_LOG_PREFIX + "Login canceled. User does not exist."); return null; } } catch (DAOException exception) { @@ -411,7 +405,6 @@ public void openIDIFLogin() { ec.redirect(builder.build().toString()); } catch (URISyntaxException | IOException e) { - // TODO Auto-generated catch block log.error(e); } diff --git a/src/main/resources/goobi_config.properties b/src/main/resources/goobi_config.properties index 33fc1156e..128d59721 100644 --- a/src/main/resources/goobi_config.properties +++ b/src/main/resources/goobi_config.properties @@ -198,6 +198,8 @@ MaxParallelThumbnailRequests=100 # OCR-Button fuer ausgewaehltes Strukturelement anzeigen showOcrButton=false +# if true, the alto editor within the metadata editor allows setting named entity tags for words the alto file +showNamedEntityEditor=false # Basispfad fuer OCR (ohne Parameter) ocrUrl= diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 85dc78505..3e98dafae 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -58,6 +58,12 @@ + + + + + + diff --git a/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties index 96f0b1dbb..77e9c4647 100644 --- a/src/main/resources/messages_de.properties +++ b/src/main/resources/messages_de.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Abgeschlossene Aufgaben l\u00F6schen abschliessen=Abschlie\u00DFen accessLicence=Zugriffslizenz accessStatus=Zugriffsstatus +action__delete_entity=Entit\u00E4t l\u00F6schen zzz +action__edit_entity=Entit\u00E4t editieren zzz +action__set_named_entity=Verkn\u00FCpfen zzz active=Aktiv add=Hinzuf\u00FCgen zzz addAnnotation=Kommentierung hinzuf\u00FCgen @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Fehler melden korrekturmeldungSendenForAll=Korrekturmeldung f\u00FCr alle Schritte senden korrekturmeldungSendenSingle=Korrekturmeldung f\u00FCr aktuellen Schritt senden kw=KW +label__named_entity=Entit\u00E4t zzz +label__named_entity_type=Typ zzz langLaufendeAufgaben=Lang laufende Aufgaben language=Sprache lastImage=Letztes Bild @@ -3506,6 +3511,7 @@ timeoutWarningDMS=W\u00E4hrend des Exports m\u00FCssen viele Daten in das Zielsy titel=Titel title=Vorgangstitel titleEmpty=Vorgangstitel ist nicht ausgef\u00FCllt. +title__named_entity_editor=Named Entities zzz to=bis toolentwicklung=intranda GmbH, G\u00F6ttingen too\u1E3EanyBatchesSelected=Es sind derzeit zu viele Batches ausgew\u00E4hlt, um die gew\u00FCnschte Aktion auszuf\u00FChren. diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 5ab7b83ce..79882ab30 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Remove finished tasks abschliessen=Finish accessLicence=Access licence accessStatus=Access status +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Active add=Add zzz addAnnotation=Add annotation @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error korrekturmeldungSendenForAll=Send correction message for all steps korrekturmeldungSendenSingle=Send correction message for current step kw=W +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Long-running tasks language=Language lastImage=Last image @@ -3506,6 +3511,7 @@ timeoutWarningDMS=During export a lot of data needs to be transferred to the tar titel=Title title=Process title titleEmpty=Process title is empty. +title__named_entity_editor=Named entities zzz to=to toolentwicklung=intranda GmbH, G\u00F6ttingen too\u1E3EanyBatchesSelected=There are too many batches selected currently to execute this action. diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties index 9147b5ec3..dcbce825e 100644 --- a/src/main/resources/messages_es.properties +++ b/src/main/resources/messages_es.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Borrar las tareas terminadas abschliessen=Terminar accessLicence=Licencia de acceso accessStatus=Estado de acceso +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Activo add=Add zzz addAnnotation=A\u00F1adir anotaci\u00F3n @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error zzz korrekturmeldungSendenForAll=Enviar mensaje de correcci\u00F3n para todos los pasos korrekturmeldungSendenSingle=Enviar mensaje de correcci\u00F3n para el paso actual kw=SC +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Tareas de larga duraci\u00F3n language=Idioma lastImage=\u00DAltima imagen zzz @@ -3505,6 +3510,7 @@ timeoutWarningDMS=Durante la exportaci\u00F3n deben copiarse muchos datos en el titel=T\u00EDtulo title=T\u00EDtulo del proceso titleEmpty=El t\u00EDtulo del proceso est\u00E1 vac\u00EDo. +title__named_entity_editor=Named entities zzz to=hasta toolentwicklung=intranda GmbH, Gotinga too\u1E3EanyBatchesSelected=Actualmente hay demasiados lotes seleccionados para realizar la acci\u00F3n deseada. zzz diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index faac2b95b..861d637b8 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Remove finished tasks zzz abschliessen=Finish zzz accessLicence=Access licence zzz accessStatus=Access status zzz +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Active zzz add=Add zzz addAnnotation=Add annotation zzz @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error zzz korrekturmeldungSendenForAll=Send correction message for all steps zzz korrekturmeldungSendenSingle=Send correction message for current step zzz kw=W zzz +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Long-running tasks zzz language=Language zzz lastImage=Last image zzz @@ -3505,6 +3510,7 @@ timeoutWarningDMS=During export a lot of data needs to be transferred to the tar titel=Title zzz title=Process title zzz titleEmpty=Process title is empty. zzz +title__named_entity_editor=Named entities zzz to=to zzz toolentwicklung=intranda GmbH, G\u00F6ttingen zzz too\u1E3EanyBatchesSelected=There are too many batches selected currently to execute this action. zzz diff --git a/src/main/resources/messages_it.properties b/src/main/resources/messages_it.properties index 8d45f49ca..cf53ba51f 100644 --- a/src/main/resources/messages_it.properties +++ b/src/main/resources/messages_it.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Cancellare le attivit\u00E0 completate zzz abschliessen=Finalizzare zzz accessLicence=Licenza d'accesso zzz accessStatus=Stato di accesso zzz +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Attivo zzz add=Add zzz addAnnotation=Aggiunta di commenti zzz @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error zzz korrekturmeldungSendenForAll=Inviare il messaggio di correzione per tutti i passi zzz korrekturmeldungSendenSingle=Inviare il messaggio di correzione per il passo corrente zzz kw=KW zzz +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Compiti a lungo termine zzz language=Lingua zzz lastImage=Ultima foto zzz @@ -3505,6 +3510,7 @@ timeoutWarningDMS=Durante l'esportazione, molti dati devono essere copiati nel s titel=Titolo zzz title=Titolo della transazione zzz titleEmpty=Il titolo dell'attivit\u00E0 non \u00E8 compilato. zzz +title__named_entity_editor=Named entities zzz to=a zzz toolentwicklung=intranda GmbH, G\u00F6ttingen too\u1E3EanyBatchesSelected=Attualmente ci sono troppi lotti selezionati per eseguire l'azione desiderata. zzz diff --git a/src/main/resources/messages_nl.properties b/src/main/resources/messages_nl.properties index b1daa4cc1..71a714bae 100644 --- a/src/main/resources/messages_nl.properties +++ b/src/main/resources/messages_nl.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Voltooide taken verwijderen zzz abschliessen=Afronden zzz accessLicence=Toegangslicentie zzz accessStatus=Toegangsstatus zzz +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Actief zzz add=Add zzz addAnnotation=Opmerkingen toevoegen zzz @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error zzz korrekturmeldungSendenForAll=Stuur een correctiebericht voor alle stappen zzz korrekturmeldungSendenSingle=Stuur een correctiebericht voor de huidige stap zzz kw=KW zzz +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Lange termijn taken zzz language=Taal zzz lastImage=Laatste foto zzz @@ -3505,6 +3510,7 @@ timeoutWarningDMS=Tijdens de export moeten veel gegevens worden gekopieerd naar titel=Titel zzz title=Transactietitel zzz titleEmpty=De titel van de activiteit is niet ingevuld. zzz +title__named_entity_editor=Named entities zzz to=naar zzz toolentwicklung=intranda GmbH, G\u00F6ttingen zzz too\u1E3EanyBatchesSelected=Er zijn momenteel te veel partijen geselecteerd om de gewenste actie uit te voeren. zzz diff --git a/src/main/resources/messages_pt.properties b/src/main/resources/messages_pt.properties index 42316cf7a..2af99ced4 100644 --- a/src/main/resources/messages_pt.properties +++ b/src/main/resources/messages_pt.properties @@ -244,6 +244,9 @@ abgeschlosseneTasksAusListeEntfernen=Eliminar tarefas conclu\u00EDdas zzz abschliessen=Finalizar zzz accessLicence=Licen\u00E7a de acesso zzz accessStatus=Estado do acesso zzz +action__delete_entity=Delete entity zzz +action__edit_entity=Edit entity zzz +action__set_named_entity=Apply zzz active=Ativo zzz add=Add zzz addAnnotation=Adicionando coment\u00E1rios zzz @@ -1400,6 +1403,8 @@ korrekturmeldungSenden=Report error zzz korrekturmeldungSendenForAll=Enviar mensagem de corre\u00E7\u00E3o para todos os passos zzz korrekturmeldungSendenSingle=Enviar mensagem de corre\u00E7\u00E3o para a etapa atual zzz kw=KW zzz +label__named_entity=Named entity zzz +label__named_entity_type=Named entity type zzz langLaufendeAufgaben=Tarefas a longo prazo zzz language=Idioma zzz lastImage=\u00DAltima foto zzz @@ -3505,6 +3510,7 @@ timeoutWarningDMS=Durante a exporta\u00E7\u00E3o, muitos dados devem ser copiado titel=T\u00EDtulo zzz title=T\u00EDtulo da transa\u00E7\u00E3o zzz titleEmpty=O t\u00EDtulo da atividade n\u00E3o \u00E9 preenchido. zzz +title__named_entity_editor=Named entities zzz to=para zzz toolentwicklung=intranda GmbH, G\u00F6ttingen zzz too\u1E3EanyBatchesSelected=Existem actualmente demasiados lotes seleccionados para realizar a ac\u00E7\u00E3o desejada. zzz diff --git a/src/main/resources/template.properties b/src/main/resources/template.properties index 98bea5376..d152fa862 100644 --- a/src/main/resources/template.properties +++ b/src/main/resources/template.properties @@ -172,7 +172,7 @@ ldap_use=false #OIDCClientID= # The notifying method #OIDCIdClaim=email -# The claims you are interested in +# Space separated list of requested scopes #OIDCScope=openid # Can be set to true to use SSO logout #useOIDCSSOLogout=false diff --git a/src/main/webapp/gulpfile.js b/src/main/webapp/gulpfile.js index 1ca25246c..e05c52b07 100644 --- a/src/main/webapp/gulpfile.js +++ b/src/main/webapp/gulpfile.js @@ -17,7 +17,7 @@ const cleanup = require('rollup-plugin-cleanup'); const terser = require('@rollup/plugin-terser'); // provide custom asset location for watch task -const customLocation = ``; +const customLocation = `/home/florian/eclipse-workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/wtpwebapps/workflow-core/`; // source directories, files, globs const legacySources = { @@ -43,7 +43,8 @@ const sources = { 'uii/**/*.png', 'uii/**/*.svg', 'uii/**/*.gif', - 'uii/**/*.ico' + 'uii/**/*.ico', + 'uii/**/*.riot' ] } // target directories @@ -58,7 +59,8 @@ const targetFolder = { // FUNCTIONS function static() { - return src(sources.static, {since: gulp.lastRun(static)}) + console.log("copy " + sources.static + " to " + `${customLocation}${targetFolder.static}`); + return src(sources.static) .pipe(dest(`${customLocation}${targetFolder.static}`)) }; diff --git a/src/main/webapp/uii/includes/metseditor/modals/inc_me_modal_altoeditor.xhtml b/src/main/webapp/uii/includes/metseditor/modals/inc_me_modal_altoeditor.xhtml index 49b74fac1..3d45336d5 100644 --- a/src/main/webapp/uii/includes/metseditor/modals/inc_me_modal_altoeditor.xhtml +++ b/src/main/webapp/uii/includes/metseditor/modals/inc_me_modal_altoeditor.xhtml @@ -68,9 +68,8 @@ role="document"> @@ -207,7 +206,26 @@ altoDivSelector: '#jsonAlto', altoChangesInputSelector: "#altoChanges", tileSourceSelector: '#tileSource', - alignRtl: #{Metadaten.pagesRTL} + alignRtl: #{Metadaten.pagesRTL}, + namedEntityEditor: { + enabled: #{Metadaten.showNamedEntityEditor}, + msgs: { + title__named_entity_editor: "#{msgs.title__named_entity_editor}", + label__named_entity_type: "#{msgs.label__named_entity_type}", + label__named_entity: "#{msgs.label__named_entity}", + action__set_named_entity: "#{msgs.action__set_named_entity}", + action__edit_entity: "#{msgs.action__edit_entity}", + bitteAuswaehlen: "#{msgs.bitteAuswaehlen}", + action__delete_entity: "#{msgs.action__delete_entity}" + }, + types: [ + {value: "person", label: "#{msgs.person}"}, + {value: "corporation", label: "#{msgs.corporation}"}, + {value: "location", label: "#{msgs.location}"}, + {value: "keyword", label: "#{msgs.keyword}"} + ], + entities: '#{Metadaten.getAuthorityMetadataJSON()}' + } } riot.mount('alto-editor', opts); } diff --git a/src/main/webapp/uii/template/riot/alto-editor.riot b/src/main/webapp/uii/template/riot/alto-editor.riot index 8fa6a598d..2582933f6 100644 --- a/src/main/webapp/uii/template/riot/alto-editor.riot +++ b/src/main/webapp/uii/template/riot/alto-editor.riot @@ -3,23 +3,24 @@
-
+
-
+
{word.value}
+
@@ -31,12 +32,32 @@ .altoword { padding: 3px; } + .alto-edit-wrapper { + --alto-padding: 0.5em; + height: 82vh; + display: flex; + flex-direction: column; + } .alto-edit { - min-height: 82vh; - max-height: 82vh; overflow-y: scroll; border: 1px solid #ddd; - padding: 15px; + padding: var(--alto-padding); + flex-grow: 1; + } + .named-entity-edit { + border: 1px solid #ddd; + margin: 1.5em 0 0 0; + max-height: 50%; + display: none; + } + .named-entity-edit > :first-child { + border-bottom: 1px solid #bbb; + padding: var(--alto-padding); + } + .named-entity-edit.-active { + display: flex; + flex-direction: column; + justify-content: space-between; } #bigImage { height: 82vh; @@ -57,12 +78,15 @@ .alignRight{ text-align: right; } + .selected-word { + background: HSLA(209, 73%, 55%, 33%); + } + + + + + \ No newline at end of file diff --git a/src/main/webapp/uii/template/template_metseditor.html b/src/main/webapp/uii/template/template_metseditor.html index b9acd9d1d..72b8434d0 100644 --- a/src/main/webapp/uii/template/template_metseditor.html +++ b/src/main/webapp/uii/template/template_metseditor.html @@ -204,6 +204,7 @@ +