Java tutorial
/** * Copyright (C) 2010-2016 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr 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. * * Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.core.entity; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.structr.api.util.Iterables; import org.structr.common.PropertyView; import org.structr.common.ValidationHelper; import org.structr.common.View; import org.structr.common.error.ErrorBuffer; import org.structr.common.error.FrameworkException; import org.structr.core.GraphObject; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.relationship.SchemaRelationship; import org.structr.core.entity.relationship.SchemaRelationshipSourceNode; import org.structr.core.entity.relationship.SchemaRelationshipTargetNode; import org.structr.core.graph.NodeInterface; import org.structr.core.property.BooleanProperty; import org.structr.core.property.EndNode; import org.structr.core.property.EndNodes; import org.structr.core.property.IntProperty; import org.structr.core.property.Property; import org.structr.core.property.PropertyKey; import org.structr.core.property.PropertyMap; import org.structr.core.property.StartNode; import org.structr.core.property.StartNodes; import org.structr.core.property.StringProperty; import org.structr.core.validator.TypeUniquenessValidator; import org.structr.module.StructrModule; import org.structr.schema.SchemaHelper; import org.structr.schema.action.ActionEntry; import org.structr.schema.action.Actions; import org.structr.schema.parser.Validator; /** * * */ public class SchemaNode extends AbstractSchemaNode { private static final Logger logger = Logger.getLogger(SchemaNode.class.getName()); public static final Property<List<SchemaRelationshipNode>> relatedTo = new EndNodes<>("relatedTo", SchemaRelationshipSourceNode.class); public static final Property<List<SchemaRelationshipNode>> relatedFrom = new StartNodes<>("relatedFrom", SchemaRelationshipTargetNode.class); public static final Property<String> extendsClass = new StringProperty("extendsClass").indexed(); public static final Property<String> defaultSortKey = new StringProperty("defaultSortKey"); public static final Property<String> defaultSortOrder = new StringProperty("defaultSortOrder"); public static final Property<Boolean> isBuiltinType = new BooleanProperty("isBuiltinType").readOnly().indexed(); public static final Property<Integer> hierarchyLevel = new IntProperty("hierarchyLevel").indexed(); public static final Property<Integer> relCount = new IntProperty("relCount").indexed(); public static final Property<Boolean> shared = new BooleanProperty("shared").indexed(); static { name.addValidator(new TypeUniquenessValidator<>(SchemaNode.class)); } public static final View defaultView = new View(SchemaNode.class, PropertyView.Public, extendsClass, relatedTo, relatedFrom, defaultSortKey, defaultSortOrder, isBuiltinType, hierarchyLevel, relCount); public static final View uiView = new View(SchemaNode.class, PropertyView.Ui, name, extendsClass, relatedTo, relatedFrom, defaultSortKey, defaultSortOrder, isBuiltinType, hierarchyLevel, relCount); public static final View schemaView = new View(SchemaMethod.class, "schema", name, extendsClass, relatedTo, relatedFrom, defaultSortKey, defaultSortOrder, isBuiltinType, hierarchyLevel, relCount); public static final View exportView = new View(SchemaMethod.class, "export", extendsClass, defaultSortKey, defaultSortOrder, isBuiltinType, hierarchyLevel, relCount); private final Set<String> dynamicViews = new LinkedHashSet<>(); @Override public Iterable<PropertyKey> getPropertyKeys(final String propertyView) { final List<PropertyKey> propertyKeys = new LinkedList<>( Iterables.toList(super.getPropertyKeys(propertyView))); // add "custom" property keys as String properties for (final String key : SchemaHelper.getProperties(getNode())) { final PropertyKey newKey = new StringProperty(key); newKey.setDeclaringClass(getClass()); propertyKeys.add(newKey); } Collections.sort(propertyKeys, new Comparator<PropertyKey>() { @Override public int compare(PropertyKey o1, PropertyKey o2) { return o1.jsonName().compareTo(o2.jsonName()); } }); return new LinkedHashSet<>(propertyKeys); } @Override public String getSource(final ErrorBuffer errorBuffer) throws FrameworkException { final Collection<StructrModule> modules = StructrApp.getConfiguration().getModules().values(); final App app = StructrApp.getInstance(securityContext); final Map<Actions.Type, List<ActionEntry>> saveActions = new EnumMap<>(Actions.Type.class); final Map<String, Set<String>> viewProperties = new LinkedHashMap<>(); final Set<String> existingPropertyNames = new LinkedHashSet<>(); final Set<String> propertyNames = new LinkedHashSet<>(); final Set<Validator> validators = new LinkedHashSet<>(); final Set<String> enums = new LinkedHashSet<>(); final StringBuilder src = new StringBuilder(); final Class baseType = AbstractNode.class; final String _className = getProperty(name); final String _extendsClass = getProperty(extendsClass); final String superClass = _extendsClass != null ? _extendsClass : baseType.getSimpleName(); src.append("package org.structr.dynamic;\n\n"); SchemaHelper.formatImportStatements(this, src, baseType); src.append("public class "); src.append(_className); src.append(" extends "); src.append(superClass); SchemaHelper.formatInterfacesFromModules(src, this); src.append(" {\n\n"); // migrate schema relationships for (final SchemaRelationship outRel : getOutgoingRelationships(SchemaRelationship.class)) { final PropertyMap relNodeProperties = new PropertyMap(); relNodeProperties.put(SchemaRelationshipNode.sourceNode, outRel.getSourceNode()); relNodeProperties.put(SchemaRelationshipNode.targetNode, outRel.getTargetNode()); relNodeProperties.put(SchemaRelationshipNode.name, outRel.getProperty(SchemaRelationship.name)); relNodeProperties.put(SchemaRelationshipNode.sourceNotion, outRel.getProperty(SchemaRelationship.sourceNotion)); relNodeProperties.put(SchemaRelationshipNode.targetNotion, outRel.getProperty(SchemaRelationship.targetNotion)); relNodeProperties.put(SchemaRelationshipNode.extendsClass, outRel.getProperty(SchemaRelationship.extendsClass)); relNodeProperties.put(SchemaRelationshipNode.cascadingDeleteFlag, outRel.getProperty(SchemaRelationship.cascadingDeleteFlag)); relNodeProperties.put(SchemaRelationshipNode.autocreationFlag, outRel.getProperty(SchemaRelationship.autocreationFlag)); relNodeProperties.put(SchemaRelationshipNode.relationshipType, outRel.getProperty(SchemaRelationship.relationshipType)); relNodeProperties.put(SchemaRelationshipNode.sourceMultiplicity, outRel.getProperty(SchemaRelationship.sourceMultiplicity)); relNodeProperties.put(SchemaRelationshipNode.targetMultiplicity, outRel.getProperty(SchemaRelationship.targetMultiplicity)); relNodeProperties.put(SchemaRelationshipNode.sourceJsonName, outRel.getProperty(SchemaRelationship.sourceJsonName)); relNodeProperties.put(SchemaRelationshipNode.targetJsonName, outRel.getProperty(SchemaRelationship.targetJsonName)); app.create(SchemaRelationshipNode.class, relNodeProperties); app.delete(outRel); } for (final SchemaRelationship inRel : getIncomingRelationships(SchemaRelationship.class)) { final PropertyMap relNodeProperties = new PropertyMap(); relNodeProperties.put(SchemaRelationshipNode.sourceNode, inRel.getSourceNode()); relNodeProperties.put(SchemaRelationshipNode.targetNode, inRel.getTargetNode()); relNodeProperties.put(SchemaRelationshipNode.name, inRel.getProperty(SchemaRelationship.name)); relNodeProperties.put(SchemaRelationshipNode.sourceNotion, inRel.getProperty(SchemaRelationship.sourceNotion)); relNodeProperties.put(SchemaRelationshipNode.targetNotion, inRel.getProperty(SchemaRelationship.targetNotion)); relNodeProperties.put(SchemaRelationshipNode.extendsClass, inRel.getProperty(SchemaRelationship.extendsClass)); relNodeProperties.put(SchemaRelationshipNode.cascadingDeleteFlag, inRel.getProperty(SchemaRelationship.cascadingDeleteFlag)); relNodeProperties.put(SchemaRelationshipNode.autocreationFlag, inRel.getProperty(SchemaRelationship.autocreationFlag)); relNodeProperties.put(SchemaRelationshipNode.relationshipType, inRel.getProperty(SchemaRelationship.relationshipType)); relNodeProperties.put(SchemaRelationshipNode.sourceMultiplicity, inRel.getProperty(SchemaRelationship.sourceMultiplicity)); relNodeProperties.put(SchemaRelationshipNode.targetMultiplicity, inRel.getProperty(SchemaRelationship.targetMultiplicity)); relNodeProperties.put(SchemaRelationshipNode.sourceJsonName, inRel.getProperty(SchemaRelationship.sourceJsonName)); relNodeProperties.put(SchemaRelationshipNode.targetJsonName, inRel.getProperty(SchemaRelationship.targetJsonName)); app.create(SchemaRelationshipNode.class, relNodeProperties); app.delete(inRel); } // output related node definitions, collect property views for (final SchemaRelationshipNode outRel : getProperty(SchemaNode.relatedTo)) { final String propertyName = outRel.getPropertyName(_className, existingPropertyNames, true); //outRel.setProperty(SchemaRelationship.targetJsonName, propertyName); src.append(outRel.getPropertySource(propertyName, true)); //existingPropertyNames.clear(); addPropertyNameToViews(propertyName, viewProperties); } // output related node definitions, collect property views for (final SchemaRelationshipNode inRel : getProperty(SchemaNode.relatedFrom)) { final String propertyName = inRel.getPropertyName(_className, existingPropertyNames, false); //inRel.setProperty(SchemaRelationship.sourceJsonName, propertyName); src.append(inRel.getPropertySource(propertyName, false)); //existingPropertyNames.clear(); addPropertyNameToViews(propertyName, viewProperties); } // extract properties from node src.append(SchemaHelper.extractProperties(this, propertyNames, validators, enums, viewProperties, errorBuffer)); src.append(SchemaHelper.extractViews(this, viewProperties, errorBuffer)); src.append(SchemaHelper.extractMethods(this, saveActions)); // output possible enum definitions for (final String enumDefition : enums) { src.append(enumDefition); } for (Entry<String, Set<String>> entry : viewProperties.entrySet()) { final String viewName = entry.getKey(); final Set<String> view = entry.getValue(); if (!view.isEmpty()) { dynamicViews.add(viewName); SchemaHelper.formatView(src, _className, viewName, viewName, view); } } if (getProperty(defaultSortKey) != null) { String order = getProperty(defaultSortOrder); if (order == null || "desc".equals(order)) { order = "GraphObjectComparator.DESCENDING"; } else { order = "GraphObjectComparator.ASCENDING"; } src.append("\n\t@Override\n"); src.append("\tpublic PropertyKey getDefaultSortKey() {\n"); src.append("\t\treturn ").append(getProperty(defaultSortKey)).append("Property;\n"); src.append("\t}\n"); src.append("\n\t@Override\n"); src.append("\tpublic String getDefaultSortOrder() {\n"); src.append("\t\treturn ").append(order).append(";\n"); src.append("\t}\n"); } SchemaHelper.formatValidators(src, validators); SchemaHelper.formatSaveActions(this, src, saveActions); // insert source code from module for (final StructrModule module : modules) { src.append("\n\n// ----- dynamic code inserted by "); src.append(module.getName()); src.append(" -----\n"); module.insertSourceCode(this, src); } src.append("}\n"); return src.toString(); } @Override public Set<String> getViews() { return dynamicViews; } @Override public boolean isValid(final ErrorBuffer errorBuffer) { return ValidationHelper.checkStringMatchesRegex(this, name, "[A-Z][a-zA-Z0-9_]+", errorBuffer); } @Override public String getMultiplicity(final String propertyNameToCheck) { String multiplicity = getMultiplicity(this, propertyNameToCheck); if (multiplicity == null) { try { final String parentClass = getProperty(SchemaNode.extendsClass); if (parentClass != null) { // check if property is defined in parent class final SchemaNode parentSchemaNode = StructrApp.getInstance().nodeQuery(SchemaNode.class) .andName(StringUtils.substringAfterLast(parentClass, ".")).getFirst(); if (parentSchemaNode != null) { multiplicity = getMultiplicity(parentSchemaNode, propertyNameToCheck); } } } catch (FrameworkException ex) { logger.log(Level.WARNING, "Can't find schema node for parent class!", ex); } } if (multiplicity != null) { return multiplicity; } // fallback, search NodeInterface (this allows the owner relationship to be used in Notions!) final PropertyKey key = StructrApp.getConfiguration().getPropertyKeyForJSONName(NodeInterface.class, propertyNameToCheck, false); if (key != null) { // return "extended" multiplicity when the falling back to a NodeInterface property // to signal the code generator that it must not append "Property" to the name of // the generated NotionProperty parameter, i.e. NotionProperty(owner, ...) instead // of NotionProperty(ownerProperty, ...).. if (key instanceof StartNode || key instanceof EndNode) { return "1X"; } if (key instanceof StartNodes || key instanceof EndNodes) { return "*X"; } } return null; } private String getMultiplicity(final SchemaNode schemaNode, final String propertyNameToCheck) { final Set<String> existingPropertyNames = new LinkedHashSet<>(); final String _className = schemaNode.getProperty(name); for (final SchemaRelationshipNode outRel : schemaNode.getProperty(SchemaNode.relatedTo)) { if (propertyNameToCheck.equals(outRel.getPropertyName(_className, existingPropertyNames, true))) { return outRel.getMultiplicity(true); } } // output related node definitions, collect property views for (final SchemaRelationshipNode inRel : schemaNode.getProperty(SchemaNode.relatedFrom)) { if (propertyNameToCheck.equals(inRel.getPropertyName(_className, existingPropertyNames, false))) { return inRel.getMultiplicity(false); } } return null; } @Override public String getRelatedType(final String propertyNameToCheck) { String relatedType = getRelatedType(this, propertyNameToCheck); if (relatedType == null) { try { final String parentClass = getProperty(SchemaNode.extendsClass); if (parentClass != null) { // check if property is defined in parent class final SchemaNode parentSchemaNode = StructrApp.getInstance().nodeQuery(SchemaNode.class) .andName(StringUtils.substringAfterLast(parentClass, ".")).getFirst(); if (parentSchemaNode != null) { relatedType = getRelatedType(parentSchemaNode, propertyNameToCheck); } } } catch (FrameworkException ex) { logger.log(Level.WARNING, "Can't find schema node for parent class!", ex); } } if (relatedType != null) { return relatedType; } // fallback, search NodeInterface (this allows the owner relationship to be used in Notions!) final PropertyKey key = StructrApp.getConfiguration().getPropertyKeyForJSONName(NodeInterface.class, propertyNameToCheck, false); if (key != null) { final Class relatedTypeClass = key.relatedType(); if (relatedTypeClass != null) { return relatedTypeClass.getSimpleName(); } } return null; } private String getRelatedType(final SchemaNode schemaNode, final String propertyNameToCheck) { final Set<String> existingPropertyNames = new LinkedHashSet<>(); final String _className = schemaNode.getProperty(name); for (final SchemaRelationshipNode outRel : schemaNode.getProperty(SchemaNode.relatedTo)) { if (propertyNameToCheck.equals(outRel.getPropertyName(_className, existingPropertyNames, true))) { return outRel.getSchemaNodeTargetType(); } } // output related node definitions, collect property views for (final SchemaRelationshipNode inRel : schemaNode.getProperty(SchemaNode.relatedFrom)) { if (propertyNameToCheck.equals(inRel.getPropertyName(_className, existingPropertyNames, false))) { return inRel.getSchemaNodeSourceType(); } } return null; } @Override public String getAuxiliarySource() throws FrameworkException { // only File needs to return auxiliary code! if (!"File".equals(getClassName())) { return null; } final Map<Actions.Type, List<ActionEntry>> saveActions = new EnumMap<>(Actions.Type.class); final Map<String, Set<String>> viewProperties = new LinkedHashMap<>(); final Set<String> propertyNames = new LinkedHashSet<>(); final Set<Validator> validators = new LinkedHashSet<>(); final Set<String> enums = new LinkedHashSet<>(); final String _className = getProperty(name); final ErrorBuffer dummyErrorBuffer = new ErrorBuffer(); // extract properties final String propertyDefinitions = SchemaHelper.extractProperties(this, propertyNames, validators, enums, viewProperties, dummyErrorBuffer); final String viewDefinitions = SchemaHelper.extractViews(this, viewProperties, dummyErrorBuffer); final String methodDefinitions = SchemaHelper.extractMethods(this, saveActions); if (!propertyNames.isEmpty() || !viewProperties.isEmpty() || validators.isEmpty() || !saveActions.isEmpty()) { final StringBuilder src = new StringBuilder(); src.append("package org.structr.dynamic;\n\n"); SchemaHelper.formatImportStatements(this, src, AbstractNode.class); src.append("public class _").append(_className).append("Helper {\n"); // output possible enum definitions for (final String enumDefition : enums) { src.append(enumDefition); } // formatting is important :) if (!enums.isEmpty()) { src.append("\n"); } if (!propertyNames.isEmpty()) { src.append("\n"); src.append(propertyDefinitions); src.append(viewDefinitions); src.append(methodDefinitions); src.append("\n\tstatic {\n\n"); for (final String name : propertyNames) { final String propertyName = name + "Property"; src.append("\t\t").append(propertyName).append(".setDeclaringClass(").append(_className) .append(".class);\n\n"); src.append("\t\tStructrApp.getConfiguration().registerDynamicProperty(").append(_className) .append(".class, ").append(propertyName).append(");\n"); src.append("\t\tStructrApp.getConfiguration().registerPropertySet(").append(_className) .append(".class, PropertyView.Ui, ").append(propertyName).append(");\n\n"); } // Code for custom Views on "File" type for (final String viewName : viewProperties.keySet()) { for (String propertyName : viewProperties.get(viewName)) { if (propertyName.endsWith("Property")) { propertyName = propertyName.substring(0, propertyName.length() - 8); } src.append("\t\tStructrApp.getConfiguration().registerPropertySet(").append(_className) .append(".class, \"").append(viewName).append("\", \"").append(propertyName) .append("\");\n\n"); } } src.append("\t}\n\n"); } SchemaHelper.formatDynamicValidators(src, validators); SchemaHelper.formatDynamicSaveActions(src, saveActions); src.append("}\n"); return src.toString(); } return null; } // ----- private methods ----- private void addPropertyNameToViews(final String propertyName, final Map<String, Set<String>> viewProperties) { //SchemaHelper.addPropertyToView(PropertyView.Public, propertyName, viewProperties); SchemaHelper.addPropertyToView(PropertyView.Ui, propertyName, viewProperties); } // ----- interface GraphObject ----- @Override public List<GraphObject> getSyncData() throws FrameworkException { final List<GraphObject> data = super.getSyncData(); // special SchemaNode behaviour when syncing: // the schema needs to be transferred as a whole // because the individual nodes and relationships // depend on each other, so each schema node adds // all other schema nodes to its list. data.addAll(StructrApp.getInstance(securityContext).nodeQuery(SchemaNode.class).getAsList()); // outgoing relationships for (final SchemaRelationshipNode rel : getProperty(SchemaNode.relatedTo)) { data.add(rel); } // incoming relationships for (final SchemaRelationshipNode rel : getProperty(SchemaNode.relatedFrom)) { data.add(rel); } return data; } }