org.mycore.frontend.cli.MCRObjectCommands Maven / Gradle / Ivy
/*
* This file is part of *** M y C o R e ***
* See http://www.mycore.de/ for details.
*
* MyCoRe 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.
*
* MyCoRe 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 MyCoRe. If not, see .
*/
package org.mycore.frontend.cli;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.transform.JDOMResult;
import org.jdom2.transform.JDOMSource;
import org.mycore.access.MCRAccessException;
import org.mycore.backend.jpa.MCREntityManagerProvider;
import org.mycore.backend.jpa.MCRStreamQuery;
import org.mycore.common.MCRException;
import org.mycore.common.MCRPersistenceException;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.MCRStreamUtils;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.common.content.MCRContent;
import org.mycore.common.content.MCRSourceContent;
import org.mycore.common.xml.MCREntityResolver;
import org.mycore.common.xml.MCRURIResolver;
import org.mycore.common.xml.MCRXMLHelper;
import org.mycore.common.xml.MCRXMLParserFactory;
import org.mycore.common.xsl.MCRErrorListener;
import org.mycore.datamodel.common.MCRActiveLinkException;
import org.mycore.datamodel.common.MCRLinkTableManager;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
import org.mycore.datamodel.ifs2.MCRMetadataVersion;
import org.mycore.datamodel.metadata.MCRBase;
import org.mycore.datamodel.metadata.MCRDerivate;
import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkID;
import org.mycore.datamodel.metadata.MCRMetaLinkID;
import org.mycore.datamodel.metadata.MCRMetadataManager;
import org.mycore.datamodel.metadata.MCRObject;
import org.mycore.datamodel.metadata.MCRObjectID;
import org.mycore.datamodel.metadata.MCRObjectUtils;
import org.mycore.datamodel.niofs.MCRPath;
import org.mycore.datamodel.niofs.utils.MCRRecursiveDeleter;
import org.mycore.datamodel.niofs.utils.MCRTreeCopier;
import org.mycore.frontend.cli.annotation.MCRCommand;
import org.mycore.frontend.cli.annotation.MCRCommandGroup;
import org.mycore.tools.MCRTopologicalSort;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
/**
* Provides static methods that implement commands for the MyCoRe command line interface. Robert: Ideas for clean-up -
* "transform ..." and "xslt..." do the same thing and should thereform be named uniquely - "transformm ...." -
* "delete by Query ..." can be deleted - "select ..." and "delete selected ..." supply the same behaviour in 2 commands
* - "list objects matching ..." can be deleted - "select ..." and "list selected" supply the same behaviour in 2
* commands
*
* @author Jens Kupferschmidt
* @author Frank Lützenkirchen
* @author Robert Stephan
* @version $Revision$ $Date$
*/
@MCRCommandGroup(
name = "Object Commands")
public class MCRObjectCommands extends MCRAbstractCommands {
private static final String EXPORT_OBJECT_TO_DIRECTORY_COMMAND = "export object {0} to directory {1} with {2}";
/** The logger */
private static Logger LOGGER = LogManager.getLogger(MCRObjectCommands.class);
/** Default transformer script */
public static final String DEFAULT_TRANSFORMER = "save-object.xsl";
/** static compiled transformer stylesheets */
private static Hashtable translist = new Hashtable<>();
public static void setSelectedObjectIDs(List selected) {
LOGGER.info("{} objects selected", selected.size());
MCRSessionMgr.getCurrentSession().put("mcrSelectedObjects", selected);
}
@SuppressWarnings("unchecked")
public static List getSelectedObjectIDs() {
final List list = (List) MCRSessionMgr.getCurrentSession().get("mcrSelectedObjects");
if (list == null) {
return Collections.EMPTY_LIST;
}
return list;
}
/**
* Delete all MCRObject from the datastore for a given type.
*
* @param type
* the type of the MCRObjects that should be deleted
*/
@MCRCommand(
syntax = "delete all objects of type {0}",
help = "Removes MCRObjects of type {0}.",
order = 20)
public static List deleteAllObjects(String type) {
final List objectIds = MCRXMLMetadataManager.instance().listIDsOfType(type);
List cmds = new ArrayList<>(objectIds.size());
for (String id : objectIds) {
cmds.add("delete object " + id);
}
return cmds;
}
/**
* Delete all MCRObjects from the datastore in topological order
*
*/
@MCRCommand(
syntax = "delete all objects in topological order",
help = "Removes all MCRObjects in topological order.",
order = 25)
public static List deleteTopologicalAllObjects() {
final List objectIds = MCRXMLMetadataManager.instance().listIDs();
String[] objects = objectIds.stream().filter(id -> !id.contains("_derivate_")).toArray(String[]::new);
MCRTopologicalSort ts = new MCRTopologicalSort();
ts.prepareMCRObjects(objects);
int[] order = ts.doTopoSort();
List cmds = new ArrayList<>(objectIds.size());
if (order != null) {
//delete in reverse order
for (int o = order.length - 1; o >= 0; o--) {
cmds.add("delete object " + ts.getNodeName(order[o]));
}
}
return cmds;
}
/**
* Delete a MCRObject from the datastore.
*
* @param id
* the id of the MCRObject that should be deleted
* @throws MCRPersistenceException if a persistence problem is occurred
* @throws MCRAccessException see {@link MCRMetadataManager#deleteMCRObject(MCRObjectID)}
* @throws MCRActiveLinkException if object is referenced by other objects
*/
@MCRCommand(
syntax = "delete object {0}",
help = "Removes a MCRObject with the MCRObjectID {0}",
order = 40)
public static void delete(String id) throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
MCRObjectID mcrId = MCRObjectID.getInstance(id);
MCRMetadataManager.deleteMCRObject(mcrId);
LOGGER.info("{} deleted.", mcrId);
}
/**
* Runs though all mycore objects which are linked with the given object and removes its link. This includes
* parent/child relations and all {@link MCRMetaLinkID} in the metadata section.
*
* @param id
* the id of the MCRObject that should be deleted
* @throws MCRPersistenceException if a persistence problem is occurred
*/
@MCRCommand(
syntax = "clear links of object {0}",
help = "removes all links of this object, including parent/child relations"
+ " and all MetaLinkID's in the metadata section",
order = 45)
public static void clearLinks(String id) throws MCRPersistenceException {
final MCRObjectID mcrId = MCRObjectID.getInstance(id);
AtomicInteger counter = new AtomicInteger(0);
MCRObjectUtils.removeLinks(mcrId).forEach(linkedObject -> {
try {
LOGGER.info("removing link '{}' of '{}'.", mcrId, linkedObject.getId());
MCRMetadataManager.update(linkedObject);
counter.incrementAndGet();
} catch (Exception exc) {
LOGGER.error(String.format(Locale.ROOT, "Unable to update object '%s'", linkedObject), exc);
}
});
LOGGER.info("{} link(s) removed of {}.", counter.get(), mcrId);
}
/**
* Delete MCRObject's form ID to ID from the datastore.
*
* @param idFrom
* the start ID for deleting the MCRObjects
* @param idTo
* the stop ID for deleting the MCRObjects
* @return list of delete commands
*/
@MCRCommand(
syntax = "delete object from {0} to {1}",
help = "Removes MCRObjects in the number range between the MCRObjectID {0} and {1}.",
order = 30)
public static List deleteFromTo(String idFrom, String idTo) {
MCRObjectID from = MCRObjectID.getInstance(idFrom);
MCRObjectID to = MCRObjectID.getInstance(idTo);
int lowerBound = from.getNumberAsInteger();
int upperBound = to.getNumberAsInteger();
if (lowerBound > upperBound) {
throw new MCRException("The from-to-interval is false.");
}
List cmds = new ArrayList<>(upperBound - lowerBound);
for (int i = lowerBound; i < upperBound + 1; i++) {
String id = MCRObjectID.formatID(from.getProjectId(), from.getTypeId(), i);
if (MCRMetadataManager.exists(MCRObjectID.getInstance(id))) {
cmds.add("delete object " + id);
}
}
return cmds;
}
/**
* Load MCRObject's from all XML files in a directory in proper order (respecting parent-child-relationships).
*
* @param directory
* the directory containing the XML files
*/
@MCRCommand(
syntax = "load all objects in topological order from directory {0}",
help = "Loads all MCRObjects form the directory {0} to the system "
+ "respecting the order of parents and children.",
order = 75)
public static List loadTopologicalFromDirectory(String directory) {
return processFromDirectory(true, directory, false);
}
/**
* Update MCRObject's from all XML files in a directory in proper order (respecting parent-child-relationships).
*
* @param directory
* the directory containing the XML files
*/
@MCRCommand(
syntax = "update all objects in topological order from directory {0}",
help = "Updates all MCRObjects from the directory {0} in the system "
+ "respecting the order of parents and children.",
order = 95)
public static List updateTopologicalFromDirectory(String directory) {
return processFromDirectory(true, directory, true);
}
/**
* Load MCRObject's from all XML files in a directory.
*
* @param directory
* the directory containing the XML files
*/
@MCRCommand(
syntax = "load all objects from directory {0}",
help = "Loads all MCRObjects from the directory {0} to the system.",
order = 70)
public static List loadFromDirectory(String directory) {
return processFromDirectory(false, directory, false);
}
/**
* Update MCRObject's from all XML files in a directory.
*
* @param directory
* the directory containing the XML files
*/
@MCRCommand(
syntax = "update all objects from directory {0}",
help = "Updates all MCRObjects from the directory {0} in the system.",
order = 90)
public static List updateFromDirectory(String directory) {
return processFromDirectory(false, directory, true);
}
/**
* Load or update MCRObject's from all XML files in a directory.
*
* @param topological
* if true, the dependencies of parent and child objects will be respected
* @param directory
* the directory containing the XML files
* @param update
* if true, object will be updated, else object is created
*/
private static List processFromDirectory(boolean topological, String directory, boolean update) {
File dir = new File(directory);
if (!dir.isDirectory()) {
LOGGER.warn("{} ignored, is not a directory.", directory);
return null;
}
String[] list = dir.list();
if (list == null || list.length == 0) {
LOGGER.warn("No files found in directory {}", directory);
return null;
}
Predicate isMetaXML = file -> file.endsWith(".xml") && !file.contains("derivate");
Function cmdFromFile = file -> (update ? "update" : "load") + " object from file "
+ new File(dir, file).getAbsolutePath();
if (topological) {
MCRTopologicalSort ts = new MCRTopologicalSort();
ts.prepareData(list, dir);
return Optional.ofNullable(ts.doTopoSort())
.map(Arrays::stream)
.map(is -> is.mapToObj(i -> list[i]))
.orElse(Stream.empty())
.filter(isMetaXML)
.map(cmdFromFile)
.collect(Collectors.toList());
} else {
return Arrays.stream(list)
.filter(isMetaXML)
.sorted()
.map(cmdFromFile)
.collect(Collectors.toList());
}
}
/**
* Load a MCRObjects from an XML file.
*
* @param file
* the location of the xml file
* @throws MCRAccessException see {@link MCRMetadataManager#create(MCRObject)}
*/
@MCRCommand(
syntax = "load object from file {0}",
help = "Adds a MCRObject from the file {0} to the system.",
order = 60)
public static boolean loadFromFile(String file) throws MCRException, SAXParseException,
IOException, MCRAccessException {
return loadFromFile(file, true);
}
/**
* Load a MCRObjects from an XML file.
*
* @param file
* the location of the xml file
* @param importMode
* if true, servdates are taken from xml file
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
public static boolean loadFromFile(String file, boolean importMode) throws MCRException,
SAXParseException, IOException, MCRAccessException {
return processFromFile(new File(file), false, importMode);
}
/**
* Update a MCRObject's from an XML file.
*
* @param file
* the location of the xml file
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
@MCRCommand(
syntax = "update object from file {0}",
help = "Updates a MCRObject from the file {0} in the system.",
order = 80)
public static boolean updateFromFile(String file) throws MCRException, SAXParseException,
IOException, MCRAccessException {
return updateFromFile(file, true);
}
/**
* Update a MCRObject's from an XML file.
*
* @param file
* the location of the xml file
* @param importMode
* if true, servdates are taken from xml file
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
public static boolean updateFromFile(String file, boolean importMode) throws MCRException,
SAXParseException, IOException, MCRAccessException {
return processFromFile(new File(file), true, importMode);
}
/**
* Load or update an MCRObject's from an XML file.
*
* @param file
* the location of the xml file
* @param update
* if true, object will be updated, else object is created
* @param importMode
* if true, servdates are taken from xml file
* @throws SAXParseException
* unable to build the mycore object from the file's URI
* @throws MCRException
* the parent of the given object does not exists
* @throws MCRAccessException
* if write permission is missing
*/
private static boolean processFromFile(File file, boolean update, boolean importMode)
throws MCRException, SAXParseException, IOException, MCRAccessException {
if (!file.getName().endsWith(".xml")) {
LOGGER.warn("{} ignored, does not end with *.xml", file);
return false;
}
if (!file.isFile()) {
LOGGER.warn("{} ignored, is not a file.", file);
return false;
}
LOGGER.info("Reading file {} ...", file);
MCRObject mcrObject = new MCRObject(file.toURI());
if (mcrObject.hasParent()) {
MCRObjectID parentID = mcrObject.getStructure().getParentID();
if (!MCRMetadataManager.exists(mcrObject.getStructure().getParentID())) {
throw new MCRException("The parent object " + parentID + "does not exist for " + mcrObject + ".");
}
}
mcrObject.setImportMode(importMode);
LOGGER.debug("Label --> {}", mcrObject.getLabel());
if (update) {
MCRMetadataManager.update(mcrObject);
LOGGER.info("{} updated.", mcrObject.getId());
} else {
MCRMetadataManager.create(mcrObject);
LOGGER.info("{} loaded.", mcrObject.getId());
}
return true;
}
/**
* Shows the next free MCRObjectIDs.
*
* @param base
* the base String of the MCRObjectID
*/
public static void showNextID(String base) {
try {
LOGGER.info("The next free ID is {}", MCRObjectID.getNextFreeId(base));
} catch (MCRException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Shows the last used MCRObjectIDs.
*
* @param base
* the base String of the MCRObjectID
*/
public static void showLastID(String base) {
try {
LOGGER.info("The last used ID is {}", MCRObjectID.getLastID(base));
} catch (MCRException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Export an MCRObject to a file named MCRObjectID .xml in a directory. The method use the converter
* stylesheet mcr_style_object.xsl.
*
* @param id
* the id of the MCRObject to be save.
* @param dirname
* the dirname to store the object
* @param style
* the type of the stylesheet
*/
@MCRCommand(
syntax = EXPORT_OBJECT_TO_DIRECTORY_COMMAND,
help = "Stores the MCRObject with the MCRObjectID {0} to the directory {1} with the stylesheet {2}-object.xsl."
+ " For {2} save is the default.",
order = 110)
public static void export(String id, String dirname, String style) {
export(id, id, dirname, style);
}
/**
* Save any MCRObject's to files named MCRObjectID .xml in a directory. The saving starts with fromID and
* runs to toID. ID's they was not found will skiped. The method use the converter stylesheet mcr_style
* _object.xsl.
*
* @param fromID
* the ID of the MCRObject from be save.
* @param toID
* the ID of the MCRObject to be save.
* @param dirname
* the filename to store the object
* @param style
* the type of the stylesheet
*/
@MCRCommand(
syntax = "export object from {0} to {1} to directory {2} with {3}",
help = "Stores all MCRObjects with MCRObjectID's between {0} and {1} to the directory {2} "
+ "with the stylesheet {3}-object.xsl. For {3} save is the default.",
order = 100)
public static void export(String fromID, String toID, String dirname, String style) {
MCRObjectID fid, tid;
// check fromID and toID
try {
fid = MCRObjectID.getInstance(fromID);
tid = MCRObjectID.getInstance(toID);
} catch (Exception ex) {
LOGGER.error("FromID : {}", ex.getMessage());
return;
}
// check dirname
File dir = new File(dirname);
if (!dir.isDirectory()) {
LOGGER.error("{} is not a dirctory.", dirname);
return;
}
int k = 0;
try {
Transformer trans = getTransformer(style);
for (int i = fid.getNumberAsInteger(); i < tid.getNumberAsInteger() + 1; i++) {
String id = MCRObjectID.formatID(fid.getProjectId(), fid.getTypeId(), i);
if (!MCRMetadataManager.exists(MCRObjectID.getInstance(id))) {
continue;
}
if (!exportMCRObject(dir, trans, id)) {
continue;
}
k++;
}
} catch (Exception ex) {
LOGGER.error(ex.getMessage());
LOGGER.error("Exception while store file to {}", dir.getAbsolutePath());
return;
}
LOGGER.info("{} Object's stored under {}.", k, dir.getAbsolutePath());
}
/**
* Save all MCRObject's to files named MCRObjectID .xml in a dirnamedirectory for the data type
* type. The method use the converter stylesheet mcr_style_object.xsl.
*
* @param type
* the MCRObjectID type
* @param dirname
* the filename to store the object
* @param style
* the type of the stylesheet
*/
@MCRCommand(
syntax = "export all objects of type {0} to directory {1} with {2}",
help = "Stores all MCRObjects of type {0} to directory {1} with the stylesheet mcr_{2}-object.xsl."
+ " For {2} save is the default.",
order = 120)
public static List exportAllObjectsOfType(String type, String dirname, String style) {
List objectIds = MCRXMLMetadataManager.instance().listIDsOfType(type);
return buildExportCommands(new File(dirname), style, objectIds);
}
/**
* Save all MCRObject's to files named MCRObjectID .xml in a dirnamedirectory for the data base
* project_type. The method use the converter stylesheet mcr_style_object.xsl.
*
* @param base
* the MCRObjectID base
* @param dirname
* the filename to store the object
* @param style
* the type of the stylesheet
*/
@MCRCommand(
syntax = "export all objects of base {0} to directory {1} with {2}",
help = "Stores all MCRObjects of base {0} to directory {1} with the stylesheet mcr_{2}-object.xsl."
+ " For {2} save is the default.",
order = 130)
public static List exportAllObjectsOfBase(String base, String dirname, String style) {
List objectIds = MCRXMLMetadataManager.instance().listIDsForBase(base);
return buildExportCommands(new File(dirname), style, objectIds);
}
private static List buildExportCommands(File dir, String style, List objectIds) {
if (dir.isFile()) {
LOGGER.error("{} is not a dirctory.", dir);
return Collections.emptyList();
}
List cmds = new ArrayList<>(objectIds.size());
for (String id : objectIds) {
String command = new MessageFormat(EXPORT_OBJECT_TO_DIRECTORY_COMMAND, Locale.ROOT)
.format(new Object[] { id, dir.getAbsolutePath(), style });
cmds.add(command);
}
return cmds;
}
/**
* The method search for a stylesheet mcr_style_object.xsl and build the transformer. Default is
* mcr_save-object.xsl.
*
* @param style
* the style attribute for the transformer stylesheet
* @return the transformer
*/
private static Transformer getTransformer(String style) {
String xslfile = DEFAULT_TRANSFORMER;
if (style != null && style.trim().length() != 0) {
xslfile = style + "-object.xsl";
}
Transformer trans = translist.get(xslfile);
if (trans != null) {
return trans;
}
LOGGER.debug("Will load transformer stylesheet {}for export.", xslfile);
URL xslURL = MCRObjectCommands.class.getResource("/" + xslfile);
if (xslURL == null) {
xslURL = MCRObjectCommands.class.getResource("/xsl/" + DEFAULT_TRANSFORMER);
}
try {
if (xslURL != null) {
StreamSource source = new StreamSource(xslURL.toURI().toASCIIString());
TransformerFactory transfakt = TransformerFactory.newInstance();
transfakt.setURIResolver(MCRURIResolver.instance());
trans = transfakt.newTransformer(source);
translist.put(xslfile, trans);
return trans;
} else {
LOGGER.warn("Can't load transformer ressource {} or " + DEFAULT_TRANSFORMER + ".", xslfile);
}
} catch (Exception e) {
LOGGER.warn("Error while load transformer ressource {} or " + DEFAULT_TRANSFORMER + ".", xslfile);
if (LOGGER.isDebugEnabled()) {
e.printStackTrace();
}
}
return null;
}
/**
* The method read a MCRObject and use the transformer to write the data to a file. They are any steps to handel
* errors and save the damaged data.
*
* - Read data for object ID in the MCRObject, add ACL's and store it as checked and transformed XML. Return true.
*
* - If it can't find a transformer instance (no script file found) it store the checked data with ACL's native in
* the file. Warning and return true.
* - If it get an exception while build the MCRObject, it try to read the XML blob and stor it without check and
* ACL's to the file. Warning and return true.
* - If it get an exception while store the native data without check, ACÖ's and transformation it return a
* warning and false.
*
*
* @param dir
* the file instance to store
* @param trans
* the XML transformer
* @param nid
* the MCRObjectID
* @return true if the store was okay (see description), else return false
* @throws TransformerException
* @throws IOException
* @throws MCRException
* @throws SAXParseException
*/
private static boolean exportMCRObject(File dir, Transformer trans, String nid)
throws TransformerException, IOException, MCRException, SAXParseException {
MCRContent content;
try {
// if object do'snt exist - no exception is catched!
content = MCRXMLMetadataManager.instance().retrieveContent(MCRObjectID.getInstance(nid));
} catch (MCRException ex) {
return false;
}
File xmlOutput = new File(dir, nid + ".xml");
if (trans != null) {
FileOutputStream out = new FileOutputStream(xmlOutput);
StreamResult sr = new StreamResult(out);
Document doc = MCRXMLParserFactory.getNonValidatingParser().parseXML(content);
trans.transform(new JDOMSource(doc), sr);
} else {
content.sendTo(xmlOutput);
}
LOGGER.info("Object {} saved to {}.", nid, xmlOutput.getCanonicalPath());
return true;
}
/**
* Get the next free MCRObjectID for the given MCRObjectID base.
*
* @param base
* the MCRObjectID base string
*/
@MCRCommand(
syntax = "get next ID for base {0}",
help = "Returns the next free MCRObjectID for the ID base {0}.",
order = 150)
public static void getNextID(String base) {
try {
LOGGER.info(MCRObjectID.getNextFreeId(base));
} catch (MCRException ex) {
LOGGER.error(ex.getMessage());
}
}
/**
* Get the last used MCRObjectID for the given MCRObjectID base.
*
* @param base
* the MCRObjectID base string
*/
@MCRCommand(
syntax = "get last ID for base {0}",
help = "Returns the last used MCRObjectID for the ID base {0}.",
order = 140)
public static void getLastID(String base) {
LOGGER.info(MCRObjectID.getLastID(base));
}
/**
* List all selected MCRObjects.
*/
@MCRCommand(
syntax = "list selected",
help = "Prints the id of selected objects",
order = 190)
public static void listSelected() {
LOGGER.info("List selected MCRObjects");
if (getSelectedObjectIDs().isEmpty()) {
LOGGER.info("No Resultset to work with, use command \"select objects with query {0}\" to build one");
return;
}
StringBuilder out = new StringBuilder();
for (String id : getSelectedObjectIDs()) {
out.append(id).append(" ");
}
LOGGER.info(out.toString());
}
/**
* List revisions of an MyCoRe Object.
*
* @param id
* id of MyCoRe Object
*/
@MCRCommand(
syntax = "list revisions of {0}",
help = "List revisions of MCRObject.",
order = 260)
public static void listRevisions(String id) {
MCRObjectID mcrId = MCRObjectID.getInstance(id);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
try {
StringBuilder log = new StringBuilder("Revisions:\n");
List revisions = MCRXMLMetadataManager.instance().listRevisions(mcrId);
for (MCRMetadataVersion revision : revisions) {
log.append(revision.getRevision()).append(" ");
log.append(revision.getType()).append(" ");
log.append(sdf.format(revision.getDate())).append(" ");
log.append(revision.getUser());
log.append("\n");
}
LOGGER.info(log.toString());
} catch (Exception exc) {
LOGGER.error("While print revisions.", exc);
}
}
/**
* This method restores a MyCoRe Object to the selected revision. Please note that children and derivates are not
* deleted or reverted!
*
* @param id
* id of MyCoRe Object
* @param revision
* revision to restore
*/
@MCRCommand(
syntax = "restore {0} to revision {1}",
help = "Restores the selected MCRObject to the selected revision.",
order = 270)
public static void restoreToRevision(String id, long revision) {
LOGGER.info("Try to restore object {} with revision {}", id, revision);
MCRObjectID mcrId = MCRObjectID.getInstance(id);
try {
MCRObjectUtils.restore(mcrId, revision);
LOGGER.info("Object {} successfully restored!", id);
} catch (Exception exc) {
LOGGER.error("While retrieving object {} with revision {}", id, revision, exc);
}
}
/**
* Does a xsl transform with the given mycore object.
*
* To use this command create a new xsl file and copy following xslt code into it.
*
*
*
* {@code
*
*
*
*
*
*
*
*
*
*
*
* }
*
*
* Insert a new template match, for example:
*
*
*
* {@code
*
*
*
* }
*
*
* @param objectId
* object to transform
* @param xslFilePath
* path to xsl file
* @throws MCRPersistenceException see {@link MCRMetadataManager#update(MCRObject)}
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
@MCRCommand(
syntax = "xslt {0} with file {1}",
help = "transforms a mycore object {0} with the given file or URI {1}",
order = 280)
public static void xslt(String objectId, String xslFilePath) throws IOException, JDOMException, SAXException,
TransformerException, MCRPersistenceException, MCRAccessException,
ParserConfigurationException {
xslt(objectId, xslFilePath, false);
}
/**
* @see #xslt(String, String)
*
* Forces the xml to overwrite even if the root name of the original and the result differs.
*
* @param objectId
* object to transform
* @param xslFilePath
* path to xsl file
* @throws MCRPersistenceException see {@link MCRMetadataManager#update(MCRObject)}
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
@MCRCommand(
syntax = "force xslt {0} with file {1}",
help = "transforms a mycore object {0} with the given file or URI {1}. Overwrites anyway if original "
+ "root name and result root name are different.",
order = 285)
public static void forceXSLT(String objectId, String xslFilePath) throws IOException, JDOMException, SAXException,
TransformerException, MCRPersistenceException, MCRAccessException,
ParserConfigurationException {
xslt(objectId, xslFilePath, true);
}
private static void xslt(String objectId, String xslFilePath, boolean force) throws IOException, JDOMException,
SAXException, TransformerException, MCRPersistenceException, MCRAccessException, ParserConfigurationException {
File xslFile = new File(xslFilePath);
Source xslSource;
if (xslFile.exists()) {
xslSource = new StreamSource(xslFile);
} else {
xslSource = MCRURIResolver.instance().resolve(xslFilePath, null);
if (xslSource == null) {
xslSource = new StreamSource(xslFilePath);
}
}
MCRSourceContent style = new MCRSourceContent(xslSource);
MCRObjectID mcrId = MCRObjectID.getInstance(objectId);
Document document = MCRXMLMetadataManager.instance().retrieveXML(mcrId);
// do XSL transform
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setErrorListener(MCRErrorListener.getInstance());
transformerFactory.setURIResolver(MCRURIResolver.instance());
XMLReader xmlReader = MCRXMLParserFactory.getNonValidatingParser().getXMLReader();
xmlReader.setEntityResolver(MCREntityResolver.instance());
SAXSource styleSource = new SAXSource(xmlReader, style.getInputSource());
Transformer transformer = transformerFactory.newTransformer(styleSource);
for (Entry property : MCRConfiguration2.getPropertiesMap().entrySet()) {
transformer.setParameter(property.getKey(), property.getValue());
}
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
JDOMResult result = new JDOMResult();
transformer.transform(new JDOMSource(document), result);
Document resultDocument = Objects.requireNonNull(result.getDocument(), "Could not get transformation result");
String originalName = document.getRootElement().getName();
String resultName = resultDocument.getRootElement().getName();
if (!force && !originalName.equals(resultName)) {
LOGGER.error("{}: root name '{}' does not match result name '{}'.", objectId, originalName, resultName);
return;
}
// update on diff
if (MCRXMLHelper.deepEqual(document, resultDocument)) {
return;
}
switch (resultName) {
case MCRObject.ROOT_NAME:
MCRMetadataManager.update(new MCRObject(resultDocument));
break;
case MCRDerivate.ROOT_NAME:
MCRMetadataManager.update(new MCRDerivate(resultDocument));
break;
default:
LOGGER.error("Unable to transform '{}' because unknown result root name '{}'.", objectId, resultName);
break;
}
}
/**
* Moves object to new parent.
*
* @param sourceId
* object that should be attached to new parent
* @param newParentId
* the ID of the new parent
* @throws MCRAccessException see {@link MCRMetadataManager#update(MCRObject)}
*/
@MCRCommand(
syntax = "set parent of {0} to {1}",
help = "replaces a parent of an object (first parameter) to the given new one (second parameter)",
order = 300)
public static void replaceParent(String sourceId, String newParentId) throws MCRPersistenceException,
MCRAccessException {
// child
MCRObject sourceMCRObject = MCRMetadataManager.retrieveMCRObject(MCRObjectID.getInstance(sourceId));
// old parent
MCRObjectID oldParentId = sourceMCRObject.getStructure().getParentID();
MCRObjectID newParentObjectID = MCRObjectID.getInstance(newParentId);
if (newParentObjectID.equals(oldParentId)) {
LOGGER.info("Object {} is already child of {}", sourceId, newParentId);
return;
}
MCRObject oldParentMCRObject = null;
if (oldParentId != null) {
try {
oldParentMCRObject = MCRMetadataManager.retrieveMCRObject(oldParentId);
} catch (Exception exc) {
LOGGER.error("Unable to get old parent object {}, its probably deleted.", oldParentId, exc);
}
}
// change href to new parent
LOGGER.info("Setting link in \"{}\" to parent \"{}\"", sourceId, newParentObjectID);
MCRMetaLinkID parentLinkId = new MCRMetaLinkID("parent", 0);
parentLinkId.setReference(newParentObjectID, null, null);
sourceMCRObject.getStructure().setParent(parentLinkId);
if (oldParentMCRObject != null) {
// remove Child in old parent
LOGGER.info("Remove child \"{}\" in old parent \"{}\"", sourceId, oldParentId);
oldParentMCRObject.getStructure().removeChild(sourceMCRObject.getId());
LOGGER.info("Update old parent \"{}\n", oldParentId);
MCRMetadataManager.update(oldParentMCRObject);
}
LOGGER.info("Update \"{}\" in datastore (saving new link)", sourceId);
MCRMetadataManager.update(sourceMCRObject);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Structure: {}", sourceMCRObject.getStructure().isValid());
LOGGER.debug("Object: {}", sourceMCRObject.isValid());
}
}
/**
* Check the derivate links in objects of MCR base ID for existing. It looks to the XML store on the disk to get all
* object IDs.
*
* @param baseId
* the base part of a MCRObjectID e.g. DocPortal_document
*/
@MCRCommand(
syntax = "check derivate entries in objects for base {0}",
help = "check in all objects with MCR base ID {0} for existing linked derivates",
order = 400)
public static void checkDerivatesInObjects(String baseId) {
if (baseId == null || baseId.length() == 0) {
LOGGER.error("Base ID missed for check derivate entries in objects for base {0}");
return;
}
MCRXMLMetadataManager mgr = MCRXMLMetadataManager.instance();
List idList = mgr.listIDsForBase(baseId);
int counter = 0;
int maxresults = idList.size();
for (String objid : idList) {
counter++;
LOGGER.info("Processing dataset {} from {} with ID: {}", counter, maxresults, objid);
// get from data
MCRObjectID mcrobjid = MCRObjectID.getInstance(objid);
MCRObject obj = MCRMetadataManager.retrieveMCRObject(mcrobjid);
List derivateEntries = obj.getStructure().getDerivates();
for (MCRMetaLinkID derivateEntry : derivateEntries) {
String derid = derivateEntry.getXLinkHref();
if (!mgr.exists(MCRObjectID.getInstance(derid))) {
LOGGER.error(" !!! Missing derivate {} in database for base ID {}", derid, baseId);
}
}
}
LOGGER.info("Check done for {} entries", Integer.toString(counter));
}
@MCRCommand(
syntax = "execute for selected {0}",
help = "Calls the given command multiple times for all selected objects." +
" The replacement is defined by an {x}.E.g. 'execute for selected set" +
" parent of {x} to myapp_container_00000001'",
order = 450)
public static List executeForSelected(String command) {
if (!command.contains("{x}")) {
LOGGER.info("No replacement defined. Use the {x} variable in order to execute your command with all "
+ "selected objects.");
return Collections.emptyList();
}
return getSelectedObjectIDs().stream()
.map(objID -> command.replaceAll("\\{x}", objID))
.collect(Collectors.toList());
}
/**
* The method start the repair of the metadata search for a given MCRObjectID type.
*
* @param type
* the MCRObjectID type
*/
@MCRCommand(
syntax = "repair metadata search of type {0}",
help = "Scans the metadata store for MCRObjects of type {0} and restore them in the search store.",
order = 170)
public static List repairMetadataSearch(String type) {
LOGGER.info("Start the repair for type {}", type);
String typetest = MCRConfiguration2.getString("MCR.Metadata.Type." + type).orElse("");
if (typetest.length() == 0) {
LOGGER.error("The type {} was not found.", type);
return Collections.emptyList();
}
List ar = MCRXMLMetadataManager.instance().listIDsOfType(type);
if (ar.size() == 0) {
LOGGER.warn("No ID's was found for type {}.", type);
return Collections.emptyList();
}
List cmds = new ArrayList<>(ar.size());
for (String stid : ar) {
cmds.add("repair metadata search of ID " + stid);
}
return cmds;
}
/**
* The method start the repair of the metadata search for a given MCRObjectID as String.
*
* @param id
* the MCRObjectID as String
*/
@MCRCommand(
syntax = "repair metadata search of ID {0}",
help = "Retrieves the MCRObject with the MCRObjectID {0} and restores it in the search store.",
order = 180)
public static void repairMetadataSearchForID(String id) {
LOGGER.info("Start the repair for the ID {}", id);
if (!MCRObjectID.isValid(id)) {
LOGGER.error("The String {} is not a MCRObjectID.", id);
return;
}
MCRObjectID mid = MCRObjectID.getInstance(id);
MCRBase obj = MCRMetadataManager.retrieve(mid);
MCRMetadataManager.fireRepairEvent(obj);
LOGGER.info("Repaired {}", mid);
}
@MCRCommand(
syntax = "repair mcrlinkhref table",
help = "Runs through the whole table and checks for already deleted mcr objects and deletes them.",
order = 185)
public static void repairMCRLinkHrefTable() {
EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
MCRStreamQuery fromQuery = MCRStreamQuery
.getInstance(em, "SELECT DISTINCT m.key.mcrfrom FROM MCRLINKHREF m", String.class);
MCRStreamQuery toQuery = MCRStreamQuery
.getInstance(em, "SELECT DISTINCT m.key.mcrto FROM MCRLINKHREF m", String.class);
String query = "DELETE FROM MCRLINKHREF m WHERE m.key.mcrfrom IN (:invalidIds) or m.key.mcrto IN (:invalidIds)";
// open streams
try (Stream fromStream = fromQuery.getResultStream()) {
try (Stream toStream = toQuery.getResultStream()) {
List invalidIds = Stream.concat(fromStream, toStream)
.distinct()
.filter(MCRObjectID::isValid)
.map(MCRObjectID::getInstance)
.filter(MCRStreamUtils.not(MCRMetadataManager::exists))
.map(MCRObjectID::toString)
.collect(Collectors.toList());
// delete
em.createQuery(query).setParameter("invalidIds", invalidIds).executeUpdate();
}
}
}
@MCRCommand(
syntax = "rebuild mcrlinkhref table for object {0}",
help = "Rebuilds (remove/create) all entries of the link href table for the given object id.",
order = 188)
public static void rebuildMCRLinkHrefTableForObject(String objectId) {
MCRLinkTableManager.instance().update(MCRObjectID.getInstance(objectId));
}
@MCRCommand(syntax = "merge derivates of object {0}",
help = "Retrieves the MCRObject with the MCRObjectID {0} and if it has more then one MCRDerivate, then all" +
" Files will be copied to the first Derivate and all other will be deleted.",
order = 190)
public static void mergeDerivatesOfObject(String id) {
MCRObjectID objectID = MCRObjectID.getInstance(id);
if (!MCRMetadataManager.exists(objectID)) {
LOGGER.error("The object with the id {} does not exist!", id);
return;
}
MCRObject object = MCRMetadataManager.retrieveMCRObject(objectID);
List derivateLinkIDs = object.getStructure().getDerivates();
List derivateIDs = derivateLinkIDs.stream().map(MCRMetaLinkID::getXLinkHrefID)
.collect(Collectors.toList());
if (derivateIDs.size() <= 1) {
LOGGER.error("The object with the id {} has no Derivates to merge!", id);
return;
}
String mainID = derivateIDs.get(0).toString();
MCRPath mainDerivateRootPath = MCRPath.getPath(mainID, "/");
derivateIDs.stream().skip(1).forEach(derivateID -> {
LOGGER.info("Merge {} into {}...", derivateID, mainID);
MCRPath copyRootPath = MCRPath.getPath(derivateID.toString(), "/");
try {
MCRTreeCopier treeCopier = new MCRTreeCopier(copyRootPath, mainDerivateRootPath);
Files.walkFileTree(copyRootPath, treeCopier);
Files.walkFileTree(copyRootPath, MCRRecursiveDeleter.instance());
MCRMetadataManager.deleteMCRDerivate(derivateID);
} catch (IOException | MCRAccessException e) {
throw new MCRException(e);
}
});
}
}