diff --git a/CHANGES.txt b/CHANGES.txt index ccb1b10e..d92cbb38 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -58,6 +58,7 @@ for detailed view of each issue, please consult http://issues.apache.org/jira/br - IMPROVE: New "modules in use" section in console report at the end of resolve (IVY-373) (thanks to John Wiliams) - IMPROVE: Generated XML reports now contains more information about the resolved module (IVY-446) +- FIX: Invalid error report with m2compatible resolver (IVY-456) - FIX: IvyPostResolve Task doesn't use specified cache for the resolve (IVY-453) - FIX: XmlModuleDescriptorWriterTest not working with Java 6 (IVY-374) - FIX: Conflict managers ignored, when assigned to modules in Ivy configuration (setting, ivyconf.xml) (IVY-448) diff --git a/src/java/org/apache/ivy/core/IvyContext.java b/src/java/org/apache/ivy/core/IvyContext.java index 980e37b1..759ac0e6 100644 --- a/src/java/org/apache/ivy/core/IvyContext.java +++ b/src/java/org/apache/ivy/core/IvyContext.java @@ -21,12 +21,14 @@ import java.io.File; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; +import java.util.Stack; import org.apache.ivy.Ivy; import org.apache.ivy.core.cache.CacheManager; import org.apache.ivy.core.event.EventManager; import org.apache.ivy.core.settings.IvySettings; import org.apache.ivy.plugins.circular.CircularDependencyStrategy; +import org.apache.ivy.plugins.resolver.DependencyResolver; import org.apache.ivy.util.MessageImpl; @@ -49,11 +51,13 @@ public class IvyContext { private WeakReference _ivy = new WeakReference(null); private File _cache; private MessageImpl _messageImpl; + private Stack _resolver = new Stack(); // Stack(DependencyResolver) private Map _contextMap = new HashMap(); private Thread _operatingThread; + public static IvyContext getContext() { IvyContext cur = (IvyContext)_current.get(); @@ -154,6 +158,18 @@ public class IvyContext { public void checkInterrupted() { getIvy().checkInterrupted(); } + + public DependencyResolver getResolver() { + return (DependencyResolver) _resolver.peek(); + } + + public void pushResolver(DependencyResolver resolver) { + _resolver.push(resolver); + } + + public void popResolver() { + _resolver.pop(); + } // should be better to use context to store this kind of information, but not yet ready to do so... // private WeakReference _root = new WeakReference(null); diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractResourceResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractResourceResolver.java index a7f5b247..6167fc67 100644 --- a/src/java/org/apache/ivy/plugins/resolver/AbstractResourceResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/AbstractResourceResolver.java @@ -116,15 +116,18 @@ public abstract class AbstractResourceResolver extends BasicResolver { Date date) { ResolvedResource found = null; List sorted = strategy.sort(rress); + List rejected = new ArrayList(); for (ListIterator iter = sorted.listIterator(sorted.size()); iter.hasPrevious();) { ResolvedResource rres = (ResolvedResource) iter.previous(); if ((date != null && rres.getLastModified() > date.getTime())) { - Message.debug("\t"+name+": too young: "+rres); + Message.verbose("\t"+name+": too young: "+rres); + rejected.add(rres.getRevision()+" ("+rres.getLastModified()+")"); continue; } ModuleRevisionId foundMrid = ModuleRevisionId.newInstance(mrid, rres.getRevision()); if (!versionMatcher.accept(mrid, foundMrid)) { Message.debug("\t"+name+": rejected by version matcher: "+rres); + rejected.add(rres.getRevision()); continue; } if (versionMatcher.needModuleDescriptor(mrid, foundMrid)) { @@ -132,9 +135,11 @@ public abstract class AbstractResourceResolver extends BasicResolver { ModuleDescriptor md = ((MDResolvedResource)r).getResolvedModuleRevision().getDescriptor(); if (md.isDefault()) { Message.debug("\t"+name+": default md rejected by version matcher requiring module descriptor: "+rres); + rejected.add(rres.getRevision()+" (MD)"); continue; } else if (!versionMatcher.accept(mrid, md)) { Message.debug("\t"+name+": md rejected by version matcher: "+rres); + rejected.add(rres.getRevision()+" (MD)"); continue; } else { found = r; @@ -146,73 +151,77 @@ public abstract class AbstractResourceResolver extends BasicResolver { if (found != null) { if (!found.getResource().exists()) { Message.debug("\t"+name+": resource not reachable for "+mrid+": res="+found.getResource()); + logAttempt(found.getResource().toString()); continue; } break; } } + if (found == null && !rejected.isEmpty()) { + logAttempt(rejected.toString()); + } return found; } - /** - * Output message to log indicating what have been done to look for an artifact which - * has finally not been found - * - * @param artifact the artifact which has not been found - */ - protected void logIvyNotFound(ModuleRevisionId mrid) { - if (isM2compatible()) { - mrid = convertM2IdForResourceSearch(mrid); - } - Artifact artifact = DefaultArtifact.newIvyArtifact(mrid, null); - logMdNotFound(mrid, artifact); - } +// /** +// * Output message to log indicating what have been done to look for an artifact which +// * has finally not been found +// * +// * @param artifact the artifact which has not been found +// */ +// protected void logIvyNotFound(ModuleRevisionId mrid) { +// if (isM2compatible()) { +// mrid = convertM2IdForResourceSearch(mrid); +// } +// Artifact artifact = DefaultArtifact.newIvyArtifact(mrid, null); +// logMdNotFound(mrid, artifact); +// } +// +// protected void logMdNotFound(ModuleRevisionId mrid, Artifact artifact) { +// String revisionToken = mrid.getRevision().startsWith("latest.")?"[any "+mrid.getRevision().substring("latest.".length())+"]":"["+mrid.getRevision()+"]"; +// Artifact latestArtifact = new DefaultArtifact(ModuleRevisionId.newInstance(mrid, revisionToken), null, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttributes()); +// if (_ivyPatterns.isEmpty()) { +// logIvyAttempt("no ivy pattern => no attempt to find module descriptor file for "+mrid); +// } else { +// for (Iterator iter = _ivyPatterns.iterator(); iter.hasNext();) { +// String pattern = (String)iter.next(); +// String resolvedFileName = IvyPatternHelper.substitute(pattern, artifact); +// logIvyAttempt(resolvedFileName); +// if (getSettings().getVersionMatcher().isDynamic(mrid)) { +// resolvedFileName = IvyPatternHelper.substitute(pattern, latestArtifact); +// logIvyAttempt(resolvedFileName); +// } +// } +// } +// } - protected void logMdNotFound(ModuleRevisionId mrid, Artifact artifact) { - String revisionToken = mrid.getRevision().startsWith("latest.")?"[any "+mrid.getRevision().substring("latest.".length())+"]":"["+mrid.getRevision()+"]"; - Artifact latestArtifact = new DefaultArtifact(ModuleRevisionId.newInstance(mrid, revisionToken), null, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttributes()); - if (_ivyPatterns.isEmpty()) { - logIvyAttempt("no ivy pattern => no attempt to find module descriptor file for "+mrid); - } else { - for (Iterator iter = _ivyPatterns.iterator(); iter.hasNext();) { - String pattern = (String)iter.next(); - String resolvedFileName = IvyPatternHelper.substitute(pattern, artifact); - logIvyAttempt(resolvedFileName); - if (getSettings().getVersionMatcher().isDynamic(mrid)) { - resolvedFileName = IvyPatternHelper.substitute(pattern, latestArtifact); - logIvyAttempt(resolvedFileName); - } - } - } - } - - /** - * Output message to log indicating what have been done to look for an artifact which - * has finally not been found - * - * @param artifact the artifact which has not been found - */ - protected void logArtifactNotFound(Artifact artifact) { - if (_artifactPatterns.isEmpty()) { - if (artifact.getUrl() == null) { - logArtifactAttempt(artifact, "no artifact pattern => no attempt to find artifact "+artifact); - } - } - Artifact used = artifact; - if (isM2compatible()) { - used = DefaultArtifact.cloneWithAnotherMrid(artifact, convertM2IdForResourceSearch(artifact.getModuleRevisionId())); - } - - for (Iterator iter = _artifactPatterns.iterator(); iter.hasNext();) { - String pattern = (String)iter.next(); - String resolvedFileName = IvyPatternHelper.substitute(pattern, used); - logArtifactAttempt(artifact, resolvedFileName); - } - if (used.getUrl() != null) { - logArtifactAttempt(artifact, used.getUrl().toString()); - } - } +// /** +// * Output message to log indicating what have been done to look for an artifact which +// * has finally not been found +// * +// * @param artifact the artifact which has not been found +// */ +// protected void logArtifactNotFound(Artifact artifact) { +// if (_artifactPatterns.isEmpty()) { +// if (artifact.getUrl() == null) { +// logArtifactAttempt(artifact, "no artifact pattern => no attempt to find artifact "+artifact); +// } +// } +// Artifact used = artifact; +// if (isM2compatible()) { +// used = DefaultArtifact.cloneWithAnotherMrid(artifact, convertM2IdForResourceSearch(artifact.getModuleRevisionId())); +// } +// +// for (Iterator iter = _artifactPatterns.iterator(); iter.hasNext();) { +// String pattern = (String)iter.next(); +// String resolvedFileName = IvyPatternHelper.substitute(pattern, used); +// logArtifactAttempt(artifact, resolvedFileName); +// } +// if (used.getUrl() != null) { +// logArtifactAttempt(artifact, used.getUrl().toString()); +// } +// } protected Collection findNames(Map tokenValues, String token) { Collection names = new HashSet(); diff --git a/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java b/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java index d1f28e34..d5b4b212 100644 --- a/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/BasicResolver.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import org.apache.ivy.core.IvyContext; import org.apache.ivy.core.IvyPatternHelper; import org.apache.ivy.core.cache.ArtifactOrigin; import org.apache.ivy.core.cache.CacheManager; @@ -99,7 +100,7 @@ public abstract class BasicResolver extends AbstractResolver { private String _checksums = null; private URLRepository _extartifactrep = new URLRepository(); // used only to download external artifacts - + public BasicResolver() { _workspaceName = HostUtil.getLocalHostName(); } @@ -143,202 +144,204 @@ public abstract class BasicResolver extends AbstractResolver { } public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException { - DependencyDescriptor systemDd = dd; - dd = fromSystem(dd); - - clearIvyAttempts(); - boolean downloaded = false; - boolean searched = false; - ModuleRevisionId mrid = dd.getDependencyRevisionId(); - // check revision - int index = mrid.getRevision().indexOf("@"); - if (index != -1 && !mrid.getRevision().substring(index+1).equals(_workspaceName)) { - Message.verbose("\t"+getName()+": unhandled revision => "+mrid.getRevision()); - return null; - } - - boolean isDynamic = getSettings().getVersionMatcher().isDynamic(mrid); - if (isDynamic && !acceptLatest()) { - Message.error("dynamic revisions not handled by "+getClass().getName()+". impossible to resolve "+mrid); - return null; - } - - boolean isChangingRevision = getChangingMatcher().matches(mrid.getRevision()); - boolean isChangingDependency = isChangingRevision || dd.isChanging(); + IvyContext.getContext().pushResolver(this); + try { + DependencyDescriptor systemDd = dd; + dd = fromSystem(dd); - // if we do not have to check modified and if the revision is exact and not changing, - // we first search for it in cache - ResolvedModuleRevision cachedRmr = null; - boolean checkedCache = false; - if (!isDynamic && !isCheckmodified() && !isChangingDependency) { - cachedRmr = findModuleInCache(data, mrid); - checkedCache = true; - if (cachedRmr != null) { - if (cachedRmr.getDescriptor().isDefault() && cachedRmr.getResolver() != this) { - Message.verbose("\t"+getName()+": found revision in cache: "+mrid+" (resolved by "+cachedRmr.getResolver().getName()+"): but it's a default one, maybe we can find a better one"); - } else { - Message.verbose("\t"+getName()+": revision in cache: "+mrid); - return toSystem(cachedRmr); - } - } - } - checkInterrupted(); - URL cachedIvyURL = null; - ResolvedResource ivyRef = findIvyFileRef(dd, data); - checkInterrupted(); - searched = true; - - // get module descriptor - ModuleDescriptorParser parser; - ModuleDescriptor md; - ModuleDescriptor systemMd = null; - if (ivyRef == null) { - if (!isAllownomd()) { - Message.verbose("\t"+getName()+": no ivy file found for "+mrid); - logIvyNotFound(mrid); - return null; - } - parser = XmlModuleDescriptorParser.getInstance(); - md = DefaultModuleDescriptor.newDefaultInstance(mrid, dd.getAllDependencyArtifactsIncludes()); - ResolvedResource artifactRef = findFirstArtifactRef(md, dd, data); - checkInterrupted(); - if (artifactRef == null) { - Message.verbose("\t"+getName()+": no ivy file nor artifact found for "+mrid); - logIvyNotFound(mrid); - String[] conf = md.getConfigurationsNames(); - for (int i = 0; i < conf.length; i++) { - Artifact[] artifacts = md.getArtifacts(conf[i]); - for (int j = 0; j < artifacts.length; j++) { - logArtifactNotFound(artifacts[j]); - } - } - if (!checkedCache) { - cachedRmr = findModuleInCache(data, mrid); - } - if (cachedRmr != null) { - Message.verbose("\t"+getName()+": revision in cache: "+mrid); - return toSystem(cachedRmr); - } - return null; - } else { - long lastModified = artifactRef.getLastModified(); - if (lastModified != 0 && md instanceof DefaultModuleDescriptor) { - ((DefaultModuleDescriptor) md).setLastModified(lastModified); - } - Message.verbose("\t"+getName()+": no ivy file found for "+mrid+": using default data"); - logIvyNotFound(mrid); - if (isDynamic) { - md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(mrid, artifactRef.getRevision())); - } - } - } else { - ResolvedModuleRevision rmr = null; - if (ivyRef instanceof MDResolvedResource) { - rmr = ((MDResolvedResource)ivyRef).getResolvedModuleRevision(); - } - if (rmr == null) { - rmr = parse(ivyRef, dd, data); - if (rmr == null) { - return null; - } - } - if (!rmr.isDownloaded()) { - return toSystem(rmr); - } else { - md = rmr.getDescriptor(); - parser = ModuleDescriptorParserRegistry.getInstance().getParser(ivyRef.getResource()); - cachedIvyURL = rmr.getLocalMDUrl(); - - // check descriptor data is in sync with resource revision and names - systemMd = toSystem(md); - if (_checkconsistency) { - checkDescriptorConsistency(mrid, md, ivyRef); - checkDescriptorConsistency(systemDd.getDependencyRevisionId(), systemMd, ivyRef); - } else { - if (md instanceof DefaultModuleDescriptor) { - String revision = getRevision(ivyRef, mrid, md); - ((DefaultModuleDescriptor)md).setModuleRevisionId(ModuleRevisionId.newInstance(mrid, revision)); - } else { - Message.warn("consistency disabled with instance of non DefaultModuleDescriptor... module info can't be updated, so consistency check will be done"); - checkDescriptorConsistency(mrid, md, ivyRef); - checkDescriptorConsistency(systemDd.getDependencyRevisionId(), systemMd, ivyRef); - } - } - } - } - - if (systemMd == null) { - systemMd = toSystem(md); - } - - // resolve revision - ModuleRevisionId resolvedMrid = mrid; - if (isDynamic) { - resolvedMrid = md.getResolvedModuleRevisionId(); - if (resolvedMrid.getRevision() == null || resolvedMrid.getRevision().length() == 0) { - if (ivyRef.getRevision() == null || ivyRef.getRevision().length() == 0) { - resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, "working@"+getName()); - } else { - resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, ivyRef.getRevision()); - } - } - Message.verbose("\t\t["+resolvedMrid.getRevision()+"] "+mrid.getModuleId()); - } - md.setResolvedModuleRevisionId(resolvedMrid); - systemMd.setResolvedModuleRevisionId(toSystem(resolvedMrid)); // keep system md in sync with md + clearIvyAttempts(); + clearArtifactAttempts(); + boolean downloaded = false; + boolean searched = false; + ModuleRevisionId mrid = dd.getDependencyRevisionId(); + // check revision + int index = mrid.getRevision().indexOf("@"); + if (index != -1 && !mrid.getRevision().substring(index+1).equals(_workspaceName)) { + Message.verbose("\t"+getName()+": unhandled revision => "+mrid.getRevision()); + return null; + } - // check module descriptor revision - if (!getSettings().getVersionMatcher().accept(mrid, md)) { - Message.info("\t"+getName()+": unacceptable revision => was="+md.getModuleRevisionId().getRevision()+" required="+mrid.getRevision()); - return null; - } - - - // resolve and check publication date - if (data.getDate() != null) { - long pubDate = getPublicationDate(md, dd, data); - if (pubDate > data.getDate().getTime()) { - Message.info("\t"+getName()+": unacceptable publication date => was="+new Date(pubDate)+" required="+data.getDate()); - return null; - } else if (pubDate == -1) { - Message.info("\t"+getName()+": impossible to guess publication date: artifact missing for "+mrid); - return null; - } - md.setResolvedPublicationDate(new Date(pubDate)); - systemMd.setResolvedPublicationDate(new Date(pubDate)); // keep system md in sync with md - } - - try { - File ivyFile = data.getCacheManager().getIvyFileInCache(systemMd.getResolvedModuleRevisionId()); - if (ivyRef == null) { - // a basic ivy file is written containing default data - XmlModuleDescriptorWriter.write(systemMd, ivyFile); - } else { - if (md instanceof DefaultModuleDescriptor) { - DefaultModuleDescriptor dmd = (DefaultModuleDescriptor)md; - if (data.getSettings().logNotConvertedExclusionRule() && dmd.isNamespaceUseful()) { - Message.warn("the module descriptor "+ivyRef.getResource()+" has information which can't be converted into the system namespace. It will require the availability of the namespace '"+getNamespace().getName()+"' to be fully usable."); - } - } - // copy and update ivy file from source to cache - parser.toIvyFile(cachedIvyURL.openStream(), ivyRef.getResource(), ivyFile, systemMd); - long repLastModified = ivyRef.getLastModified(); - if (repLastModified > 0) { - ivyFile.setLastModified(repLastModified); - } - } - } catch (Exception e) { - if (ivyRef == null) { - Message.warn("impossible to create ivy file in cache for module : " + resolvedMrid); - } else { - e.printStackTrace(); - Message.warn("impossible to copy ivy file to cache : "+ivyRef.getResource()); - } - } - - data.getCacheManager().saveResolver(systemMd, getName()); - data.getCacheManager().saveArtResolver(systemMd, getName()); - return new DefaultModuleRevision(this, this, systemMd, searched, downloaded, cachedIvyURL); + boolean isDynamic = getSettings().getVersionMatcher().isDynamic(mrid); + if (isDynamic && !acceptLatest()) { + Message.error("dynamic revisions not handled by "+getClass().getName()+". impossible to resolve "+mrid); + return null; + } + + boolean isChangingRevision = getChangingMatcher().matches(mrid.getRevision()); + boolean isChangingDependency = isChangingRevision || dd.isChanging(); + + // if we do not have to check modified and if the revision is exact and not changing, + // we first search for it in cache + ResolvedModuleRevision cachedRmr = null; + boolean checkedCache = false; + if (!isDynamic && !isCheckmodified() && !isChangingDependency) { + cachedRmr = findModuleInCache(data, mrid); + checkedCache = true; + if (cachedRmr != null) { + if (cachedRmr.getDescriptor().isDefault() && cachedRmr.getResolver() != this) { + Message.verbose("\t"+getName()+": found revision in cache: "+mrid+" (resolved by "+cachedRmr.getResolver().getName()+"): but it's a default one, maybe we can find a better one"); + } else { + Message.verbose("\t"+getName()+": revision in cache: "+mrid); + return toSystem(cachedRmr); + } + } + } + checkInterrupted(); + URL cachedIvyURL = null; + ResolvedResource ivyRef = findIvyFileRef(dd, data); + checkInterrupted(); + searched = true; + + // get module descriptor + ModuleDescriptorParser parser; + ModuleDescriptor md; + ModuleDescriptor systemMd = null; + if (ivyRef == null) { + if (!isAllownomd()) { + Message.verbose("\t"+getName()+": no ivy file found for "+mrid); + return null; + } + parser = XmlModuleDescriptorParser.getInstance(); + md = DefaultModuleDescriptor.newDefaultInstance(mrid, dd.getAllDependencyArtifactsIncludes()); + ResolvedResource artifactRef = findFirstArtifactRef(md, dd, data); + checkInterrupted(); + if (artifactRef == null) { + Message.verbose("\t"+getName()+": no ivy file nor artifact found for "+mrid); + String[] conf = md.getConfigurationsNames(); + for (int i = 0; i < conf.length; i++) { + Artifact[] artifacts = md.getArtifacts(conf[i]); + for (int j = 0; j < artifacts.length; j++) { + } + } + if (!checkedCache) { + cachedRmr = findModuleInCache(data, mrid); + } + if (cachedRmr != null) { + Message.verbose("\t"+getName()+": revision in cache: "+mrid); + return toSystem(cachedRmr); + } + return null; + } else { + long lastModified = artifactRef.getLastModified(); + if (lastModified != 0 && md instanceof DefaultModuleDescriptor) { + ((DefaultModuleDescriptor) md).setLastModified(lastModified); + } + Message.verbose("\t"+getName()+": no ivy file found for "+mrid+": using default data"); + if (isDynamic) { + md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(mrid, artifactRef.getRevision())); + } + } + } else { + ResolvedModuleRevision rmr = null; + if (ivyRef instanceof MDResolvedResource) { + rmr = ((MDResolvedResource)ivyRef).getResolvedModuleRevision(); + } + if (rmr == null) { + rmr = parse(ivyRef, dd, data); + if (rmr == null) { + return null; + } + } + if (!rmr.isDownloaded()) { + return toSystem(rmr); + } else { + md = rmr.getDescriptor(); + parser = ModuleDescriptorParserRegistry.getInstance().getParser(ivyRef.getResource()); + cachedIvyURL = rmr.getLocalMDUrl(); + + // check descriptor data is in sync with resource revision and names + systemMd = toSystem(md); + if (_checkconsistency) { + checkDescriptorConsistency(mrid, md, ivyRef); + checkDescriptorConsistency(systemDd.getDependencyRevisionId(), systemMd, ivyRef); + } else { + if (md instanceof DefaultModuleDescriptor) { + String revision = getRevision(ivyRef, mrid, md); + ((DefaultModuleDescriptor)md).setModuleRevisionId(ModuleRevisionId.newInstance(mrid, revision)); + } else { + Message.warn("consistency disabled with instance of non DefaultModuleDescriptor... module info can't be updated, so consistency check will be done"); + checkDescriptorConsistency(mrid, md, ivyRef); + checkDescriptorConsistency(systemDd.getDependencyRevisionId(), systemMd, ivyRef); + } + } + } + } + + if (systemMd == null) { + systemMd = toSystem(md); + } + + // resolve revision + ModuleRevisionId resolvedMrid = mrid; + if (isDynamic) { + resolvedMrid = md.getResolvedModuleRevisionId(); + if (resolvedMrid.getRevision() == null || resolvedMrid.getRevision().length() == 0) { + if (ivyRef.getRevision() == null || ivyRef.getRevision().length() == 0) { + resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, "working@"+getName()); + } else { + resolvedMrid = ModuleRevisionId.newInstance(resolvedMrid, ivyRef.getRevision()); + } + } + Message.verbose("\t\t["+resolvedMrid.getRevision()+"] "+mrid.getModuleId()); + } + md.setResolvedModuleRevisionId(resolvedMrid); + systemMd.setResolvedModuleRevisionId(toSystem(resolvedMrid)); // keep system md in sync with md + + // check module descriptor revision + if (!getSettings().getVersionMatcher().accept(mrid, md)) { + Message.info("\t"+getName()+": unacceptable revision => was="+md.getModuleRevisionId().getRevision()+" required="+mrid.getRevision()); + return null; + } + + + // resolve and check publication date + if (data.getDate() != null) { + long pubDate = getPublicationDate(md, dd, data); + if (pubDate > data.getDate().getTime()) { + Message.info("\t"+getName()+": unacceptable publication date => was="+new Date(pubDate)+" required="+data.getDate()); + return null; + } else if (pubDate == -1) { + Message.info("\t"+getName()+": impossible to guess publication date: artifact missing for "+mrid); + return null; + } + md.setResolvedPublicationDate(new Date(pubDate)); + systemMd.setResolvedPublicationDate(new Date(pubDate)); // keep system md in sync with md + } + + try { + File ivyFile = data.getCacheManager().getIvyFileInCache(systemMd.getResolvedModuleRevisionId()); + if (ivyRef == null) { + // a basic ivy file is written containing default data + XmlModuleDescriptorWriter.write(systemMd, ivyFile); + } else { + if (md instanceof DefaultModuleDescriptor) { + DefaultModuleDescriptor dmd = (DefaultModuleDescriptor)md; + if (data.getSettings().logNotConvertedExclusionRule() && dmd.isNamespaceUseful()) { + Message.warn("the module descriptor "+ivyRef.getResource()+" has information which can't be converted into the system namespace. It will require the availability of the namespace '"+getNamespace().getName()+"' to be fully usable."); + } + } + // copy and update ivy file from source to cache + parser.toIvyFile(cachedIvyURL.openStream(), ivyRef.getResource(), ivyFile, systemMd); + long repLastModified = ivyRef.getLastModified(); + if (repLastModified > 0) { + ivyFile.setLastModified(repLastModified); + } + } + } catch (Exception e) { + if (ivyRef == null) { + Message.warn("impossible to create ivy file in cache for module : " + resolvedMrid); + } else { + e.printStackTrace(); + Message.warn("impossible to copy ivy file to cache : "+ivyRef.getResource()); + } + } + + data.getCacheManager().saveResolver(systemMd, getName()); + data.getCacheManager().saveArtResolver(systemMd, getName()); + return new DefaultModuleRevision(this, this, systemMd, searched, downloaded, cachedIvyURL); + } finally { + IvyContext.getContext().popResolver(); + } } private String getRevision(ResolvedResource ivyRef, ModuleRevisionId askedMrid, ModuleDescriptor md) throws ParseException { @@ -605,30 +608,44 @@ public abstract class BasicResolver extends AbstractResolver { Message.verbose("\t\ttried "+attempt); } + protected static void logAttempt(String attempt) { + DependencyResolver resolver = IvyContext.getContext().getResolver(); + if (resolver instanceof BasicResolver) { + Artifact currentArtifact = (Artifact) IvyContext.getContext().get(resolver.getName()+".artifact"); + if (currentArtifact != null) { + ((BasicResolver) resolver).logArtifactAttempt(currentArtifact, attempt); + } else { + ((BasicResolver) resolver).logIvyAttempt(attempt); + } + } + } + public void reportFailure() { + Message.warn("==== "+getName()+": tried"); for (ListIterator iter = _ivyattempts.listIterator(); iter.hasNext();) { String m = (String)iter.next(); - Message.warn("\t\t"+getName()+": tried "+m); + Message.warn(" "+m); } for (Iterator iter = _artattempts.keySet().iterator(); iter.hasNext();) { Artifact art = (Artifact)iter.next(); List attempts = (List)_artattempts.get(art); if (attempts != null) { - Message.warn("\t\t"+getName()+": tried artifact "+art+":"); + Message.warn(" -- artifact "+art+":"); for (ListIterator iterator = attempts.listIterator(); iterator.hasNext();) { String m = (String)iterator.next(); - Message.warn("\t\t\t"+m); + Message.warn(" "+m); } } } } public void reportFailure(Artifact art) { + Message.warn("==== "+getName()+": tried"); List attempts = (List)_artattempts.get(art); if (attempts != null) { for (ListIterator iter = attempts.listIterator(); iter.hasNext();) { String m = (String)iter.next(); - Message.warn("\t\t"+getName()+": tried "+m); + Message.warn(" "+m); } } } @@ -638,107 +655,111 @@ public abstract class BasicResolver extends AbstractResolver { } public DownloadReport download(Artifact[] artifacts, DownloadOptions options) { - CacheManager cacheManager = options.getCacheManager(); - EventManager eventManager = options.getEventManager(); - - boolean useOrigin = options.isUseOrigin(); - - clearArtifactAttempts(); - DownloadReport dr = new DownloadReport(); - for (int i = 0; i < artifacts.length; i++) { - final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]); - dr.addArtifactReport(adr); - if (eventManager != null) { - eventManager.fireIvyEvent(new NeedArtifactEvent(this, artifacts[i])); - } - ArtifactOrigin origin = cacheManager.getSavedArtifactOrigin(artifacts[i]); - // if we can use origin file, we just ask ivy for the file in cache, and it will return - // the original one if possible. If we are not in useOrigin mode, we use the getArchivePath - // method which always return a path in the actual cache - File archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin, options.isUseOrigin()); - - if (archiveFile.exists()) { - Message.verbose("\t[NOT REQUIRED] "+artifacts[i]); - adr.setDownloadStatus(DownloadStatus.NO); - adr.setSize(archiveFile.length()); - adr.setArtifactOrigin(origin); - } else { - Artifact artifact = fromSystem(artifacts[i]); - if (!artifact.equals(artifacts[i])) { - Message.verbose("\t"+getName()+"looking for artifact "+artifact+ " (is "+artifacts[i]+" in system namespace)"); - } - long start = System.currentTimeMillis(); - try { - ResolvedResource artifactRef = getArtifactRef(artifact, null); - if (artifactRef != null) { - origin = new ArtifactOrigin(artifactRef.getResource().isLocal(), artifactRef.getResource().getName()); - if (useOrigin && artifactRef.getResource().isLocal()) { - Message.verbose("\t[NOT REQUIRED] "+artifacts[i]); - cacheManager.saveArtifactOrigin(artifacts[i], origin); - archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin); - adr.setDownloadStatus(DownloadStatus.NO); - adr.setSize(archiveFile.length()); - adr.setArtifactOrigin(origin); - } else { - // refresh archive file now that we better now its origin - archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin, useOrigin); - if (ResourceHelper.equals(artifactRef.getResource(), - archiveFile)) { - Message.error("invalid configuration for resolver '"+getName()+"': pointing artifacts to ivy cache is forbidden !"); - return null; - } - Message.info("downloading "+artifactRef.getResource()+" ..."); - if (eventManager != null) { - eventManager.fireIvyEvent(new StartArtifactDownloadEvent(this, artifacts[i], origin)); - } - - File tmp = cacheManager.getArchiveFileInCache( - new DefaultArtifact( - artifacts[i].getModuleRevisionId(), - artifacts[i].getPublicationDate(), - artifacts[i].getName(), - artifacts[i].getType(), - artifacts[i].getExt()+".part", - artifacts[i].getExtraAttributes()), - origin, useOrigin); + IvyContext.getContext().pushResolver(this); + try { + CacheManager cacheManager = options.getCacheManager(); + EventManager eventManager = options.getEventManager(); - // deal with artifact with url special case - if (artifactRef.getResource().getName().equals(String.valueOf(artifacts[i].getUrl()))) { - Message.verbose("\t"+getName()+": downloading "+artifactRef.getResource().getName()); - Message.debug("\t\tto "+tmp); - if (tmp.getParentFile() != null) { - tmp.getParentFile().mkdirs(); - } - _extartifactrep.get(artifactRef.getResource().getName(), tmp); - adr.setSize(tmp.length()); - } else { - adr.setSize(getAndCheck(artifactRef.getResource(), tmp)); - } - if (!tmp.renameTo(archiveFile)) { - Message.warn("\t[FAILED ] "+artifacts[i]+" impossible to move temp file to definitive one ("+(System.currentTimeMillis()-start)+"ms)"); - adr.setDownloadStatus(DownloadStatus.FAILED); - } else { - cacheManager.saveArtifactOrigin(artifacts[i], origin); - Message.info("\t[SUCCESSFUL ] "+artifacts[i]+" ("+(System.currentTimeMillis()-start)+"ms)"); - adr.setDownloadStatus(DownloadStatus.SUCCESSFUL); - adr.setArtifactOrigin(origin); - } - } - } else { - logArtifactNotFound(artifacts[i]); - adr.setDownloadStatus(DownloadStatus.FAILED); - } - } catch (Exception ex) { - Message.warn("\t[FAILED ] "+artifacts[i]+" : "+ex.getMessage()+" ("+(System.currentTimeMillis()-start)+"ms)"); - adr.setDownloadStatus(DownloadStatus.FAILED); - } - checkInterrupted(); - } - if (eventManager != null) { - eventManager.fireIvyEvent(new EndArtifactDownloadEvent(this, artifacts[i], adr, archiveFile)); - } - } - return dr; + boolean useOrigin = options.isUseOrigin(); + + clearArtifactAttempts(); + DownloadReport dr = new DownloadReport(); + for (int i = 0; i < artifacts.length; i++) { + final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]); + dr.addArtifactReport(adr); + if (eventManager != null) { + eventManager.fireIvyEvent(new NeedArtifactEvent(this, artifacts[i])); + } + ArtifactOrigin origin = cacheManager.getSavedArtifactOrigin(artifacts[i]); + // if we can use origin file, we just ask ivy for the file in cache, and it will return + // the original one if possible. If we are not in useOrigin mode, we use the getArchivePath + // method which always return a path in the actual cache + File archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin, options.isUseOrigin()); + + if (archiveFile.exists()) { + Message.verbose("\t[NOT REQUIRED] "+artifacts[i]); + adr.setDownloadStatus(DownloadStatus.NO); + adr.setSize(archiveFile.length()); + adr.setArtifactOrigin(origin); + } else { + Artifact artifact = fromSystem(artifacts[i]); + if (!artifact.equals(artifacts[i])) { + Message.verbose("\t"+getName()+"looking for artifact "+artifact+ " (is "+artifacts[i]+" in system namespace)"); + } + long start = System.currentTimeMillis(); + try { + ResolvedResource artifactRef = getArtifactRef(artifact, null); + if (artifactRef != null) { + origin = new ArtifactOrigin(artifactRef.getResource().isLocal(), artifactRef.getResource().getName()); + if (useOrigin && artifactRef.getResource().isLocal()) { + Message.verbose("\t[NOT REQUIRED] "+artifacts[i]); + cacheManager.saveArtifactOrigin(artifacts[i], origin); + archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin); + adr.setDownloadStatus(DownloadStatus.NO); + adr.setSize(archiveFile.length()); + adr.setArtifactOrigin(origin); + } else { + // refresh archive file now that we better now its origin + archiveFile = cacheManager.getArchiveFileInCache(artifacts[i], origin, useOrigin); + if (ResourceHelper.equals(artifactRef.getResource(), + archiveFile)) { + Message.error("invalid configuration for resolver '"+getName()+"': pointing artifacts to ivy cache is forbidden !"); + return null; + } + Message.info("downloading "+artifactRef.getResource()+" ..."); + if (eventManager != null) { + eventManager.fireIvyEvent(new StartArtifactDownloadEvent(this, artifacts[i], origin)); + } + + File tmp = cacheManager.getArchiveFileInCache( + new DefaultArtifact( + artifacts[i].getModuleRevisionId(), + artifacts[i].getPublicationDate(), + artifacts[i].getName(), + artifacts[i].getType(), + artifacts[i].getExt()+".part", + artifacts[i].getExtraAttributes()), + origin, useOrigin); + + // deal with artifact with url special case + if (artifactRef.getResource().getName().equals(String.valueOf(artifacts[i].getUrl()))) { + Message.verbose("\t"+getName()+": downloading "+artifactRef.getResource().getName()); + Message.debug("\t\tto "+tmp); + if (tmp.getParentFile() != null) { + tmp.getParentFile().mkdirs(); + } + _extartifactrep.get(artifactRef.getResource().getName(), tmp); + adr.setSize(tmp.length()); + } else { + adr.setSize(getAndCheck(artifactRef.getResource(), tmp)); + } + if (!tmp.renameTo(archiveFile)) { + Message.warn("\t[FAILED ] "+artifacts[i]+" impossible to move temp file to definitive one ("+(System.currentTimeMillis()-start)+"ms)"); + adr.setDownloadStatus(DownloadStatus.FAILED); + } else { + cacheManager.saveArtifactOrigin(artifacts[i], origin); + Message.info("\t[SUCCESSFUL ] "+artifacts[i]+" ("+(System.currentTimeMillis()-start)+"ms)"); + adr.setDownloadStatus(DownloadStatus.SUCCESSFUL); + adr.setArtifactOrigin(origin); + } + } + } else { + adr.setDownloadStatus(DownloadStatus.FAILED); + } + } catch (Exception ex) { + Message.warn("\t[FAILED ] "+artifacts[i]+" : "+ex.getMessage()+" ("+(System.currentTimeMillis()-start)+"ms)"); + adr.setDownloadStatus(DownloadStatus.FAILED); + } + checkInterrupted(); + } + if (eventManager != null) { + eventManager.fireIvyEvent(new EndArtifactDownloadEvent(this, artifacts[i], adr, archiveFile)); + } + } + return dr; + } finally { + IvyContext.getContext().popResolver(); + } } protected void clearArtifactAttempts() { @@ -874,23 +895,25 @@ public abstract class BasicResolver extends AbstractResolver { protected ResolvedResource getArtifactRef(Artifact artifact, Date date) { - ResolvedResource ret = findArtifactRef(artifact, date); - if (ret == null && artifact.getUrl() != null) { - URL url = artifact.getUrl(); - Message.verbose("\tusing url for "+artifact+": "+url); - ret = new ResolvedResource(new URLResource(url), artifact.getModuleRevisionId().getRevision()); - } - return ret; + IvyContext.getContext().set(getName()+".artifact", artifact); + try { + ResolvedResource ret = findArtifactRef(artifact, date); + if (ret == null && artifact.getUrl() != null) { + URL url = artifact.getUrl(); + Message.verbose("\tusing url for "+artifact+": "+url); + logArtifactAttempt(artifact, url.toExternalForm()); + ret = new ResolvedResource(new URLResource(url), artifact.getModuleRevisionId().getRevision()); + } + return ret; + } finally { + IvyContext.getContext().set(getName()+".artifact", null); + } } protected abstract ResolvedResource findArtifactRef(Artifact artifact, Date date); protected abstract long get(Resource resource, File dest) throws IOException; - protected abstract void logIvyNotFound(ModuleRevisionId mrid); - - protected abstract void logArtifactNotFound(Artifact artifact); - public boolean isCheckconsistency() { return _checkconsistency; } diff --git a/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java b/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java index 00eebcb3..e8467f4c 100644 --- a/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/CacheResolver.java @@ -21,8 +21,10 @@ import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.Collections; +import java.util.Date; import org.apache.ivy.core.module.descriptor.Artifact; +import org.apache.ivy.core.module.descriptor.DefaultArtifact; import org.apache.ivy.core.module.descriptor.DependencyDescriptor; import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.report.ArtifactDownloadReport; @@ -66,6 +68,7 @@ public class CacheResolver extends FileSystemResolver { Message.verbose("\t"+getName()+": revision in cache: "+mrid); return rmr; } else { + logIvyAttempt(data.getCacheManager().getArchiveFileInCache(DefaultArtifact.newIvyArtifact(mrid, new Date())).getAbsolutePath()); Message.verbose("\t"+getName()+": no ivy file in cache found for "+mrid); return null; } @@ -110,7 +113,7 @@ public class CacheResolver extends FileSystemResolver { adr.setDownloadStatus(DownloadStatus.NO); adr.setSize(archiveFile.length()); } else { - logArtifactNotFound(artifacts[i]); + logArtifactAttempt(artifacts[i], archiveFile.getAbsolutePath()); adr.setDownloadStatus(DownloadStatus.FAILED); } } diff --git a/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java b/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java index 9ac71836..f826d936 100644 --- a/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/IBiblioResolver.java @@ -68,13 +68,6 @@ public class IBiblioResolver extends URLResolver { return null; } } - - protected void logIvyNotFound(ModuleRevisionId mrid) { - if (isM2compatible() && isUsepoms()) { - Artifact artifact = DefaultArtifact.newPomArtifact(mrid, null); - logMdNotFound(mrid, artifact); - } - } public void setM2compatible(boolean m2compatible) { super.setM2compatible(m2compatible); diff --git a/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java b/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java index a744ac2a..cc6c841f 100644 --- a/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/RepositoryResolver.java @@ -88,6 +88,7 @@ public class RepositoryResolver extends AbstractResourceResolver { if (!versionMatcher.isDynamic(mrid) || alwaysCheckExactRevision) { String resourceName = IvyPatternHelper.substitute(pattern, mrid, artifact); Message.debug("\t trying "+resourceName); + logAttempt(resourceName); Resource res = repository.getResource(resourceName); boolean reachable = res.exists(); if (reachable) { @@ -117,6 +118,11 @@ public class RepositoryResolver extends AbstractResourceResolver { String pattern, Artifact artifact, Date date) { + logAttempt(IvyPatternHelper.substitute(pattern, + ModuleRevisionId.newInstance( + mrid, + IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY)), + artifact)); ResolvedResource[] rress = ResolverHelper.findAll(repository, mrid, pattern, artifact); if (rress == null) { Message.debug("\t"+name+": unable to list resources for "+mrid+": pattern="+pattern); diff --git a/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java b/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java index 6c4bba09..4a1478ae 100644 --- a/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java +++ b/src/java/org/apache/ivy/plugins/resolver/util/ResolverHelper.java @@ -85,7 +85,8 @@ public class ResolverHelper { return null; } } catch (Exception e) { - Message.warn("problem while listing resources in "+root+" with "+rep+": "+e.getClass()+" "+e.getMessage()); + Message.warn("problem while listing resources in "+root+" with "+rep+":"); + Message.warn(" "+e.getClass().getName()+" "+e.getMessage()); return null; } } @@ -113,7 +114,8 @@ public class ResolverHelper { return null; } } catch (Exception e) { - Message.warn("problem while listing resources in "+parent+" with "+rep+": "+e.getClass()+" "+e.getMessage()); + Message.warn("problem while listing resources in "+parent+" with "+rep+":"); + Message.warn(" "+e.getClass().getName()+" "+e.getMessage()); return null; } } diff --git a/test/java/org/apache/ivy/plugins/resolver/IBiblioResolverTest.java b/test/java/org/apache/ivy/plugins/resolver/IBiblioResolverTest.java index 1a107211..d685e190 100644 --- a/test/java/org/apache/ivy/plugins/resolver/IBiblioResolverTest.java +++ b/test/java/org/apache/ivy/plugins/resolver/IBiblioResolverTest.java @@ -18,6 +18,7 @@ package org.apache.ivy.plugins.resolver; import java.io.File; +import java.text.ParseException; import java.util.List; import junit.framework.TestCase; @@ -40,6 +41,8 @@ import org.apache.ivy.core.resolve.ResolvedModuleRevision; import org.apache.ivy.core.settings.IvySettings; import org.apache.ivy.core.sort.SortEngine; import org.apache.ivy.plugins.matcher.ExactPatternMatcher; +import org.apache.ivy.util.Message; +import org.apache.ivy.util.MockMessageImpl; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Delete; @@ -177,6 +180,25 @@ public class IBiblioResolverTest extends TestCase { assertEquals(artifact, ar.getArtifact()); assertEquals(DownloadStatus.NO, ar.getDownloadStatus()); } + + public void testErrorReport() throws Exception { + IBiblioResolver resolver = new IBiblioResolver(); + resolver.setRoot("http://unknown.host.comx/"); + resolver.setName("test"); + resolver.setM2compatible(true); + resolver.setSettings(_settings); + assertEquals("test", resolver.getName()); + + MockMessageImpl mockMessageImpl = new MockMessageImpl(); + Message.setImpl(mockMessageImpl); + + ModuleRevisionId mrid = ModuleRevisionId.newInstance("org.apache", "commons-fileupload", "1.0"); + ResolvedModuleRevision rmr = resolver.getDependency(new DefaultDependencyDescriptor(mrid, false), _data); + assertNull(rmr); + + mockMessageImpl.assertLogContains("tried http://unknown.host.comx/org/apache/commons-fileupload/1.0/commons-fileupload-1.0.pom"); + mockMessageImpl.assertLogContains("tried http://unknown.host.comx/org/apache/commons-fileupload/1.0/commons-fileupload-1.0.jar"); + } public void testIBiblioArtifacts() throws Exception { String ibiblioRoot = IBiblioHelper.getIBiblioMirror(); diff --git a/test/java/org/apache/ivy/util/MockMessageImpl.java b/test/java/org/apache/ivy/util/MockMessageImpl.java new file mode 100644 index 00000000..6f74bc96 --- /dev/null +++ b/test/java/org/apache/ivy/util/MockMessageImpl.java @@ -0,0 +1,92 @@ +/* + * 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.ivy.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import junit.framework.AssertionFailedError; + +public class MockMessageImpl implements MessageImpl { + + private List _endProgress = new ArrayList(); + private List _logs = new ArrayList(); + private List _rawLogs = new ArrayList(); + private int _progressCalls; + + public void endProgress(String msg) { + _endProgress .add(msg); + } + + public void log(String msg, int level) { + _logs.add(level+" "+msg); + } + + public void progress() { + _progressCalls++; + } + + public void rawlog(String msg, int level) { + _rawLogs.add(level+" "+msg); + } + + public List getEndProgress() { + return _endProgress; + } + + public List getLogs() { + return _logs; + } + + public int getProgressCalls() { + return _progressCalls; + } + + public List getRawLogs() { + return _rawLogs; + } + + public void clear() { + _logs.clear(); + _rawLogs.clear(); + _endProgress.clear(); + _progressCalls = 0; + } + + public void assertLogContains(String message) { + for (Iterator iter = _logs.iterator(); iter.hasNext();) { + String log = (String) iter.next(); + if (log.indexOf(message) != -1) { + return; + } + } + throw new AssertionFailedError("logs do not contain expected message: expected='"+message+"' logs='"+join(_logs)+"'" ); + + } + + private String join(List logs) { + StringBuffer sb = new StringBuffer(); + for (Iterator iter = logs.iterator(); iter.hasNext();) { + String log = (String) iter.next(); + sb.append(log).append("\n"); + } + return sb.toString(); + } + +}