1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.rice.vc.test;
18
19 import com.predic8.schema.ComplexType;
20 import com.predic8.schema.Sequence;
21 import com.predic8.soamodel.Difference;
22 import com.predic8.wsdl.Definitions;
23 import com.predic8.wsdl.Operation;
24 import com.predic8.wsdl.PortType;
25 import com.predic8.wsdl.WSDLParser;
26 import com.predic8.wsdl.diff.WsdlDiffGenerator;
27 import org.apache.commons.lang.StringUtils;
28 import org.apache.log4j.Logger;
29 import org.codehaus.jackson.JsonNode;
30 import org.codehaus.jackson.map.ObjectMapper;
31 import org.kuali.rice.core.api.config.property.Config;
32 import org.kuali.rice.core.api.config.property.ConfigContext;
33 import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
34 import org.kuali.rice.core.api.lifecycle.Lifecycle;
35 import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader;
36 import org.kuali.rice.test.BaselineTestCase;
37
38 import javax.xml.namespace.QName;
39 import java.io.BufferedReader;
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStreamReader;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.Iterator;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.regex.Pattern;
53
54 import static org.junit.Assert.assertTrue;
55 import static org.junit.Assert.fail;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.ROLLBACK)
81 public abstract class WsdlCompareTestCase extends BaselineTestCase {
82 private static final Logger LOG = Logger.getLogger(WsdlCompareTestCase.class);
83 private static final String WSDL_URL = "wsdl.test.previous.url";
84 private static final String WSDL_PREVIOUS_VERSION = "wsdl.test.previous.version";
85 private static final String LINE_SEPARATOR = System.getProperty("line.separator");
86
87 private String previousVersion;
88
89 private static final List<String> ignoreBreakageRegexps = Arrays.asList(
90 ".*Position of any null changed.$",
91 ".*Position of element null changed.$"
92 );
93
94 public WsdlCompareTestCase(String moduleName) {
95 super(moduleName);
96 }
97
98 protected List<String> verifyWsdlDifferences(Difference diff, String level) {
99 List<String> results = new ArrayList<String>();
100
101 if (diff.isBreaks()) {
102 boolean ignore = false;
103 for (String ignoreBreakageRegexp : ignoreBreakageRegexps) {
104 if (diff.getDescription().matches(ignoreBreakageRegexp)) {
105 ignore = true;
106 break;
107 }
108 }
109
110 if (ignore) {
111 LOG.info(level + "non-breaking change" + diff.getDescription());
112 } else {
113 LOG.error(level + "breaking change: " + diff.getType() + diff.getDescription());
114 results.add(level + diff.getDescription());
115 }
116 }
117
118
119
120 String opBreakageString = checkForOperationBasedChanges(diff);
121 if (opBreakageString != null) {
122 results.add(level + opBreakageString);
123 }
124
125 for (Difference moreDiff : diff.getDiffs()) {
126 List<String> childBreakages = verifyWsdlDifferences(moreDiff, level + " ");
127 for (String childBreakage : childBreakages) {
128 if (!diff.getDescription().trim().startsWith("Schema ")) {
129 results.add(level + diff.getDescription() + LINE_SEPARATOR + childBreakage);
130 } else {
131 results.add(childBreakage);
132 }
133 }
134 }
135
136 return results;
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150 private String checkForOperationBasedChanges(Difference diff) {
151 if ("sequence".equals(diff.getType())
152 && diff.getA() != null
153 && diff.getB() != null) {
154 Sequence oldSequence = (Sequence)diff.getA();
155 Sequence newSequence = (Sequence)diff.getB();
156 if (newSequence.getParent() instanceof ComplexType) {
157 ComplexType parent = (ComplexType)newSequence.getParent();
158 String serviceName = newSequence.getSchema().getDefinitions().getName();
159 PortType portType = newSequence.getSchema().getDefinitions().getPortType(serviceName);
160 if (portType != null) {
161 Operation operation = portType.getOperation(parent.getName());
162
163 if (operation != null) {
164 return "Element cannot be added to a sequence if sequence is an Operation " +
165 diff.getDescription();
166 }
167
168
169 }
170 }
171 }
172 return null;
173 }
174
175 protected List<Difference> compareWsdlDefinitions(String oldWsdl, String newWsdl) {
176 WSDLParser parser = new WSDLParser();
177
178 Definitions wsdl1;
179 Definitions wsdl2;
180 try {
181 wsdl1 = parser.parse(oldWsdl);
182 } catch (com.predic8.xml.util.ResourceDownloadException e) {
183 LOG.error("COULDN'T PARSE " + oldWsdl);
184 return Collections.emptyList();
185 }
186 try {
187 wsdl2 = parser.parse(newWsdl);
188 } catch (com.predic8.xml.util.ResourceDownloadException e) {
189 LOG.error("COULDN'T PARSE " + newWsdl);
190 return Collections.emptyList();
191 }
192
193 WsdlDiffGenerator diffGen = new WsdlDiffGenerator(wsdl1, wsdl2);
194 return diffGen.compare();
195 }
196
197 protected String getPreviousVersionWsdlUrl(String wsdlFile, MavenVersion previousVersion) {
198
199 StringBuilder oldWsdl = new StringBuilder(buildWsdlUrlPrefix(previousVersion.getOriginalForm()));
200 oldWsdl.append("rice-");
201 oldWsdl.append(getModuleName());
202 oldWsdl.append("-api-");
203 oldWsdl.append(previousVersion.getOriginalForm());
204 oldWsdl.append("-");
205 oldWsdl.append(wsdlFile);
206
207 return oldWsdl.toString();
208 }
209
210
211 private String buildWsdlUrlPrefix(String previousVersion) {
212 String wsdlUrl = ConfigContext.getCurrentContextConfig().getProperty(WSDL_URL);
213
214 if (StringUtils.isNotBlank(wsdlUrl)
215 && StringUtils.isNotBlank(previousVersion)) {
216 StringBuilder urlBuilder = new StringBuilder(wsdlUrl);
217 if (!wsdlUrl.endsWith("/")) {
218 urlBuilder.append("/");
219 }
220 urlBuilder.append("rice-");
221 urlBuilder.append(getModuleName());
222 urlBuilder.append("-api/");
223 urlBuilder.append(previousVersion);
224 urlBuilder.append("/");
225
226 return urlBuilder.toString();
227
228 } else {
229 throw new RuntimeException("Couldn't build wsdl url prefix");
230 }
231 }
232
233
234
235
236
237
238
239 protected Map<String, List<MavenVersion>> getWsdlVersionBlacklists() {
240 return null;
241 }
242
243 protected void compareWsdlFiles(File[] files) {
244 List<VersionCompatibilityBreakage> breakages = new ArrayList<VersionCompatibilityBreakage>();
245
246 assertTrue("There should be wsdls to compare", files != null && files.length > 0);
247
248 MavenVersion currentVersion = getCurrentMavenVersion();
249 List<MavenVersion> versions = getInterveningVersions();
250
251 for (File file : files) {
252 if (file.getName().endsWith(".wsdl")) {
253 LOG.info("new wsdl: " + file.getAbsolutePath());
254 String newWsdl = file.getAbsolutePath();
255
256
257
258 List<MavenVersion> filteredVersions = new ArrayList<MavenVersion>(versions);
259 Map<String, List<MavenVersion>> wsdlVersionBlacklists = getWsdlVersionBlacklists();
260
261 if (wsdlVersionBlacklists != null) {
262 for (Map.Entry<String, List<MavenVersion>> wsdlVersionBlacklist : wsdlVersionBlacklists.entrySet()) {
263 if (file.getName().equals(wsdlVersionBlacklist.getKey())) {
264 LOG.info("filtering blacklisted versions of " + wsdlVersionBlacklist.getKey() + ": " +
265 StringUtils.join(wsdlVersionBlacklist.getValue(), ","));
266 filteredVersions.removeAll(wsdlVersionBlacklist.getValue());
267 }
268 }
269 }
270
271 Iterator<MavenVersion> versionsIter = filteredVersions.iterator();
272
273 boolean processedCurrent = false;
274
275 MavenVersion v1;
276 MavenVersion v2 = versionsIter.next();
277
278
279 while (versionsIter.hasNext() || !processedCurrent) {
280 v1 = v2;
281
282 String v1Wsdl = getPreviousVersionWsdlUrl(file.getName(), v1);
283 String v2Wsdl;
284
285 if (versionsIter.hasNext()) {
286 v2 = versionsIter.next();
287 v2Wsdl = getPreviousVersionWsdlUrl(file.getName(), v2);
288 } else {
289 v2 = currentVersion;
290 v2Wsdl = file.getAbsolutePath();
291 processedCurrent = true;
292 }
293
294 LOG.info("checking version transition: " + v1.getOriginalForm() + " -> " + v2.getOriginalForm());
295
296 if (v1Wsdl == null) {
297 LOG.warn("SKIPPING check, wsdl not found for " + v1Wsdl);
298 } else if (v2Wsdl == null) {
299 LOG.warn("SKIPPING check, wsdl not found for " + v2Wsdl);
300 } else {
301
302 List<Difference> differences = compareWsdlDefinitions(v1Wsdl, v2Wsdl);
303 for (Difference diff : differences) {
304 List<String> breakageStrings = verifyWsdlDifferences(diff, "");
305
306 for (String breakage : breakageStrings) {
307 breakages.add(new VersionCompatibilityBreakage(v1, v2, v1Wsdl, v2Wsdl, breakage));
308 }
309 }
310 }
311 }
312
313 }
314
315
316 }
317
318 if (!breakages.isEmpty()) {
319 fail(buildBreakagesSummary(breakages));
320 }
321 }
322
323 protected String buildBreakagesSummary(List<VersionCompatibilityBreakage> breakages) {
324 StringBuilder errorsStringBuilder =
325 new StringBuilder(LINE_SEPARATOR + "!!!!! Detected " + breakages.size() + " VC Breakages !!!!!"
326 + LINE_SEPARATOR);
327
328 MavenVersion lastOldVersion = null;
329 String lastOldWsdlUrl = "";
330
331 for (VersionCompatibilityBreakage breakage : breakages) {
332
333 if (lastOldVersion != breakage.oldMavenVersion || lastOldWsdlUrl != breakage.oldWsdlUrl) {
334 lastOldVersion = breakage.oldMavenVersion;
335 lastOldWsdlUrl = breakage.oldWsdlUrl;
336
337 errorsStringBuilder.append(LINE_SEPARATOR + "Old Version: " + lastOldVersion.getOriginalForm()
338 +", wsdl: " + lastOldWsdlUrl);
339 errorsStringBuilder.append(LINE_SEPARATOR + "New Version: " + breakage.newMavenVersion.getOriginalForm()
340 +", wsdl: " + breakage.newWsdlUrl + LINE_SEPARATOR + LINE_SEPARATOR);
341 }
342 errorsStringBuilder.append(breakage.breakageMessage + LINE_SEPARATOR);
343 }
344 return errorsStringBuilder.toString();
345 }
346
347 public String getPreviousVersion() {
348 if (StringUtils.isEmpty(this.previousVersion)) {
349 this.previousVersion = ConfigContext.getCurrentContextConfig().getProperty(WSDL_PREVIOUS_VERSION);
350 }
351 return this.previousVersion;
352 }
353
354 public void setPreviousVersion(String previousVersion) {
355 this.previousVersion = previousVersion;
356 }
357
358 @Override
359 protected Lifecycle getLoadApplicationLifecycle() {
360 SpringResourceLoader springResourceLoader = new SpringResourceLoader(new QName("VCTestHarnessResourceLoader"), "classpath:VCTestHarnessSpringBeans.xml", null);
361 springResourceLoader.setParentSpringResourceLoader(getTestHarnessSpringResourceLoader());
362 return springResourceLoader;
363 }
364
365 @Override
366 protected List<Lifecycle> getPerTestLifecycles() {
367 return new ArrayList<Lifecycle>();
368 }
369
370 @Override
371 protected List<Lifecycle> getSuiteLifecycles() {
372 List<Lifecycle> lifecycles = new LinkedList<Lifecycle>();
373
374
375
376
377 lifecycles.add(new BaseLifecycle() {
378 @Override
379 public void start() throws Exception {
380 Config config = getTestHarnessConfig();
381 ConfigContext.init(config);
382 super.start();
383 }
384 });
385
386 return lifecycles;
387 }
388
389
390 protected List<MavenVersion> getInterveningVersions() {
391 ArrayList<MavenVersion> results = new ArrayList<MavenVersion>();
392
393 MavenVersion previousVersion = new MavenVersion(getPreviousVersion());
394
395 MavenVersion currentVersion = getCurrentMavenVersion();
396
397 if (currentVersion.compareTo(previousVersion) <= 0) {
398 throw new IllegalStateException("currentVersion " + currentVersion +
399 " is <= previousVersion " + previousVersion);
400 }
401 String searchContent = getMavenSearchResults();
402
403 LinkedList<MavenVersion> riceVersions = parseSearchResults(searchContent);
404
405 for (MavenVersion riceVersion : riceVersions) {
406 if ( currentVersion.compareTo(riceVersion) > 0 &&
407 previousVersion.compareTo(riceVersion) <= 0 &&
408 "".equals(riceVersion.getQualifier()) ) {
409 results.add(riceVersion);
410 }
411 }
412
413 return results;
414 }
415
416 private MavenVersion getCurrentMavenVersion() {
417 return new MavenVersion(ConfigContext.getCurrentContextConfig().getProperty("rice.version"));
418 }
419
420 private LinkedList<MavenVersion> parseSearchResults(String searchContent) {
421 LinkedList<MavenVersion> riceVersions = new LinkedList<MavenVersion>();
422
423 ObjectMapper mapper = new ObjectMapper();
424 JsonNode rootNode;
425 try {
426 rootNode = mapper.readTree(searchContent);
427 } catch (IOException e) {
428 throw new RuntimeException("Can't parse maven search results", e);
429 }
430 JsonNode docsNode = rootNode.get("response").get("docs");
431
432 for (JsonNode node : docsNode) {
433 String versionStr = node.get("v").toString();
434
435 riceVersions.add(new MavenVersion(versionStr.replace(
436 }
437
438 Collections.sort(riceVersions);
439 return riceVersions;
440 }
441
442 private String getMavenSearchResults() {
443
444
445 final String mavenSearchUrlString =
446 "http://search.maven.org/solrsearch/select?q=g:%22org.kuali.rice%22+AND+a:%22rice%22&core=gav&rows=20&wt=json";
447
448 URL mavenSearchUrl;
449
450 try {
451 mavenSearchUrl = new URL(mavenSearchUrlString);
452 } catch (MalformedURLException e) {
453 throw new RuntimeException("can't parse maven search url", e);
454 }
455
456 StringBuilder contentBuilder = new StringBuilder();
457 BufferedReader contentReader;
458 try {
459 contentReader = new BufferedReader(new InputStreamReader(mavenSearchUrl.openStream()));
460 String line;
461 while (null != (line = contentReader.readLine())) {
462 contentBuilder.append(line + LINE_SEPARATOR);
463 }
464 } catch (IOException e) {
465 throw new RuntimeException("Unable to read search results", e);
466 }
467 return contentBuilder.toString();
468 }
469
470
471
472
473 protected static class MavenVersion implements Comparable<MavenVersion> {
474 private static final Pattern PERIOD_PATTERN = Pattern.compile("\\.");
475 private final List<Integer> numbers;
476 private final String originalForm;
477 private final String qualifier;
478
479 public MavenVersion(String versionString) {
480 originalForm = versionString;
481 if (versionString == null || "".equals(versionString.trim())) {
482 throw new IllegalArgumentException("empty or null version string");
483 }
484 String versionPart;
485 int dashIndex = versionString.indexOf('-');
486 if (dashIndex != -1 && versionString.length()-1 > dashIndex) {
487 qualifier = versionString.substring(dashIndex+1).trim();
488 versionPart = versionString.substring(0,dashIndex);
489 } else {
490 versionPart = versionString;
491 qualifier = "";
492 }
493 String [] versionArray = PERIOD_PATTERN.split(versionPart);
494
495 List<Integer> numbersBuilder = new ArrayList<Integer>(versionArray.length);
496
497 for (String versionParticle : versionArray) {
498 numbersBuilder.add(Integer.valueOf(versionParticle));
499 }
500
501 numbers = Collections.unmodifiableList(numbersBuilder);
502 }
503
504 @Override
505 public int compareTo(MavenVersion that) {
506 Iterator<Integer> thisNumbersIter = this.numbers.iterator();
507 Iterator<Integer> thatNumbersIter = that.numbers.iterator();
508
509 while (thisNumbersIter.hasNext()) {
510
511 if (!thatNumbersIter.hasNext()) return 1;
512
513 int numberComparison = thisNumbersIter.next().compareTo(thatNumbersIter.next());
514
515
516 if (numberComparison != 0) return numberComparison;
517 }
518
519 if (thatNumbersIter.hasNext()) return -1;
520
521 return compareQualifiers(this.qualifier, that.qualifier);
522 }
523
524 private static int compareQualifiers(String thisQ, String thatQ) {
525
526 if ("".equals(thisQ)) {
527 if ("".equals(thatQ)) {
528 return 0;
529 }
530 return 1;
531 } else if ("".equals(thatQ)) {
532 return -1;
533 }
534
535 return thisQ.compareTo(thatQ);
536 }
537
538 public List<Integer> getNumbers() {
539 return Collections.unmodifiableList(numbers);
540 }
541
542 public String getQualifier() {
543 return qualifier;
544 }
545
546 public String getOriginalForm() {
547 return originalForm;
548 }
549
550 @Override
551 public String toString() {
552 return "MavenVersion{" +
553 originalForm +
554 '}';
555 }
556
557 @Override
558 public boolean equals(Object o) {
559 if (this == o) {
560 return true;
561 }
562 if (o == null || getClass() != o.getClass()) {
563 return false;
564 }
565
566 final MavenVersion that = (MavenVersion) o;
567
568 if (!originalForm.equals(that.originalForm)) {
569 return false;
570 }
571
572 return true;
573 }
574
575 @Override
576 public int hashCode() {
577 return originalForm.hashCode();
578 }
579 }
580
581
582
583
584 protected static class VersionCompatibilityBreakage {
585 private final MavenVersion oldMavenVersion;
586 private final MavenVersion newMavenVersion;
587 private final String oldWsdlUrl;
588 private final String newWsdlUrl;
589 private final String breakageMessage;
590
591 public VersionCompatibilityBreakage(MavenVersion oldMavenVersion, MavenVersion newMavenVersion, String oldWsdlUrl, String newWsdlUrl, String breakageMessage) {
592 if (oldMavenVersion == null) throw new IllegalArgumentException("oldMavenVersion must not be null");
593 if (newMavenVersion == null) throw new IllegalArgumentException("newMavenVersion must not be null");
594 if (StringUtils.isEmpty(oldWsdlUrl)) throw new IllegalArgumentException("oldWsdlUrl must not be empty/null");
595 if (StringUtils.isEmpty(newWsdlUrl)) throw new IllegalArgumentException("newWsdlUrl must not be empty/null");
596 if (StringUtils.isEmpty(breakageMessage)) throw new IllegalArgumentException("breakageMessage must not be empty/null");
597 this.oldWsdlUrl = oldWsdlUrl;
598 this.newWsdlUrl = newWsdlUrl;
599 this.oldMavenVersion = oldMavenVersion;
600 this.newMavenVersion = newMavenVersion;
601 this.breakageMessage = breakageMessage;
602 }
603
604 @Override
605 public String toString() {
606 return "VersionCompatibilityBreakage{" +
607 "oldMavenVersion=" + oldMavenVersion +
608 ", newMavenVersion=" + newMavenVersion +
609 ", oldWsdlUrl='" + oldWsdlUrl + '\'' +
610 ", newWsdlUrl='" + newWsdlUrl + '\'' +
611 ", breakageMessage='" + breakageMessage + '\'' +
612 '}';
613 }
614 }
615
616 }