From e124fb6373a641dbf73dbd7d8b5c8ddb942b5936 Mon Sep 17 00:00:00 2001 From: pavel-stastny Date: Mon, 4 Dec 2023 16:12:49 +0100 Subject: [PATCH] Issues #1021, #1024, #1023, #1025, #1022 --- processes/collections-backup/build.gradle | 11 + .../java/cz/inovatika/collections/Backup.java | 249 +++++++++ .../cz/inovatika/collections/Restore.java | 100 ++++ .../migrations/FromK5Instance.java | 486 ++++++++++++++++++ .../utils/ImportCollectionUtils.java | 22 + .../src/main/java/org/kramerius/Import.java | 87 +++- .../statistics/impl/nkp/SendEmail.java | 49 ++ rest/build.gradle | 1 + .../apiNew/admin/v70/ServerFilesResource.java | 41 ++ .../admin/v70/collections/Collection.java | 203 +++++--- .../collections/CollectionsFoxmlBuilder.java | 53 ++ .../v70/collections/CollectionsResource.java | 390 +++++++++++++- .../apiNew/admin/v70/collections/CutItem.java | 179 +++++++ .../v70/collections/thumbs/IIFUtils.java | 59 +++ .../thumbs/SimpleIIIFGenerator.java | 234 +++++++++ .../collections/thumbs/ThumbsGenerator.java | 27 + .../admin/v70/processes/ProcessResource.java | 77 ++- .../rest/apiNew/client/v70/ItemsResource.java | 164 +++++- search/build.gradle | 1 + .../incad/Kramerius/AbstractImageServlet.java | 1 + .../java/cz/incad/Kramerius/CORSFilter.java | 8 +- .../imaging/ImageStreamsServlet.java | 6 + settings.gradle | 2 + shared/common/build.gradle | 1 + .../processes/NextSchedulerTask.java | 8 +- .../processes/new_api/ProcessScheduler.java | 11 + .../cz/incad/kramerius/processes/res/lp.st | 37 ++ .../repository/KrameriusRepositoryApi.java | 5 +- .../kramerius/repository/RepositoryApi.java | 5 + .../repository/RepositoryApiImpl.java | 43 +- .../cz/incad/kramerius/utils/Dom4jUtils.java | 2 +- .../main/java/res/configuration.properties | 3 + 32 files changed, 2440 insertions(+), 125 deletions(-) create mode 100644 processes/collections-backup/build.gradle create mode 100644 processes/collections-backup/src/main/java/cz/inovatika/collections/Backup.java create mode 100644 processes/collections-backup/src/main/java/cz/inovatika/collections/Restore.java create mode 100644 processes/collections-backup/src/main/java/cz/inovatika/collections/migrations/FromK5Instance.java create mode 100644 processes/collections-backup/src/main/java/cz/inovatika/collections/utils/ImportCollectionUtils.java create mode 100644 processes/nkp-logs/src/main/java/cz/incad/kramerius/statistics/impl/nkp/SendEmail.java create mode 100644 rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CutItem.java create mode 100644 rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/IIFUtils.java create mode 100644 rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/SimpleIIIFGenerator.java create mode 100644 rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/ThumbsGenerator.java diff --git a/processes/collections-backup/build.gradle b/processes/collections-backup/build.gradle new file mode 100644 index 0000000000..a7e3aa47e0 --- /dev/null +++ b/processes/collections-backup/build.gradle @@ -0,0 +1,11 @@ +description "Collections backup/restore" + + +dependencies { + implementation project(':shared:common') + implementation project(':processes:import') + + implementation 'org.json:json:20140107' + +} + diff --git a/processes/collections-backup/src/main/java/cz/inovatika/collections/Backup.java b/processes/collections-backup/src/main/java/cz/inovatika/collections/Backup.java new file mode 100644 index 0000000000..702fb0c412 --- /dev/null +++ b/processes/collections-backup/src/main/java/cz/inovatika/collections/Backup.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) Nov 29, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.inovatika.collections; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +//import static cz.incad.kramerius.utils.XMLUtils.LOGGER; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; + +import cz.incad.kramerius.FedoraNamespaces; +import cz.incad.kramerius.statistics.StatisticReport; +import cz.incad.kramerius.utils.RESTHelper; +import cz.incad.kramerius.utils.XMLUtils; +import cz.incad.kramerius.utils.conf.KConfiguration; + + +public class Backup { + + public static final Logger LOGGER = Logger.getLogger(Backup.class.getName()); + + public static void main(String[] args) throws TransformerException, ParserConfigurationException, SAXException, IOException { + LOGGER.log(Level.INFO, "Process parameters: " + Arrays.asList(args).toString()); + if (args.length > 2) { + Client client = Client.create(); + + String target = args[1]; + String nameOfBackup = args[2]; + String tmpDirPath = System.getProperty("java.io.tmpdir"); + + String subdirectoryPath = tmpDirPath + File.separator + nameOfBackup; + FileUtils.forceMkdir(new File(subdirectoryPath)); + + + for (String pid : extractPids(target)) { + List collectionProcessed = new ArrayList<>(); + Stack processingStack = new Stack<>(); + processingStack.add(pid); + while(!processingStack.isEmpty()) { + String processingPid = processingStack.pop(); + if (collectionProcessed.contains(processingPid)) { + LOGGER.warning(String.format("Found cycle on %s", processingPid)); + continue; + } + collectionProcessed.add(processingPid); + if (head(client, processingPid) == 200) { + Document parsed = foxml(client, processingPid); + StringWriter writer = new StringWriter(); + XMLUtils.print(parsed, writer); + LOGGER.info(String.format("Writing to %s", new File(new File(subdirectoryPath), processingPid.replace(":", "_")).getAbsolutePath())); + FileUtils.writeByteArrayToFile(new File(new File(subdirectoryPath), processingPid.replace(":", "_")+".xml"), writer.toString().getBytes("UTF-8")); + List recursiveElements = XMLUtils.getElementsRecursive(parsed.getDocumentElement(), new XMLUtils.ElementsFilter() { + + @Override + public boolean acceptElement(Element element) { + boolean equals = element.getLocalName().equals("contains"); + return equals; + } + }); + + + List pids = recursiveElements.stream().map(elm-> { + String attributeNS = elm.getAttributeNS(FedoraNamespaces.RDF_NAMESPACE_URI, "resource"); + if (attributeNS.contains("info:fedora/")) { + String containsPid = attributeNS.substring("info:fedora/".length()); + return containsPid; + } else return null; + }).filter(Objects::nonNull).collect(Collectors.toList()); + + + if (pids.size() > 0) { + + int batchSize = 40; + int numberOfIteration = pids.size() / batchSize; + if (pids.size() % batchSize != 0) { + numberOfIteration = numberOfIteration + 1; + } + for (int iteration = 0; iteration < numberOfIteration; iteration++) { + int start = iteration* batchSize; + int stop = Math.min((iteration+1)*batchSize, pids.size()); + List subPids = pids.subList(start, stop); + + String query = subPids.stream().map(it-> {return '"' + it +'"';}).collect(Collectors.joining(" OR ")); + String encodedCondition = URLEncoder.encode(" AND pid:(" + query + ")", "UTF-8"); + + + String solrSearchHost = KConfiguration.getInstance().getSolrSearchHost()+String.format("/select?fq=model:collection%s&q=*&fl=pid&wt=json", encodedCondition); + + InputStream inputStream = RESTHelper.inputStream(solrSearchHost, "", ""); + String string = IOUtils.toString(inputStream, "UTF-8"); + JSONObject object = new JSONObject(string); + JSONObject response = object.getJSONObject("response"); + JSONArray docs = response.getJSONArray("docs"); + for (int i = 0; i < docs.length(); i++) { + JSONObject doc = docs.getJSONObject(i); + String collectionPid = doc.optString("pid"); + processingStack.push(collectionPid); + } + + } + + } + } else { + LOGGER.warning(String.format("Pid %s doesnt exists",processingPid)); + } + } + } + + File tmpDir = new File(subdirectoryPath); + File[] listFiles = tmpDir.listFiles(); + if (listFiles != null) { + String parentZipFolder = KConfiguration.getInstance().getConfiguration().getString("collections.backup.folder"); + if (parentZipFolder == null) throw new IllegalStateException("configuration property 'collections.backup.folder' must be set "); + FileUtils.forceMkdir(new File(parentZipFolder)); + + String zipFile = parentZipFolder + File.separator + nameOfBackup+".zip"; + try { + FileOutputStream fos = new FileOutputStream(zipFile); + ZipOutputStream zos = new ZipOutputStream(fos); + + for (File lF : listFiles) { + addFileToZip("", lF, zos); + } + + zos.close(); + fos.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + } + } + } + + + private static List extractPids(String target) { + if (target.startsWith("pid:")) { + String pid = target.substring("pid:".length()); + List result = new ArrayList<>(); + result.add(pid); + return result; + } else if (target.startsWith("pidlist:")) { + List pids = Arrays.stream(target.substring("pidlist:".length()).split(";")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toList()); + return pids; + } else if (target.startsWith("pidlist_file:")) { + String filePath = target.substring("pidlist_file:".length()); + File file = new File(filePath); + if (file.exists()) { + try { + return IOUtils.readLines(new FileInputStream(file), Charset.forName("UTF-8")); + } catch (IOException e) { + throw new RuntimeException("IOException " + e.getMessage()); + } + } else { + throw new RuntimeException("file " + file.getAbsolutePath() + " doesnt exist "); + } + } else { + throw new RuntimeException("invalid target " + target); + } + } + + private static int head(Client client, String pid) { + String url = KConfiguration.getInstance().getConfiguration().getString("api.client.point") + (KConfiguration.getInstance().getConfiguration().getString("api.point").endsWith("/") ? "" : "/") + String.format("items/%s/metadata/mods", pid); + LOGGER.info(String.format("Url %s", url)); + + WebResource r = client.resource(url); + + WebResource.Builder builder = r.accept(MediaType.APPLICATION_XML); + ClientResponse head = builder.head(); + int status = head.getStatus(); + return status; + } + + + private static Document foxml(Client client, String processingPid) + throws ParserConfigurationException, SAXException, IOException { + String url = KConfiguration.getInstance().getConfiguration().getString("api.client.point") + (KConfiguration.getInstance().getConfiguration().getString("api.point").endsWith("/") ? "" : "/") + String.format("items/%s/foxml", processingPid); + LOGGER.info(String.format( "Requesting url is %s", url)); + WebResource r = client.resource(url); + + WebResource.Builder builder = r.accept(MediaType.APPLICATION_XML); + InputStream clientResponse = builder.get(InputStream.class); + Document parsed = XMLUtils.parseDocument(clientResponse, true); + return parsed; + } + + +private static void addFileToZip(String path, File srcFile, ZipOutputStream zipOut) throws IOException { + FileInputStream fis = new FileInputStream(srcFile); + ZipEntry zipEntry = new ZipEntry(path + "/" + srcFile.getName()); + zipOut.putNextEntry(zipEntry); + + byte[] bytes = new byte[1024]; + int length; + while ((length = fis.read(bytes)) >= 0) { + zipOut.write(bytes, 0, length); + } + fis.close(); +} + +} diff --git a/processes/collections-backup/src/main/java/cz/inovatika/collections/Restore.java b/processes/collections-backup/src/main/java/cz/inovatika/collections/Restore.java new file mode 100644 index 0000000000..bdffadbf47 --- /dev/null +++ b/processes/collections-backup/src/main/java/cz/inovatika/collections/Restore.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) Nov 29, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.inovatika.collections; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.xml.bind.JAXBException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.commons.io.FileUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.xml.sax.SAXException; + +import com.sun.jersey.api.client.Client; + +import cz.incad.kramerius.processes.new_api.ProcessScheduler; +import cz.incad.kramerius.utils.conf.KConfiguration; +import cz.inovatika.collections.migrations.FromK5Instance; + +public class Restore { + + public static final Logger LOGGER = Logger.getLogger(Restore.class.getName()); + + public static void main(String[] args) throws TransformerException, ParserConfigurationException, SAXException, IOException, JAXBException, InterruptedException, SolrServerException { + LOGGER.log(Level.INFO, "Process parameters: " + Arrays.asList(args).toString()); + if (args.length > 1) { + String authToken = args[0]; + String target = args[1]; + + String parentZipFolder = KConfiguration.getInstance().getConfiguration().getString("collections.backup.folder"); + if (parentZipFolder == null) throw new IllegalStateException("configuration property 'collections.backup.folder' must be set "); + String zipFile = parentZipFolder + File.separator + target; + + + + String tmpDirPath = System.getProperty("java.io.tmpdir"); + String subdirectoryPath = tmpDirPath + File.separator + target; + FileUtils.forceMkdir(new File(subdirectoryPath)); + unzip(zipFile, subdirectoryPath); + + + LOGGER.info("Scheduling import "+subdirectoryPath); + FromK5Instance.importTmpDir(subdirectoryPath, true, authToken); + } else { + throw new IllegalArgumentException("expecting 2 arguments (authtoken, zipfile)"); + } + } + + + public static void unzip(String zipFile, String outputFolder) throws IOException { + LOGGER.info("Unzipping file to "+outputFolder); + + byte[] buffer = new byte[1024]; + + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { + ZipEntry zipEntry = zis.getNextEntry(); + while (zipEntry != null) { + String fileName = zipEntry.getName(); + File newFile = new File(outputFolder + File.separator + fileName); + + // Vytvoření nadřazeného adresáře pro soubor, pokud neexistuje + new File(newFile.getParent()).mkdirs(); + + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + + zipEntry = zis.getNextEntry(); + } + + zis.closeEntry(); + } + } +} diff --git a/processes/collections-backup/src/main/java/cz/inovatika/collections/migrations/FromK5Instance.java b/processes/collections-backup/src/main/java/cz/inovatika/collections/migrations/FromK5Instance.java new file mode 100644 index 0000000000..f30303235d --- /dev/null +++ b/processes/collections-backup/src/main/java/cz/inovatika/collections/migrations/FromK5Instance.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) Dec 3, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.inovatika.collections.migrations; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import javax.ws.rs.core.MediaType; +import javax.xml.bind.JAXBException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.json.JSONArray; +import org.json.JSONObject; +import org.kramerius.Import; +import org.kramerius.ImportModule; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; + +import cz.incad.kramerius.FedoraAccess; +import cz.incad.kramerius.FedoraNamespaces; +import cz.incad.kramerius.fedora.RepoModule; +import cz.incad.kramerius.resourceindex.ProcessingIndexFeeder; +import cz.incad.kramerius.resourceindex.ResourceIndexModule; +import cz.incad.kramerius.service.SortingService; +import cz.incad.kramerius.solr.SolrModule; +import cz.incad.kramerius.statistics.NullStatisticsModule; +import cz.incad.kramerius.utils.IterationUtils; +import cz.incad.kramerius.utils.XMLUtils; +import cz.incad.kramerius.utils.IterationUtils.IterationCallback; +import cz.incad.kramerius.utils.IterationUtils.IterationEndCallback; +import cz.incad.kramerius.utils.conf.KConfiguration; +import cz.inovatika.collections.Restore; + +import static cz.incad.kramerius.utils.IterationUtils.*; + +public class FromK5Instance { + + + private static final String ALLOWED_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + public static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(ALLOWED_CHARACTERS.length()); + char randomChar = ALLOWED_CHARACTERS.charAt(randomIndex); + sb.append(randomChar); + } + + return sb.toString(); + } + + public static Logger LOGGER = Logger.getLogger(FromK5Instance.class.getName()); + + public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException, TransformerException, JAXBException, InterruptedException, SolrServerException, BrokenBarrierException { + LOGGER.log(Level.INFO, "Process parameters: " + Arrays.asList(args).toString()); + if (args.length > 1) { + Client client = Client.create(); + + LOGGER.info("Reloading cloection"); + String authToken = args[0]; + String url = args[1]; + if (url.endsWith("/")) { + url = url.substring(0,url.length()-1); + } + + String tmpDirPath = System.getProperty("java.io.tmpdir"); + + String subdirectoryPath = tmpDirPath + File.separator + generateRandomString(5); + FileUtils.forceMkdir(new File(subdirectoryPath)); + + LOGGER.info(String.format("Generating temp folder %s", subdirectoryPath)); + + + JSONArray collections = collections(client, url); + for (int i = 0; i < collections.length(); i++) { + JSONObject col = collections.getJSONObject(i); + String pid = col.getString("pid"); + JSONObject desc = col.getJSONObject("descs"); + + List pids = pids(client, pid, url); + LOGGER.info(String.format("Found root pids %d", pids.size())); + + Document foxml = foxml(client, pid, url); + createBiblioMods(foxml, desc); + + // remove dc + removeStream(foxml,"DC"); + removeStream(foxml,"AUDIT"); + removeStream(foxml,"TEXT"); + removeStream(foxml,"TEXT_cs"); + removeStream(foxml,"TEXT_en"); + addContainsRelations(foxml, pids); + + File vcFile = new File(subdirectoryPath, pid.replace(':', '_')+".xml"); + try (FileOutputStream fos = new FileOutputStream(vcFile)) { + XMLUtils.print(foxml, new FileOutputStream(vcFile)); + } + } + + LOGGER.info("Scheduling import "+subdirectoryPath); + FromK5Instance.importTmpDir(subdirectoryPath, true, authToken); + + } else { + throw new IllegalArgumentException(""); + } + } + + private static void addContainsRelations(Document foxml, List pids) { + // + Element relsExt = XMLUtils.findElement(foxml.getDocumentElement(), new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + String localName = element.getLocalName(); + String attribute = element.getAttribute("ID"); + return localName != null && localName.equals("datastream") && attribute != null && attribute.equals("RELS-EXT"); + } + }); + + if (relsExt != null) { + Element rdfDecription = XMLUtils.findElement(relsExt, new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + String localName = element.getLocalName(); + String nameSpace = element.getNamespaceURI(); + return localName != null && localName.equals("Description") && nameSpace.equals(FedoraNamespaces.RDF_NAMESPACE_URI); + } + }); + + if (rdfDecription != null) { + for (String pid : pids) { + Element contains = foxml.createElementNS(FedoraNamespaces.ONTOLOGY_RELATIONSHIP_NAMESPACE_URI, "rel:contains"); + contains.setPrefix("rel"); + contains.setAttributeNS(FedoraNamespaces.RDF_NAMESPACE_URI, "rdf:resource", String.format("info:fedora/%s", pid)); + rdfDecription.appendChild(contains); + } + } + } + } + + private static void createBiblioMods(Document foxml, JSONObject desc) { + Element dataStream = foxml.createElementNS(FedoraNamespaces.FEDORA_FOXML_URI, "datastream"); + dataStream.setAttribute("ID", "BIBLIO_MODS"); + dataStream.setAttribute("CONTROL_GROUP", "X"); + dataStream.setAttribute("STATE", "A"); + dataStream.setAttribute("VERSIONABLE", "false"); + foxml.getDocumentElement().appendChild(dataStream); + + + Element datastreamVersion = foxml.createElementNS(FedoraNamespaces.FEDORA_FOXML_URI, "datastreamVersion"); + datastreamVersion.setAttribute("ID", "BIBLIO_MODS.0"); + + DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; + // Získejte aktuální datum a čas jako LocalDateTime + LocalDateTime currentDateTime = LocalDateTime.now(); + // Naformátujte aktuální datum a čas podle zadaného formátu + String formattedDateTime = currentDateTime.format(formatter); + datastreamVersion.setAttribute("CREATED", formattedDateTime); + datastreamVersion.setAttribute("MIMETYPE", "text/xml"); + datastreamVersion.setAttribute("FORMAT_URI", "http://www.loc.gov/mods/v3"); + dataStream.appendChild(datastreamVersion); + + Element xmlContent = foxml.createElementNS(FedoraNamespaces.FEDORA_FOXML_URI, "xmlContent"); + datastreamVersion.appendChild(xmlContent); + + Element modsCollection = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "modsCollection"); + xmlContent.appendChild(modsCollection); + + Element mods = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "mods"); + mods.setAttribute("version", "3.4"); + modsCollection.appendChild(mods); + + if (desc.has("cs")) { + String csAbstract = null; + + // TEXT_cs - stream + Element textcs = XMLUtils.findElement(foxml.getDocumentElement(), new XMLUtils.ElementsFilter() { + + @Override + public boolean acceptElement(Element element) { + String id = element.getAttribute("ID"); + return id != null && id.equals("TEXT_cs"); + } + }); + if (textcs != null) { + Element binary = XMLUtils.findElement(textcs, new XMLUtils.ElementsFilter() { + + @Override + public boolean acceptElement(Element element) { + String lname = element.getLocalName(); + return lname != null && lname.equals("binaryContent"); + } + }); + + csAbstract= new String(Base64.decodeBase64(binary.getTextContent())); + Element abstractElm = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "abstract"); + abstractElm.setAttribute("lang", "cze"); + abstractElm.setTextContent(csAbstract); + mods.appendChild(abstractElm); + } + + + + Element titleInfo = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "titleInfo"); + titleInfo.setAttribute("lang", "cze"); + mods.appendChild(titleInfo); + + Element title = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "title"); + title.setTextContent(desc.getString("cs")); + titleInfo.appendChild(title); + + } + + if (desc.has("en")) { + String csAbstract = null; + + // TEXT_cs - stream + Element textcs = XMLUtils.findElement(foxml.getDocumentElement(), new XMLUtils.ElementsFilter() { + + @Override + public boolean acceptElement(Element element) { + String id = element.getAttribute("ID"); + return id != null && id.equals("TEXT_en"); + } + }); + if (textcs != null) { + Element binary = XMLUtils.findElement(textcs, new XMLUtils.ElementsFilter() { + + @Override + public boolean acceptElement(Element element) { + String lname = element.getLocalName(); + return lname != null && lname.equals("binaryContent"); + } + }); + + csAbstract= new String(Base64.decodeBase64(binary.getTextContent())); + Element abstractElm = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "abstract"); + abstractElm.setAttribute("lang", "eng"); + abstractElm.setTextContent(csAbstract); + mods.appendChild(abstractElm); + } + + Element titleInfo = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "titleInfo"); + titleInfo.setAttribute("lang", "eng"); + mods.appendChild(titleInfo); + + Element title = foxml.createElementNS(FedoraNamespaces.BIBILO_MODS_URI, "title"); + title.setTextContent(desc.getString("en")); + titleInfo.appendChild(title); + } + } + + private static void removeStream(Document foxml, String id) { + Element elm = XMLUtils.findElement(foxml.getDocumentElement(), new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + String idAttrVal = element.getAttribute("ID"); + return idAttrVal != null && idAttrVal.equals(id); + } + }); + if (elm != null) elm.getParentNode().removeChild(elm); + } + + //https://kramerius.lib.cas.cz/search/api/v5.0/vc + private static JSONArray collections(Client client, String api) throws IOException { + String url = String.format("%s/search/api/v5.0/vc", api); + LOGGER.info(String.format( "Requesting url is %s", url)); + WebResource r = client.resource(url); + + WebResource.Builder builder = r.accept(MediaType.APPLICATION_JSON); + InputStream clientResponse = builder.get(InputStream.class); + String string = IOUtils.toString(clientResponse, "UTF-8"); + return new JSONArray(string); + + } + + private static Document foxml(Client client, String vcPid, String api) + throws ParserConfigurationException, SAXException, IOException { + String url = String.format("%s/search/api/v5.0/item/%s/foxml",api, vcPid); + LOGGER.info(String.format( "Requesting url is %s", url)); + WebResource r = client.resource(url); + + WebResource.Builder builder = r.accept(MediaType.APPLICATION_XML); + InputStream clientResponse = builder.get(InputStream.class); + String document = IOUtils.toString(clientResponse, "UTF-8"); + String replaced = document.replaceAll("vc\\:", "uuid:"); + Document parsed = XMLUtils.parseDocument(new StringReader(replaced), true); + return parsed; + } + + //https://kramerius.lib.cas.cz/search/api/v5.0/search?q=*:*&fq=(fedora.model:monograph%20OR%20fedora.model:periodical%20OR%20fedora.model:sheetmusic%20OR%20fedora.model:monographunit)%20AND%20(collection:%22vc:5c2321df-2d8d-4a87-a262-15ffff990d81%22)&fl=PID,dostupnost,fedora.model,dc.creator,dc.title,dc.title,root_title,datum_str,dnnt-labels&facet=true&facet.mincount=1&facet.field=keywords&facet.field=language&facet.field=dnnt-labels&facet.field=mods.physicalLocation&facet.field=geographic_names&facet.field=facet_autor&facet.field=model_path&sort=created_date%20desc&rows=60&start=0 + + public static void collectionIterations(Client client,String address, String masterQuery,IterationCallback callback, IterationEndCallback endCallback) throws ParserConfigurationException, SAXException, IOException, InterruptedException, BrokenBarrierException { + String cursorMark = null; + String queryCursorMark = null; + do { + Element element = pidsCursorQuery(client, address, masterQuery, cursorMark); + cursorMark = IterationUtils.findCursorMark(element); + queryCursorMark = IterationUtils.findQueryCursorMark(element); + callback.call(element, cursorMark); + } while((cursorMark != null && queryCursorMark != null) && !cursorMark.equals(queryCursorMark)); + endCallback.end(); + } + + static Element pidsCursorQuery(Client client, String url, String mq, String cursor) throws ParserConfigurationException, SAXException, IOException{ + int rows = 1000; + String query = "search" + "?q="+mq + (cursor!= null ? String.format("&rows=%d&cursorMark=%s", rows, cursor) : String.format("&rows=%d&cursorMark=*", rows))+"&sort=" + URLEncoder.encode("PID desc", "UTF-8")+"&fl=PID"; + return IterationUtils.executeQuery(client, url, query); + } + + + private static List pids(Client client, String vcPid, String api) + throws ParserConfigurationException, SAXException, IOException, InterruptedException, BrokenBarrierException { + + final List returnPids = new ArrayList<>(); + + String url = null; + if (api.endsWith("/")) { + url = String.format("%ssearch/api/v5.0/",api); + + } else { + url = String.format("%s/search/api/v5.0/",api); + } + + + List models = Arrays.asList( + "monograph", + "periodical", + "sheetmusic", + "monographunit" + ); + + String fqModel = "fedora.model:("+models.stream().collect(Collectors.joining(" OR "))+") AND "; + String collectionPid="(collection:\""+vcPid+"\")"; + String masterQuery= URLEncoder.encode(fqModel + collectionPid, "UTF-8"); + + collectionIterations(client, url, masterQuery , (elm, i) -> { + + Element result = XMLUtils.findElement(elm, new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + String nodeName = element.getNodeName(); + return nodeName.equals("result"); + } + }); + if (result != null) { + List elements = XMLUtils.getElements(result, new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + String nodeName = element.getNodeName(); + return nodeName.equals("doc"); + } + }); + + List idents = elements.stream().map(item -> { + Element str = XMLUtils.findElement(item, new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + return element.getNodeName().equals("str"); + } + } + ); + return str.getTextContent(); + } + ).collect(Collectors.toList()); + + returnPids.addAll(idents); + + } + }, ()->{}); + return returnPids; + } + + + public static void importTmpDir(String exportRoot, boolean startIndexer, String authToken) throws JAXBException, IOException, InterruptedException, SAXException, SolrServerException { + Injector injector = Guice.createInjector(new SolrModule(), new ResourceIndexModule(), new RepoModule(), new NullStatisticsModule(), new ImportModule()); + FedoraAccess fa = injector.getInstance(Key.get(FedoraAccess.class, Names.named("rawFedoraAccess"))); + SortingService sortingServiceLocal = injector.getInstance(SortingService.class); + ProcessingIndexFeeder feeder = injector.getInstance(ProcessingIndexFeeder.class); + + Import.run(fa, feeder, sortingServiceLocal, + KConfiguration.getInstance().getProperty("ingest.url"), + KConfiguration.getInstance().getProperty("ingest.user"), + KConfiguration.getInstance().getProperty("ingest.password"), + exportRoot, startIndexer, authToken); + + Restore.LOGGER.info(String.format("Deleting directory %s", exportRoot)); + File exportFolder = new File(exportRoot); + FileUtils.deleteDirectory(exportFolder); + } + +// public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, InterruptedException, BrokenBarrierException { +// Client client = Client.create(); +// List models = Arrays.asList( +// "monograph", +// "periodical", +// "sheetmusic", +// "monographunit" +// ); +// +// String fqModel = "fedora.model:("+models.stream().collect(Collectors.joining(" OR "))+") AND "; +// String collectionPid="(collection:\"vc:809d92e6-abd0-4642-ba73-f1f259275d96\")"; +// String masterQuery= URLEncoder.encode(fqModel + collectionPid, "UTF-8"); +// +// collectionIterations(client, "https://kramerius.lib.cas.cz/search/api/v5.0", masterQuery, (elm, i) -> { +// +// Element result = XMLUtils.findElement(elm, new XMLUtils.ElementsFilter() { +// @Override +// public boolean acceptElement(Element element) { +// String nodeName = element.getNodeName(); +// return nodeName.equals("result"); +// } +// }); +// if (result != null) { +// List elements = XMLUtils.getElements(result, new XMLUtils.ElementsFilter() { +// @Override +// public boolean acceptElement(Element element) { +// String nodeName = element.getNodeName(); +// return nodeName.equals("doc"); +// } +// }); +// +// List idents = elements.stream().map(item -> { +// Element str = XMLUtils.findElement(item, new XMLUtils.ElementsFilter() { +// @Override +// public boolean acceptElement(Element element) { +// return element.getNodeName().equals("str"); +// } +// } +// ); +// return str.getTextContent(); +// } +// ).collect(Collectors.toList()); +// +// System.out.println(idents); +// } +// }, ()->{}); +// } + + +} + + diff --git a/processes/collections-backup/src/main/java/cz/inovatika/collections/utils/ImportCollectionUtils.java b/processes/collections-backup/src/main/java/cz/inovatika/collections/utils/ImportCollectionUtils.java new file mode 100644 index 0000000000..17db0ea37e --- /dev/null +++ b/processes/collections-backup/src/main/java/cz/inovatika/collections/utils/ImportCollectionUtils.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) Dec 3, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.inovatika.collections.utils; + +public class ImportCollectionUtils { + + private ImportCollectionUtils() {} +} diff --git a/processes/import/src/main/java/org/kramerius/Import.java b/processes/import/src/main/java/org/kramerius/Import.java index df578223f4..64839898ae 100644 --- a/processes/import/src/main/java/org/kramerius/Import.java +++ b/processes/import/src/main/java/org/kramerius/Import.java @@ -6,6 +6,8 @@ import com.google.inject.name.Names; import com.qbizm.kramerius.imp.jaxb.*; import cz.incad.kramerius.FedoraAccess; +import cz.incad.kramerius.FedoraNamespaceContext; +import cz.incad.kramerius.FedoraNamespaces; import cz.incad.kramerius.fedora.RepoModule; import cz.incad.kramerius.fedora.om.Repository; import cz.incad.kramerius.fedora.om.RepositoryDatastream; @@ -26,6 +28,7 @@ import cz.incad.kramerius.utils.pid.LexerException; import cz.incad.kramerius.utils.pid.PIDParser; import org.apache.solr.client.solrj.SolrServerException; +import org.fcrepo.common.rdf.FedoraNamespace; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -167,9 +170,12 @@ public static void run(FedoraAccess fa, ProcessingIndexFeeder feeder, SortingSer Set classicRoots = new HashSet(); Set convolutes = new HashSet(); + + Set collections = new HashSet(); + Set sortRelations = new HashSet(); if (importFile.isDirectory()) { - visitAllDirsAndFiles(fa, importFile, classicRoots, convolutes, sortRelations, updateExisting); + visitAllDirsAndFiles(fa, importFile, classicRoots, convolutes, collections, sortRelations, updateExisting); } else { BufferedReader reader = null; try { @@ -193,7 +199,7 @@ public static void run(FedoraAccess fa, ProcessingIndexFeeder feeder, SortingSer continue; } log.info("Importing " + importItem.getAbsolutePath()); - visitAllDirsAndFiles(fa, importItem, classicRoots, convolutes, sortRelations, updateExisting); + visitAllDirsAndFiles(fa, importItem, classicRoots, convolutes, collections, sortRelations, updateExisting); } reader.close(); } catch (IOException e) { @@ -220,6 +226,31 @@ public static void run(FedoraAccess fa, ProcessingIndexFeeder feeder, SortingSer } if (startIndexer) { + + if (collections.isEmpty()) { + log.info("NO COLLECTIONS FOR INDEXING FOUND."); + } else { + try { + String waitIndexerProperty = System.getProperties().containsKey("ingest.startIndexer.wait") ? System.getProperty("ingest.startIndexer.wait") : KConfiguration.getInstance().getConfiguration().getString("ingest.startIndexer.wait", "1000"); + // should wait + log.info("Waiting for soft commit :" + waitIndexerProperty + " s"); + Thread.sleep(Integer.parseInt(waitIndexerProperty)); + + if (authToken != null) { + for (TitlePidTuple col : collections) { + + ProcessScheduler.scheduleIndexation(col.pid, col.title, false, authToken); + } + log.info("ALL COLLECTIONS SCHEDULED FOR INDEXING."); + } else { + log.warning("cannot schedule indexation due to missing process credentials"); + } + } catch (Exception e) { + log.log(Level.WARNING, e.getMessage(), e); + } + } + + if (classicRoots.isEmpty()) { log.info("NO ROOT OBJECTS FOR INDEXING FOUND."); } else { @@ -231,7 +262,13 @@ public static void run(FedoraAccess fa, ProcessingIndexFeeder feeder, SortingSer if (authToken != null) { for (TitlePidTuple root : classicRoots) { - ProcessScheduler.scheduleIndexation(root.pid, root.title, true, authToken); + + if (fa.isObjectAvailable(root.pid)) { + ProcessScheduler.scheduleIndexation(root.pid, root.title, true, authToken); + } else { + LOGGER.warning(String.format("Object '%s' does not exist in the repository. ", root.pid)); + } + } log.info("ALL ROOT OBJECTS SCHEDULED FOR INDEXING."); } else { @@ -282,7 +319,11 @@ protected PasswordAuthentication getPasswordAuthentication() { of = new ObjectFactory(); } - private static void visitAllDirsAndFiles(FedoraAccess fa, File importFile, Set classicRoots, Set convolutes, Set sortRelations, boolean updateExisting) { + private static void visitAllDirsAndFiles(FedoraAccess fa, File importFile, Set classicRoots, + Set convolutes, + Set collections, + + Set sortRelations, boolean updateExisting) { if (importFile == null) { return; } @@ -301,7 +342,7 @@ private static void visitAllDirsAndFiles(FedoraAccess fa, File importFile, Set convolutes) { + private static void checkModelIsConvoluteOrCollection(DigitalObject dobj, Set convolutes, Set collections, Set roots) { try { boolean isConvolute = false; + boolean isCollection = false; String title = ""; for (DatastreamType ds : dobj.getDatastream()) { if ("DC".equals(ds.getID())) {//obtain title from DC stream @@ -702,6 +744,30 @@ private static void checkModelIsConvolute(DigitalObject dobj, Set if (type.startsWith("info:fedora/model:")) { String model = type.substring(18);//get the string after info:fedora/model: isConvolute = "convolute".equals(model); + isCollection = "collection".equals(model); + } + } + } + // + XmlContentType xmlContent = v.getXmlContent(); + List any = xmlContent.getAny(); + for (Element elm : any) { + List pids = XMLUtils.getElementsRecursive(elm, new XMLUtils.ElementsFilter() { + @Override + public boolean acceptElement(Element element) { + boolean equals = element.getLocalName().equals("contains"); + return equals; + } + }); + for (int i = 0; i < pids.size();i++) { + String attributeNS = pids.get(i).getAttributeNS(FedoraNamespaces.RDF_NAMESPACE_URI, "resource"); + if (attributeNS.contains("info:fedora/")) { + String rootPid = attributeNS.substring("info:fedora/".length()); + TitlePidTuple npt = new TitlePidTuple(rootPid, rootPid); + + LOGGER.info(String.format("Adding contains relation from collection %s", rootPid)); + + roots.add(npt); } } } @@ -714,6 +780,13 @@ private static void checkModelIsConvolute(DigitalObject dobj, Set convolutes.add(npt); log.info("Found (convolute) object for indexing - " + npt); } + } else if (isCollection) { + TitlePidTuple npt = new TitlePidTuple(title, dobj.getPID()); + if (collections != null) { + collections.add(npt); + log.info("Found (collection) object for indexing - " + npt); + } + } } catch (Exception ex) { diff --git a/processes/nkp-logs/src/main/java/cz/incad/kramerius/statistics/impl/nkp/SendEmail.java b/processes/nkp-logs/src/main/java/cz/incad/kramerius/statistics/impl/nkp/SendEmail.java new file mode 100644 index 0000000000..5dcf27838e --- /dev/null +++ b/processes/nkp-logs/src/main/java/cz/incad/kramerius/statistics/impl/nkp/SendEmail.java @@ -0,0 +1,49 @@ +package cz.incad.kramerius.statistics.impl.nkp; +import java.util.Properties; +import javax.mail.*; +import javax.mail.internet.*; + +public class SendEmail { + + public static void main(String[] args) { + + // Nastavení e-mailového účtu Gmail + String username = "k4system@gmail.com"; + //String username = "pavel.stastny@inovatika.cz"; + + //String password = "veje vbng xuhv goyq"; + String password = "cast wosb orpa jyny"; + + // Nastavení vlastností pro spojení s Gmail SMTP serverem + Properties props = new Properties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.host", "smtp.gmail.com"); + props.put("mail.smtp.port", "587"); + + // Vytvoření Session + Session session = Session.getInstance(props, new Authenticator() { + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + + try { + // Vytvoření zprávy + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(username)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("pavel.stastny@gmail.com")); + message.setSubject("Předmět zprávy"); + message.setText("Toto je text zprávy."); + + // Odeslání zprávy + Transport.send(message); + + System.out.println("Zpráva byla úspěšně odeslána."); + + } catch (MessagingException e) { + e.printStackTrace(); + System.out.println("Chyba při odesílání zprávy: " + e.getMessage()); + } + } +} diff --git a/rest/build.gradle b/rest/build.gradle index 1bff81ea4d..1a0f919078 100644 --- a/rest/build.gradle +++ b/rest/build.gradle @@ -13,6 +13,7 @@ dependencies { // sdnnt api project(':processes:sdnnt') //TODO: Move + api group: 'commons-fileupload', name: 'commons-fileupload', version: '1.4' api name:"iiif-presentation-model-api-3.2.5" api name:"iiif-presentation-model-impl-3.2.5" diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/ServerFilesResource.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/ServerFilesResource.java index 31c900cd08..0bbce2c023 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/ServerFilesResource.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/ServerFilesResource.java @@ -52,6 +52,47 @@ public class ServerFilesResource extends AdminApiResource { @javax.inject.Inject GenerateDownloadLinks genDownloadLinks; + @GET + @Path("/output-data-dir-for_collectionsbackup{path: (.+)?}") + @Produces(MediaType.APPLICATION_JSON) + public Response listFilesInOutputDataDirFor_collections_backup(@PathParam("path") String path,@QueryParam("generatedownloads") Boolean downloadLinks) { + try { + User user1 = this.userProvider.get(); + List roles = Arrays.stream(user1.getGroups()).map(Role::getName).collect(Collectors.toList()); + if (!permitNKPLogsFolders(user1)) { + throw new ForbiddenException("user '%s' is not allowed to list files on server (missing action '%s')", user1.getLoginname(), SecuredActions.A_IMPORT.getFormalName()); //403 + } + + String collectionsFolder = KConfiguration.getInstance().getConfiguration().getString("collections.backup.folder"); + if (collectionsFolder != null) { + File f = new File(collectionsFolder, path); + if (f.isDirectory()) { + return listFilesInDir("collections.backup.folder", path, (file)->{ + return file.isFile() && file.getName().endsWith(".zip"); + },Comparator.comparing(File::lastModified).reversed(), -1); + } else { + + JSONObject fileJson = new JSONObject(); + fileJson.put("name", f.getName()); + fileJson.put("isDir", f.isDirectory()); + + fileInfo(f, fileJson); + + if (downloadLinks) { + fileJson.put("downloadlink", genDownloadLinks.generateTmpLink(f)); + } + + return Response.ok(fileJson).build(); + } + } else { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + /** Output from nkplogs */ @GET @Path("/output-data-dir-for_nkplogs{path: (.+)?}") @Produces(MediaType.APPLICATION_JSON) diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/Collection.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/Collection.java index eaa006ebdc..e30f4bf95e 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/Collection.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/Collection.java @@ -6,38 +6,53 @@ import org.json.JSONObject; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +/** + * Collection data object + */ public final class Collection { - + + public static enum ThumbnailbStateEnum { + thumb, content, none; + } + + /** pid */ public String pid; + /** Localized names */ public String nameUndefined; public Map names = new HashMap<>(); + + /** Localized descriptions */ + public Map descriptions = new HashMap<>(); -// public String descriptionCz; -// public String descriptionEn; + /** Localized keywords **/ + public Map> keywords = new HashMap<>(); - public Map descriptions = new HashMap<>(); public String descriptionUndefined; -// public String contentCz; -// public String contentEn; - + /** Localized contents */ public Map contents = new HashMap<>(); public String contentUndefined; public LocalDateTime created; public LocalDateTime modified; public Boolean standalone; - + + /** Content; pids of objects */ public List items; + public List clippingItems; - + public String author; + + public ThumbnailbStateEnum thumbnailInfo = ThumbnailbStateEnum.none; + @Override public String toString() { return "Collection [pid=" + pid + ", names=" + names + ", descriptions=" + descriptions + ", contents=" @@ -50,74 +65,86 @@ public Collection() { public Collection(Collection original) { this.pid = original.pid; -// this.nameCz = original.nameCz; -// this.nameEn = original.nameEn; this.names = original.names; - -// this.descriptionCz = original.descriptionCz; -// this.descriptionEn = original.descriptionEn; this.descriptions = original.descriptions; - -// this.contentCz = original.contentCz; -// this.contentEn = original.contentEn; this.contents = original.contents; this.created = original.created; this.modified = original.modified; this.standalone = original.standalone; this.items = original.items; + + this.author = original.author; + + this.keywords = original.keywords; + + this.thumbnailInfo = original.thumbnailInfo; + this.clippingItems = original.clippingItems; } public Collection(JSONObject definition) throws JSONException { - System.out.println(definition.toString(1)); if (definition.has("pid")) { this.pid = definition.getString("pid"); } -// if (definition.has("name_cze")) { -// this.nameCz = definition.getString("name_cze").trim(); -// } -// if (definition.has("name_eng")) { -// this.nameEn = definition.getString("name_eng").trim(); -// } - mapLoadFromJSON(definition,"names", this.names); - -// if (this.nameCz != null) this.names.put("cze", this.nameCz); -// if (this.nameEn != null) this.names.put("eng", this.nameCz); - - -// if (definition.has("description_cze")) { -// this.descriptionCz = definition.getString("description_cze").trim(); -// } -// if (definition.has("description_eng")) { -// this.descriptionEn = definition.getString("description_eng").trim(); -// } - - mapLoadFromJSON(definition,"descriptions", this.descriptions); -// if (this.descriptionCz != null) this.descriptions.put("cze", this.descriptionCz); -// if (this.descriptionEn != null) this.descriptions.put("eng", this.descriptionEn); -// -// -// if (definition.has("content_cze")) { -// this.contentCz = definition.getString("content_cze").trim(); -// } -// if (definition.has("content_eng")) { -// this.contentEn = definition.getString("content_eng").trim(); -// } - - mapLoadFromJSON(definition,"contents", this.contents); - -// if (this.contentCz != null) this.contents.put("cze", this.contentCz); -// if (this.contentEn != null) this.contents.put("eng", this.contentEn); + if (definition.has("author")) { + this.author = definition.getString("author"); + } + + mapLoadFromJSONString(definition,"names", this.names); + mapLoadFromJSONString(definition,"descriptions", this.descriptions); + mapLoadFromJSONString(definition,"contents", this.contents); + + mapLoadFromArray(definition, "keywords", this.keywords); if (definition.has("standalone")) { this.standalone = definition.getBoolean("standalone"); } - + if (definition.has("thumbnail")) { + this.thumbnailInfo = ThumbnailbStateEnum.valueOf(definition.optString("thumbnail")); + } + + if (definition.has("clippingitems")) { + List items = new ArrayList<>(); + JSONArray jsonArray = definition.getJSONArray("clippingitems"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject clippingDef = jsonArray.getJSONObject(i); + items.add(CutItem.fromJSONObject(clippingDef)); + } + this.clippingItems = items; + } } - private void mapLoadFromJSON(JSONObject definition, String mkey, Map map) { + private void mapLoadFromArray(JSONObject definition, String mkey, Map> map) { + Iso639Converter converter = new Iso639Converter(); + + if (definition.has(mkey)) { + JSONObject subObj = definition.getJSONObject(mkey); + subObj.keySet().forEach(key-> { + if (converter.isConvertable(key.toString())) { + List list = converter.convert(key.toString()); + list.forEach(remappedKey-> { + List vals = new ArrayList<>(); + + JSONArray jsonArr = subObj.getJSONArray(key.toString()); + for (int i = 0; i < jsonArr.length(); i++) { vals.add(jsonArr.getString(i));} + + map.put(remappedKey.toString(), vals); + }); + } else { + List vals = new ArrayList<>(); + + JSONArray jsonArr = subObj.getJSONArray(key.toString()); + for (int i = 0; i < jsonArr.length(); i++) { vals.add(jsonArr.getString(i));} + + map.put(key.toString(), vals); + } + }); + } + } + + private void mapLoadFromJSONString(JSONObject definition, String mkey, Map map) { Iso639Converter converter = new Iso639Converter(); if (definition.has(mkey)) { @@ -138,42 +165,30 @@ private void mapLoadFromJSON(JSONObject definition, String mkey, Map0) { + JSONArray jsonArr = new JSONArray(); + this.clippingItems.stream().forEach(itm-> { + JSONObject itmJSON = itm.toJSON(); + jsonArr.put(itmJSON); + }); + json.put("clippingitems", jsonArr); + } return json; } - private void mapToObj(JSONObject json, String masterKey, Map map) { + private void simpleMapToObj(JSONObject json, String masterKey, Map map) { if (map != null && map.size()>0) { JSONObject obj = new JSONObject(); map.keySet().forEach(key-> { @@ -203,7 +227,22 @@ private void mapToObj(JSONObject json, String masterKey, Map map } } - + + private void arrayMapToObj(JSONObject json, String masterKey, Map> map) { + if (map != null && map.size()>0) { + JSONObject obj = new JSONObject(); + map.keySet().forEach(key-> { + List vals = map.get(key); + JSONArray jsonArr = new JSONArray(); + vals.stream().forEach(jsonArr::put); + obj.put(key, jsonArr); + }); + json.put(masterKey, obj); + + } + } + + public boolean equalsInDataModifiableByClient(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -211,7 +250,9 @@ public boolean equalsInDataModifiableByClient(Object o) { return Objects.equals(names, that.names) && Objects.equals(descriptions, that.descriptions) && Objects.equals(contents, that.contents) && - Objects.equals(standalone, that.standalone); + Objects.equals(standalone, that.standalone) && + Objects.equals(keywords, that.keywords) && + Objects.equals(author, that.author); } diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsFoxmlBuilder.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsFoxmlBuilder.java index ed678a30b0..94223a235d 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsFoxmlBuilder.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsFoxmlBuilder.java @@ -3,16 +3,21 @@ import cz.incad.kramerius.repository.KrameriusRepositoryApi; import cz.incad.kramerius.repository.RepositoryApi; import cz.incad.kramerius.rest.apiNew.admin.v70.FoxmlBuilder; +import cz.incad.kramerius.utils.StringUtils; + import org.apache.commons.lang.StringEscapeUtils; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; +import java.text.BreakIterator; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; public class CollectionsFoxmlBuilder extends FoxmlBuilder { public Document buildFoxml(Collection collection, List pidsOfItemsInCollection) { @@ -102,7 +107,55 @@ public Document buildMods(Collection collection) { note.addText(StringEscapeUtils.escapeHtml(collection.contents.get(key))); }); } + if (collection.keywords.size() > 0) { + collection.keywords.keySet().forEach(key-> { + List keywords = collection.keywords.get(key); + for (String keyword : keywords) { + + Element subjectElm = addModsElement(mods, "subject"); + subjectElm.addAttribute("lang", key); + + Element topic = addModsElement(subjectElm, "topic"); + topic.addText(keyword); + } + + }); + } + + + if (collection.author != null && StringUtils.isAnyString(collection.author)) { + + List authorParts = new ArrayList<>(); + BreakIterator wordIterator = + BreakIterator.getWordInstance(Locale.getDefault()); + wordIterator.setText(collection.author); + + // Iterace přes jednotlivá slova + int start = wordIterator.first(); + for (int end = wordIterator.next(); end != BreakIterator.DONE; start = end, end = wordIterator.next()) { + String word = collection.author.substring(start, end); + authorParts.add(word); + } + + if (authorParts.size() > 0) { + + Element personalName = addModsElement(mods, "name"); + personalName.addAttribute("type", "personal"); + personalName.addAttribute("usage", "primary"); + + Element personalNamePart1 = addModsElement(personalName, "namePart"); + personalNamePart1.addAttribute("type", "family"); + personalNamePart1.setText(authorParts.get(0)); + + Element personalNamePart2 = addModsElement(personalName, "namePart"); + personalNamePart2.addAttribute("type", "given"); + personalNamePart2.setText(authorParts.subList(1, authorParts.size()).stream().collect(Collectors.joining(" "))); + + } + + } + return document; } diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsResource.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsResource.java index 0adff96faa..7609cd8f86 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsResource.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CollectionsResource.java @@ -5,7 +5,12 @@ import cz.incad.kramerius.fedora.om.RepositoryException; import cz.incad.kramerius.repository.KrameriusRepositoryApi; import cz.incad.kramerius.repository.RepositoryApi; +import cz.incad.kramerius.repository.KrameriusRepositoryApi.KnownDatastreams; +import cz.incad.kramerius.repository.KrameriusRepositoryApi.KnownXmlFormatUris; import cz.incad.kramerius.rest.apiNew.admin.v70.AdminApiResource; +import cz.incad.kramerius.rest.apiNew.admin.v70.collections.Collection.ThumbnailbStateEnum; +import cz.incad.kramerius.rest.apiNew.admin.v70.collections.thumbs.SimpleIIIFGenerator; +import cz.incad.kramerius.rest.apiNew.admin.v70.collections.thumbs.ThumbsGenerator; import cz.incad.kramerius.rest.apiNew.exceptions.BadRequestException; import cz.incad.kramerius.rest.apiNew.exceptions.ForbiddenException; import cz.incad.kramerius.rest.apiNew.exceptions.InternalErrorException; @@ -15,9 +20,20 @@ import cz.incad.kramerius.security.SpecialObjects; import cz.incad.kramerius.security.User; import cz.incad.kramerius.utils.Dom4jUtils; +import cz.incad.kramerius.utils.StringUtils; +import cz.incad.kramerius.utils.imgs.ImageMimeType; +import cz.incad.kramerius.utils.imgs.KrameriusImageSupport; import cz.incad.kramerius.utils.java.Pair; import org.apache.commons.collections4.map.HashedMap; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.MultipartStream; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.io.FileSystemUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; +import org.apache.http.protocol.HTTP; import org.apache.solr.client.solrj.SolrServerException; import org.dom4j.Attribute; import org.dom4j.Document; @@ -26,21 +42,44 @@ import org.json.JSONException; import org.json.JSONObject; +import javax.imageio.ImageIO; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; + @Path("/admin/v7.0/collections") public class CollectionsResource extends AdminApiResource { - + + // Stream name + private static final String COLLECTION_CLIPS = "COLLECTION_CLIPS"; + private static final List THUMBS_GENERATOR = new ArrayList<>(); + static { + THUMBS_GENERATOR.add(new SimpleIIIFGenerator()); + } + public static final Logger LOGGER = Logger.getLogger(CollectionsResource.class.getName()); private static final int MAX_BATCH_SIZE = 100; @@ -58,6 +97,9 @@ public class CollectionsResource extends AdminApiResource { @Inject RightsResolver rightsResolver; + + @Inject + Provider requestProvider; /** * Creates new collection and assigns a pid to it. @@ -110,8 +152,6 @@ public Response createCollection(JSONObject collectionDefinition) { public Response getCollection(@PathParam("pid") String pid) { try { checkSupportedObjectPid(pid); - //authentication - //AuthenticatedUser user = getAuthenticatedUserByOauth(); User user1 = this.userProvider.get(); List roles = Arrays.stream(user1.getGroups()).map(Role::getName).collect(Collectors.toList()); @@ -134,6 +174,8 @@ public Response getCollection(@PathParam("pid") String pid) { } } + + @GET @Path("/prefix") @Produces(MediaType.APPLICATION_JSON) @@ -191,7 +233,8 @@ public Response getCollections(@QueryParam("withItem") String itemPid) { !permitCollectionEdit(this.rightsResolver, user1, SpecialObjects.REPOSITORY.getPid())) { throw new ForbiddenException("user '%s' is not allowed to create collections (missing action '%s')", user1.getLoginname(), SecuredActions.A_COLLECTIONS_READ); //403 } - + + // TODO: this kind of sync ?? synchronized (CollectionsResource.class) { List pids = null; if (itemPid != null) { @@ -224,6 +267,64 @@ public Response getCollections(@QueryParam("withItem") String itemPid) { throw new InternalErrorException(e.getMessage()); } } + + @POST + @Path("{pid}/image/thumb") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response uploadFile(@PathParam("pid") String pid, + InputStream mimeTypeStream + ) { + try { + + HttpServletRequest req = this.requestProvider.get(); + + DiskFileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List fileItems = upload.parseRequest(req); + if (fileItems.size() == 1) { + FileItem fileItem = fileItems.get(0); + + InputStream fileItemStream = fileItem.getInputStream(); + File tmpFile = File.createTempFile("image", "img"); + + // Kopírování dat ze vstupního proudu do souboru + try (OutputStream out = new FileOutputStream(tmpFile)) { + IOUtils.copy(fileItemStream, out); + } + synchronized (CollectionsResource.class) { + //Collection collection = fetchCollectionFromRepository(pid, false, false); + + BufferedImage read = ImageIO.read(new FileInputStream(tmpFile)); + + // 127 height + // calculate scale factor + int height = read.getHeight(); + int width = read.getWidth(); + + double factor = 127d / (double)height; + double newHeight = ((double)height * factor); + double newWidth = ((double)width * factor); + + + BufferedImage scaled = KrameriusImageSupport.scale(read, (int)newWidth, (int)newHeight); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageIO.write(scaled, "png", bos); + + RepositoryApi repoApi = krameriusRepositoryApi.getLowLevelApi(); + repoApi.updateBinaryDatastream(pid, KnownDatastreams.IMG_THUMB.name(), "image/png", bos.toByteArray()); + + Collection nCol = fetchCollectionFromRepository(pid, true, true); + return Response.ok(nCol.toJson()).build(); + } + } else { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } catch (RepositoryException | SolrServerException | IOException | FileUploadException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + throw new InternalErrorException(e.getMessage()); + } + } /** * Updates collections metadata, but not items that collection directly contains. @@ -276,14 +377,6 @@ public Response updateCollection(@PathParam("pid") String pid, JSONObject collec } } - /* private void scheduleReindexation(String objectPid, String userid, String username, String indexationType, String batchToken, boolean ignoreInconsistentObjects, String title) { - List paramsList = new ArrayList<>(); - paramsList.add(indexationType); - paramsList.add(objectPid); - paramsList.add(Boolean.toString(ignoreInconsistentObjects)); - paramsList.add(title); - processSchedulingHelper.scheduleProcess("new_indexer_index_object", paramsList, userid, username, batchToken); - }*/ /** * Sets items that the collection directly contains. I.e. removes all existing items and adds all items from method's data. @@ -515,12 +608,6 @@ private String findCyclicPath(String pid, String pidOfObjectNotAllowedOnPath, St return null; } - -// @PUT -// @Path("{pid}/children_order") -// @Produces(MediaType.APPLICATION_JSON) -// public Response setChildrenOrder(@PathParam("pid") String pid, JSONObject newChildrenOrder) { - @PUT @Path("{collectionPid}/items/delete_batch_items") @Produces(MediaType.APPLICATION_JSON) @@ -700,7 +787,220 @@ public Response deleteCollection(@PathParam("pid") String pid) { throw new InternalErrorException(e.getMessage()); } } + + + @POST + @Path("{pid}/delete_clip_item") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response removeClipItem(@PathParam("pid") String collectionPid, String itemJsonObj) { + try { + JSONObject json = new JSONObject(itemJsonObj); + CutItem clipItem = CutItem.fromJSONObject(json); + if (clipItem == null) { + throw new BadRequestException("badREquest"); + } + + //check collection + checkSupportedObjectPid(collectionPid); + if (!isModelCollection(collectionPid)) { + throw new ForbiddenException("not a collection: " + collectionPid); + } + User user = this.userProvider.get(); + if (!permitCollectionEdit(this.rightsResolver, user, collectionPid)) { + throw new ForbiddenException("user '%s' is not allowed to modify collection (missing action '%s')", user.getLoginname(), SecuredActions.A_COLLECTIONS_EDIT); //403 + } + + synchronized (CollectionsResource.class) { + + JSONArray jsonArray = new JSONArray(); + if (krameriusRepositoryApi.getLowLevelApi().datastreamExists(collectionPid, COLLECTION_CLIPS)) { + try(InputStream latestVersionOfDatastream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(collectionPid, COLLECTION_CLIPS)){ + jsonArray = new JSONArray( IOUtils.toString(latestVersionOfDatastream, "UTF-8")); + } + } + + int index = -1; + for (int i = 0; i < jsonArray.length(); i++) { + CutItem rawCutItem = CutItem.fromJSONObject(jsonArray.getJSONObject(i)); + if (clipItem.equals(rawCutItem)) { + index = i; + break; + } + } + + if (index > -1) { + if (clipItem.getUrl() != null) { + String thumbName = clipItem.getThumbnailmd5(); + if (this.krameriusRepositoryApi.getLowLevelApi().datastreamExists(collectionPid, thumbName)) { + this.krameriusRepositoryApi.getLowLevelApi().deleteDatastream(collectionPid, thumbName); + } + + } + + jsonArray.remove(index); + + krameriusRepositoryApi.getLowLevelApi().updateBinaryDatastream(collectionPid, COLLECTION_CLIPS, "application/json", jsonArray.toString().getBytes(Charset.forName("UTF-8"))); + Collection collection = fetchCollectionFromRepository(collectionPid, true, true); + return Response.ok(collection.toJson()).build(); + + } else { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + } catch (WebApplicationException e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new InternalErrorException(e.getMessage()); + } + } + + + @PUT + @Path("{collectionPid}/delete_batch_clipitems") + @Produces(MediaType.APPLICATION_JSON) + public Response removeClipItemsBatch(@PathParam("collectionPid") String collectionPid, String stringBatch) { + try { + JSONObject batch = new JSONObject(stringBatch); + JSONArray batchArray = batch.optJSONArray("clipitems"); + + checkSupportedObjectPid(collectionPid); + if (!isModelCollection(collectionPid)) { + throw new ForbiddenException("not a collection: " + collectionPid); + } + User user = this.userProvider.get(); + if (!permitCollectionEdit(this.rightsResolver, user, collectionPid)) { + throw new ForbiddenException("user '%s' is not allowed to modify collection (missing action '%s')", user.getLoginname(), SecuredActions.A_COLLECTIONS_EDIT); //403 + } + if (batchArray != null && batchArray.length() > 0) { + synchronized (CollectionsResource.class) { + boolean cuttingsModified = false; + Set thumbsToDelete = new LinkedHashSet<>(); + JSONArray fetchedJSONArray = new JSONArray(); + if (krameriusRepositoryApi.getLowLevelApi().datastreamExists(collectionPid, COLLECTION_CLIPS)) { + try(InputStream latestVersionOfDatastream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(collectionPid, COLLECTION_CLIPS)){ + fetchedJSONArray = new JSONArray( IOUtils.toString(latestVersionOfDatastream, "UTF-8")); + } + } + + for (int i = 0; i < batchArray.length(); i++) { + CutItem toDelete = CutItem.fromJSONObject(batchArray.getJSONObject(i)); + if (toDelete == null) { + throw new BadRequestException("badREquest"); + } + + int index = -1; + for (int j = 0; j < fetchedJSONArray.length(); j++) { + CutItem rawCutItem = CutItem.fromJSONObject(fetchedJSONArray.getJSONObject(j)); + if (toDelete.equals(rawCutItem)) { + index = j; + cuttingsModified = true; + break; + } + } + + if (index > -1) { + if (toDelete.getUrl() != null) { + String thumbName = toDelete.getThumbnailmd5(); + thumbsToDelete.add(thumbName); + } + fetchedJSONArray.remove(index); + + } else { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + if (cuttingsModified) { + krameriusRepositoryApi.getLowLevelApi().updateBinaryDatastream(collectionPid, COLLECTION_CLIPS, "application/json", fetchedJSONArray.toString().getBytes(Charset.forName("UTF-8"))); + for (String thumbName : thumbsToDelete) { + if (this.krameriusRepositoryApi.getLowLevelApi().datastreamExists(collectionPid, thumbName)) { + this.krameriusRepositoryApi.getLowLevelApi().deleteDatastream(collectionPid, thumbName); + } + } + } + + } + Collection collection = fetchCollectionFromRepository(collectionPid, true, true); + return Response.ok(collection.toJson()).build(); + } else { + throw new BadRequestException("badREquest"); + } + } catch (WebApplicationException e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new InternalErrorException(e.getMessage()); + } + } + + @POST + @Path("{pid}/add_clip_item") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response addClipItem(@PathParam("pid") String collectionPid, String itemJsonObj) { + try { + JSONObject json = new JSONObject(itemJsonObj); + CutItem clipItem = CutItem.fromJSONObject(json); + if (clipItem == null) { + throw new BadRequestException("badREquest"); + } + + //check collection + checkSupportedObjectPid(collectionPid); + if (!isModelCollection(collectionPid)) { + throw new ForbiddenException("not a collection: " + collectionPid); + } + User user = this.userProvider.get(); + if (!permitCollectionEdit(this.rightsResolver, user, collectionPid)) { + throw new ForbiddenException("user '%s' is not allowed to modify collection (missing action '%s')", user.getLoginname(), SecuredActions.A_COLLECTIONS_EDIT); //403 + } + + synchronized (CollectionsResource.class) { + + JSONArray jsonArray = new JSONArray(); + if (krameriusRepositoryApi.getLowLevelApi().datastreamExists(collectionPid, COLLECTION_CLIPS)) { + try(InputStream latestVersionOfDatastream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(collectionPid, COLLECTION_CLIPS)){ + jsonArray = new JSONArray( IOUtils.toString(latestVersionOfDatastream, "UTF-8")); + } + } + + if (clipItem.getUrl() != null) { + String url = clipItem.getUrl(); + String thumbName = clipItem.getThumbnailmd5(); + + THUMBS_GENERATOR.forEach(gen-> { + if (gen.acceptUrl(url)) { + try { + BufferedImage thumb = gen.generateThumbnail(url); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageIO.write(thumb, "png", bos); + + krameriusRepositoryApi.getLowLevelApi().updateBinaryDatastream(collectionPid, thumbName, "image/png", bos.toByteArray()); + } catch (IOException | RepositoryException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + } + }); + } + jsonArray.put(json); + + krameriusRepositoryApi.getLowLevelApi().updateBinaryDatastream(collectionPid, COLLECTION_CLIPS, "application/json", jsonArray.toString().getBytes(Charset.forName("UTF-8"))); + Collection collection = fetchCollectionFromRepository(collectionPid, true, true); + return Response.ok(collection.toJson()).build(); + } + } catch (WebApplicationException e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new InternalErrorException(e.getMessage()); + } + } + + + + // Udelat castecne ulozene na CDK private Collection fetchCollectionFromRepository(String pid, boolean withContent, boolean withItems) throws IOException, RepositoryException, SolrServerException { @@ -711,6 +1011,8 @@ private Collection fetchCollectionFromRepository(String pid, boolean withContent // index + akubra synchronization problem } + + collection.created = krameriusRepositoryApi.getLowLevelApi().getPropertyCreated(pid); collection.modified = krameriusRepositoryApi.getLowLevelApi().getPropertyLastModified(pid); @@ -773,14 +1075,62 @@ private Collection fetchCollectionFromRepository(String pid, boolean withContent }); } } + } + List subjectXPath = Dom4jUtils.elementsByXpath(mods.getRootElement(), "//mods/subject[@lang]"); + for (Element s : subjectXPath) { + Attribute langAttr = s.attribute("lang"); + List elms = s.elements(); + List texts = s.elements().stream().map(Element::getTextTrim).collect(Collectors.toList()); + if (!collection.keywords.containsKey(langAttr.getStringValue())) { + collection.keywords.put(langAttr.getStringValue(), new ArrayList<>()); + } + collection.keywords.get(langAttr.getStringValue()).addAll(texts); + } + + Element authorsXPath = Dom4jUtils.firstElementByXpath(mods.getRootElement(), "//mods/name[@type='personal']"); + if (authorsXPath != null) { + String author = authorsXPath.elements().stream().map(Element::getTextTrim).collect(Collectors.joining(" ")); + if (StringUtils.isAnyString(author)) { + collection.author = author; + } } + //data from RELS-EXT Document relsExt = krameriusRepositoryApi.getRelsExt(pid, false); collection.standalone = Boolean.valueOf(Dom4jUtils.stringOrNullFromFirstElementByXpath(relsExt.getRootElement(), "//standalone")); - //data from Processing index + + List items = krameriusRepositoryApi.getPidsOfItemsInCollection(pid); if (withItems) { - collection.items = krameriusRepositoryApi.getPidsOfItemsInCollection(pid); + collection.items = items; + } + + List streams = krameriusRepositoryApi.getLowLevelApi().getDatastreamNames(pid); + if (streams.contains(cz.kramerius.krameriusRepositoryAccess.KrameriusRepositoryFascade.KnownDatastreams.IMG_THUMB)) { + collection.thumbnailInfo = ThumbnailbStateEnum.thumb; + } else if (items.size() >0) { + collection.thumbnailInfo = ThumbnailbStateEnum.content; + } else { + collection.thumbnailInfo = ThumbnailbStateEnum.none; + } + + + + if (krameriusRepositoryApi.getLowLevelApi().datastreamExists(pid, COLLECTION_CLIPS)) { + + try(InputStream latestVersionOfDatastream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(pid, COLLECTION_CLIPS)) { + JSONArray jsonArray = new JSONArray( IOUtils.toString(latestVersionOfDatastream, "UTF-8")); + collection.clippingItems = CutItem.fromJSONArray(jsonArray); + collection.clippingItems.forEach(cl-> { + try { + cl.initGeneratedThumbnail(krameriusRepositoryApi.getLowLevelApi(), pid); + } catch (NoSuchAlgorithmException | RepositoryException | IOException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + }); + } + } else { + collection.clippingItems = new ArrayList<>(); } return collection; } diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CutItem.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CutItem.java new file mode 100644 index 0000000000..6c44c5a247 --- /dev/null +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/CutItem.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) Nov 19, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.incad.kramerius.rest.apiNew.admin.v70.collections; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import cz.incad.kramerius.fedora.om.RepositoryException; +import cz.incad.kramerius.repository.RepositoryApi; + +public class CutItem { + + public static final Logger LOGGER = Logger.getLogger(CutItem.class.getName()); + + private boolean generatedThumbnail; + private String name; + private String description; + private String url; + + + public CutItem() {} + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + + public void setGeneratedThumbnail(boolean generatedThumbnail) { + this.generatedThumbnail = generatedThumbnail; + } + + public boolean containsGeneratedThumbnail() { + return generatedThumbnail; + } + + + + + public String getThumbnailmd5() throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(url.getBytes()); + byte[] digest = md.digest(); + + StringBuilder hexString = new StringBuilder(); + for (byte b : digest) { + hexString.append(String.format("%02x", b)); + } + return "IMG_"+hexString.toString().toUpperCase(); + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public static final List fromJSONArray(JSONArray jsonArray) { + List items = new ArrayList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject clippingDef = jsonArray.getJSONObject(i); + items.add(CutItem.fromJSONObject(clippingDef)); + } + return items; + } + + public static final CutItem fromJSONObject(JSONObject obj) { + CutItem item = new CutItem(); + if (obj.has("name")) { + item.setName(obj.getString("name")); + } + + if (obj.has("description")) { + item.setDescription(obj.getString("description")); + } + + if (obj.has("description")) { + item.setDescription(obj.getString("description")); + } + + if (obj.has("url")) { + + item.setUrl(obj.getString("url")); + } + + return item; + + } + + + @Override + public int hashCode() { + return Objects.hash(description, name, url); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CutItem other = (CutItem) obj; + return Objects.equals(description, other.description) && Objects.equals(name, other.name) + && Objects.equals(url, other.url); + } + + @Override + public String toString() { + return "ClippingItem [name=" + name + ", description=" + description + ", url=" + url + "]"; + } + + public JSONObject toJSON() { + try { + JSONObject object = new JSONObject(); + object.put("name", this.name); + object.put("description", this.description); + object.put("url", this.url); + if (this.generatedThumbnail) { + try { + String thumb = getThumbnailmd5(); + object.put("thumb", thumb); + } catch(Exception ex) { + LOGGER.log(Level.SEVERE,ex.getMessage(),ex); + + } + } + return object; + } catch (JSONException /*| NoSuchAlgorithmException */e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + return new JSONObject(); + } + } + + + public void initGeneratedThumbnail(RepositoryApi repoApi, String pid) throws NoSuchAlgorithmException, RepositoryException, IOException { + if (repoApi.datastreamExists(pid, getThumbnailmd5())) { + this.setGeneratedThumbnail(true); + } + } + +} diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/IIFUtils.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/IIFUtils.java new file mode 100644 index 0000000000..1e7c6ae2c2 --- /dev/null +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/IIFUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) Nov 20, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.incad.kramerius.rest.apiNew.admin.v70.collections.thumbs; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; + +import org.apache.commons.lang3.tuple.Pair; + +public class IIFUtils { + + public static void main(String[] args) throws MalformedURLException, IOException { + //String url = "https://k7.inovatika.dev/search/api/client/v7.0/items/uuid:722b67fb-0e53-44b4-b4fe-12b89cca4da7/image/iiif/395,715,780,419/max/0/default.jpg"; + String url = "https://k7.inovatika.dev/search/api/client/v7.0/items/uuid:e7385d75-fac8-4ac0-b673-9e298d65dd68/image/iiif/360,913,1147,664/max/0/default.jpg"; + + Pair,Pair> parseBoundingBox = SimpleIIIFGenerator.parseBoundingBox(url); + Pair iiifInfo = SimpleIIIFGenerator.getIIIFDescriptor(url); + + BufferedImage thumb = SimpleIIIFGenerator.getThumb(url); + + + double widthScaleFactor = (double) thumb.getWidth() / iiifInfo.getLeft(); + double heightScaleFactor = (double) thumb.getHeight() / iiifInfo.getRight(); + + // Vybrání menšího scale factoru, aby zůstal obrázek uvnitř maximálních rozměrů + double scaleFactor = Math.min(widthScaleFactor, heightScaleFactor); + System.out.println(scaleFactor); + + System.out.println(SimpleIIIFGenerator.scaleBoundingBox(parseBoundingBox, scaleFactor)); + + BufferedImage createImate = SimpleIIIFGenerator.createImate(thumb, SimpleIIIFGenerator.scaleBoundingBox(parseBoundingBox, scaleFactor)); + + File f = new File(System.getProperty("user.home")+File.separator+"test.png"); + + ImageIO.write(createImate, "png", f); + + + + } +} diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/SimpleIIIFGenerator.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/SimpleIIIFGenerator.java new file mode 100644 index 0000000000..86449f2f9b --- /dev/null +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/SimpleIIIFGenerator.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) Nov 20, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.incad.kramerius.rest.apiNew.admin.v70.collections.thumbs; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.log4j.Logger; +import org.json.JSONObject; + + +import cz.incad.kramerius.utils.RESTHelper; +import cz.incad.kramerius.utils.imgs.ImageMimeType; +import cz.incad.kramerius.utils.imgs.KrameriusImageSupport; + +public class SimpleIIIFGenerator extends ThumbsGenerator { + + public static final Logger LOGGER = Logger.getLogger(SimpleIIIFGenerator.class.getName()); + + public static final String REGEXP_LEGACY = ".+/iiif/(uuid:[a-fA-F0-9-]+)/\\d+,\\d+,\\d+,\\d+/max/0/default\\.jpg"; + + public static final String REGEXP = ".+/(uuid:[a-fA-F0-9-]+)(/image/iiif/)\\d+,\\d+,\\d+,\\d+/max/0/default\\.jpg"; + public static final Pattern REGEXP_PATTERN = Pattern.compile(REGEXP); + public static final Pattern REGEXP_LEGACY_PATTERN = Pattern.compile(REGEXP_LEGACY); + + @Override + public BufferedImage generateThumbnail(String url) throws IOException { + Pair,Pair> parseBoundingBox = parseBoundingBox(url); + Pair iiifInfo = getIIIFDescriptor(url); + BufferedImage thumb = getThumb(url); + double widthScaleFactor = (double) thumb.getWidth() / iiifInfo.getLeft(); + double heightScaleFactor = (double) thumb.getHeight() / iiifInfo.getRight(); + double scaleFactor = Math.min(widthScaleFactor, heightScaleFactor); + BufferedImage image = SimpleIIIFGenerator.createImate(thumb, scaleBoundingBox(parseBoundingBox, scaleFactor)); + return image; + } + + @Override + public boolean acceptUrl(String url) { + Matcher matcher = REGEXP_PATTERN.matcher(url); + if(!matcher.matches()) { + return REGEXP_LEGACY_PATTERN.matcher(url).matches(); + } else return true; + } + + + static Pair extractBaseUrl(String imageUrl) throws MalformedURLException { + Matcher matcher = REGEXP_PATTERN.matcher(imageUrl); + Matcher legacyMatcher = REGEXP_LEGACY_PATTERN.matcher(imageUrl); + if (matcher.matches()) { + String pid = matcher.group(1); + String imageIiif = matcher.group(2); + + int startIndex = imageUrl.indexOf(pid); + int endIndex = imageUrl.indexOf(imageIiif); + + return Pair.of(imageUrl.substring(0, endIndex), true); + } else if (legacyMatcher.matches()) { + String pid = legacyMatcher.group(1); + + int startIndex = imageUrl.indexOf(pid); + int endIndex = imageUrl.indexOf(pid)+pid.length(); + + return Pair.of(imageUrl.substring(0, endIndex),false); + + } else { + throw new IllegalArgumentException(); + } + } + + public static BufferedImage getThumb(String url) throws MalformedURLException, IOException { + Pair base = SimpleIIIFGenerator.extractBaseUrl(url); + if (base.getRight()) { + BufferedImage img = KrameriusImageSupport.readImage(new URL( base.getLeft()+"/image/thumb" ), ImageMimeType.JPEG, -1); + return img; + } else { + String baseUrl = base.getLeft(); + baseUrl = baseUrl.replace("/iiif/", "/api/client/v7.0/items/"); + + BufferedImage img = KrameriusImageSupport.readImage(new URL( baseUrl+"/image/thumb" ), ImageMimeType.JPEG, -1); + return img; + } + } + + + public static String buildInfoJsonUrl(Pair baseUrl) throws MalformedURLException { + if (baseUrl.getRight()) { + return baseUrl.getLeft() + "/image/iiif/info.json"; + } else { + return baseUrl.getLeft() + "/info.json"; + } + } + + public static Pair, Pair> scaleBoundingBox( + Pair, Pair> boundingBox, double scaleFactor) { + + // Extrahování hodnot z bounding boxu + int x = boundingBox.getKey().getKey(); + int y = boundingBox.getKey().getValue(); + int width = boundingBox.getValue().getKey(); + int height = boundingBox.getValue().getValue(); + + + int scaledX = (int) (x * scaleFactor); + int scaledY = (int) (y * scaleFactor); + + int scaledWidth = (int) (width * scaleFactor); + int scaledHeight = (int) (height * scaleFactor); + + Pair scaledOrigin = Pair.of(scaledX, scaledY); + Pair scaledSize = Pair.of(scaledWidth, scaledHeight); + + return Pair.of(scaledOrigin, scaledSize); + } + + public static Pair getIIIFDescriptor(String url) throws IOException { + + Pair baseUrl = SimpleIIIFGenerator.extractBaseUrl(url); + //String itemId = SimpleIIIFGenerator.extractItemId(url); + String infoJsonUrl = SimpleIIIFGenerator.buildInfoJsonUrl(baseUrl); + + InputStream inputStream = RESTHelper.inputStream(infoJsonUrl, null, null); + JSONObject jsonObject = new JSONObject( IOUtils.toString(inputStream, "UTF-8") ); + if (jsonObject != null) { + int maxWidth = (int) jsonObject.get("width"); + int maxHeight = (int) jsonObject.get("height"); + + return Pair.of(maxWidth, maxHeight); + } else { + throw new IllegalArgumentException(); + } + } + + public static Pair, Pair> parseBoundingBox(String url) { + String regex = "/(\\d+),(\\d+),(\\d+),(\\d+)/"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + int x = Integer.parseInt(matcher.group(1)); + int y = Integer.parseInt(matcher.group(2)); + int w = Integer.parseInt(matcher.group(3)); + int h = Integer.parseInt(matcher.group(4)); + + Pair firstPair = Pair.of(x, y); + Pair secondPair = Pair.of(w, h); + Pair, Pair> resultPair = Pair.of(firstPair, secondPair); + + return resultPair; + } else { + throw new IllegalArgumentException(); + } + } + + public static BufferedImage createImate(BufferedImage puvodniObrazek, Pair, Pair> range) { + + Graphics graphics = puvodniObrazek.getGraphics(); + BufferedImage novyObrazek = new BufferedImage( + puvodniObrazek.getWidth(), puvodniObrazek.getHeight(), BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < puvodniObrazek.getHeight(); y++) { + for (int x = 0; x < puvodniObrazek.getWidth(); x++) { + Color novaBarva = null; + if (naOkrajiOblasti(x, y, range)) { + novaBarva = new Color(0,0,0,255); + } else { + int alpha = jeVOblasti(x, y, range) ? 0 : 200; + novaBarva = new Color(196, 200, 207, alpha); + } + novyObrazek.setRGB(x, y, novaBarva.getRGB()); + } + } + + graphics.drawImage(novyObrazek, 0, 0, novyObrazek.getWidth(), novyObrazek.getHeight(), 0, 0, novyObrazek.getWidth(), novyObrazek.getHeight(), null); + return puvodniObrazek; + } + + private static boolean naOkrajiOblasti(int x, int y, Pair, Pair> oblast) { + /** + * x horni startX - startX + sirka -1 + * y - startY - startY + 1 + */ + + int startX = oblast.getKey().getKey(); + int startY = oblast.getKey().getValue(); + int sirka = oblast.getValue().getKey(); + int vyska = oblast.getValue().getValue(); + + if ((x >= startX && x <= startX + sirka - 1) && (y == startY || y == startY + vyska-1)) { + return true; + } else if ((y >= startY && y <= startY + vyska - 1) && (x == startX || x == startX + sirka-1)) { + return true; + } + + return false; + } + + private static boolean jeVOblasti(int x, int y, Pair, Pair> oblast) { + int startX = oblast.getKey().getKey(); + int startY = oblast.getKey().getValue(); + int sirka = oblast.getValue().getKey(); + int vyska = oblast.getValue().getValue(); + + return x >= startX && x < startX + sirka && y >= startY && y < startY + vyska; + } + + + +} diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/ThumbsGenerator.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/ThumbsGenerator.java new file mode 100644 index 0000000000..93485253c6 --- /dev/null +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/collections/thumbs/ThumbsGenerator.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) Nov 27, 2023 Pavel Stastny + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package cz.incad.kramerius.rest.apiNew.admin.v70.collections.thumbs; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +public abstract class ThumbsGenerator { + + public abstract BufferedImage generateThumbnail(String url) throws IOException; + + public abstract boolean acceptUrl(String url); +} diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/processes/ProcessResource.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/processes/ProcessResource.java index 2c7116f561..1e812f5b1a 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/processes/ProcessResource.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/admin/v70/processes/ProcessResource.java @@ -259,7 +259,6 @@ public Response getProcessLogsOutByProcessUuid(@PathParam("process_uuid") String @GET @Path("by_process_uuid/{process_uuid}/logs/err") @Produces(MediaType.APPLICATION_OCTET_STREAM) - public Response getProcessLogsErrByProcessUuid(@PathParam("process_uuid") String processUuid, @DefaultValue("err.txt") @QueryParam("fileName") String fileName) { try { @@ -776,6 +775,7 @@ private JSONObject lrPRocessToJSONObject(LRProcess lrProcess) throws JSONExcepti //TODO: I18N private String buildInitialProcessName(String defId, List params) { + try { switch (defId) { case "new_process_api_test": @@ -839,9 +839,22 @@ private String buildInitialProcessName(String defId, List params) { case "nkplogs": { return String.format("Generování NKP logů pro období %s - %s", params.get(0), params.get(1)); } + case "backup-collections": { + return String.format("Vytváření zálohy '%s' pro %s", params.get(0), params.get(1)); + } + + case "restore-collections": { + return String.format("Obnoveni ze zálohy '%s'", params.get(0)); + } + + case "migrate-collections-from-k5": { + return String.format("Migrace sbírek z K5 instance - ('%s')", params.get(0)); + } + case "sdnnt-sync": { return "Synchronizace se SDNNT"; } + case "delete_tree": { String pid = params.get(0); String title = params.get(1); @@ -1064,6 +1077,7 @@ private List paramsToList(String id, JSONObject params, Consumer paramsToList(String id, JSONObject params, Consumer result = new ArrayList<>(); return result; } - + + case "backup-collections": { + String backupname = extractMandatoryParamString(params, "backupname"); + List pidlist = extractOptionalParamStringList(params, "pidlist", null); + + String target = "pidlist:" + pidlist.stream().collect(Collectors.joining(";")); + + if (pidlist != null) { + pidlist.forEach(p-> { + try { + ObjectPidsPath[] pidPaths = this.solrAccess.getPidPaths(p); + User user = this.userProvider.get(); + LRProcessDefinition definition = this.definitionManager.getLongRunningProcessDefinition("add_license"); + boolean permit = SecurityProcessUtils.permitProcessByDefinedActionWithPid(rightsResolver, user, definition, p, pidPaths); + consumer.accept(permit); + } catch (IOException e) { + consumer.accept(false); + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + }); + } + + List result = new ArrayList<>(); + result.add(target); + result.add(backupname); + return result; + } + + case "restore-collections": { + String backupname = extractMandatoryParamString(params, "backupname"); + + ObjectPidsPath[] pidPaths = new ObjectPidsPath[] { + ObjectPidsPath.REPOSITORY_PATH + }; + User user = this.userProvider.get(); + LRProcessDefinition definition = this.definitionManager.getLongRunningProcessDefinition("add_license"); + boolean permit = SecurityProcessUtils.permitProcessByDefinedActionWithPid(rightsResolver, user, definition, SpecialObjects.REPOSITORY.getPid(), pidPaths); + consumer.accept(permit); + + List result = new ArrayList<>(); + result.add(backupname); + return result; + } + + case "migrate-collections-from-k5": { + String k5 = extractMandatoryParamString(params, "k5"); + + ObjectPidsPath[] pidPaths = new ObjectPidsPath[] { + ObjectPidsPath.REPOSITORY_PATH + }; + User user = this.userProvider.get(); + LRProcessDefinition definition = this.definitionManager.getLongRunningProcessDefinition("add_license"); + boolean permit = SecurityProcessUtils.permitProcessByDefinedActionWithPid(rightsResolver, user, definition, SpecialObjects.REPOSITORY.getPid(), pidPaths); + consumer.accept(permit); + + List result = new ArrayList<>(); + result.add(k5); + return result; + } + case "delete_tree": { String pid = extractMandatoryParamWithValuePrefixed(params, "pid", "uuid:"); Boolean ignoreIncostencies = extractOptionalParamBoolean(params, "ignoreIncosistencies", false); diff --git a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/client/v70/ItemsResource.java b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/client/v70/ItemsResource.java index 0e7fdba2c3..53f3024eec 100644 --- a/rest/src/main/java/cz/incad/kramerius/rest/apiNew/client/v70/ItemsResource.java +++ b/rest/src/main/java/cz/incad/kramerius/rest/apiNew/client/v70/ItemsResource.java @@ -15,6 +15,7 @@ import cz.incad.kramerius.repository.RepositoryApi; import cz.incad.kramerius.repository.utils.Utils; import cz.incad.kramerius.rest.api.exceptions.ActionNotAllowed; +import cz.incad.kramerius.rest.apiNew.admin.v70.collections.CutItem; import cz.incad.kramerius.rest.apiNew.client.v70.epub.EPubFileTypes; import cz.incad.kramerius.rest.apiNew.client.v70.utils.ProvidedLicensesUtils; import cz.incad.kramerius.rest.apiNew.exceptions.BadRequestException; @@ -26,21 +27,30 @@ import cz.incad.kramerius.security.Role; import cz.incad.kramerius.security.SecuredActions; import cz.incad.kramerius.security.User; +import cz.incad.kramerius.service.ReplicateException; import cz.incad.kramerius.service.replication.FormatType; +import cz.incad.kramerius.service.replication.ReplicationUtils; import cz.incad.kramerius.statistics.accesslogs.AggregatedAccessLogs; import cz.incad.kramerius.utils.ApplicationURL; import cz.incad.kramerius.utils.Dom4jUtils; import cz.incad.kramerius.utils.FedoraUtils; import cz.incad.kramerius.utils.RESTHelper; +import cz.incad.kramerius.utils.StringUtils; import cz.incad.kramerius.utils.XMLUtils; import cz.incad.kramerius.utils.imgs.ImageMimeType; import cz.incad.kramerius.utils.java.Pair; + +import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.solr.client.solrj.SolrServerException; import org.codehaus.jettison.json.JSONArray; +import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; +import org.dom4j.Namespace; import org.dom4j.Node; +import org.dom4j.QName; +import org.dom4j.io.DOMWriter; import org.json.JSONException; import org.json.JSONObject; import org.xml.sax.SAXException; @@ -67,9 +77,12 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -551,15 +564,62 @@ public Response getImgFull(@PathParam("pid") String pid) { public Response getFoxml(@PathParam("pid") String pid) { try { checkSupportedObjectPid(pid); - //authentication - // zrusit autentizaci a vratit k //checkUserIsAllowedToReadObject(pid); //autorizace podle zdroje přístupu, POLICY apod. (by JSESSIONID) checkObjectExists(pid); Document foxml = krameriusRepositoryApi.getLowLevelApi().getFoxml(pid); // remove streams Document modifiedFoxml = removeSecuredDatastreams(foxml); + + if (modifiedFoxml != null) { + String model = model(modifiedFoxml); + if (StringUtils.isAnyString(model) && model.equals("info:fedora/model:collection")) { + + + String streamName = "COLLECTION_CLIPS"; + String collectionClipsContent = null; + // clipping_items + try(InputStream cutters = this.krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(pid,streamName)) { + if (cutters != null) { + byte[] content = replaceLocationByBinaryContent(modifiedFoxml, cutters, streamName); + collectionClipsContent = new String(content, "UTF-8"); + } + } + // thumbs from cutters + if (collectionClipsContent!= null) { + org.json.JSONArray jsonArray = new org.json.JSONArray(collectionClipsContent); + + List cutItems = CutItem.fromJSONArray(jsonArray); + for (CutItem cutItem : cutItems) { + try { + cutItem.initGeneratedThumbnail(krameriusRepositoryApi.getLowLevelApi(), pid); + } catch (NoSuchAlgorithmException | RepositoryException | IOException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + + if (cutItem.containsGeneratedThumbnail()) { + String clipThumbName = cutItem.getThumbnailmd5(); + // clipThumbName + try(InputStream cutters = this.krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(pid,clipThumbName)) { + if (cutters != null) { + replaceLocationByBinaryContent(modifiedFoxml, cutters, clipThumbName); + } + } + + } + } + } + + + // thumb + try(InputStream imgThumb = this.krameriusRepositoryApi.getImgThumb(pid)) { + if (imgThumb != null) { + String streamThumbName = "IMG_THUMB"; + replaceLocationByBinaryContent(modifiedFoxml, imgThumb, streamThumbName); + } + } + } return Response.ok().entity(modifiedFoxml.asXML()).build(); } else { throw new InternalErrorException("I cannot return the foxml object => Not all protected datastreams could be removed."); @@ -573,6 +633,37 @@ public Response getFoxml(@PathParam("pid") String pid) { } } + private byte[] replaceLocationByBinaryContent(Document modifiedFoxml, InputStream imgThumb, String streamName) + throws IOException { + String datastreamXPath = String.format("/foxml:digitalObject/foxml:datastream[@ID='%s']/foxml:datastreamVersion", streamName); + Node thumbNode = Dom4jUtils.buildXpath(datastreamXPath).selectSingleNode(modifiedFoxml.getRootElement()); + + byte[] bytes = IOUtils.toByteArray(imgThumb); + if (thumbNode != null) { + List contentLocation = Dom4jUtils.buildXpath("//foxml:contentLocation").selectNodes(thumbNode); + for (Node node : contentLocation) { node.detach(); } + Element thumbElement = (Element) thumbNode; + Element binaryContent = thumbElement.addElement("binaryContent", thumbElement.getNamespaceURI()); + binaryContent.setText(new String(Base64.encodeBase64(bytes))); + } + return bytes; + } + + private String model(Document modifiedFoxml) { + Element modelNode = (Element) Dom4jUtils.buildXpath("//model:hasModel").selectSingleNode(modifiedFoxml.getRootElement()); + if (modelNode != null) { + Namespace ns = new Namespace("rdf", Dom4jUtils.NAMESPACE_URIS.get("rdf")); + QName qname = new QName("resource", ns); + Attribute attribute = modelNode.attribute(qname); + if (attribute != null) { + return attribute.getStringValue(); + } + } + return null; + } + + + private static Document removeSecuredDatastreams(Document foxmlDoc) { synchronized(foxmlDoc) { @@ -1107,9 +1198,73 @@ private void reportAccess(String pid, String streamName) { LOGGER.log(Level.WARNING, "Can't write statistic records for " + pid + ", stream name: " + streamName, e); } } + + // =========== Collection specific endpoints - // =========== EPub specific endpoints + @GET + @Path("{pid}/collection/cuttings") + @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") + public Response getCollectionClips(@PathParam("pid") String pid) { + try { + checkSupportedObjectPid(pid); + checkObjectExists(pid); + if(!krameriusRepositoryApi.getLowLevelApi().datastreamExists(pid, "COLLECTION_CLIPS")) { + throw new NotFoundException(); + } else { + String mimetype = krameriusRepositoryApi.getLowLevelApi().getDatastreamMimetype(pid, "COLLECTION_CLIPS"); + org.json.JSONArray outputValue = new org.json.JSONArray(); + try(InputStream istream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(pid, "COLLECTION_CLIPS")) { + org.json.JSONArray inputValue = new org.json.JSONArray(IOUtils.toString(istream, "UTF-8")); + + List cutItems = CutItem.fromJSONArray(inputValue); + cutItems.forEach(cl-> { + try { + cl.initGeneratedThumbnail(krameriusRepositoryApi.getLowLevelApi(), pid); + } catch (NoSuchAlgorithmException | RepositoryException | IOException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + outputValue.put(cl.toJSON()); + }); + } + return Response.ok().entity(outputValue).type(mimetype).build(); + } + } catch (WebApplicationException e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new InternalErrorException(e.getMessage()); + } + } + + @GET + @Path("{pid}/collection/cuttings/image/{thumb_id}") + public Response getCollectionThumb(@PathParam("pid") String pid, @PathParam("thumb_id") String thumbId) { + try { + checkSupportedObjectPid(pid); + checkObjectExists(pid); + if(!krameriusRepositoryApi.getLowLevelApi().datastreamExists(pid, thumbId)) { + throw new NotFoundException("no image/thumb %s available for object %s ", thumbId, pid); + } else { + String mimetype = krameriusRepositoryApi.getLowLevelApi().getDatastreamMimetype(pid, thumbId); + + InputStream istream = krameriusRepositoryApi.getLowLevelApi().getLatestVersionOfDatastream(pid, thumbId); + StreamingOutput stream = output -> { + IOUtils.copy(istream, output); + IOUtils.closeQuietly(istream); + }; + return Response.ok().entity(stream).type(mimetype).build(); + } + } catch (WebApplicationException e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new InternalErrorException(e.getMessage()); + } + } + + + // =========== EPub specific endpoints @HEAD @Path("{pid}/epub") public Response isEpubAvailable(@PathParam("pid") String pid) { @@ -1143,6 +1298,9 @@ private boolean isEpubMimeType(String pid, KrameriusRepositoryApi.KnownDatastrea return epub; } + + + @GET @Path("{pid}/epub/{path: .*}") public Response getPaths(@PathParam("pid") String pid, @PathParam("path") PathSegment pathSegment,@Context UriInfo info ) { diff --git a/search/build.gradle b/search/build.gradle index dcd67ea1de..cbee6f7a9b 100644 --- a/search/build.gradle +++ b/search/build.gradle @@ -51,6 +51,7 @@ dependencies { api project(':processes:sdnnt') //TODO: Move api project(':processes:licenses') + api project(':processes:collections-backup') api 'javax.servlet:jstl:1.2' api 'taglibs:standard:1.1.2' diff --git a/search/src/java/cz/incad/Kramerius/AbstractImageServlet.java b/search/src/java/cz/incad/Kramerius/AbstractImageServlet.java index ead1d448f8..5938a7f56c 100644 --- a/search/src/java/cz/incad/Kramerius/AbstractImageServlet.java +++ b/search/src/java/cz/incad/Kramerius/AbstractImageServlet.java @@ -234,6 +234,7 @@ protected void onResponseReceived(HttpResponse response) throws HttpException, I resp.setStatus(statusCode); if (statusCode == 200) { resp.setContentType(response.getEntity().getContentType().getValue()); + LOGGER.fine(String.format("Set access-control-header %s ", "Access-Control-Allow-Origin *")); resp.setHeader("Access-Control-Allow-Origin", "*"); Header cacheControl = response.getLastHeader("Cache-Control"); if (cacheControl != null) resp.setHeader(cacheControl.getName(), cacheControl.getValue()); diff --git a/search/src/java/cz/incad/Kramerius/CORSFilter.java b/search/src/java/cz/incad/Kramerius/CORSFilter.java index 6517116260..377b91021e 100644 --- a/search/src/java/cz/incad/Kramerius/CORSFilter.java +++ b/search/src/java/cz/incad/Kramerius/CORSFilter.java @@ -3,9 +3,12 @@ import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.logging.Logger; public class CORSFilter implements Filter { + public static final Logger LOGGER = Logger.getLogger(CORSFilter.class.getName()); + @Override public void init(FilterConfig filterConfig) throws ServletException { @@ -14,7 +17,10 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; - response.setHeader("Access-Control-Allow-Origin", "*"); + if (!response.containsHeader("Access-Control-Allow-Origin")) { + LOGGER.fine(String.format("Set access-control-header %s ", "Access-Control-Allow-Origin *")); + response.setHeader("Access-Control-Allow-Origin", "*"); + } response.setHeader("Access-Control-Allow-Methods", "OPTIONS, POST, GET, PUT, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with" + diff --git a/search/src/java/cz/incad/Kramerius/imaging/ImageStreamsServlet.java b/search/src/java/cz/incad/Kramerius/imaging/ImageStreamsServlet.java index 6b8e579be2..07238882b0 100644 --- a/search/src/java/cz/incad/Kramerius/imaging/ImageStreamsServlet.java +++ b/search/src/java/cz/incad/Kramerius/imaging/ImageStreamsServlet.java @@ -23,6 +23,7 @@ import java.awt.image.BufferedImage; import java.io.*; import java.util.logging.Level; +import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -48,6 +49,8 @@ */ public class ImageStreamsServlet extends AbstractImageServlet { + public static final Logger LOGGER = Logger.getLogger(ImageStreamsServlet.class.getName()); + /** * Parameter for stream */ @@ -95,7 +98,9 @@ public boolean turnOnIterateScaling(String stream) { @Override protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + LOGGER.fine(String.format("Set access-control-header %s ", "Access-Control-Allow-Origin *")); resp.setHeader("Access-Control-Allow-Origin", "*"); + resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); String pid = req.getParameter(UUID_PARAMETER); if (pid == null || pid.trim().equals("")) { @@ -129,6 +134,7 @@ protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws S @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + LOGGER.fine(String.format("Set access-control-header %s ", "Access-Control-Allow-Origin *")); resp.setHeader("Access-Control-Allow-Origin", "*"); resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); diff --git a/settings.gradle b/settings.gradle index 79129c464a..26a3db291d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,6 +30,8 @@ include 'processes:nkp-logs' include 'processes:statistics' include 'processes:sdnnt' include 'processes:licenses' +include 'processes:collections-backup' + include 'security:authfilters' include 'security:oauth' diff --git a/shared/common/build.gradle b/shared/common/build.gradle index 1177e5b9fa..270268836b 100644 --- a/shared/common/build.gradle +++ b/shared/common/build.gradle @@ -63,6 +63,7 @@ dependencies { api "com.sun.jersey:jersey-client:${jerseyversion}" api "com.sun.jersey:jersey-json:${jerseyversion}" + api 'com.sun.jersey.contribs:jersey-multipart:1.19.4' api "com.sun.jersey.contribs:jersey-apache-client:${jerseyversion}" api "com.sun.jersey.contribs:jersey-guice:${jerseyversion}" diff --git a/shared/common/src/main/java/cz/incad/kramerius/processes/NextSchedulerTask.java b/shared/common/src/main/java/cz/incad/kramerius/processes/NextSchedulerTask.java index 47d0b38a0b..6125e930fc 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/processes/NextSchedulerTask.java +++ b/shared/common/src/main/java/cz/incad/kramerius/processes/NextSchedulerTask.java @@ -33,7 +33,7 @@ public void run() { try { definitionManager.load(); List plannedProcess = lrProcessManager.getPlannedProcess(allowRunningProcesses()); - if (!plannedProcess.isEmpty()) { + if (!plannedProcess.isEmpty() && this.processScheduler.getApplicationLib() != null /* initalized */) { List longRunningProcesses = lrProcessManager.getLongRunningProcesses(States.RUNNING); if (longRunningProcesses.size() < allowRunningProcesses()) { LRProcess lrProcess = plannedProcess.get(0); @@ -43,7 +43,11 @@ public void run() { LOGGER.fine("the maximum number of running processes is reached"); } } else { - LOGGER.fine("no planned process found"); + if (this.processScheduler.getApplicationLib() == null) { + LOGGER.fine("scheduler is not initialized"); + } else { + LOGGER.fine("no planned process found "); + } } this.processScheduler.scheduleNextTask(); } catch (Throwable e) { diff --git a/shared/common/src/main/java/cz/incad/kramerius/processes/new_api/ProcessScheduler.java b/shared/common/src/main/java/cz/incad/kramerius/processes/new_api/ProcessScheduler.java index 1184fbd199..8b124240a2 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/processes/new_api/ProcessScheduler.java +++ b/shared/common/src/main/java/cz/incad/kramerius/processes/new_api/ProcessScheduler.java @@ -57,6 +57,17 @@ public static void schedule(String processPayload, String parentProcessAuthToken } + + public static void scheduleImport(String folder,String parentProcessAuthToken) { + JSONObject data = new JSONObject(); + data.put("defid", "import"); + JSONObject params = new JSONObject(); + params.put("inputDataDir", folder); + params.put("startIndexer", true); + data.put("params", params); + schedule(data.toString(), parentProcessAuthToken); + } + //TODO: cleanup public static void scheduleIndexation(String pid, String title, boolean includingDescendants, String parentProcessAuthToken) { JSONObject data = new JSONObject(); diff --git a/shared/common/src/main/java/cz/incad/kramerius/processes/res/lp.st b/shared/common/src/main/java/cz/incad/kramerius/processes/res/lp.st index e05a1f0680..1ec5cb5f15 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/processes/res/lp.st +++ b/shared/common/src/main/java/cz/incad/kramerius/processes/res/lp.st @@ -674,4 +674,41 @@ -Xmx4096m -Xms256m -Dfile.encoding=UTF-8 a_generate_nkplogs + + + backup-collections + Collection backup + cz.inovatika.collections.Backup + + lrOut + + lrErr + -Xmx4096m -Xms256m -Dfile.encoding=UTF-8 + a_collections_edit + + + + restore-collections + Restore collections + cz.inovatika.collections.Restore + + lrOut + + lrErr + -Xmx4096m -Xms256m -Dfile.encoding=UTF-8 + a_collections_edit + + + + migrate-collections-from-k5 + Collections from K5 instance + cz.inovatika.collections.migrations.FromK5Instance + + lrOut + + lrErr + -Xmx4096m -Xms256m -Dfile.encoding=UTF-8 + a_collections_edit + + diff --git a/shared/common/src/main/java/cz/incad/kramerius/repository/KrameriusRepositoryApi.java b/shared/common/src/main/java/cz/incad/kramerius/repository/KrameriusRepositoryApi.java index 01cb255c7a..7b224ded2b 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/repository/KrameriusRepositoryApi.java +++ b/shared/common/src/main/java/cz/incad/kramerius/repository/KrameriusRepositoryApi.java @@ -46,8 +46,11 @@ enum KnownDatastreams { MIGRATION("MIGRATION"), IMG_FULL_ADM("IMG_FULL_ADM"), AUDIT("AUDIT"), - TEXT_OCR_ADM("TEXT_OCR_ADM"); + TEXT_OCR_ADM("TEXT_OCR_ADM"), + COLLECTION_CLIPPINGS("COLLECTION_CLIPPINGS"); + + private final String value; KnownDatastreams(String value) { diff --git a/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApi.java b/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApi.java index bf4f93829b..53cbb68f1c 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApi.java +++ b/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApi.java @@ -104,6 +104,11 @@ public interface RepositoryApi { //UPDATE public void updateInlineXmlDatastream(String pid, String dsId, Document streamDoc, String formatUri) throws RepositoryException, IOException; + public void updateBinaryDatastream(String pid, String streamName, String mimeType, byte[] byteArray) throws RepositoryException; + + + public void deleteDatastream(String pid, String streamName) throws RepositoryException; + /** * @param ds part of FOXML that contains definition of the datastream. I.e. root element datastream with subelement(s) datastreamVersion. */ diff --git a/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApiImpl.java b/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApiImpl.java index 7f1e988eb3..33679dc01a 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApiImpl.java +++ b/shared/common/src/main/java/cz/incad/kramerius/repository/RepositoryApiImpl.java @@ -3,6 +3,9 @@ import com.google.inject.Inject; import com.google.inject.name.Named; import com.qbizm.kramerius.imp.jaxb.DigitalObject; + +import cz.incad.kramerius.FedoraAccess; +import cz.incad.kramerius.fedora.om.Repository; import cz.incad.kramerius.fedora.om.RepositoryDatastream; import cz.incad.kramerius.fedora.om.RepositoryException; import cz.incad.kramerius.fedora.om.RepositoryObject; @@ -411,27 +414,53 @@ public List getTripletSources(String targetPid) throws RepositoryExcept }); return triplets; } + @Override public void updateInlineXmlDatastream(String pid, String dsId, Document streamDoc, String formatUri) throws RepositoryException, IOException { Lock writeLock = AkubraDOManager.getWriteLock(pid); try { RepositoryObject object = akubraRepository.getObject(pid); - + object.deleteStream(dsId); object.createStream(dsId, "text/xml", new ByteArrayInputStream(streamDoc.asXML().getBytes(Charset.forName("UTF-8")))); -// appendNewInlineXmlDatastreamVersion(foxml, dsId, streamDoc, formatUri); -// updateLastModifiedTimestamp(foxml); -// DigitalObject updatedDigitalObject = foxmlDocToDigitalObject(foxml); -// akubraRepository.deleteObject(pid, false, false); -// akubraRepository.ingestObject(updatedDigitalObject); -// akubraRepository.commitTransaction(); } finally { writeLock.unlock(); } } + public void updateBinaryDatastream(String pid, String streamName, String mimeType, byte[] byteArray) throws RepositoryException { + Lock writeLock = AkubraDOManager.getWriteLock(pid); + try { + RepositoryObject object = akubraRepository.getObject(pid); + if (object != null) { + if (object.streamExists(streamName)) { + object.deleteStream(streamName); + } + ByteArrayInputStream bos = new ByteArrayInputStream(byteArray); + object.createManagedStream(streamName, mimeType, bos); + } + } finally { + writeLock.unlock(); + } + } + + public void deleteDatastream(String pid, String streamName) throws RepositoryException { + Lock writeLock = AkubraDOManager.getWriteLock(pid); + try { + RepositoryObject object = akubraRepository.getObject(pid); + if (object != null) { + if (object.streamExists(streamName)) { + object.deleteStream(streamName); + } + } + } finally { + writeLock.unlock(); + } + } + + @Override public void setDatastreamXml(String pid, String dsId, Document ds) throws RepositoryException, IOException { Lock writeLock = AkubraDOManager.getWriteLock(pid); diff --git a/shared/common/src/main/java/cz/incad/kramerius/utils/Dom4jUtils.java b/shared/common/src/main/java/cz/incad/kramerius/utils/Dom4jUtils.java index fa40ab93dc..47b9586f95 100644 --- a/shared/common/src/main/java/cz/incad/kramerius/utils/Dom4jUtils.java +++ b/shared/common/src/main/java/cz/incad/kramerius/utils/Dom4jUtils.java @@ -19,7 +19,7 @@ public class Dom4jUtils { - private static Map NAMESPACE_URIS = new HashMap<>(); + public static Map NAMESPACE_URIS = new HashMap<>(); static { NAMESPACE_URIS.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); diff --git a/shared/common/src/main/java/res/configuration.properties b/shared/common/src/main/java/res/configuration.properties index 948df970cc..510c4791ef 100644 --- a/shared/common/src/main/java/res/configuration.properties +++ b/shared/common/src/main/java/res/configuration.properties @@ -536,3 +536,6 @@ keycloak.secret=changeme #Keycloak for generating json file keycloak.auth-server-url=http://localhost:8083/auth/ keycloak.realm=kramerius + + +collections.backup.folder=${sys:user.home}/.kramerius4/collection-backups