Java tutorial
/* Copyright 2011-2014 Red Hat, Inc This file is part of PressGang CCMS. PressGang CCMS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PressGang CCMS 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PressGang CCMS. If not, see <http://www.gnu.org/licenses/>. */ package org.jboss.pressgang.ccms.contentspec.utils; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.jboss.pressgang.ccms.contentspec.Appendix; import org.jboss.pressgang.ccms.contentspec.Chapter; import org.jboss.pressgang.ccms.contentspec.Comment; import org.jboss.pressgang.ccms.contentspec.CommonContent; import org.jboss.pressgang.ccms.contentspec.ContentSpec; import org.jboss.pressgang.ccms.contentspec.File; import org.jboss.pressgang.ccms.contentspec.FileList; import org.jboss.pressgang.ccms.contentspec.InfoTopic; import org.jboss.pressgang.ccms.contentspec.InitialContent; import org.jboss.pressgang.ccms.contentspec.KeyValueNode; import org.jboss.pressgang.ccms.contentspec.Level; import org.jboss.pressgang.ccms.contentspec.Node; import org.jboss.pressgang.ccms.contentspec.Part; import org.jboss.pressgang.ccms.contentspec.Preface; import org.jboss.pressgang.ccms.contentspec.Process; import org.jboss.pressgang.ccms.contentspec.Section; import org.jboss.pressgang.ccms.contentspec.SpecNode; import org.jboss.pressgang.ccms.contentspec.SpecNodeWithRelationships; import org.jboss.pressgang.ccms.contentspec.SpecTopic; import org.jboss.pressgang.ccms.contentspec.TextNode; import org.jboss.pressgang.ccms.contentspec.constants.CSConstants; import org.jboss.pressgang.ccms.contentspec.entities.InjectionOptions; import org.jboss.pressgang.ccms.contentspec.enums.BookType; import org.jboss.pressgang.ccms.contentspec.enums.LevelType; import org.jboss.pressgang.ccms.contentspec.enums.RelationshipType; import org.jboss.pressgang.ccms.contentspec.sort.CSNodeSorter; import org.jboss.pressgang.ccms.contentspec.sort.CSRelatedNodeSorter; import org.jboss.pressgang.ccms.contentspec.sort.EntityWrapperIDComparator; import org.jboss.pressgang.ccms.provider.DataProviderFactory; import org.jboss.pressgang.ccms.provider.ServerSettingsProvider; import org.jboss.pressgang.ccms.provider.TopicProvider; import org.jboss.pressgang.ccms.utils.constants.CommonConstants; import org.jboss.pressgang.ccms.wrapper.CSInfoNodeWrapper; import org.jboss.pressgang.ccms.wrapper.CSNodeWrapper; import org.jboss.pressgang.ccms.wrapper.CSRelatedNodeWrapper; import org.jboss.pressgang.ccms.wrapper.ContentSpecWrapper; import org.jboss.pressgang.ccms.wrapper.TagWrapper; public class CSTransformer { /** * A List of lower case metadata properties that should be ignored during transformation because they exist else where. */ private static final List<String> IGNORE_META_DATA = Arrays.asList(CommonConstants.CS_ID_TITLE.toLowerCase(), CommonConstants.CS_CHECKSUM_TITLE.toLowerCase()); /** * Transforms a content spec datasource entity into a generic content spec object. * * @param spec The content spec entity to be transformed. * @param providerFactory * @return The generic Content Spec object that was transformed from the entity. */ public static ContentSpec transform(final ContentSpecWrapper spec, final DataProviderFactory providerFactory, final boolean includeChecksum) { // local variables that are used to map transformed content Map<Integer, Node> nodes = new HashMap<Integer, Node>(); Map<String, SpecTopic> topicTargets = new HashMap<String, SpecTopic>(); List<CSNodeWrapper> relationshipFromNodes = new ArrayList<CSNodeWrapper>(); List<Process> processes = new ArrayList<Process>(); // Start the transformation final ContentSpec contentSpec = new ContentSpec(); contentSpec.setId(spec.getId()); transformGlobalOptions(spec, contentSpec); // Add all the levels/topics boolean localeFound = false; if (spec.getChildren() != null) { final List<CSNodeWrapper> childNodes = spec.getChildren().getItems(); final HashMap<CSNodeWrapper, Node> levelNodes = new HashMap<CSNodeWrapper, Node>(); for (final CSNodeWrapper childNode : childNodes) { if (childNode.getNodeType() == CommonConstants.CS_NODE_TOPIC) { final SpecTopic topic = transformSpecTopic(childNode, nodes, topicTargets, relationshipFromNodes); levelNodes.put(childNode, topic); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_COMMENT) { final Comment comment = transformComment(childNode); levelNodes.put(childNode, comment); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_COMMON_CONTENT) { final CommonContent commonContent = transformCommonContent(childNode); levelNodes.put(childNode, commonContent); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_META_DATA || childNode.getNodeType() == CommonConstants.CS_NODE_META_DATA_TOPIC) { if (!IGNORE_META_DATA.contains(childNode.getTitle().toLowerCase())) { final KeyValueNode<?> metaDataNode = transformMetaData(childNode, nodes, topicTargets, relationshipFromNodes); levelNodes.put(childNode, metaDataNode); } if (CommonConstants.CS_LOCALE_TITLE.equalsIgnoreCase(childNode.getTitle())) { localeFound = true; } } else { final Level level = transformLevel(childNode, nodes, topicTargets, relationshipFromNodes, processes); levelNodes.put(childNode, level); } } // Sort the level nodes so that they are in the right order based on next/prev values. final LinkedHashMap<CSNodeWrapper, Node> sortedMap = CSNodeSorter.sortMap(levelNodes); // Add the child nodes to the content spec now that they are in the right order. boolean addToBaseLevel = false; final Iterator<Map.Entry<CSNodeWrapper, Node>> iter = sortedMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry<CSNodeWrapper, Node> entry = iter.next(); // If a level or spec topic is found then start adding to the base level instead of the content spec if ((entry.getValue() instanceof Level || entry.getValue() instanceof SpecTopic) && !addToBaseLevel) { addToBaseLevel = true; // Add the locale if it wasn't specified if (!localeFound) { contentSpec.setLocale(spec.getLocale() == null ? null : spec.getLocale().getValue()); } // Add a space between the base metadata and optional metadata contentSpec.appendChild(new TextNode("\n")); } // Add the node to the right component. if (addToBaseLevel) { contentSpec.getBaseLevel().appendChild(entry.getValue()); // Add a new line to separate chapters/parts if (isNodeASeparatorLevel(entry.getValue()) && iter.hasNext()) { contentSpec.getBaseLevel().appendChild(new TextNode("\n")); } } else { contentSpec.appendChild(entry.getValue()); } } } // Apply the relationships to the nodes applyRelationships(contentSpec, nodes, topicTargets, relationshipFromNodes, processes, providerFactory); // Set the line numbers setLineNumbers(contentSpec, includeChecksum ? 2 : 1); return contentSpec; } private static int setLineNumbers(final Node node, int current) { if (node instanceof ContentSpec) { for (final Node childNode : ((ContentSpec) node).getNodes()) { current = setLineNumbers(childNode, current); } final Level baseLevel = ((ContentSpec) node).getBaseLevel(); if (!baseLevel.getTags(false).isEmpty() || baseLevel.getConditionStatement() != null) { current++; } for (final Node childNode : ((ContentSpec) node).getChildNodes()) { current = setLineNumbers(childNode, current); } return current; } else { node.setLineNumber(current); current++; if (node instanceof Level) { for (final Node childNode : ((Level) node).getChildNodes()) { current = setLineNumbers(childNode, current); } } else if (node instanceof FileList) { final List<File> files = ((FileList) node).getValue(); current += files == null || files.isEmpty() ? 0 : (files.size() - 1); } else if (node instanceof KeyValueNode) { if (((KeyValueNode) node).getKey().equals(CommonConstants.CS_PUBLICAN_CFG_TITLE) || ((KeyValueNode) node).getKey().endsWith("-" + CommonConstants.CS_PUBLICAN_CFG_TITLE)) { final String publicanCfg = (String) ((KeyValueNode) node).getValue(); current += StringUtils.countMatches(publicanCfg, "\n"); } else if (((KeyValueNode) node).getKey().equals(CommonConstants.CS_ENTITIES_TITLE)) { final String entities = (String) ((KeyValueNode) node).getValue(); current += StringUtils.countMatches(entities, "\n"); } } else if (node instanceof SpecTopic && !((SpecTopic) node).getRelationships().isEmpty()) { int numPrereqs = ((SpecTopic) node).getPrerequisiteRelationships().size(); int numReferTo = ((SpecTopic) node).getRelatedRelationships().size(); int numLinkList = ((SpecTopic) node).getLinkListRelationships().size(); if (numPrereqs > 0) { current += numPrereqs + 1; } if (numReferTo > 0) { current += numReferTo + 1; } if (numLinkList > 0) { current += numLinkList + 1; } } return current; } } private static void transformGlobalOptions(final ContentSpecWrapper spec, final ContentSpec contentSpec) { if (spec.getCondition() != null) { contentSpec.getBaseLevel().setConditionStatement(spec.getCondition()); } // Add all of the book tags if (spec.getBookTags() != null && spec.getBookTags().getItems() != null) { final List<String> tags = new ArrayList<String>(); final List<TagWrapper> tagItems = spec.getBookTags().getItems(); // Sort the tags to make sure they always appear the same Collections.sort(tagItems, new EntityWrapperIDComparator()); // Add the tags for (final TagWrapper tag : tagItems) { tags.add(tag.getName()); } contentSpec.setTags(tags); } } /** * Transforms a MetaData CSNode into a KeyValuePair that can be added to a ContentSpec object. * * @param node The CSNode to be transformed. * @return The transformed KeyValuePair object. */ public static KeyValueNode<?> transformMetaData(final CSNodeWrapper node) { return transformMetaData(node, new HashMap<Integer, Node>(), new HashMap<String, SpecTopic>(), new ArrayList<CSNodeWrapper>()); } /** * Transforms a MetaData CSNode into a KeyValuePair that can be added to a ContentSpec object. * * @param node The CSNode to be transformed. * @param nodes A mapping of node entity ids to their transformed counterparts. * @param targetTopics A mapping of target ids to SpecTopics. * @param relationshipFromNodes A list of CSNode entities that have relationships. * @return The transformed KeyValuePair object. */ protected static KeyValueNode<?> transformMetaData(final CSNodeWrapper node, final Map<Integer, Node> nodes, final Map<String, SpecTopic> targetTopics, final List<CSNodeWrapper> relationshipFromNodes) { final KeyValueNode<?> keyValueNode; if (node.getTitle().equalsIgnoreCase(CommonConstants.CS_BOOK_TYPE_TITLE)) { keyValueNode = new KeyValueNode<BookType>(node.getTitle(), BookType.getBookType(node.getAdditionalText())); } else if (node.getTitle().equalsIgnoreCase(CommonConstants.CS_INLINE_INJECTION_TITLE)) { keyValueNode = new KeyValueNode<InjectionOptions>(node.getTitle(), new InjectionOptions(node.getAdditionalText())); } else if (node.getNodeType().equals(CommonConstants.CS_NODE_META_DATA_TOPIC)) { final SpecTopic specTopic = transformSpecTopicWithoutTypeCheck(node, nodes, targetTopics, relationshipFromNodes); keyValueNode = new KeyValueNode<SpecTopic>(node.getTitle(), specTopic); } else if (node.getTitle().equalsIgnoreCase(CommonConstants.CS_FILE_SHORT_TITLE) || node.getTitle().equalsIgnoreCase(CommonConstants.CS_FILE_TITLE)) { keyValueNode = transformFileList(node); } else { keyValueNode = new KeyValueNode<String>(node.getTitle(), node.getAdditionalText()); } keyValueNode.setUniqueId(node.getId() == null ? null : node.getId().toString()); return keyValueNode; } /** * Transforms a MetaData CSNode into a FileList that can be added to a ContentSpec object. * * @param node The CSNode to be transformed. * @return The transformed FileList object. */ protected static FileList transformFileList(final CSNodeWrapper node) { final List<File> files = new LinkedList<File>(); // Add all the child files if (node.getChildren() != null && node.getChildren().getItems() != null) { final List<CSNodeWrapper> childNodes = node.getChildren().getItems(); final HashMap<CSNodeWrapper, File> fileNodes = new HashMap<CSNodeWrapper, File>(); for (final CSNodeWrapper childNode : childNodes) { fileNodes.put(childNode, transformFile(childNode)); } // Sort the file list nodes so that they are in the right order based on next/prev values. final LinkedHashMap<CSNodeWrapper, File> sortedMap = CSNodeSorter.sortMap(fileNodes); // Add the child nodes to the file list now that they are in the right order. final Iterator<Map.Entry<CSNodeWrapper, File>> iter = sortedMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry<CSNodeWrapper, File> entry = iter.next(); files.add(entry.getValue()); } } return new FileList(CommonConstants.CS_FILE_TITLE, files); } private static File transformFile(final CSNodeWrapper node) { final File file = new File(node.getTitle(), node.getEntityId()); // Basic data file.setRevision(node.getEntityRevision()); file.setUniqueId(node.getId() == null ? null : node.getId().toString()); return file; } /** * Transform a Level CSNode entity object into a Level Object that can be added to a Content Specification. * * @param node The CSNode entity object to be transformed. * @param nodes A mapping of node entity ids to their transformed counterparts. * @param targetTopics A mapping of target ids to SpecTopics. * @param relationshipFromNodes A list of CSNode entities that have relationships. * @param processes * @return The transformed level entity. */ protected static Level transformLevel(final CSNodeWrapper node, final Map<Integer, Node> nodes, final Map<String, SpecTopic> targetTopics, final List<CSNodeWrapper> relationshipFromNodes, List<Process> processes) { final Level level; if (node.getNodeType() == CommonConstants.CS_NODE_APPENDIX) { level = new Appendix(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_CHAPTER) { level = new Chapter(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_PART) { level = new Part(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_PROCESS) { level = new Process(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_SECTION) { level = new Section(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_PREFACE) { level = new Preface(node.getTitle()); } else if (node.getNodeType() == CommonConstants.CS_NODE_INITIAL_CONTENT) { level = new InitialContent(); } else { throw new IllegalArgumentException("The passed node is not a Level"); } level.setConditionStatement(node.getCondition()); level.setTargetId(node.getTargetId()); level.setUniqueId(node.getId() == null ? null : node.getId().toString()); // Set the fixed url properties applyFixedURLs(node, level); // Collect any relationships for processing after everything is transformed if (node.getRelatedToNodes() != null && node.getRelatedToNodes().getItems() != null && !node.getRelatedToNodes().getItems().isEmpty()) { relationshipFromNodes.add(node); } // Transform the info topic node if one exists for the level if (node.getInfoTopicNode() != null) { final InfoTopic infoTopic = transformInfoTopic(node, node.getInfoTopicNode()); level.setInfoTopic(infoTopic); } // Add all the levels/topics if (node.getChildren() != null && node.getChildren().getItems() != null) { final List<CSNodeWrapper> childNodes = node.getChildren().getItems(); final HashMap<CSNodeWrapper, Node> levelNodes = new HashMap<CSNodeWrapper, Node>(); final HashMap<CSNodeWrapper, SpecTopic> initialContentNodes = new HashMap<CSNodeWrapper, SpecTopic>(); for (final CSNodeWrapper childNode : childNodes) { if (childNode.getNodeType() == CommonConstants.CS_NODE_TOPIC) { final SpecTopic topic = transformSpecTopic(childNode, nodes, targetTopics, relationshipFromNodes); levelNodes.put(childNode, topic); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_COMMENT) { final Comment comment = transformComment(childNode); levelNodes.put(childNode, comment); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_COMMON_CONTENT) { final CommonContent commonContent = transformCommonContent(childNode); levelNodes.put(childNode, commonContent); } else if (childNode.getNodeType() == CommonConstants.CS_NODE_INITIAL_CONTENT_TOPIC) { final SpecTopic initialContentTopic = transformSpecTopicWithoutTypeCheck(childNode, nodes, targetTopics, relationshipFromNodes); if (level instanceof InitialContent) { levelNodes.put(childNode, initialContentTopic); } else { initialContentNodes.put(childNode, initialContentTopic); } } else { final Level childLevel = transformLevel(childNode, nodes, targetTopics, relationshipFromNodes, processes); levelNodes.put(childNode, childLevel); } } // Sort the level nodes so that they are in the right order based on next/prev values. final LinkedHashMap<CSNodeWrapper, Node> sortedMap = CSNodeSorter.sortMap(levelNodes); // PressGang 1.4+ stores the initial content inside it's own container, instead of on the level if (!(level instanceof InitialContent) && !initialContentNodes.isEmpty()) { final LinkedHashMap<CSNodeWrapper, SpecTopic> sortedInitialContentMap = CSNodeSorter .sortMap(initialContentNodes); final InitialContent initialContent = new InitialContent(); level.appendChild(initialContent); // Add the initial content topics to the level now that they are in the right order. final Iterator<Map.Entry<CSNodeWrapper, SpecTopic>> frontMatterIter = sortedInitialContentMap .entrySet().iterator(); while (frontMatterIter.hasNext()) { final Map.Entry<CSNodeWrapper, SpecTopic> entry = frontMatterIter.next(); initialContent.appendSpecTopic(entry.getValue()); } } // Add the child nodes to the level now that they are in the right order. final Iterator<Map.Entry<CSNodeWrapper, Node>> iter = sortedMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry<CSNodeWrapper, Node> entry = iter.next(); level.appendChild(entry.getValue()); // Add a new line to separate chapters/parts if (isNodeASeparatorLevel(entry.getValue()) && iter.hasNext()) { level.appendChild(new TextNode("\n")); } } } // Add the node to the list of processed nodes so that the relationships can be added once everything is processed nodes.put(node.getId(), level); // We need to keep track of processes to process their relationships if (level instanceof Process) { processes.add((Process) level); } return level; } /** * Transform a Topic CSNode entity object into a SpecTopic Object that can be added to a Content Specification. * * @param node The CSNode entity object to be transformed. * @param nodes A mapping of node entity ids to their transformed counterparts. * @param targetTopics A mapping of target ids to SpecTopics. * @param relationshipFromNodes A list of CSNode entities that have relationships. * @return The transformed SpecTopic entity. */ protected static SpecTopic transformSpecTopic(final CSNodeWrapper node, final Map<Integer, Node> nodes, final Map<String, SpecTopic> targetTopics, final List<CSNodeWrapper> relationshipFromNodes) { if (node.getNodeType() != CommonConstants.CS_NODE_TOPIC) { throw new IllegalArgumentException("The passed node is not a Spec Topic"); } return transformSpecTopicWithoutTypeCheck(node, nodes, targetTopics, relationshipFromNodes); } /** * Transform a Topic CSNode entity object into a SpecTopic Object that can be added to a Content Specification. * * @param node The CSNode entity object to be transformed. * @param nodes A mapping of node entity ids to their transformed counterparts. * @param targetTopics A mapping of target ids to SpecTopics. * @param relationshipFromNodes A list of CSNode entities that have relationships. * @return The transformed SpecTopic entity. */ private static SpecTopic transformSpecTopicWithoutTypeCheck(final CSNodeWrapper node, final Map<Integer, Node> nodes, final Map<String, SpecTopic> targetTopics, final List<CSNodeWrapper> relationshipFromNodes) { final SpecTopic specTopic = new SpecTopic(node.getEntityId(), node.getTitle()); // Basic data specTopic.setRevision(node.getEntityRevision()); specTopic.setConditionStatement(node.getCondition()); specTopic.setTargetId(node.getTargetId()); specTopic.setUniqueId(node.getId() == null ? null : node.getId().toString()); // Set the fixed url properties applyFixedURLs(node, specTopic); // Collect any relationships for processing after everything is transformed if (node.getRelatedToNodes() != null && node.getRelatedToNodes().getItems() != null && !node.getRelatedToNodes().getItems().isEmpty()) { relationshipFromNodes.add(node); } // Add the node to the list of processed nodes so that the relationships can be added once everything is processed nodes.put(node.getId(), specTopic); // If there is a target add it to the list if (node.getTargetId() != null) { targetTopics.put(node.getTargetId(), specTopic); } return specTopic; } /** * Transform a Topic CSNode entity object into an InfoTopic Object that can be added to a Content Specification. * * @param node The CSNode entity object to be transformed. * @return The transformed InfoTopic entity. */ protected static InfoTopic transformInfoTopic(final CSNodeWrapper parentNode, final CSInfoNodeWrapper node) { final InfoTopic infoTopic = new InfoTopic(node.getTopicId(), null); // Basic data infoTopic.setRevision(node.getTopicRevision()); infoTopic.setConditionStatement(node.getCondition()); infoTopic.setUniqueId(parentNode.getId() == null ? null : parentNode.getId().toString()); return infoTopic; } /** * Transform a Comment CSNode entity into a Comment object that can be added to a Content Specification. * * @param node The CSNode to be transformed. * @return The transformed Comment object. */ protected static Comment transformComment(CSNodeWrapper node) { final Comment comment; if (node.getNodeType() == CommonConstants.CS_NODE_COMMENT) { comment = new Comment(node.getTitle()); } else { throw new IllegalArgumentException("The passed node is not a Comment"); } comment.setUniqueId(node.getId() == null ? null : node.getId().toString()); return comment; } /** * Transform a Common Content CSNode entity into a Comment object that can be added to a Content Specification. * * @param node The CSNode to be transformed. * @return The transformed Comment object. */ protected static CommonContent transformCommonContent(CSNodeWrapper node) { final CommonContent commonContent; if (node.getNodeType() == CommonConstants.CS_NODE_COMMON_CONTENT) { commonContent = new CommonContent(node.getTitle()); } else { throw new IllegalArgumentException("The passed node is not a Comment"); } commonContent.setUniqueId(node.getId() == null ? null : node.getId().toString()); return commonContent; } /** * Apply the relationships to all of the nodes in the content spec. This should be the last step since all nodes have to be converted * to levels and topics before this method can work. */ protected static void applyRelationships(final ContentSpec contentSpec, final Map<Integer, Node> nodes, final Map<String, SpecTopic> targetTopics, final List<CSNodeWrapper> relationshipFromNodes, final List<Process> processes, final DataProviderFactory providerFactory) { // Apply the user defined relationships stored in the database for (final CSNodeWrapper node : relationshipFromNodes) { boolean initialContentTopic = node.getNodeType() == CommonConstants.CS_NODE_INITIAL_CONTENT_TOPIC; boolean level = EntityUtilities.isNodeALevel(node); // In 1.3 or lower initial content relationships were stored on the topic, however in 1.4+ they are now on the initial // content level, so do the migration here final SpecNodeWithRelationships fromNode; if (initialContentTopic) { final CSNodeWrapper parentNode = node.getParent(); final SpecNodeWithRelationships parent = (SpecNodeWithRelationships) nodes.get(parentNode.getId()); if (parent instanceof InitialContent) { fromNode = parent; } else { final Level parentLevel = (Level) parent; if (parentLevel.getFirstSpecNode() instanceof InitialContent) { fromNode = (SpecNodeWithRelationships) parentLevel.getFirstSpecNode(); } else { fromNode = new InitialContent(parent.getLineNumber(), CSConstants.LEVEL_INITIAL_CONTENT + ":"); if (parentLevel.getChildNodes().isEmpty()) { parentLevel.appendChild(fromNode); } else { parentLevel.insertBefore(fromNode, parentLevel.getChildNodes().get(0)); } } } } else { fromNode = (SpecNodeWithRelationships) nodes.get(node.getId()); } // Check if we have any relationships to process if (node.getRelatedToNodes() == null || node.getRelatedToNodes().isEmpty()) continue; final List<CSRelatedNodeWrapper> relatedToNodes = node.getRelatedToNodes().getItems(); // Sort the relationships into the correct order based on the sort variable Collections.sort(relatedToNodes, new CSRelatedNodeSorter()); // Add the relationships to the topic for (final CSRelatedNodeWrapper relatedToNode : relatedToNodes) { final Node toNode = nodes.get(relatedToNode.getId()); if (toNode == null) { throw new IllegalStateException("The related node does not exist in the content specification"); } else if (toNode instanceof Level) { // Relationships to levels final Level toLevel = (Level) toNode; // Ensure that the level has a target id if not create one. if (toLevel.getTargetId() == null) { toLevel.setTargetId("T00" + relatedToNode.getId()); } // Add the relationship fromNode.addRelationshipToTarget(toLevel, RelationshipType.getRelationshipType(relatedToNode.getRelationshipType()), initialContentTopic || level ? null : toLevel.getTitle()); } else { // Relationships to topics final SpecTopic toSpecTopic = (SpecTopic) toNode; final String title = toSpecTopic.getTitle(); // Add the relationship if (relatedToNode.getRelationshipMode().equals(CommonConstants.CS_RELATIONSHIP_MODE_TARGET)) { fromNode.addRelationshipToTarget(toSpecTopic, RelationshipType.getRelationshipType(relatedToNode.getRelationshipType()), initialContentTopic || level ? null : title); } else { fromNode.addRelationshipToTopic(toSpecTopic, RelationshipType.getRelationshipType(relatedToNode.getRelationshipType()), initialContentTopic || level ? null : title); } } } } // Create the unique id map final Map<String, SpecTopic> uniqueIdSpecTopicMap = ContentSpecUtilities .getUniqueIdSpecTopicMap(contentSpec); // Apply the process relationships for (final Process process : processes) { process.processTopics(uniqueIdSpecTopicMap, targetTopics, providerFactory.getProvider(TopicProvider.class), providerFactory.getProvider(ServerSettingsProvider.class)); } } protected static void applyFixedURLs(final CSNodeWrapper node, final SpecNode specNode) { if (!isNullOrEmpty(node.getFixedURL())) { specNode.setFixedUrl(node.getFixedURL()); } } protected static boolean isNodeASeparatorLevel(final Node node) { if (node instanceof Chapter || node instanceof Part || node instanceof Appendix || node instanceof Preface) { return true; } else if (node instanceof Process) { final Process process = (Process) node; return process.getParent() != null && (process.getParent().getLevelType() == LevelType.BASE || process.getParent().getLevelType() == LevelType.PART); } else { return false; } } }