| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.karaf.features.internal.region; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| import org.apache.felix.utils.manifest.Clause; |
| import org.apache.felix.utils.manifest.Parser; |
| import org.apache.felix.utils.version.VersionRange; |
| import org.apache.felix.utils.version.VersionTable; |
| import org.apache.karaf.features.BundleInfo; |
| import org.apache.karaf.features.Conditional; |
| import org.apache.karaf.features.Dependency; |
| import org.apache.karaf.features.Feature; |
| import org.apache.karaf.features.FeaturesNamespaces; |
| import org.apache.karaf.features.FeaturesService; |
| import org.apache.karaf.features.Library; |
| import org.apache.karaf.features.ScopeFilter; |
| import org.apache.karaf.features.internal.download.DownloadCallback; |
| import org.apache.karaf.features.internal.download.DownloadManager; |
| import org.apache.karaf.features.internal.download.Downloader; |
| import org.apache.karaf.features.internal.download.StreamProvider; |
| import org.apache.karaf.features.internal.repository.BaseRepository; |
| import org.apache.karaf.features.internal.resolver.CapabilityImpl; |
| import org.apache.karaf.features.internal.resolver.FeatureResource; |
| import org.apache.karaf.features.internal.resolver.RequirementImpl; |
| import org.apache.karaf.features.internal.resolver.ResolverUtil; |
| import org.apache.karaf.features.internal.resolver.ResourceBuilder; |
| import org.apache.karaf.features.internal.resolver.ResourceImpl; |
| import org.apache.karaf.features.internal.resolver.ResourceUtils; |
| import org.apache.karaf.features.internal.resolver.SimpleFilter; |
| import org.apache.karaf.features.internal.service.Overrides; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Version; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| import org.osgi.resource.Resource; |
| |
| import static java.util.jar.JarFile.MANIFEST_NAME; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEATURE; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement; |
| import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet; |
| import static org.eclipse.equinox.region.RegionFilter.VISIBLE_ALL_NAMESPACE; |
| import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE; |
| import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; |
| import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE; |
| import static org.osgi.resource.Namespace.REQUIREMENT_FILTER_DIRECTIVE; |
| |
| public class Subsystem extends ResourceImpl { |
| |
| private static final String ALL_FILTER = "(|(!(all=*))(all=*))"; |
| |
| private static final String SUBSYSTEM_FILTER = String.format("(%s=%s)", CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM); |
| |
| private static final String FEATURE_FILTER = String.format("(%s=%s)", CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE); |
| |
| private static final String SUBSYSTEM_OR_FEATURE_FILTER = String.format("(|%s%s)", SUBSYSTEM_FILTER, FEATURE_FILTER); |
| |
| // Everything is visible |
| private static final Map<String, Set<String>> SHARE_ALL_POLICY = |
| Collections.singletonMap( |
| VISIBLE_ALL_NAMESPACE, |
| Collections.singleton(ALL_FILTER)); |
| |
| // Nothing (but systems) is visible |
| private static final Map<String, Set<String>> SHARE_NONE_POLICY = |
| Collections.singletonMap( |
| IDENTITY_NAMESPACE, |
| Collections.singleton(SUBSYSTEM_FILTER)); |
| |
| private final String name; |
| private final boolean acceptDependencies; |
| private final Subsystem parent; |
| private final Feature feature; |
| private final boolean mandatory; |
| private final List<Subsystem> children = new ArrayList<>(); |
| private final Map<String, Set<String>> importPolicy; |
| private final Map<String, Set<String>> exportPolicy; |
| private final List<Resource> installable = new ArrayList<>(); |
| private final Map<String, DependencyInfo> dependencies = new HashMap<>(); |
| private final List<Requirement> dependentFeatures = new ArrayList<>(); |
| |
| private final List<String> bundles = new ArrayList<>(); |
| |
| public Subsystem(String name) { |
| super(name, TYPE_SUBSYSTEM, Version.emptyVersion); |
| this.name = name; |
| this.parent = null; |
| this.acceptDependencies = true; |
| this.feature = null; |
| this.importPolicy = SHARE_NONE_POLICY; |
| this.exportPolicy = SHARE_NONE_POLICY; |
| this.mandatory = true; |
| } |
| |
| public Subsystem(String name, Feature feature, Subsystem parent, boolean mandatory) { |
| super(name, TYPE_SUBSYSTEM, Version.emptyVersion); |
| this.name = name; |
| this.parent = parent; |
| this.acceptDependencies = feature.getScoping() != null && feature.getScoping().acceptDependencies(); |
| this.feature = feature; |
| this.mandatory = mandatory; |
| if (feature.getScoping() != null) { |
| this.importPolicy = createPolicy(feature.getScoping().getImports()); |
| this.importPolicy.put(IDENTITY_NAMESPACE, Collections.singleton(SUBSYSTEM_OR_FEATURE_FILTER)); |
| this.exportPolicy = createPolicy(feature.getScoping().getExports()); |
| this.exportPolicy.put(IDENTITY_NAMESPACE, Collections.singleton(SUBSYSTEM_OR_FEATURE_FILTER)); |
| } else { |
| this.importPolicy = SHARE_ALL_POLICY; |
| this.exportPolicy = SHARE_ALL_POLICY; |
| } |
| |
| addIdentityRequirement(this, |
| feature.getName(), |
| TYPE_FEATURE, |
| new VersionRange(VersionTable.getVersion(feature.getVersion()), true)); |
| } |
| |
| public Subsystem(String name, Subsystem parent, boolean acceptDependencies, boolean mandatory) { |
| super(name, TYPE_SUBSYSTEM, Version.emptyVersion); |
| this.name = name; |
| this.parent = parent; |
| this.acceptDependencies = acceptDependencies; |
| this.feature = null; |
| this.mandatory = mandatory; |
| this.importPolicy = SHARE_ALL_POLICY; |
| this.exportPolicy = SHARE_NONE_POLICY; |
| } |
| |
| public List<Resource> getInstallable() { |
| return installable; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public Subsystem getParent() { |
| return parent; |
| } |
| |
| public Collection<Subsystem> getChildren() { |
| return children; |
| } |
| |
| public Subsystem getChild(String name) { |
| for (Subsystem child : children) { |
| if (child.getName().equals(name)) { |
| return child; |
| } |
| } |
| return null; |
| } |
| |
| public boolean isAcceptDependencies() { |
| return acceptDependencies; |
| } |
| |
| public Map<String, Set<String>> getImportPolicy() { |
| return importPolicy; |
| } |
| |
| public Map<String, Set<String>> getExportPolicy() { |
| return exportPolicy; |
| } |
| |
| public Feature getFeature() { |
| return feature; |
| } |
| |
| public Subsystem createSubsystem(String name, boolean acceptDependencies) { |
| if (feature != null) { |
| throw new UnsupportedOperationException("Can not create application subsystems inside a feature subsystem"); |
| } |
| // Create subsystem |
| String childName = getName() + "/" + name; |
| Subsystem as = new Subsystem(childName, this, acceptDependencies, true); |
| children.add(as); |
| // Add a requirement to force its resolution |
| ResourceUtils.addIdentityRequirement(this, childName, TYPE_SUBSYSTEM, (VersionRange) null); |
| // Add it to repo |
| installable.add(as); |
| return as; |
| } |
| |
| public void addSystemResource(Resource resource) { |
| installable.add(resource); |
| } |
| |
| public void requireFeature(String name, String range, boolean mandatory) { |
| if (mandatory) { |
| ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range); |
| } else { |
| ResourceImpl res = new ResourceImpl(); |
| ResourceUtils.addIdentityRequirement(res, name, TYPE_FEATURE, range, false); |
| dependentFeatures.addAll(res.getRequirements(null)); |
| } |
| } |
| |
| public void require(String requirement) throws BundleException { |
| int idx = requirement.indexOf(":"); |
| String type, req; |
| if (idx >= 0) { |
| type = requirement.substring(0, idx); |
| req = requirement.substring(idx + 1); |
| } else { |
| type = "feature"; |
| req = requirement; |
| } |
| switch (type) { |
| case "feature": |
| addRequirement(toFeatureRequirement(req)); |
| break; |
| case "requirement": |
| addRequirement(req); |
| break; |
| case "bundle": |
| bundles.add(req); |
| break; |
| } |
| } |
| |
| protected void addRequirement(String requirement) throws BundleException { |
| for (Requirement req : ResourceBuilder.parseRequirement(this, requirement)) { |
| Object range = req.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE); |
| if (range instanceof String) { |
| req.getAttributes().put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((String) range)); |
| } |
| addRequirement(req); |
| } |
| } |
| |
| public Map<String, BundleInfo> getBundleInfos() { |
| Map<String, BundleInfo> infos = new HashMap<>(); |
| for (DependencyInfo di : dependencies.values()) { |
| infos.put(di.getLocation(), di); |
| } |
| return infos; |
| } |
| |
| @SuppressWarnings("InfiniteLoopStatement") |
| public void build(Collection<Feature> features) throws Exception { |
| doBuild(features, true); |
| } |
| |
| private void doBuild(Collection<Feature> features, boolean mandatory) throws Exception { |
| for (Subsystem child : children) { |
| child.doBuild(features, true); |
| } |
| if (feature != null) { |
| for (Dependency dep : feature.getDependencies()) { |
| Subsystem ss = this; |
| while (!ss.isAcceptDependencies()) { |
| ss = ss.getParent(); |
| } |
| ss.requireFeature(dep.getName(), dep.getVersion(), false); |
| } |
| for (Conditional cond : feature.getConditional()) { |
| Feature fcond = cond.asFeature(); |
| String ssName = this.name + "#" + (fcond.hasVersion() ? fcond.getName() + "-" + fcond.getVersion() : fcond.getName()); |
| Subsystem fs = getChild(ssName); |
| if (fs == null) { |
| fs = new Subsystem(ssName, fcond, this, true); |
| fs.doBuild(features, false); |
| installable.add(fs); |
| children.add(fs); |
| } |
| } |
| } |
| List<Requirement> processed = new ArrayList<>(); |
| while (true) { |
| List<Requirement> requirements = getRequirements(IDENTITY_NAMESPACE); |
| requirements.addAll(dependentFeatures); |
| requirements.removeAll(processed); |
| if (requirements.isEmpty()) { |
| break; |
| } |
| for (Requirement requirement : requirements) { |
| String name = (String) requirement.getAttributes().get(IDENTITY_NAMESPACE); |
| String type = (String) requirement.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE); |
| VersionRange range = (VersionRange) requirement.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE); |
| if (TYPE_FEATURE.equals(type)) { |
| for (Feature feature : features) { |
| if (feature.getName().equals(name) |
| && (range == null || range.contains(VersionTable.getVersion(feature.getVersion())))) { |
| if (feature != this.feature) { |
| String ssName = this.name + "#" + (feature.hasVersion() ? feature.getName() + "-" + feature.getVersion() : feature.getName()); |
| Subsystem fs = getChild(ssName); |
| if (fs == null) { |
| fs = new Subsystem(ssName, feature, this, mandatory && !SubsystemResolveContext.isOptional(requirement)); |
| fs.build(features); |
| installable.add(fs); |
| children.add(fs); |
| } |
| } |
| } |
| } |
| } |
| processed.add(requirement); |
| } |
| } |
| } |
| |
| public Set<String> collectPrerequisites() { |
| Set<String> prereqs = new HashSet<>(); |
| doCollectPrerequisites(prereqs); |
| return prereqs; |
| } |
| |
| private void doCollectPrerequisites(Set<String> prereqs) { |
| for (Subsystem child : children) { |
| child.doCollectPrerequisites(prereqs); |
| } |
| if (feature != null) { |
| for (Dependency dep : feature.getDependencies()) { |
| if (dep.isPrerequisite()) { |
| prereqs.add(dep.toString()); |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("InfiniteLoopStatement") |
| public void downloadBundles(DownloadManager manager, |
| Set<String> overrides, |
| String featureResolutionRange, |
| final String serviceRequirements, RepositoryManager repos) throws Exception { |
| for (Subsystem child : children) { |
| child.downloadBundles(manager, overrides, featureResolutionRange, serviceRequirements, repos); |
| } |
| final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>(); |
| final Downloader downloader = manager.createDownloader(); |
| final Map<BundleInfo, Conditional> infos = new HashMap<>(); |
| if (feature != null) { |
| for (Conditional cond : feature.getConditional()) { |
| for (final BundleInfo bi : cond.getBundles()) { |
| infos.put(bi, cond); |
| } |
| } |
| for (BundleInfo bi : feature.getBundles()) { |
| infos.put(bi, null); |
| } |
| } |
| boolean removeServiceRequirements; |
| if (FeaturesService.SERVICE_REQUIREMENTS_DISABLE.equals(serviceRequirements)) { |
| removeServiceRequirements = true; |
| } else if (feature != null && FeaturesService.SERVICE_REQUIREMENTS_DEFAULT.equals(serviceRequirements)) { |
| removeServiceRequirements = !FeaturesNamespaces.URI_1_3_0.equals(feature.getNamespace()) |
| && !FeaturesNamespaces.URI_1_4_0.equals(feature.getNamespace()); |
| } else { |
| removeServiceRequirements = false; |
| } |
| for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) { |
| final BundleInfo bi = entry.getKey(); |
| final String loc = bi.getLocation(); |
| downloader.download(loc, new DownloadCallback() { |
| @Override |
| public void downloaded(StreamProvider provider) throws Exception { |
| ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements); |
| bundles.put(loc, res); |
| } |
| }); |
| } |
| for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) { |
| final String loc = bundle.getName(); |
| downloader.download(loc, new DownloadCallback() { |
| @Override |
| public void downloaded(StreamProvider provider) throws Exception { |
| ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements); |
| bundles.put(loc, res); |
| } |
| }); |
| } |
| for (String override : overrides) { |
| final String loc = Overrides.extractUrl(override); |
| downloader.download(loc, new DownloadCallback() { |
| @Override |
| public void downloaded(StreamProvider provider) throws Exception { |
| ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements); |
| bundles.put(loc, res); |
| } |
| }); |
| } |
| if (feature != null) { |
| for (Library library : feature.getLibraries()) { |
| if (library.isExport()) { |
| final String loc = library.getLocation(); |
| downloader.download(loc, new DownloadCallback() { |
| @Override |
| public void downloaded(StreamProvider provider) throws Exception { |
| ResourceImpl res = createResource(loc, getMetadata(provider), removeServiceRequirements); |
| bundles.put(loc, res); |
| } |
| }); |
| } |
| } |
| } |
| downloader.await(); |
| Overrides.override(bundles, overrides); |
| if (feature != null) { |
| // Add conditionals |
| Map<Conditional, Resource> resConds = new HashMap<>(); |
| for (Conditional cond : feature.getConditional()) { |
| FeatureResource resCond = FeatureResource.build(feature, cond, featureResolutionRange, bundles); |
| addIdentityRequirement(this, resCond, false); |
| addIdentityRequirement(resCond, this, true); |
| installable.add(resCond); |
| resConds.put(cond, resCond); |
| } |
| // Add features |
| FeatureResource resFeature = FeatureResource.build(feature, featureResolutionRange, bundles); |
| addIdentityRequirement(resFeature, this); |
| installable.add(resFeature); |
| // Add dependencies |
| for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) { |
| final BundleInfo bi = entry.getKey(); |
| final String loc = bi.getLocation(); |
| final Conditional cond = entry.getValue(); |
| ResourceImpl res = bundles.get(loc); |
| int sl = bi.getStartLevel() <= 0 ? feature.getStartLevel() : bi.getStartLevel(); |
| if (bi.isDependency()) { |
| addDependency(res, false, bi.isStart(), sl); |
| } else { |
| doAddDependency(res, cond == null, bi.isStart(), sl); |
| } |
| if (cond != null) { |
| addIdentityRequirement(res, resConds.get(cond), true); |
| } |
| } |
| for (Library library : feature.getLibraries()) { |
| if (library.isExport()) { |
| final String loc = library.getLocation(); |
| ResourceImpl res = bundles.get(loc); |
| addDependency(res, false, false, 0); |
| } |
| } |
| for (String uri : feature.getResourceRepositories()) { |
| BaseRepository repo = repos.getRepository(feature.getRepositoryUrl(), uri); |
| for (Resource resource : repo.getResources()) { |
| ResourceImpl res = cloneResource(resource); |
| addDependency(res, false, true, 0); |
| } |
| } |
| } |
| for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) { |
| final String loc = bundle.getName(); |
| boolean dependency = Boolean.parseBoolean(bundle.getAttribute("dependency")); |
| boolean start = bundle.getAttribute("start") == null || Boolean.parseBoolean(bundle.getAttribute("start")); |
| int startLevel = 0; |
| try { |
| startLevel = Integer.parseInt(bundle.getAttribute("start-level")); |
| } catch (NumberFormatException e) { |
| // Ignore |
| } |
| if (dependency) { |
| addDependency(bundles.get(loc), false, start, startLevel); |
| } else { |
| doAddDependency(bundles.get(loc), true, start, startLevel); |
| addIdentityRequirement(this, bundles.get(loc)); |
| } |
| } |
| // Compute dependencies |
| for (DependencyInfo info : dependencies.values()) { |
| installable.add(info.resource); |
| addIdentityRequirement(info.resource, this, info.mandatory); |
| } |
| } |
| |
| ResourceImpl cloneResource(Resource resource) { |
| ResourceImpl res = new ResourceImpl(); |
| for (Capability cap : resource.getCapabilities(null)) { |
| res.addCapability(new CapabilityImpl(res, cap.getNamespace(), |
| new HashMap<>(cap.getDirectives()), new HashMap<>(cap.getAttributes()))); |
| } |
| for (Requirement req : resource.getRequirements(null)) { |
| SimpleFilter sf; |
| if (req instanceof RequirementImpl) { |
| sf = ((RequirementImpl) req).getFilter(); |
| } else if (req.getDirectives().containsKey(REQUIREMENT_FILTER_DIRECTIVE)) { |
| sf = SimpleFilter.parse(req.getDirectives().get(REQUIREMENT_FILTER_DIRECTIVE)); |
| } else { |
| sf = SimpleFilter.convert(req.getAttributes()); |
| } |
| res.addRequirement(new RequirementImpl(res, req.getNamespace(), |
| new HashMap<>(req.getDirectives()), new HashMap<>(req.getAttributes()), sf)); |
| } |
| return res; |
| } |
| |
| Map<String, String> getMetadata(StreamProvider provider) throws IOException { |
| try ( |
| InputStream is = provider.open(); |
| ) { |
| ZipInputStream zis = new ZipInputStream(is); |
| ZipEntry entry; |
| while ((entry = zis.getNextEntry()) != null) { |
| if (MANIFEST_NAME.equals(entry.getName())) { |
| Attributes attributes = new Manifest(zis).getMainAttributes(); |
| Map<java.lang.String, java.lang.String> headers = new HashMap<java.lang.String, java.lang.String>(); |
| for (Map.Entry attr : attributes.entrySet()) { |
| headers.put(attr.getKey().toString(), attr.getValue().toString()); |
| } |
| return headers; |
| } |
| } |
| } |
| throw new IllegalArgumentException("Resource " + provider.getUrl() + " does not contain a manifest"); |
| } |
| |
| void addDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) { |
| if (isAcceptDependencies()) { |
| doAddDependency(resource, mandatory, start, startLevel); |
| } else { |
| parent.addDependency(resource, mandatory, start, startLevel); |
| } |
| } |
| |
| private void doAddDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) { |
| String id = ResolverUtil.getSymbolicName(resource) + "|" + ResolverUtil.getVersion(resource); |
| DependencyInfo info = dependencies.get(id); |
| if (info == null) { |
| info = new DependencyInfo(); |
| dependencies.put(id, info); |
| } |
| info.resource = resource; |
| info.mandatory |= mandatory; |
| info.start |= start; |
| if (info.startLevel > 0 && startLevel > 0) { |
| info.startLevel = Math.min(info.startLevel, startLevel); |
| } else { |
| info.startLevel = Math.max(info.startLevel, startLevel); |
| } |
| } |
| |
| class DependencyInfo implements BundleInfo { |
| ResourceImpl resource; |
| boolean mandatory; |
| boolean start; |
| int startLevel; |
| |
| @Override |
| public boolean isStart() { |
| return start; |
| } |
| |
| @Override |
| public int getStartLevel() { |
| return startLevel; |
| } |
| |
| @Override |
| public String getLocation() { |
| return getUri(resource); |
| } |
| |
| @Override |
| public boolean isDependency() { |
| return !mandatory; |
| } |
| } |
| |
| Map<String, Set<String>> createPolicy(List<? extends ScopeFilter> filters) { |
| Map<String, Set<String>> policy = new HashMap<>(); |
| for (ScopeFilter filter : filters) { |
| addToMapSet(policy, filter.getNamespace(), filter.getFilter()); |
| } |
| return policy; |
| } |
| |
| ResourceImpl createResource(String uri, Map<String, String> headers, boolean removeServiceRequirements) throws Exception { |
| try { |
| return ResourceBuilder.build(uri, headers, removeServiceRequirements); |
| } catch (BundleException e) { |
| throw new Exception("Unable to create resource for bundle " + uri, e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getName(); |
| } |
| } |