diff options
9 files changed, 442 insertions, 17 deletions
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/actions/Key.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/actions/Key.java index fb43a425..1ee655f4 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/actions/Key.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/actions/Key.java @@ -3,6 +3,8 @@ * org.onap.aaf * =========================================================================== * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * + * Modifications Copyright (C) 2019 IBM. * =========================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +23,7 @@ package org.onap.aaf.auth.batch.actions; +@FunctionalInterface public interface Key<HELPER> { public String key(HELPER H); } diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Expiring.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Expiring.java index d8eee6d5..7ed26ce5 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Expiring.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Expiring.java @@ -3,6 +3,8 @@ * org.onap.aaf * =========================================================================== * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * + * Modifications Copyright (C) 2019 IBM. * =========================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,20 +121,17 @@ public class Expiring extends Batch { Map<String, Set<UserRole>> owners = new TreeMap<String, Set<UserRole>>(); trans.info().log("Process UserRoles"); - UserRole.load(trans, session, UserRole.v2_0_11, new Visitor<UserRole>() { - @Override - public void visit(UserRole ur) { - // Cannot just delete owners, unless there is at least one left. Process later - if ("owner".equals(ur.rname())) { - Set<UserRole> urs = owners.get(ur.role()); - if (urs == null) { - urs = new HashSet<UserRole>(); - owners.put(ur.role(), urs); - } - urs.add(ur); - } else { - writeAnalysis(trans,ur); + UserRole.load(trans, session, UserRole.v2_0_11, ur -> { + // Cannot just delete owners, unless there is at least one left. Process later + if ("owner".equals(ur.rname())) { + Set<UserRole> urs = owners.get(ur.role()); + if (urs == null) { + urs = new HashSet<UserRole>(); + owners.put(ur.role(), urs); } + urs.add(ur); + } else { + writeAnalysis(trans,ur); } }); diff --git a/auth/auth-certman/pom.xml b/auth/auth-certman/pom.xml index cd20bd02..76f26222 100644 --- a/auth/auth-certman/pom.xml +++ b/auth/auth-certman/pom.xml @@ -49,6 +49,12 @@ </properties> <dependencies> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4-rule-agent</artifactId> + <version>1.6.4</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.onap.aaf.authz</groupId> <artifactId>aaf-auth-core</artifactId> diff --git a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/AAF_CM.java b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/AAF_CM.java index 689326e1..bfdb977d 100644 --- a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/AAF_CM.java +++ b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/AAF_CM.java @@ -80,7 +80,12 @@ public class AAF_CM extends AbsService<AuthzEnv, AuthzTrans> { public final Cluster cluster; public final LocateDAO locateDAO; public static AuthzEnv envLog; + CMService service; + //Added for junits + public CMService getService() { + return null; + } /** * Construct AuthzAPI with all the Context Supporting Routes that Authz needs * @@ -117,7 +122,6 @@ public class AAF_CM extends AbsService<AuthzEnv, AuthzTrans> { if (key.startsWith(CA.CM_CA_PREFIX)) { int idx = key.indexOf('.'); if (idx==key.lastIndexOf('.')) { // else it's a regular property - env.log(Level.INIT, "Loading Certificate Authority Module: " + key.substring(idx+1)); String[] segs = Split.split(',', env.getProperty(key)); if (segs.length>0) { @@ -145,7 +149,10 @@ public class AAF_CM extends AbsService<AuthzEnv, AuthzTrans> { throw new APIException("No Certificate Authorities have been configured in CertMan"); } - CMService service = new CMService(trans, this); + service = getService(); + if(service == null) { + service = new CMService(trans, this); + } // note: Service knows how to shutdown Cluster on Shutdown, etc. See Constructor facade1_0 = FacadeFactory.v1_0(this,trans, service,Data.TYPE.JSON); // Default Facade facade1_0_XML = FacadeFactory.v1_0(this,trans,service,Data.TYPE.XML); @@ -172,6 +179,7 @@ public class AAF_CM extends AbsService<AuthzEnv, AuthzTrans> { public CA getCA(String key) { return certAuths.get(key); } + /** * Setup XML and JSON implementations for each supported Version type diff --git a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/LocalCAImpl.java b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/LocalCAImpl.java new file mode 100644 index 00000000..632e719e --- /dev/null +++ b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/LocalCAImpl.java @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed 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. + * ============LICENSE_END==================================================== + * + */ +package org.onap.aaf.auth.cm; + +import java.io.IOException; + +import org.onap.aaf.auth.cm.ca.CA; +import org.onap.aaf.auth.cm.ca.X509andChain; +import org.onap.aaf.auth.cm.cert.CSRMeta; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.configure.CertException; +import org.onap.aaf.misc.env.Trans; + +public class LocalCAImpl extends CA { + + protected LocalCAImpl(Access access, String caName, String env) throws IOException, CertException { + super(access, caName, env); + // TODO Auto-generated constructor stub + } + + public LocalCAImpl(Access access, final String name, final String env, final String[][] params) throws IOException, CertException { + super(access, name, env); + } + + @Override + public X509andChain sign(Trans trans, CSRMeta csrmeta) throws IOException, CertException { + // TODO Auto-generated method stub + return null; + } +}
\ No newline at end of file diff --git a/auth/auth-certman/src/test/java/org/onap/aaf/auth/cm/JU_AAF_CM.java b/auth/auth-certman/src/test/java/org/onap/aaf/auth/cm/JU_AAF_CM.java new file mode 100644 index 00000000..e770fec7 --- /dev/null +++ b/auth/auth-certman/src/test/java/org/onap/aaf/auth/cm/JU_AAF_CM.java @@ -0,0 +1,226 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed 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. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.cm; + +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import javax.servlet.Filter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.onap.aaf.auth.cm.ca.CA; +import org.onap.aaf.auth.cm.facade.Facade1_0; +import org.onap.aaf.auth.cm.facade.FacadeFactory; +import org.onap.aaf.auth.cm.mapper.Mapper.API; +import org.onap.aaf.auth.cm.service.CMService; +import org.onap.aaf.auth.cm.service.Code; +import org.onap.aaf.auth.env.AuthzEnv; +import org.onap.aaf.auth.env.AuthzTransImpl; +import org.onap.aaf.auth.rserv.HttpMethods; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.aaf.v2_0.AAFConHttp; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.misc.env.Data; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.impl.BasicEnv; +import org.onap.aaf.misc.rosetta.env.RosettaDF; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(FacadeFactory.class) +public class JU_AAF_CM { + + @Mock + AuthzEnv env; + + BasicEnv baseEnv; + + @Mock + PropAccess access; + + AuthzTransImpl1 trans; + + AAF_CMImpl rosettaObj = null; + + @Before + public void setUp() { + initMocks(this); + + try { + Mockito.doReturn(access).when(env).access(); + Mockito.doReturn("test.test").when(access).getProperty(Config.AAF_ROOT_NS,"org.osaaf.aaf"); + Properties props=new Properties(); + Mockito.doReturn(props).when(access).getProperties(); + props.setProperty("cm_ca.props", "test"); + Mockito.doReturn("test:2.1").when(access).getProperty(Config.AAF_COMPONENT, null); + Mockito.doReturn("test").when(access).getProperty("https.protocols","TLSv1.1,TLSv1.2"); + Mockito.doReturn("test").when(env).getProperty("cm_ca.props.perm_type",null); + Mockito.doReturn("test").when(env).getProperty("cm_ca.props.baseSubject",null); + Mockito.doReturn("10").when(env).getProperty("CACHE_CLEAN_INTERVAL","60000"); + Mockito.doReturn("10").when(env).getProperty("CACHE_HIGH_COUNT","5000"); + trans = new AuthzTransImpl1(env); + Mockito.doReturn(trans).when(env).newTrans(); +// Mockito.doReturn("test").when(trans).getProperty("cm_ca.props.baseSubject",null); +// Mockito.doReturn(Mockito.mock(TimeTaken.class)).when(trans).start("Clear Reset Deque",8); + + Mockito.doReturn("TLSv1.1").when(access).getProperty("cadi_protocols","test"); + Mockito.doReturn("https://www.google.com").when(access).getProperty(Config.AAF_URL,null); + Mockito.doReturn("test").when(env).getProperty(Config.AAF_ENV); + Mockito.doReturn("10").when(env).getProperty(Config.CADI_LATITUDE); + Mockito.doReturn("10").when(env).getProperty(Config.CADI_LONGITUDE); + Mockito.doReturn("org.onap.aaf.auth.cm.LocalCAImpl,test;test").when(env).getProperty("cm_ca.props"); + Mockito.doReturn("google.com").when(env).getProperty("cassandra.clusters",null); +// Mockito.doReturn(Mockito.mock(AuthzTransImpl.class)).when(env).newTrans(); + Mockito.doReturn(Mockito.mock(LogTarget.class)).when(env).init(); + AAF_CM tempObj = Mockito.mock(AAF_CM.class); + Field envField = tempObj.getClass().getField("env"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(envField, envField.getModifiers() & ~Modifier.FINAL); + envField.setAccessible(true); + envField.set(tempObj, env); + RosettaDF rosettaObjTemp = Mockito.mock(RosettaDF.class); + Mockito.doReturn(rosettaObjTemp).when(rosettaObjTemp).in(Data.TYPE.JSON); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(aaf.v2_0.Error.class); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(certman.v1_0.CertificateRequest.class); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(certman.v1_0.CertificateRenew.class); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(certman.v1_0.CertificateDrop.class); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(certman.v1_0.CertInfo.class); + Mockito.doReturn(rosettaObjTemp).when(env).newDataFactory(certman.v1_0.Artifacts.class); + Mockito.doReturn(Data.TYPE.XML).when(rosettaObjTemp).getOutType(); + + Facade1_0 facadeObj = Mockito.mock(Facade1_0.class); + PowerMockito.mockStatic(FacadeFactory.class); + FacadeFactory factObj = PowerMockito.mock(FacadeFactory.class); + PowerMockito.when(factObj.v1_0(tempObj,trans, null,Data.TYPE.JSON)).thenReturn(facadeObj); + +// Mockito.doReturn(Mockito.mock(Mapper.class)).when(facadeObj).mapper(); + + + rosettaObj = new AAF_CMImpl(env); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + assertTrue(e instanceof NullPointerException); + } + } + + @Test + public void testTestCA() { + CA obj = rosettaObj.getCA("props"); + assertTrue(obj instanceof CA); + } + +// @Test +// public void testRoute() { +// try { +// rosettaObj.route(null, "", null, null); +// } catch (Exception e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +//// System.out.println(obj); +//// assertTrue(obj instanceof CA); +// } + + @Test + public void testFilters() { + try { + Filter[] obj = rosettaObj._filters(new Object[] {"props"}); + System.out.println(obj); + } catch (CadiException | LocatorException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + assertTrue(e.getMessage().contains("NoSuchAlgorithmException")); + } +// assertTrue(obj instanceof CA); + } + + class AAF_CMImpl extends AAF_CM{ + + public AAF_CMImpl(AuthzEnv env) throws Exception { + super(env); + // TODO Auto-generated constructor stub + } + + @Override + public synchronized AAFConHttp aafCon() throws CadiException, LocatorException { + return Mockito.mock(AAFConHttp.class); + } + + public CMService getService() { + return Mockito.mock(CMService.class); + } + + @Override + public void route(HttpMethods meth, String path, API api, Code code) throws Exception { + + } + } + + + + class AuthzTransImpl1 extends AuthzTransImpl{ + + public AuthzTransImpl1(AuthzEnv env) { + super(env); + // TODO Auto-generated constructor stub + } + + @Override + protected TimeTaken newTimeTaken(String name, int flag) { + // TODO Auto-generated method stub + TimeTaken tt= new TimeTaken("nameTest", Env.XML) { + + @Override + public void output(StringBuilder sb) { + // TODO Auto-generated method stub + + } + }; + return tt; + } + + @Override + public Metric auditTrail(int indent, StringBuilder sb, int ... flag) { + return null; + } + + } + + +} diff --git a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/perm/Grant.java b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/perm/Grant.java index dd45fb4b..ca958c20 100644 --- a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/perm/Grant.java +++ b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/perm/Grant.java @@ -3,6 +3,8 @@ * org.onap.aaf * =========================================================================== * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * + * Modifications Copyright (C) 2018 IBM. * =========================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +43,7 @@ import aaf.v2_0.RolePermRequest; * */ public class Grant extends Cmd { - private final static String[] options = {"grant","ungrant","setTo"}; + private static final String[] options = {"grant","ungrant","setTo"}; public Grant(Perm parent) { super(parent,null, @@ -74,7 +76,8 @@ public class Grant extends Cmd { if (option != 2) { String[] roles = args[idx++].split(","); - String strA,strB; + String strA; + String strB; for (String role : roles) { rpr.setRole(role); if (option==0) { diff --git a/auth/auth-core/src/test/java/org/onap/aaf/auth/log4j/test/JU_Log4jAccessAppender.java b/auth/auth-core/src/test/java/org/onap/aaf/auth/log4j/test/JU_Log4jAccessAppender.java new file mode 100644 index 00000000..2c5d86b4 --- /dev/null +++ b/auth/auth-core/src/test/java/org/onap/aaf/auth/log4j/test/JU_Log4jAccessAppender.java @@ -0,0 +1,112 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed 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. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.log4j.test; + +import static org.junit.Assert.assertFalse; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.Date; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.onap.aaf.auth.log4j.Log4JAccessAppender; +import org.onap.aaf.cadi.Access; + +public class JU_Log4jAccessAppender { + + @Mock + Access access; + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void testRequiresLayout() { + Log4JAccessAppender log4jObj = new Log4JAccessAppender(access); + boolean retObj = log4jObj.requiresLayout(); + assertFalse(retObj); + } + + @Test + public void testClose() { + Log4JAccessAppender log4jObj = new Log4JAccessAppender(access); + log4jObj.close(); + + } + + @Test + public void testAppend() { + Log4jAccessAppenderImpl log4jObj = new Log4jAccessAppenderImpl(access); + LoggingEvent event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.FATAL,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + Mockito.doReturn(true).when(access).willLog(Access.Level.ERROR); + event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.ERROR,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.ALL,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + } + + @Test + public void testAppendWARN() { + Log4jAccessAppenderImpl log4jObj = new Log4jAccessAppenderImpl(access); + Mockito.doReturn(false).when(access).willLog(Access.Level.WARN); + LoggingEvent event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.WARN,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + } + + @Test + public void testAppendINFO() { + Log4jAccessAppenderImpl log4jObj = new Log4jAccessAppenderImpl(access); + Mockito.doReturn(true).when(access).willLog(Access.Level.INFO); + LoggingEvent event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.INFO,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + } + + @Test + public void testAppendWTrace() { + Log4jAccessAppenderImpl log4jObj = new Log4jAccessAppenderImpl(access); + Mockito.doReturn(false).when(access).willLog(Access.Level.TRACE); + LoggingEvent event=new LoggingEvent("com.chililog.server.engine",Logger.getLogger(Log4JAccessAppender.class),(new Date()).getTime(),Level.TRACE,"test",Thread.currentThread().getName(),null,null,null,null); + log4jObj.append(event); + } + + class Log4jAccessAppenderImpl extends Log4JAccessAppender{ + + public Log4jAccessAppenderImpl(Access access) { + super(access); + // TODO Auto-generated constructor stub + } + + @Override + protected void append(LoggingEvent event) { + super.append(event); + } + + } +} diff --git a/cadi/servlet-sample/pom.xml b/cadi/servlet-sample/pom.xml index c7922440..ec73e2f3 100644 --- a/cadi/servlet-sample/pom.xml +++ b/cadi/servlet-sample/pom.xml @@ -10,6 +10,26 @@ <name>CADI Servlet Sample (Test Only)</name> <artifactId>aaf-cadi-servlet-sample</artifactId> <packaging>jar</packaging> + + <properties> + <!-- SONAR --> + <sonar.skip>true</sonar.skip> + <jacoco.version>0.7.7.201606060606</jacoco.version> + <sonar-jacoco-listeners.version>3.2</sonar-jacoco-listeners.version> + <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin> + <!-- Default Sonar configuration --> + <sonar.jacoco.reportPaths>target/code-coverage/jacoco-ut.exec</sonar.jacoco.reportPaths> + <sonar.jacoco.itReportPaths>target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPaths> + <!-- Note: This list should match jacoco-maven-plugin's exclusion list + below --> + <sonar.exclusions>**/gen/**,**/generated-sources/**,**/yang-gen**,**/pax/**</sonar.exclusions> + <nexusproxy>https://nexus.onap.org</nexusproxy> + <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> + <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> + <stagingNexusPath>/content/repositories/staging/</stagingNexusPath> + <sitePath>/content/sites/site/org/onap/aaf/authz/${project.artifactId}/${project.version}</sitePath> + <project.bouncyCastleVersion>1.60</project.bouncyCastleVersion> + </properties> <dependencies> <!-- needs to be first to avoid jar signer implications for servlet api --> <dependency> |