diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-27 15:05:21 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-27 15:05:21 +0200 |
commit | c3a6e8d99b0878180a448ce632d7458e3b1e634d (patch) | |
tree | ea396c05dc686886df779c8ac5b020c73c9b8421 /config-model/src/main/java | |
parent | 6d54cff8aa96d946c04af5c6591cb9452b318fd4 (diff) |
Move building to builder
Diffstat (limited to 'config-model/src/main/java')
8 files changed, 163 insertions, 195 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java index a36175accd0..d4b6751c356 100644 --- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java +++ b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java @@ -118,7 +118,7 @@ public class ConfigModelRepo implements ConfigModelRepoAdder, Serializable, Iter String tagName = servicesElement.getTagName(); if (tagName.equals("config")) continue; // TODO: Remove on Vespa 6 if (tagName.equals("cluster")) continue; // TODO: Remove on Vespa 6 - if ((tagName.equals("clients")) && deployState.isHostedVespa()) + if ((tagName.equals("clients")) && deployState.isHosted()) throw new IllegalArgumentException("<" + tagName + "> is not allowed when running Vespa in a hosted environment"); String tagVersion = servicesElement.getAttribute("version"); @@ -236,7 +236,7 @@ public class ConfigModelRepo implements ConfigModelRepoAdder, Serializable, Iter // TODO: Doctoring on the XML is the wrong level for this. We should be able to mark a model as default instead -Jon private static Element getImplicitAdmin(DeployState deployState) throws IOException, SAXException { - String defaultAdminElement = deployState.isHostedVespa() ? getImplicitAdminV4() : getImplicitAdminV2(); + String defaultAdminElement = deployState.isHosted() ? getImplicitAdminV4() : getImplicitAdminV2(); log.log(LogLevel.DEBUG, "No <admin> defined, using " + defaultAdminElement); return XmlHelper.getDocumentBuilder().parse(new InputSource(new StringReader(defaultAdminElement))).getDocumentElement(); } diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index 5896dc59df2..8ca82692e98 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -241,7 +241,7 @@ public class DeployState implements ConfigDefinitionStore { public Optional<Model> getPreviousModel() { return previousModel; } - public boolean isHostedVespa() { + public boolean isHosted() { return properties.hostedVespa(); } diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java index 41927bc09a9..8e1097907f1 100644 --- a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java @@ -49,7 +49,7 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce return (parent != null) && (parent.getRoot() != null) && (parent.getRoot().getDeployState() != null) - && parent.getRoot().getDeployState().isHostedVespa(); + && parent.getRoot().getDeployState().isHosted(); } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaConfigModelRegistry.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaConfigModelRegistry.java index 6b9aa3fe8c5..ff13a1321b4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaConfigModelRegistry.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaConfigModelRegistry.java @@ -8,6 +8,7 @@ import com.yahoo.config.model.builder.xml.ConfigModelId; import com.yahoo.vespa.model.builder.xml.dom.*; import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; import com.yahoo.vespa.model.container.xml.ContainerModelBuilder.Networking; +import com.yahoo.vespa.model.content.Content; import com.yahoo.vespa.model.generic.GenericServicesBuilder; import java.util.ArrayList; @@ -32,7 +33,7 @@ public class VespaConfigModelRegistry extends ConfigModelRegistry { builderList.add(new AdminModel.BuilderV4()); builderList.add(new DomRoutingBuilder()); builderList.add(new DomClientsBuilder()); - builderList.add(new DomContentBuilder()); + builderList.add(new Content.Builder()); builderList.add(new ContainerModelBuilder(false, Networking.enable)); builderList.add(new GenericServicesBuilder()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomContentBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomContentBuilder.java deleted file mode 100644 index f427bfbc575..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomContentBuilder.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.builder.xml.dom; - -import com.yahoo.config.model.ConfigModelContext; -import com.yahoo.config.model.builder.xml.ConfigModelBuilder; -import com.yahoo.config.model.builder.xml.ConfigModelId; -import com.yahoo.vespa.model.admin.Admin; -import com.yahoo.vespa.model.content.Content; -import com.yahoo.vespa.model.content.cluster.ContentCluster; - -import org.w3c.dom.Element; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * @author baldersheim - */ -public class DomContentBuilder extends ConfigModelBuilder<Content> { - - public static final List<ConfigModelId> configModelIds = Collections.singletonList(ConfigModelId.fromName("content")); - - public DomContentBuilder() { - super(Content.class); - } - - @Override - public List<ConfigModelId> handlesElements() { - return configModelIds; - } - - @Override - public void doBuild(Content content, Element xml, ConfigModelContext modelContext) { - content.build(xml, modelContext); - } - -} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java index d3ccefc3e26..bce1d28a863 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java @@ -72,7 +72,7 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> throw new IllegalArgumentException(String.format("Invalid port %d.", port)); int legalPortInHostedVespa = Container.BASEPORT; - if (deployState.isHostedVespa() && port != legalPortInHostedVespa) { + if (deployState.isHosted() && port != legalPortInHostedVespa) { deployState.getDeployLogger().log(LogLevel.WARNING, String.format("Trying to set port to %d for http server with id %s. You cannot set port to anything else than %s", port, spec.getAttribute("id"), legalPortInHostedVespa)); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index a8055afeea8..b9611921fc9 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -188,7 +188,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } protected void addStatusHandlers(ContainerCluster cluster, ConfigModelContext configModelContext) { - if (configModelContext.getDeployState().isHostedVespa()) { + if (configModelContext.getDeployState().isHosted()) { String name = "status.html"; Optional<String> statusFile = Optional.ofNullable(System.getenv(HOSTED_VESPA_STATUS_FILE_YINST_SETTING)); cluster.addComponent( diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java index 3b52a5a3bfb..97857e3bc34 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java @@ -11,13 +11,12 @@ import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.model.ConfigModelRepo; import com.yahoo.config.model.ConfigModelRepoAdder; import com.yahoo.config.model.admin.AdminModel; +import com.yahoo.config.model.builder.xml.ConfigModelBuilder; +import com.yahoo.config.model.builder.xml.ConfigModelId; import com.yahoo.config.model.producer.AbstractConfigProducer; -import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.log.LogLevel; import com.yahoo.vespa.model.*; import com.yahoo.vespa.model.admin.Admin; -import com.yahoo.vespa.model.builder.VespaModelBuilder; -import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.ContainerModel; @@ -47,7 +46,7 @@ public class Content extends ConfigModel { private ContentCluster cluster; private Optional<ContainerCluster> ownedIndexingCluster = Optional.empty(); - private final boolean hostedVespa; + private final boolean isHosted; // Dependencies to other models private final AdminModel adminModel; @@ -57,23 +56,11 @@ public class Content extends ConfigModel { public Content(ConfigModelContext modelContext, AdminModel adminModel, Collection<ContainerModel> containers) { super(modelContext); modelContext.getParentProducer().getRoot(); - hostedVespa = modelContext.getDeployState().isHostedVespa(); + isHosted = modelContext.getDeployState().isHosted(); this.adminModel = adminModel; this.containers = containers; } - /** Returns the admin model of this system */ - public AdminModel adminModel() { return adminModel; } - - /** Called by DomContentBuilder during build */ - public void build(Element xml, ConfigModelContext configModelContext) { - Admin admin = adminModel() != null ? adminModel().getAdmin() : null; // This is null in tests only - cluster = new ContentCluster.Builder(admin, configModelContext.getDeployLogger()).build(configModelContext.getParentProducer(), xml); - initializeIndexingClusters(containers, - configModelContext.getConfigModelRepoAdder(), - (ApplicationConfigProducerRoot)configModelContext.getParentProducer()); - } - public ContentCluster getCluster() { return cluster; } /** @@ -105,83 +92,6 @@ public class Content extends ConfigModel { indexedCluster.setupDispatchGroups(); } - /** Select/creates and initializes the indexing cluster coupled to this */ - private void initializeIndexingClusters(Collection<ContainerModel> containers, - ConfigModelRepoAdder configModelRepoAdder, - ApplicationConfigProducerRoot root) { - if (getCluster().getSearch().hasIndexedCluster()) - initializeOrSetExistingIndexingCluster(getCluster().getSearch().getIndexed(), hostedVespa, - containers, configModelRepoAdder, root); - } - - private void initializeOrSetExistingIndexingCluster(IndexedSearchCluster indexedSearchCluster, - boolean isHostedVespa, - Collection<ContainerModel> containers, - ConfigModelRepoAdder configModelRepoAdder, - ApplicationConfigProducerRoot root) { - if (indexedSearchCluster.hasExplicitIndexingCluster()) { - setExistingIndexingCluster(indexedSearchCluster, containers); - } else if (isHostedVespa) { - setContainerAsIndexingCluster(indexedSearchCluster, containers, configModelRepoAdder, root); - } else { - createImplicitIndexingCluster(indexedSearchCluster, configModelRepoAdder, root); - } - } - - private void setContainerAsIndexingCluster(IndexedSearchCluster indexedSearchCluster, - Collection<ContainerModel> containers, - ConfigModelRepoAdder configModelRepoAdder, - ApplicationConfigProducerRoot root) { - if (containers.isEmpty()) { - createImplicitIndexingCluster(indexedSearchCluster, configModelRepoAdder, root); - } else { - ContainerCluster targetCluster = getContainerWithDocproc(containers); - if (targetCluster == null) - targetCluster = getContainerWithSearch(containers); - if (targetCluster == null) - targetCluster = containers.iterator().next().getCluster(); - - addDocproc(targetCluster); - indexedSearchCluster.setIndexingClusterName(targetCluster.getName()); - addIndexingChainsTo(targetCluster, indexedSearchCluster); - } - } - - private void setExistingIndexingCluster(IndexedSearchCluster cluster, Collection<ContainerModel> containers) { - String indexingClusterName = cluster.getIndexingClusterName(); - ContainerModel containerModel = findByName(indexingClusterName, containers); - if (containerModel == null) - throw new RuntimeException("Content cluster '" + cluster.getClusterName() + "' refers to docproc " + - "cluster '" + indexingClusterName + "', but this cluster does not exist."); - addIndexingChainsTo(containerModel.getCluster(), cluster); - } - - private ContainerModel findByName(String name, Collection<ContainerModel> containers) { - for (ContainerModel container : containers) - if (container.getId().equals(name)) - return container; - return null; - } - - private void addIndexingChainsTo(ContainerCluster indexer, IndexedSearchCluster cluster) { - addIndexingChain(indexer); - DocprocChain indexingChain; - ComponentRegistry<DocprocChain> allChains = indexer.getDocprocChains().allChains(); - if (cluster.hasExplicitIndexingChain()) { - indexingChain = allChains.getComponent(cluster.getIndexingChainName()); - if (indexingChain == null) { - throw new RuntimeException("Indexing cluster " + cluster.getClusterName() + " refers to docproc " + - "chain " + cluster.getIndexingChainName() + " for indexing, which does not exist."); - } else { - checkThatExplicitIndexingChainInheritsCorrectly(allChains, indexingChain.getChainSpecification()); - } - } else { - indexingChain = allChains.getComponent(IndexingDocprocChain.NAME); - } - - cluster.setIndexingChain(indexingChain); - } - private static boolean checkParentChain(ComponentRegistry<DocprocChain> allChains, ChainSpecification chainSpec) { if (IndexingDocprocChain.NAME.equals(chainSpec.componentId.stringValue())) { return true; @@ -219,62 +129,6 @@ public class Content extends ConfigModel { containerCluster.getDocprocChains().add(new IndexingDocprocChain()); } - /** Create a new container cluster for indexing and add it to the Vespa model */ - private void createImplicitIndexingCluster(IndexedSearchCluster cluster, - ConfigModelRepoAdder configModelRepoAdder, - ApplicationConfigProducerRoot root) { - String indexerName = cluster.getIndexingClusterName(); - AbstractConfigProducer p = root.getChildren().get(ContainerModel.DOCPROC_RESERVED_NAME); - if (p == null) - p = new SimpleConfigProducer(root, ContainerModel.DOCPROC_RESERVED_NAME); - ConfigModelContext context = ConfigModelContext.createFromParentAndId(configModelRepoAdder, p, ContainerModel.DOCPROC_RESERVED_NAME); - ContainerCluster indexingCluster = new ContainerCluster(context.getParentProducer(), "cluster." + indexerName, indexerName); - ContainerModel indexingClusterModel = new ContainerModel(ConfigModelContext.createFromParentAndId(configModelRepoAdder, p, indexingCluster.getSubId())); - indexingClusterModel.setCluster(indexingCluster); - configModelRepoAdder.add(indexingClusterModel); - ownedIndexingCluster = Optional.of(indexingCluster); - - ContainerModelBuilder.addDefaultHandler_legacyBuilder(indexingCluster); - - addDocproc(indexingCluster); - - List<Container> nodes = new ArrayList<>(); - int index = 0; - Set<HostResource> processedHosts = new LinkedHashSet<>(); - boolean isElastic = cluster.isElastic(); - for (SearchNode searchNode : cluster.getSearchNodes()) { - HostResource host = searchNode.getHostResource(); - if (!processedHosts.contains(host)) { - String containerName = String.valueOf(isElastic ? searchNode.getDistributionKey() : index++); - Container docprocService = new Container(indexingCluster, containerName); - docprocService.setBasePort(host.nextAvailableBaseport(docprocService.getPortCount())); - docprocService.setHostResource(host); - docprocService.initService(); - nodes.add(docprocService); - processedHosts.add(host); - } - } - indexingCluster.addContainers(nodes); - - addIndexingChain(indexingCluster); - cluster.setIndexingChain(indexingCluster.getDocprocChains().allChains().getComponent(IndexingDocprocChain.NAME)); - } - - private void addDocproc(ContainerCluster cluster) { - if (cluster.getDocproc() == null) { - DocprocChains chains = new DocprocChains(cluster, "docprocchains"); - ContainerDocproc containerDocproc = new ContainerDocproc(cluster, chains); - cluster.setDocproc(containerDocproc); - } - } - - private ContainerCluster getContainerWithDocproc(Collection<ContainerModel> containers) { - for (ContainerModel container : containers) - if (container.getCluster().getDocproc() != null) - return container.getCluster(); - return null; - } - private static ContainerCluster getContainerWithSearch(Collection<ContainerModel> containers) { for (ContainerModel container : containers) if (container.getCluster().getSearch() != null) @@ -347,5 +201,156 @@ public class Content extends ConfigModel { AbstractService.distributeCpuSocketAffinity(cluster.getSearch().getSearchNodes()); } + public static class Builder extends ConfigModelBuilder<Content> { + + public static final List<ConfigModelId> configModelIds = Collections.singletonList(ConfigModelId.fromName("content")); + + public Builder() { + super(Content.class); + } + + @Override + public List<ConfigModelId> handlesElements() { + return configModelIds; + } + + @Override + public void doBuild(Content content, Element xml, ConfigModelContext modelContext) { + Admin admin = content.adminModel != null ? content.adminModel.getAdmin() : null; // This is null in tests only + content.cluster = new ContentCluster.Builder(admin, modelContext.getDeployLogger()).build(modelContext.getParentProducer(), xml); + buildIndexingClusters(content, + modelContext.getConfigModelRepoAdder(), + (ApplicationConfigProducerRoot)modelContext.getParentProducer()); + } + + /** Select/creates and initializes the indexing cluster coupled to this */ + private void buildIndexingClusters(Content content, + ConfigModelRepoAdder configModelRepoAdder, + ApplicationConfigProducerRoot root) { + if ( ! content.getCluster().getSearch().hasIndexedCluster()) return; + + IndexedSearchCluster indexedSearchCluster = content.getCluster().getSearch().getIndexed(); + if (indexedSearchCluster.hasExplicitIndexingCluster()) { + setExistingIndexingCluster(indexedSearchCluster, content.containers); + } else if (content.isHosted) { + setContainerAsIndexingCluster(indexedSearchCluster, content, configModelRepoAdder, root); + } else { + createImplicitIndexingCluster(indexedSearchCluster, content, configModelRepoAdder, root); + } + } + + private void setContainerAsIndexingCluster(IndexedSearchCluster indexedSearchCluster, + Content content, + ConfigModelRepoAdder configModelRepoAdder, + ApplicationConfigProducerRoot root) { + if (content.containers.isEmpty()) { + createImplicitIndexingCluster(indexedSearchCluster, content, configModelRepoAdder, root); + } else { + ContainerCluster targetCluster = getContainerWithDocproc(content.containers); + if (targetCluster == null) + targetCluster = getContainerWithSearch(content.containers); + if (targetCluster == null) + targetCluster = content.containers.iterator().next().getCluster(); + + addDocproc(targetCluster); + indexedSearchCluster.setIndexingClusterName(targetCluster.getName()); + addIndexingChainsTo(targetCluster, indexedSearchCluster); + } + } + + private void setExistingIndexingCluster(IndexedSearchCluster cluster, Collection<ContainerModel> containers) { + String indexingClusterName = cluster.getIndexingClusterName(); + ContainerModel containerModel = findByName(indexingClusterName, containers); + if (containerModel == null) + throw new RuntimeException("Content cluster '" + cluster.getClusterName() + "' refers to docproc " + + "cluster '" + indexingClusterName + "', but this cluster does not exist."); + addIndexingChainsTo(containerModel.getCluster(), cluster); + } + + private ContainerModel findByName(String name, Collection<ContainerModel> containers) { + for (ContainerModel container : containers) + if (container.getId().equals(name)) + return container; + return null; + } + + private void addIndexingChainsTo(ContainerCluster indexer, IndexedSearchCluster cluster) { + addIndexingChain(indexer); + DocprocChain indexingChain; + ComponentRegistry<DocprocChain> allChains = indexer.getDocprocChains().allChains(); + if (cluster.hasExplicitIndexingChain()) { + indexingChain = allChains.getComponent(cluster.getIndexingChainName()); + if (indexingChain == null) { + throw new RuntimeException("Indexing cluster " + cluster.getClusterName() + " refers to docproc " + + "chain " + cluster.getIndexingChainName() + " for indexing, which does not exist."); + } else { + checkThatExplicitIndexingChainInheritsCorrectly(allChains, indexingChain.getChainSpecification()); + } + } else { + indexingChain = allChains.getComponent(IndexingDocprocChain.NAME); + } + + cluster.setIndexingChain(indexingChain); + } + + /** Create a new container cluster for indexing and add it to the Vespa model */ + private void createImplicitIndexingCluster(IndexedSearchCluster cluster, + Content content, + ConfigModelRepoAdder configModelRepoAdder, + ApplicationConfigProducerRoot root) { + String indexerName = cluster.getIndexingClusterName(); + AbstractConfigProducer p = root.getChildren().get(ContainerModel.DOCPROC_RESERVED_NAME); + if (p == null) + p = new SimpleConfigProducer(root, ContainerModel.DOCPROC_RESERVED_NAME); + ConfigModelContext context = ConfigModelContext.createFromParentAndId(configModelRepoAdder, p, ContainerModel.DOCPROC_RESERVED_NAME); + ContainerCluster indexingCluster = new ContainerCluster(context.getParentProducer(), "cluster." + indexerName, indexerName); + ContainerModel indexingClusterModel = new ContainerModel(ConfigModelContext.createFromParentAndId(configModelRepoAdder, p, indexingCluster.getSubId())); + indexingClusterModel.setCluster(indexingCluster); + configModelRepoAdder.add(indexingClusterModel); + content.ownedIndexingCluster = Optional.of(indexingCluster); + + ContainerModelBuilder.addDefaultHandler_legacyBuilder(indexingCluster); + + addDocproc(indexingCluster); + + List<Container> nodes = new ArrayList<>(); + int index = 0; + Set<HostResource> processedHosts = new LinkedHashSet<>(); + boolean isElastic = cluster.isElastic(); + for (SearchNode searchNode : cluster.getSearchNodes()) { + HostResource host = searchNode.getHostResource(); + if (!processedHosts.contains(host)) { + String containerName = String.valueOf(isElastic ? searchNode.getDistributionKey() : index++); + Container docprocService = new Container(indexingCluster, containerName); + docprocService.setBasePort(host.nextAvailableBaseport(docprocService.getPortCount())); + docprocService.setHostResource(host); + docprocService.initService(); + nodes.add(docprocService); + processedHosts.add(host); + } + } + indexingCluster.addContainers(nodes); + + addIndexingChain(indexingCluster); + cluster.setIndexingChain(indexingCluster.getDocprocChains().allChains().getComponent(IndexingDocprocChain.NAME)); + } + + private ContainerCluster getContainerWithDocproc(Collection<ContainerModel> containers) { + for (ContainerModel container : containers) + if (container.getCluster().getDocproc() != null) + return container.getCluster(); + return null; + } + + private void addDocproc(ContainerCluster cluster) { + if (cluster.getDocproc() == null) { + DocprocChains chains = new DocprocChains(cluster, "docprocchains"); + ContainerDocproc containerDocproc = new ContainerDocproc(cluster, chains); + cluster.setDocproc(containerDocproc); + } + } + + } + } |