ru.curs.celesta.score.Grain Maven / Gradle / Ivy
The newest version!
package ru.curs.celesta.score;
import ru.curs.celesta.DBType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Grain.
*/
public final class Grain extends NamedElement {
private static final Pattern NATIVE_SQL = Pattern.compile("--\\{\\{(.*)--}}", Pattern.DOTALL);
private final AbstractScore score;
private VersionString version = VersionString.DEFAULT;
private int length;
private int checksum;
private int dependencyOrder;
private boolean parsingComplete = false;
private boolean modified = true;
private boolean autoupdate = true;
private final Set grainParts = new LinkedHashSet<>();
private Namespace namespace;
private final Map, NamedElementHolder extends GrainElement>> grainElements
= new HashMap<>();
private final NamedElementHolder indices = new NamedElementHolder<>() {
@Override
protected String getErrorMsg(String name) {
return String.format("Index '%s' defined more than once in a grain.", name);
}
};
private final Set constraintNames = new HashSet<>();
private final Map> beforeSql = new HashMap<>();
private final Map> afterSql = new HashMap<>();
public Grain(AbstractScore score, String name) throws ParseException {
super(name, score.getIdentifierParser());
if (name.contains("_")) {
throw new ParseException("Invalid grain name '" + name + "'. No underscores are allowed for grain names.");
}
this.score = score;
score.addGrain(this);
}
@SuppressWarnings("unchecked")
private NamedElementHolder getElementsHolder(Class cls) {
return (NamedElementHolder) grainElements.computeIfAbsent(cls, c -> new NamedElementHolder() {
@Override
protected String getErrorMsg(String name) {
return String.format("%s '%s' defined more than once in a grain.", c.getSimpleName(), name);
}
});
}
/**
* Adds an element to the grain.
*
* @param element new grain element
* @throws ParseException In the case if an element with the same name already exists.
*/
@SuppressWarnings("unchecked")
void addElement(T element) throws ParseException {
if (element.getGrain() != this) {
throw new IllegalArgumentException();
}
Optional typeNameOfElementWithSameName = grainElements.entrySet().stream()
// Не рассматриваем тот же тип (у его холдера своя проверка)
.filter(entry -> !entry.getKey().equals(element.getClass()))
// Сводим все Map'ы в одну
.map(entry -> (Set extends Map.Entry>) entry.getValue()
.getElements().entrySet())
.flatMap(Collection::stream)
// Ищем совпадения по имени
.filter(entry -> entry.getKey().equals(element.getName())).findAny()
.map(entry -> entry.getValue().getClass().getSimpleName());
if (typeNameOfElementWithSameName.isPresent()) {
throw new ParseException(String.format(
"Cannot create grain element '%s', a %s with the same name already exists in grain '%s'.",
element.getName(), typeNameOfElementWithSameName.get(), getName()));
}
modify();
getElementsHolder((Class) element.getClass()).addElement(element);
if (element instanceof BasicTable) {
getElementsHolder((Class) BasicTable.class).addElement(element);
}
}
/**
* Returns a set of elements of specified type defined in the grain.
*
* @param classOfElement class of elements from the set
* @param class of element
*/
public Map getElements(Class classOfElement) {
return getElementsHolder(classOfElement).getElements();
}
/**
* Returns a set of elements of specified type defined in the grain.
*
* @param classOfElement class of elements from the set
* @param gp grain part to which the returned elements should belong
*/
Collection getElements(Class classOfElement, GrainPart gp) {
Collection elements = getElements(classOfElement).values();
if (gp != null) {
elements = elements.stream()
.filter(t -> gp == t.getGrainPart())
.collect(Collectors.toList());
}
return elements;
}
/**
* Returns a set of indices defined in the grain.
*
*/
public Map getIndices() {
return indices.getElements();
}
/**
* Returns a set of materialized views defined in the grain.
*
*/
public Map getMaterializedViews() {
return getElementsHolder(MaterializedView.class).getElements();
}
/**
* Returns a set of parameterized views defined in the grain.
*
*/
public Map getParameterizedViews() {
return getElementsHolder(ParameterizedView.class).getElements();
}
/**
* Returns a set of tables defined in the grain.
*
*/
public Map getTables() {
return getElementsHolder(BasicTable.class).getElements();
}
/**
* Returns a set of tables defined in the grain by a table class.
*
* @param table type (e.g. Table or ReadOnlyTable)
* @param tableClass Table class
*/
public Map getTables(Class tableClass) {
return getElementsHolder(tableClass).getElements();
}
/**
* Returns a set of views defined in the grain.
*
*/
public Map getViews() {
return getElementsHolder(View.class).getElements();
}
/**
* Returns an element by its name and class or throws an exception with the message
* that element is not found.
*
* @param name element name
* @param classOfElement element class
* @param class of element
* @throws ParseException if element with such name and class is not found in the grain
*/
public T getElement(String name, Class classOfElement) throws ParseException {
T result = getElementsHolder(classOfElement).get(name);
if (result == null) {
throw new ParseException(
String.format("%s '%s' not found in grain '%s'", classOfElement.getSimpleName(), name, getName()));
}
return result;
}
/**
* Adds an index.
*
* @param index new index of the grain.
* @throws ParseException In case if an index with the same name already exists.
*/
public void addIndex(Index index) throws ParseException {
if (index.getGrain() != this) {
throw new IllegalArgumentException();
}
modify();
indices.addElement(index);
}
synchronized void removeIndex(Index index) throws ParseException {
modify();
indices.remove(index);
index.getTable().removeIndex(index);
}
synchronized void removeElement(T element) throws ParseException {
if (element instanceof BasicTable) {
removeTable((BasicTable) element);
} else {
modify();
getElementsHolder(element.getClass()).remove(element);
}
}
private synchronized void removeTable(BasicTable table) throws ParseException {
// Проверяем, не системную ли таблицу хотим удалить
modify();
// Удаляются все индексы на данной таблице
List indToDelete = new LinkedList<>();
for (Index ind : indices) {
if (ind.getTable() == table) {
indToDelete.add(ind);
}
}
// Удаляются все внешние ключи, ссылающиеся на данную таблицу
List fkToDelete = new LinkedList<>();
for (Grain g : score.getGrains().values()) {
for (BasicTable t : g.getElements(BasicTable.class).values()) {
for (ForeignKey fk : t.getForeignKeys()) {
if (fk.getReferencedTable() == table) {
fkToDelete.add(fk);
}
}
}
}
for (Index ind : indToDelete) {
ind.delete();
}
for (ForeignKey fk : fkToDelete) {
fk.delete();
}
// Удаляется сама таблица
getElementsHolder(BasicTable.class).remove(table);
getElementsHolder(table.getClass()).remove(table);
}
/**
* Returns model that the grain belongs to.
*
*/
public AbstractScore getScore() {
return score;
}
/**
* Value {@code false} indicates that grain was created with option WITH NO AUTOUPDATE,
* and won't be updated. Default value is {@code true}.
*
*/
public boolean isAutoupdate() {
return autoupdate;
}
/**
* Sets autoupdate option. Default value is {@code true}.
* @param autoupdate autoupdate flag
*/
public void setAutoupdate(boolean autoupdate) {
this.autoupdate = autoupdate;
}
/**
* Returns the grain version.
*
*/
public VersionString getVersion() {
return version;
}
/**
* Sets the grain version.
*
* @param version Quoted-string. In course of processing single and double quotes are removed.
* @throws ParseException in case if format of quoted string is incorrect.
*/
public void setVersion(String version) throws ParseException {
modify();
this.version = new VersionString(StringColumn.unquoteString(version));
}
/**
* Returns length of the script file that the grain was created from.
*
*/
public int getLength() {
return length;
}
void setLength(int length) {
this.length = length;
}
/**
* Returns checksum of the script file that the grain was created from.
* Coincidence of version, length and checksum is considered to be a sufficient solution for
* skipping the reading and update of the database structure.
*
*/
public int getChecksum() {
return checksum;
}
void setChecksum(int checksum) {
this.checksum = checksum;
}
/**
* Adding of constraint name (for checking if it is unique).
*
* @param name Constraint name.
* @throws ParseException In case if a constraint with the same name has already been defined.
*/
void addConstraintName(String name) throws ParseException {
name = getScore().getIdentifierParser().parse(name);
if (constraintNames.contains(name)) {
throw new ParseException(String.format("Constraint '%s' is defined more than once in a grain.", name));
}
constraintNames.add(name);
}
/**
* Indicates that the grain parsing from file is completed.
*
*/
public boolean isParsingComplete() {
return parsingComplete;
}
/**
* If a grain has a higher number than the other grain then it means that it can depend from the first one.
*
*/
public int getDependencyOrder() {
return dependencyOrder;
}
/**
* Indicates that the grain parsing is completed. A system method.
*
* @throws ParseException thrown when there are tables with illegal names.
*/
public void finalizeParsing() throws ParseException {
for (String tableName: getElements(BasicTable.class).keySet()) {
String sequenceName = tableName + "_seq";
SequenceElement se = getElementsHolder(SequenceElement.class).get(sequenceName);
if (se != null) {
throw new ParseException(
String.format(
"Identifier %s can't be used for the naming of sequence as it is reserved by Celesta.",
sequenceName
)
);
}
}
parsingComplete = true;
modified = false;
dependencyOrder = score.nextOrderCounter();
}
/**
* Returns a flag of grain modification ({@code true} if parts of grain were modified in the runtime).
*
*/
public boolean isModified() {
return modified;
}
void modify() throws ParseException {
if (getScore().getSysSchemaName().equals(getName()) && parsingComplete) {
throw new ParseException("You cannot modify system grain.");
}
modified = true;
}
/**
* Returns a view by its name or an exception with a message that the view was not found.
*
* @param name View name
* @throws ParseException If view with that name was not found in the grain.
*/
public View getView(String name) throws ParseException {
return getElement(name, View.class);
}
/**
* Returns a materialized view by its name or an exception with a message
* that the view was not found.
*
* @param name Materialized view name
* @throws ParseException If materialized view with that name was not found in the grain.
*/
public MaterializedView getMaterializedView(String name) throws ParseException {
return getElement(name, MaterializedView.class);
}
/**
* Returns a parameterized view by its name or an exception with a message
* that the view was not found.
*
* @param name Parameterized view name
* @throws ParseException If parameterized view with that name was not found in the grain.
*/
public ParameterizedView getParameterizedView(String name) throws ParseException {
return getElement(name, ParameterizedView.class);
}
/**
* Returns a table by its name or an exception with a message that the table was not found.
*
* @param name Table name
* @throws ParseException If table with that name was not found in the grain.
*/
public BasicTable getTable(String name) throws ParseException {
return getElement(name, BasicTable.class);
}
/**
* Returns a table by its name and a table class.
*
* @param table type (e.g. Table or ReadOnlyTable)
* @param name Table name
* @param tableClass Table class
* @throws ParseException If table with that name was not found in the grain.
*/
public T getTable(String name, Class tableClass) throws ParseException {
return getElement(name, tableClass);
}
void addNativeSql(String sql, boolean isBefore, DBType dbType, GrainPart grainPart) throws ParseException {
Matcher m = NATIVE_SQL.matcher(sql);
if (!m.matches()) {
throw new ParseException("Native sql should match pattern --{{...--}}, was " + sql);
}
final List sqlList;
if (isBefore) {
sqlList = beforeSql.computeIfAbsent(dbType, dbTypeVar -> new ArrayList<>());
} else {
sqlList = afterSql.computeIfAbsent(dbType, dbTypeVar -> new ArrayList<>());
}
sqlList.add(
new NativeSqlElement(grainPart, m.group(1))
);
}
public List getBeforeSqlList(DBType dbType) {
return beforeSql.getOrDefault(dbType, Collections.emptyList());
}
public List getAfterSqlList(DBType dbType) {
return afterSql.getOrDefault(dbType, Collections.emptyList());
}
/**
* Returns grain parts that this grain consists of.
*
*/
public Set getGrainParts() {
return grainParts;
}
void addGrainPart(GrainPart grainPart) {
grainParts.add(grainPart);
}
/**
* Returns namespace of the grain.
*
*/
public Namespace getNamespace() {
if (namespace != null) {
return namespace;
}
if (grainParts.isEmpty()) {
return Namespace.DEFAULT;
}
Iterator i = grainParts.iterator();
Namespace ns = i.next().getNamespace();
while (i.hasNext()) {
if (Namespace.DEFAULT.equals(ns) || !ns.equals(i.next().getNamespace())) {
return Namespace.DEFAULT;
}
}
return ns;
}
/**
* Sets namespace of the grain.
*
* @param namespace namespace
*/
public void setNamespace(Namespace namespace) {
this.namespace = namespace;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy