Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
*
* $Revision$ $Date$
*
* This file is part of *** M y C o R e *** See http://www.mycore.de/ for
* details.
*
* This program is free software; you can use it, redistribute it and / or
* modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 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, in a file called gpl.txt or license.txt. If not, write to the
* Free Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307 USA
*/
package org.mycore.datamodel.metadata;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mycore.common.MCRException;
import org.mycore.common.MCRUtils;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* This class holds all informations and methods to handle the MyCoRe Object ID.
* The MyCoRe Object ID is a special ID to identify each metadata object with
* three parts, they are the project identifier, the type identifier and a
* string with a number. The syntax of the ID is "projectID_
* typeID_ number" as "String_String_Integer".
*
* @author Jens Kupferschmidt
* @author Thomas Scheffler (yagee)
* @version $Revision$ $Date$
*/
@JsonClassDescription("MyCoRe ObjectID in form {project}_{type}_{int32}, "
+ "where project is a namespace and type defines the datamodel")
@JsonFormat(shape = JsonFormat.Shape.STRING)
public final class MCRObjectID implements Comparable {
/**
* public constant value for the MCRObjectID length
*/
public static final int MAX_LENGTH = 64;
private static final MCRObjectIDFormat ID_FORMAT = new MCRObjectIDDefaultFormat();
private static final Logger LOGGER = LogManager.getLogger(MCRObjectID.class);
// counter for the next IDs per project base ID
private static HashMap lastNumber = new HashMap<>();
private static HashSet VALID_TYPE_LIST;
static {
final String confPrefix = "MCR.Metadata.Type.";
VALID_TYPE_LIST = MCRConfiguration2.getPropertiesMap()
.entrySet()
.stream()
.filter(p -> p.getKey().startsWith(confPrefix))
.filter(p -> Boolean.parseBoolean(p.getValue()))
.map(prop -> prop.getKey().substring(confPrefix.length()))
.collect(Collectors.toCollection(HashSet::new));
}
// data of the ID
private String projectId, objectType, combinedId;
private int numberPart;
/**
* The constructor for MCRObjectID from a given string.
*
* @exception MCRException
* if the given string is not valid.
*/
MCRObjectID(String id) throws MCRException {
if (!setID(id)) {
throw new MCRException("The ID is not valid: " + id
+ " , it should match the pattern String_String_Integer");
}
}
/**
* Returns a MCRObjectID from a given base ID string. A base ID is
* project_id_type_id. The number is computed by this
* method. It is the next free number of an item in the database for the
* given project ID and type ID, with the following additional restriction:
* The ID returned can be divided by idFormat.numberDistance without rest.
* The ID returned minus the last ID returned is at least idFormat.numberDistance.
*
* Example for number distance of 1 (default):
* last ID = 7, next ID = 8
* last ID = 8, next ID = 9
*
* Example for number distance of 2:
* last ID = 7, next ID = 10
* last ID = 8, next ID = 10
* last ID = 10, next ID = 20
*
* @param baseId
* project_id_type_id
*/
public static synchronized MCRObjectID getNextFreeId(String baseId) {
return getNextFreeId(baseId, 0);
}
public static synchronized MCRObjectID getNextFreeId(String base, String type) {
return getNextFreeId(base + "_" + type);
}
/**
* Returns a MCRObjectID from a given base ID string. Same as
* {@link #getNextFreeId(String)} but the additional parameter acts as a
* lower limit for integer part of the ID.
*
* @param baseId
* project_id_type_id
* @param maxInWorkflow
* returned integer part of id will be at least
* maxInWorkflow + 1
*/
public static synchronized MCRObjectID getNextFreeId(String baseId, int maxInWorkflow) {
int last = Math.max(getLastIDNumber(baseId), maxInWorkflow);
int numberDistance = ID_FORMAT.numberDistance();
int next = last + numberDistance;
int rest = next % numberDistance;
if (rest != 0) {
next += numberDistance - rest;
}
lastNumber.put(baseId, next);
String[] idParts = getIDParts(baseId);
return getInstance(formatID(idParts[0], idParts[1], next));
}
/**
* Returns the last ID number used or reserved for the given object base
* type. This may return the value 0 when there is no ID last used or in the
* store.
*/
private static int getLastIDNumber(String baseId) {
int lastIDKnown = lastNumber.getOrDefault(baseId, 0);
String[] idParts = getIDParts(baseId);
int highestStoredID = MCRXMLMetadataManager.instance().getHighestStoredID(idParts[0], idParts[1]);
return Math.max(lastIDKnown, highestStoredID);
}
/**
* Returns the last ID used or reserved for the given object base type.
*
* @return a valid MCRObjectID, or null when there is no ID for the given
* type
*/
public static MCRObjectID getLastID(String baseId) {
int lastIDNumber = getLastIDNumber(baseId);
if (lastIDNumber == 0) {
return null;
}
String[] idParts = getIDParts(baseId);
return getInstance(formatID(idParts[0], idParts[1], lastIDNumber));
}
/**
* This method instantiate this class with a given identifier in MyCoRe schema.
*
* @param id
* the MCRObjectID
* @return an MCRObjectID class instance
* @exception MCRException if the given identifier is not valid
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static MCRObjectID getInstance(String id) {
return MCRObjectIDPool.getMCRObjectID(Objects.requireNonNull(id, "'id' must not be null."));
}
/**
* Normalizes to a object ID of form project_id_ type_id_
* number, where number has leading zeros.
* @return project_id_type_id_number
*/
public static String formatID(String projectID, String type, int number) {
if (projectID == null) {
throw new IllegalArgumentException("projectID cannot be null");
}
if (type == null) {
throw new IllegalArgumentException("type cannot be null");
}
if (number < 0) {
throw new IllegalArgumentException("number must be non negative integer");
}
return projectID + '_' + type.toLowerCase(Locale.ROOT) + '_' + ID_FORMAT.numberFormat().format(number);
}
/**
* Normalizes to a object ID of form project_id_ type_id_
* number, where number has leading zeros.
*
* @param baseID
* is project_id_type_id
* @return project_id_type_id_number
*/
public static String formatID(String baseID, int number) {
String[] idParts = getIDParts(baseID);
return formatID(idParts[0], idParts[1], number);
}
/**
* Splits the submitted id in its parts.
* MyCoRe_document_00000001 would be transformed in { "MyCoRe",
* "document", "00000001" }
*
* @param id
* either baseID or complete ID
*/
public static String[] getIDParts(String id) {
return id.split("_");
}
/**
* Returns a list of available mycore object types.
*/
public static List listTypes() {
return new ArrayList<>(VALID_TYPE_LIST);
}
/**
* Check whether the type passed is a valid type in the current mycore environment.
* That being said property MCR.Metadata.Type.<type> must be set to true in mycore.properties.
*
* @param type the type to check
* @return true if valid, false otherwise
*/
public static boolean isValidType(String type) {
return VALID_TYPE_LIST.contains(type);
}
/**
* Checks if the given id is a valid mycore id in the form of {project}_{object_type}_{number}.
*
* @param id the id to check
* @return true if the id is valid, false otherwise
*/
public static boolean isValid(String id) {
if (id == null) {
return false;
}
String mcrId = id.trim();
if (mcrId.length() > MAX_LENGTH) {
return false;
}
String[] idParts = getIDParts(mcrId);
if (idParts.length != 3) {
return false;
}
String objectType = idParts[1].toLowerCase(Locale.ROOT).intern();
if (!MCRConfiguration2.getBoolean("MCR.Metadata.Type." + objectType).orElse(false)) {
LOGGER.warn("Property MCR.Metadata.Type.{} is not set. Thus {} cannot be a valid id", objectType, id);
return false;
}
try {
Integer numberPart = Integer.parseInt(idParts[2]);
if (numberPart < 0) {
return false;
}
} catch (NumberFormatException e) {
return false;
}
return true;
}
/**
* This method get the string with project_id. If the ID is not
* valid, an empty string was returned.
*
* @return the string of the project id
*/
public String getProjectId() {
return projectId;
}
/**
* This method gets the string with type_id. If the ID is not
* valid, an empty string will be returned.
*
* @return the string of the type id
*/
public String getTypeId() {
return objectType;
}
/**
* This method gets the string with number. If the ID is not valid,
* an empty string will be returned.
*
* @return the string of the number
*/
public String getNumberAsString() {
return ID_FORMAT.numberFormat().format(numberPart);
}
/**
* This method gets the integer with number. If the ID is not
* valid, -1 will be returned.
*
* @return the number as integer
*/
public int getNumberAsInteger() {
return numberPart;
}
/**
* This method gets the basic string with project_id_
* type_id. If the Id is not valid, an empty string will be
* returned.
*
* @return the string of the schema name
*/
public String getBase() {
return projectId + "_" + objectType;
}
/**
* This method return the validation value of a MCRObjectId and store the
* components in this class. The type_id was set to lower case. The
* MCRObjectID is valid if:
*
*
The argument is not null.
*
The syntax of the ID is project_id_type_id_
* number as String_String_Integer.
*
The ID is not longer as MAX_LENGTH.
*
The ID has only characters, they must not encoded.
*
*
* @param id
* the MCRObjectID
* @return the validation value, true if the MCRObjectID is correct,
* otherwise return false
*/
private boolean setID(String id) {
if (!isValid(id)) {
return false;
}
String[] idParts = getIDParts(id.trim());
projectId = idParts[0].intern();
objectType = idParts[1].toLowerCase(Locale.ROOT).intern();
numberPart = Integer.parseInt(idParts[2]);
this.combinedId = formatID(projectId, objectType, numberPart);
return true;
}
/**
* This method check this data again the input and retuns the result as
* boolean.
*
* @param in
* the MCRObjectID to check
* @return true if all parts are equal, else return false
*/
public boolean equals(MCRObjectID in) {
return this == in || (in != null && toString().equals(in.toString()));
}
/**
* This method check this data again the input and retuns the result as
* boolean.
*
* @param in
* the MCRObjectID to check
* @return true if all parts are equal, else return false.
* @see java.lang.Object#equals(Object)
*/
@Override
public boolean equals(Object in) {
if (in instanceof MCRObjectID) {
return equals((MCRObjectID) in);
}
return false;
}
@Override
public int compareTo(MCRObjectID o) {
return MCRUtils.compareParts(this, o,
MCRObjectID::getProjectId,
MCRObjectID::getTypeId,
MCRObjectID::getNumberAsInteger);
}
/**
* @see java.lang.Object#toString()
* @return {@link #formatID(String, String, int)} with
* {@link #getProjectId()}, {@link #getTypeId()},
* {@link #getNumberAsInteger()}
*/
@Override
@JsonValue
public String toString() {
return combinedId;
}
/**
* returns toString().hashCode()
*
* @see #toString()
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return toString().hashCode();
}
public interface MCRObjectIDFormat {
int numberDistance();
NumberFormat numberFormat();
}
private static class MCRObjectIDDefaultFormat implements MCRObjectIDFormat {
private int numberDistance;
/**
* First invocation may return MCR.Metadata.ObjectID.InitialNumberDistance if set,
* following invocations will return MCR.Metadata.ObjectID.NumberDistance.
* The default for both is 1.
*/
@Override
public int numberDistance() {
if (numberDistance == 0) {
numberDistance = MCRConfiguration2.getInt("MCR.Metadata.ObjectID.NumberDistance").orElse(1);
return MCRConfiguration2.getInt("MCR.Metadata.ObjectID.InitialNumberDistance").orElse(numberDistance);
}
return numberDistance;
}
@Override
public NumberFormat numberFormat() {
String numberPattern = MCRConfiguration2.getString("MCR.Metadata.ObjectID.NumberPattern")
.orElse("0000000000").trim();
NumberFormat format = NumberFormat.getIntegerInstance(Locale.ROOT);
format.setGroupingUsed(false);
format.setMinimumIntegerDigits(numberPattern.length());
return format;
}
}
}