1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.services.info.ProductVersionHolder |
4 | |
5 | Copyright 1998, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.iapi.services.info; |
22 | |
23 | import java.io.InputStream; |
24 | import java.io.IOException; |
25 | import java.util.Properties; |
26 | |
27 | |
28 | /** |
29 | Class to hold a cloudscape Product version. |
30 | |
31 | This class includes the following product version features. |
32 | |
33 | <OL> |
34 | <LI>Save the product version information this holds as a String. We call |
35 | the string a 'product version string'. |
36 | <LI>Construct a ProductVersionHolder from a valid 'product version string'. |
37 | <LI>Determine if two product versions are feature compatible. This means |
38 | products of these versions may interoperate with ***NO*** compatibility |
39 | problems. |
40 | <LI>Determine if two product versions are the same. This is a stronger |
41 | test than the test for feature compatibility. |
42 | </OL> |
43 | |
44 | |
45 | |
46 | Cloudscape 5.1 and older versions used the majorVersion, minorVersion, maintVersion versions |
47 | directly. That is a three part version number, majorVersion.minorVersion.maintVersion, e.g. 5.1.21. |
48 | |
49 | For Cloudscape 5.2 onwards a four part name is required. |
50 | majorVersion.minorVersion.fixPack.bugVersion e.g. 5.2.1.2 |
51 | |
52 | This follows the IBM standard and allows us to state that a fix pack will be 5.2.3 without worrying |
53 | about how many maintence fixes there are between fix packs. |
54 | |
55 | We implement this using the existing format of ProductVersionHolder to reduce disruption to the |
56 | code, however we make the maintVersion encode the {fixPack.bugVersion}. Since the maintVersion |
57 | is represented by a int (2G values) we have plenty of room for encoding. If we assign a given |
58 | majorVersion.minorVersion.fixPack a 10 year life, then we about the maximum number of individual releases |
59 | it can have is 10 years * 365 days/year = 3650. Thus with the pre 5.2 scheme we would not expect a |
60 | 5.1.x to have an x > 3650 (approximately). Usually the rate of point releases has been much less than |
61 | one per day, 5.1.31 is released about 225 days after GA which makes around a point release every 7 days. |
62 | But in the encoding we need to be conservative. With fix packs the maximum is about 2 per year and fix |
63 | packs are only made to the current release, thus with a yearly minor release cycle we would imagine |
64 | only 2 fixpacks per major.minor. However like other IBM products or release cycle may be extended thus |
65 | we can expect up to a handful of fix packs. |
66 | |
67 | Thus we might imagine releases like |
68 | |
69 | 5.2.0.12 |
70 | 5.2.0.234 |
71 | 5.2.1.34 |
72 | 5.2.4.2445 |
73 | |
74 | but highly unlikey to have |
75 | |
76 | 5.2.2.59321 |
77 | 5.2.23.1 |
78 | |
79 | |
80 | The encoding number must continue to increase so that the |
81 | |
82 | encodedMaintB > encodedMaintA |
83 | |
84 | if (fixPackB > fixPackA) || ((fixPackB == fixPackA) && (bugB > bugA)) |
85 | |
86 | |
87 | Selected encoding |
88 | |
89 | encodedMaint = (fixPack * 1,000,000) + (bugVersion); |
90 | |
91 | Handles many many fixpacks and upto one million bug fixes per fix pack and remains somewhat human readable. |
92 | |
93 | Special fix packs |
94 | |
95 | fixpack == 0 = alpha (version off main codeline) |
96 | fixpack == 1 = first release of major.minor (may be marked with beta) |
97 | fixpack == 2 = first fix pack (displayed as 1) |
98 | |
99 | |
100 | The drdaMaintVersion is sent in the Network Server PRDID. It never displays |
101 | but may be used by the client for version specific behaviour. It should be |
102 | reset to 0 with each minor release. |
103 | |
104 | The product version string has the form: |
105 | <PRE> |
106 | productVendorName - ProductName - majorVersion.minorVersion.maintVersion [beta] - (buildNumber) |
107 | |
108 | </PRE> |
109 | |
110 | */ |
111 | public final class ProductVersionHolder implements java.security.PrivilegedAction |
112 | { |
113 | |
114 | // |
115 | //Used as an invalid value for numbers. This works because all |
116 | //the numbers in a product version must be non-negative. |
117 | private static final int BAD_NUMBER = -1; |
118 | private static final String ALPHA = "alpha"; |
119 | private static final String BETA = "beta"; |
120 | |
121 | private final static int MAINT_ENCODING = 1000000; |
122 | |
123 | private String productVendorName; |
124 | private String productName; |
125 | private String productTechnologyName; |
126 | private int majorVersion = BAD_NUMBER; |
127 | private int minorVersion = BAD_NUMBER; |
128 | private int maintVersion = BAD_NUMBER; |
129 | private int drdaMaintVersion = BAD_NUMBER; |
130 | private String buildNumber = "????"; |
131 | private Boolean isBeta; |
132 | |
133 | private ProductVersionHolder() { |
134 | } |
135 | |
136 | /** |
137 | Create a ProductVersionHolder |
138 | |
139 | <P>Please see the documentation for the varient of getProductVesionHolder |
140 | that takes the same parameters as this for a description of the parameters. |
141 | */ |
142 | private ProductVersionHolder(String productVendorName, |
143 | String productName, |
144 | String productTechnologyName, |
145 | int majorVersion, |
146 | int minorVersion, |
147 | int maintVersion, |
148 | int drdaMaintVersion, |
149 | String buildNumber, |
150 | Boolean isBeta) |
151 | { |
152 | if (productVendorName != null) |
153 | this.productVendorName = productVendorName.trim(); |
154 | if (productName != null) |
155 | this.productName = productName.trim(); |
156 | if (productTechnologyName != null) |
157 | this.productTechnologyName = productTechnologyName.trim(); |
158 | this.majorVersion = majorVersion; |
159 | this.minorVersion = minorVersion; |
160 | this.maintVersion = maintVersion; |
161 | this.drdaMaintVersion = drdaMaintVersion; |
162 | this.buildNumber = buildNumber; |
163 | this.isBeta = isBeta; |
164 | } |
165 | |
166 | /** |
167 | Create a valid ProductVersionHolder. If any of the |
168 | parameters provided is invalid, this returns null. |
169 | @param productName The name of the product. productName.length() |
170 | must be greater than 0. The syntax for a product name is |
171 | 'productGenus[:productSpecies]'. |
172 | @param majorVersion The most significant portion of a 3 |
173 | part product version. Must be non-negative. |
174 | @param minorVersion The second portion of a 3 part |
175 | product version. Must be non-negative. |
176 | @param maintVersion The least significant portion of a 3 part |
177 | product version. Must be non-negative. |
178 | @param drdaMaintVersion The protocol modification number for minor release. |
179 | @param buildNumber The buildNumber for a product. |
180 | @param isBeta true iff the product is beta. |
181 | @return A valid ProductVersionHolder of null if any of the parameters |
182 | provided are not valid. |
183 | */ |
184 | public static ProductVersionHolder |
185 | getProductVersionHolder( |
186 | String productVendorName, |
187 | String productName, |
188 | String productTechnologyName, |
189 | int majorVersion, |
190 | int minorVersion, |
191 | int maintVersion, |
192 | int drdaMaintVersion, |
193 | String buildNumber, |
194 | Boolean isBeta) |
195 | { |
196 | ProductVersionHolder pvh = |
197 | new ProductVersionHolder(productVendorName, |
198 | productName, |
199 | productTechnologyName, |
200 | majorVersion, |
201 | minorVersion, |
202 | maintVersion, |
203 | drdaMaintVersion, |
204 | buildNumber, |
205 | isBeta); |
206 | return pvh; |
207 | } |
208 | |
209 | /** |
210 | Get a ProductVersionHolder for a product of a given genus, |
211 | that is available in the caller's environment. |
212 | Even though this uses a priv bock, it may stil fail when |
213 | the jar the version is being fetched from, is different to the |
214 | one that loaded this class, AND the jars are in different security contexts. |
215 | |
216 | @param productGenus The genus for the product. |
217 | @return The ProductVersionHolder or null if |
218 | a product with the given genus is not available in the |
219 | caller's environment. |
220 | */ |
221 | public static ProductVersionHolder |
222 | getProductVersionHolderFromMyEnv(String productGenus) |
223 | { |
224 | |
225 | ProductVersionHolder tempPVH = new ProductVersionHolder(); |
226 | |
227 | tempPVH.productGenus = productGenus; |
228 | Properties p = (Properties) java.security.AccessController.doPrivileged(tempPVH); |
229 | |
230 | if (p == null) |
231 | return null; |
232 | |
233 | return getProductVersionHolder(p); |
234 | } |
235 | |
236 | |
237 | /** |
238 | Load the version info from the already opened properties files. |
239 | We need to do this because if the jar files (e.g. db2jtools and db2j) |
240 | are in different security contexts (entries in the policy files) then |
241 | we cannot load the version information for one of them correctly. |
242 | This is because the this class will either have been loaded from |
243 | only one of the jars and hence can only access the resource in its own jar. |
244 | By making code specific to the jar open the resource we are guaranteed it will work. |
245 | */ |
246 | public static ProductVersionHolder |
247 | getProductVersionHolderFromMyEnv(InputStream propertiesStream) |
248 | { |
249 | |
250 | if (propertiesStream == null) |
251 | return null; |
252 | |
253 | Properties p = new Properties(); |
254 | try { |
255 | p.load(propertiesStream); |
256 | } |
257 | catch (IOException ioe) { |
258 | |
259 | System.out.println("IOE " + ioe.getMessage()); |
260 | // |
261 | //This case is a bit ugly. If we get an IOException, we return |
262 | //null. Though this correctly reflects that the product is not |
263 | //available for use, it may be confusing to users that we swallow |
264 | //the IO error here. |
265 | return null; |
266 | } finally { |
267 | try { |
268 | propertiesStream.close(); |
269 | } catch (IOException ioe2) { |
270 | } |
271 | } |
272 | |
273 | return getProductVersionHolder(p); |
274 | } |
275 | |
276 | /** |
277 | Get a ProductVersionHolder based on the information in |
278 | the Properties object provided. |
279 | |
280 | @param p The properties object that holds the productVersion |
281 | information. |
282 | @return The ProductVersionHolder or null if |
283 | a product with the given genus is not available in the |
284 | caller's environment. |
285 | */ |
286 | public static ProductVersionHolder |
287 | getProductVersionHolder(Properties p) |
288 | { |
289 | String pvn = p.getProperty(PropertyNames.PRODUCT_VENDOR_NAME); |
290 | String pn = p.getProperty(PropertyNames.PRODUCT_EXTERNAL_NAME); |
291 | String ptn = p.getProperty(PropertyNames.PRODUCT_TECHNOLOGY_NAME); |
292 | int v1 = parseInt(p.getProperty(PropertyNames.PRODUCT_MAJOR_VERSION)); |
293 | int v2 = parseInt(p.getProperty(PropertyNames.PRODUCT_MINOR_VERSION)); |
294 | int v3 = parseInt(p.getProperty(PropertyNames.PRODUCT_MAINT_VERSION)); |
295 | int v4 = parseInt(p.getProperty(PropertyNames.PRODUCT_DRDA_MAINT_VERSION)); |
296 | String bn = p.getProperty(PropertyNames.PRODUCT_BUILD_NUMBER); |
297 | Boolean isBeta = |
298 | Boolean.valueOf(p.getProperty(PropertyNames.PRODUCT_BETA_VERSION)); |
299 | return getProductVersionHolder(pvn,pn,ptn,v1,v2,v3,v4,bn,isBeta); |
300 | } |
301 | |
302 | |
303 | /** |
304 | Return the product vendor name. |
305 | */ |
306 | public String getProductVendorName() |
307 | { |
308 | return productVendorName; |
309 | } |
310 | |
311 | |
312 | /** |
313 | Return the external product name. |
314 | */ |
315 | public String getProductName() |
316 | { |
317 | return productName; |
318 | } |
319 | public String getProductTechnologyName() |
320 | { |
321 | return productTechnologyName; |
322 | } |
323 | |
324 | /** |
325 | Return the major version number. |
326 | */ |
327 | public int getMajorVersion() {return majorVersion;} |
328 | /** |
329 | Return the minor version number. |
330 | */ |
331 | public int getMinorVersion() {return minorVersion;} |
332 | /** |
333 | Return the <B>encoded</B> maintainence version number. |
334 | */ |
335 | public int getMaintVersion() {return maintVersion;} |
336 | |
337 | /** |
338 | Return the drda protocol maintenance version for this minor release. |
339 | Starts at 0 for each minor release and only incremented |
340 | when client behaviour changes based on the server version. |
341 | **/ |
342 | public int getDrdaMaintVersion() {return drdaMaintVersion; } |
343 | |
344 | /** |
345 | Return the fix pack version from the maintence encoding. |
346 | */ |
347 | public int getFixPackVersion() { return maintVersion / MAINT_ENCODING; } |
348 | |
349 | |
350 | /** |
351 | Return true if this is a beta product. |
352 | */ |
353 | public boolean isBeta() {return isBeta.booleanValue();} |
354 | /** |
355 | Return true if this is a alpha product. |
356 | */ |
357 | public boolean isAlpha() { |
358 | return (majorVersion >= 5) |
359 | && (minorVersion > 2) |
360 | && ((maintVersion / MAINT_ENCODING) == 0); |
361 | } |
362 | /** |
363 | Return the build number for this product. |
364 | */ |
365 | public String getBuildNumber() {return buildNumber;} |
366 | |
367 | /** |
368 | * Return the build number as an integer if possible, |
369 | * mapping from the SVN number. |
370 | * nnnnn -> returns nnnnn |
371 | * nnnnnM -> returns -nnnnn indicates a modified code base |
372 | * nnnnn:mmmmm -> returns -nnnnn |
373 | * anything else -> returns -1 |
374 | */ |
375 | public int getBuildNumberAsInt(){ |
376 | if (buildNumber == null) |
377 | return -1; |
378 | boolean dubiousCode = false; |
379 | int offset = buildNumber.indexOf('M'); |
380 | if (offset == -1) |
381 | offset = buildNumber.indexOf(':'); |
382 | else |
383 | dubiousCode = true; |
384 | if (offset == -1) |
385 | offset = buildNumber.length(); |
386 | else |
387 | dubiousCode = true; |
388 | |
389 | try { |
390 | int bnai = Integer.parseInt(buildNumber.substring(0, offset)); |
391 | if (dubiousCode) |
392 | bnai = -bnai; |
393 | return bnai; |
394 | } catch (NumberFormatException nfe) |
395 | { |
396 | return -1; |
397 | } |
398 | } |
399 | |
400 | /** |
401 | Parse a string containing a non-negative integer. Return |
402 | a negative integer is the String is invalid. |
403 | |
404 | @param s A string with a non-negative integer (a sequence |
405 | of decimal digits.) |
406 | @return the integer or a negative number if s is invalid. |
407 | */ |
408 | private static int parseInt(String s) |
409 | { |
410 | //System.out.println("Parsing integer: "+s); |
411 | int result = BAD_NUMBER; |
412 | try |
413 | { |
414 | if (s!=null) |
415 | result = Integer.parseInt(s); |
416 | } |
417 | catch (NumberFormatException nfe) |
418 | {} |
419 | |
420 | if (result < 0) result = BAD_NUMBER; |
421 | return result; |
422 | } |
423 | |
424 | /** |
425 | Return a string representation of this ProductVersion. The |
426 | difference between this and createProductVersionString, is |
427 | that this method retruns a String when this ProductVersionHolder |
428 | holds invalid version information. |
429 | */ |
430 | public String toString() |
431 | { |
432 | StringBuffer sb = new StringBuffer(); |
433 | sb.append(getProductVendorName()); |
434 | sb.append(" - "); |
435 | sb.append(getProductName()); |
436 | sb.append(" - "); |
437 | sb.append(getVersionBuildString(true)); |
438 | return sb.toString(); |
439 | } |
440 | |
441 | /** |
442 | Return the feature version string, ie. major.minor. (e.g. 5.2) |
443 | */ |
444 | public String getSimpleVersionString() { |
445 | |
446 | return ProductVersionHolder.simpleVersionString(majorVersion, minorVersion, isBeta()); |
447 | } |
448 | |
449 | /** |
450 | Convert a major and minor number with beta status into a string. |
451 | */ |
452 | public static String simpleVersionString(int major, int minor, boolean isBeta) { |
453 | |
454 | StringBuffer sb = new StringBuffer(); |
455 | |
456 | sb.append(major); |
457 | sb.append('.'); |
458 | sb.append(minor); |
459 | if (isBeta) { |
460 | sb.append(' '); |
461 | sb.append(BETA); |
462 | } |
463 | |
464 | return sb.toString(); |
465 | } |
466 | public static String fullVersionString(int major, int minor, int maint, boolean isBeta, String build) { |
467 | StringBuffer sb = new StringBuffer(); |
468 | sb.append(major); |
469 | sb.append('.'); |
470 | sb.append(minor); |
471 | sb.append('.'); |
472 | |
473 | String preRelease = null; |
474 | if (major == 5 && minor <= 2 && maint < MAINT_ENCODING) |
475 | { |
476 | sb.append(maint); |
477 | if (isBeta) |
478 | preRelease = BETA; |
479 | } |
480 | else |
481 | { |
482 | int fixPack = maint / MAINT_ENCODING; |
483 | int bugVersion = maint % MAINT_ENCODING; |
484 | sb.append(fixPack); |
485 | sb.append('.'); |
486 | sb.append(bugVersion); |
487 | |
488 | if (fixPack == 0) |
489 | { |
490 | preRelease = ALPHA; |
491 | } |
492 | else if (isBeta) { |
493 | preRelease = BETA; |
494 | } |
495 | } |
496 | |
497 | if (preRelease != null) |
498 | { |
499 | sb.append(' '); |
500 | sb.append(preRelease); |
501 | } |
502 | if (build != null) { |
503 | sb.append(" - ("); |
504 | |
505 | sb.append(build); |
506 | sb.append(')'); |
507 | } |
508 | return sb.toString(); |
509 | } |
510 | /** |
511 | Returns a short-hand value for the product version string. |
512 | Used by Sysinfo. |
513 | Includes the optional <beta> designation |
514 | */ |
515 | public String getVersionBuildString(boolean withBuild) |
516 | { |
517 | return ProductVersionHolder.fullVersionString(majorVersion, minorVersion, maintVersion, isBeta(), |
518 | withBuild ? buildNumber : null); |
519 | } |
520 | |
521 | /* |
522 | ** Security related methods |
523 | */ |
524 | private String productGenus; |
525 | public final Object run() { |
526 | |
527 | // SECURITY PERMISSION - IP4 |
528 | return loadProperties(this.productGenus); |
529 | } |
530 | // SECURITY PERMISSION - IP4 |
531 | private Properties loadProperties(String productGenus) { |
532 | String resourceName = "/org/apache/derby/info/" + productGenus+".properties"; |
533 | |
534 | InputStream is = getClass().getResourceAsStream(resourceName); |
535 | if (is==null) { |
536 | return null; |
537 | } |
538 | |
539 | Properties p = new Properties(); |
540 | try { |
541 | p.load(is); |
542 | return p; |
543 | } |
544 | catch (IOException ioe) { |
545 | // |
546 | //This case is a bit ugly. If we get an IOException, we return |
547 | //null. Though this correctly reflects that the product is not |
548 | //available for use, it may be confusing to users that we swallow |
549 | //the IO error here. |
550 | return null; |
551 | } |
552 | } |
553 | } |