001/* 002 * (C) Copyright 2014-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Anahide Tchertchian 018 */ 019package org.nuxeo.targetplatforms.core.service; 020 021import java.time.ZoneOffset; 022import java.time.format.DateTimeFormatter; 023import java.util.ArrayList; 024import java.util.Comparator; 025import java.util.Date; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Locale; 029import java.util.Map; 030 031import org.apache.commons.lang3.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.utils.DateUtils; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.directory.BaseSession; 037import org.nuxeo.ecm.directory.Session; 038import org.nuxeo.ecm.directory.api.DirectoryService; 039import org.nuxeo.runtime.api.Framework; 040import org.nuxeo.runtime.model.ComponentContext; 041import org.nuxeo.runtime.model.ComponentInstance; 042import org.nuxeo.runtime.model.DefaultComponent; 043import org.nuxeo.targetplatforms.api.TargetInfo; 044import org.nuxeo.targetplatforms.api.TargetPackage; 045import org.nuxeo.targetplatforms.api.TargetPackageInfo; 046import org.nuxeo.targetplatforms.api.TargetPlatform; 047import org.nuxeo.targetplatforms.api.TargetPlatformFilter; 048import org.nuxeo.targetplatforms.api.TargetPlatformInfo; 049import org.nuxeo.targetplatforms.api.TargetPlatformInstance; 050import org.nuxeo.targetplatforms.api.impl.TargetPackageImpl; 051import org.nuxeo.targetplatforms.api.impl.TargetPackageInfoImpl; 052import org.nuxeo.targetplatforms.api.impl.TargetPlatformFilterImpl; 053import org.nuxeo.targetplatforms.api.impl.TargetPlatformImpl; 054import org.nuxeo.targetplatforms.api.impl.TargetPlatformInfoImpl; 055import org.nuxeo.targetplatforms.api.impl.TargetPlatformInstanceImpl; 056import org.nuxeo.targetplatforms.api.service.TargetPlatformService; 057import org.nuxeo.targetplatforms.core.descriptors.ServiceConfigurationDescriptor; 058import org.nuxeo.targetplatforms.core.descriptors.TargetPackageDescriptor; 059import org.nuxeo.targetplatforms.core.descriptors.TargetPlatformDescriptor; 060 061/** 062 * {@link TargetPlatformService} implementation relying on runtime extension points. 063 * 064 * @since 5.7.1 065 */ 066public class TargetPlatformServiceImpl extends DefaultComponent implements TargetPlatformService { 067 068 private static final Log log = LogFactory.getLog(TargetPlatformServiceImpl.class); 069 070 public static final String XP_CONF = "configuration"; 071 072 public static final String XP_PLATFORMS = "platforms"; 073 074 public static final String XP_PACKAGES = "packages"; 075 076 public static final DateTimeFormatter dateParser = DateTimeFormatter.ofPattern("yyyy/MM/dd", Locale.ENGLISH) 077 .withZone(ZoneOffset.UTC); 078 079 protected ServiceConfigurationRegistry conf; 080 081 protected TargetPlatformRegistry platforms; 082 083 protected TargetPackageRegistry packages; 084 085 // Runtime component API 086 087 @Override 088 public void activate(ComponentContext context) { 089 platforms = new TargetPlatformRegistry(); 090 packages = new TargetPackageRegistry(); 091 conf = new ServiceConfigurationRegistry(); 092 } 093 094 @Override 095 public void deactivate(ComponentContext context) { 096 platforms = null; 097 packages = null; 098 conf = null; 099 } 100 101 @Override 102 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 103 if (XP_PLATFORMS.equals(extensionPoint)) { 104 TargetPlatformDescriptor desc = (TargetPlatformDescriptor) contribution; 105 log.info(String.format("Register target platform '%s'", desc.getId())); 106 platforms.addContribution(desc); 107 } else if (XP_PACKAGES.equals(extensionPoint)) { 108 TargetPackageDescriptor desc = (TargetPackageDescriptor) contribution; 109 log.info(String.format("Register target package '%s'", desc.getId())); 110 packages.addContribution(desc); 111 } else if (XP_CONF.equals(extensionPoint)) { 112 ServiceConfigurationDescriptor desc = (ServiceConfigurationDescriptor) contribution; 113 log.info("Register TargetPlatformService configuration"); 114 conf.addContribution(desc); 115 } 116 } 117 118 @Override 119 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 120 if (XP_PLATFORMS.equals(extensionPoint)) { 121 TargetPlatformDescriptor desc = (TargetPlatformDescriptor) contribution; 122 log.info(String.format("Unregister target platform '%s'", desc.getId())); 123 platforms.removeContribution(desc); 124 } else if (XP_PACKAGES.equals(extensionPoint)) { 125 TargetPackageDescriptor desc = (TargetPackageDescriptor) contribution; 126 log.info(String.format("Unregister target package '%s'", desc.getId())); 127 packages.removeContribution(desc); 128 } else if (XP_CONF.equals(extensionPoint)) { 129 ServiceConfigurationDescriptor desc = (ServiceConfigurationDescriptor) contribution; 130 log.info("Unregister TargetPlatformService configuration"); 131 conf.removeContribution(desc); 132 } 133 } 134 135 // Service API 136 137 @Override 138 public TargetPlatform getDefaultTargetPlatform(TargetPlatformFilter filter) { 139 List<TargetPlatform> tps = getAvailableTargetPlatforms(filter); 140 if (tps.isEmpty()) { 141 return null; 142 } 143 TargetPlatform defaultTP = null; 144 for (TargetPlatform tp : tps) { 145 if (tp.isDefault()) { 146 if (!tp.isRestricted()) { 147 // Return the first default and unrestricted target platform 148 return tp; 149 } 150 // If the target platform is restricted, we keep it in case no 151 // unrestricted target platform is found 152 if (defaultTP == null) { 153 defaultTP = tp; 154 } 155 } 156 } 157 return defaultTP; 158 } 159 160 @Override 161 public String getOverrideDirectory() { 162 String res = DirectoryUpdater.DEFAULT_DIR; 163 ServiceConfigurationDescriptor desc = conf.getConfiguration(); 164 if (desc == null) { 165 return res; 166 } 167 String id = desc.getOverrideDirectory(); 168 if (!StringUtils.isBlank(id)) { 169 res = id; 170 } 171 return res; 172 } 173 174 @Override 175 public TargetPlatform getTargetPlatform(String id) { 176 if (id == null) { 177 return null; 178 } 179 TargetPlatformDescriptor desc = platforms.getTargetPlatform(id); 180 return getTargetPlatform(desc); 181 } 182 183 protected TargetPlatform getTargetPlatform(TargetPlatformDescriptor desc) { 184 if (desc == null) { 185 return null; 186 } 187 String id = desc.getId(); 188 TargetPlatformImpl tp = new TargetPlatformImpl(id, desc.getName(), desc.getVersion(), desc.getRefVersion(), 189 desc.getLabel()); 190 tp.setDeprecated(desc.isDeprecated()); 191 tp.setDescription(desc.getDescription()); 192 tp.setDownloadLink(desc.getDownloadLink()); 193 tp.setEnabled(desc.isEnabled()); 194 tp.setEndOfAvailability(toDate(desc.getEndOfAvailability())); 195 tp.setFastTrack(desc.isFastTrack()); 196 tp.setTrial(desc.isTrial()); 197 tp.setDefault(desc.isDefault()); 198 tp.setParent(getTargetPlatform(desc.getParent())); 199 tp.setRefVersion(desc.getRefVersion()); 200 tp.setReleaseDate(toDate(desc.getReleaseDate())); 201 tp.setRestricted(desc.isRestricted()); 202 tp.setStatus(desc.getStatus()); 203 tp.setTestVersions(desc.getTestVersions()); 204 tp.setTypes(desc.getTypes()); 205 // resolve available packages 206 tp.setAvailablePackages(getTargetPackages(id)); 207 208 // check if there's an override 209 DocumentModel entry = getDirectoryEntry(id); 210 if (entry != null) { 211 Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP); 212 if (enabled != null && enabled.intValue() >= 0) { 213 tp.setEnabled(enabled.intValue() != 0); 214 } 215 Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP); 216 if (restricted != null && restricted.intValue() >= 0) { 217 tp.setRestricted(restricted.intValue() != 0); 218 } 219 Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP); 220 if (deprecated != null && deprecated.intValue() >= 0) { 221 tp.setDeprecated(deprecated.intValue() != 0); 222 } 223 Long trial = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.TRIAL_PROP); 224 if (trial != null && trial.intValue() >= 0) { 225 tp.setTrial(trial.intValue() != 0); 226 } 227 Long isDefault = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEFAULT_PROP); 228 if (isDefault != null && isDefault.intValue() >= 0) { 229 tp.setDefault(isDefault.intValue() != 0); 230 } 231 tp.setOverridden(true); 232 } 233 234 return tp; 235 } 236 237 /** 238 * Lookup all packages referencing this target platform. 239 */ 240 protected Map<String, TargetPackage> getTargetPackages(String targetPlatform) { 241 Map<String, TargetPackage> tps = new HashMap<>(); 242 List<TargetPackageDescriptor> pkgs = packages.getTargetPackages(targetPlatform); 243 if (pkgs != null) { 244 for (TargetPackageDescriptor pkg : pkgs) { 245 TargetPackage tp = getTargetPackage(pkg); 246 if (tp != null) { 247 tps.put(tp.getId(), tp); 248 } 249 } 250 } 251 return tps; 252 } 253 254 protected Map<String, TargetPackageInfo> getTargetPackagesInfo(String targetPlatform) { 255 Map<String, TargetPackageInfo> tps = new HashMap<>(); 256 List<TargetPackageDescriptor> pkgs = packages.getTargetPackages(targetPlatform); 257 if (pkgs != null) { 258 for (TargetPackageDescriptor pkg : pkgs) { 259 TargetPackageInfo tp = getTargetPackageInfo(pkg.getId()); 260 if (tp != null) { 261 tps.put(tp.getId(), tp); 262 } 263 } 264 } 265 return tps; 266 } 267 268 protected Date toDate(String date) { 269 if (StringUtils.isBlank(date)) { 270 return null; 271 } 272 return DateUtils.toDate(DateUtils.parse(date, dateParser)); 273 } 274 275 @Override 276 public TargetPlatformInfo getTargetPlatformInfo(String id) { 277 if (id == null) { 278 return null; 279 } 280 TargetPlatformDescriptor desc = platforms.getTargetPlatform(id); 281 return getTargetPlatformInfo(desc); 282 } 283 284 protected TargetPlatformInfo getTargetPlatformInfo(TargetPlatformDescriptor desc) { 285 if (desc == null) { 286 return null; 287 } 288 String id = desc.getId(); 289 TargetPlatformInfoImpl tpi = new TargetPlatformInfoImpl(id, desc.getName(), desc.getVersion(), 290 desc.getRefVersion(), desc.getLabel()); 291 tpi.setDescription(desc.getDescription()); 292 tpi.setStatus(desc.getStatus()); 293 tpi.setEnabled(desc.isEnabled()); 294 tpi.setFastTrack(desc.isFastTrack()); 295 tpi.setReleaseDate(toDate(desc.getReleaseDate())); 296 tpi.setRestricted(desc.isRestricted()); 297 tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability())); 298 tpi.setDownloadLink(desc.getDownloadLink()); 299 tpi.setDeprecated(desc.isDeprecated()); 300 tpi.setAvailablePackagesInfo(getTargetPackagesInfo(id)); 301 tpi.setTypes(desc.getTypes()); 302 tpi.setTrial(desc.isTrial()); 303 tpi.setDefault(desc.isDefault()); 304 305 DocumentModel entry = getDirectoryEntry(id); 306 if (entry != null) { 307 Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP); 308 if (enabled != null && enabled.intValue() >= 0) { 309 tpi.setEnabled(enabled.intValue() != 0); 310 } 311 Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP); 312 if (restricted != null && restricted.intValue() >= 0) { 313 tpi.setRestricted(restricted.intValue() != 0); 314 } 315 Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP); 316 if (deprecated != null && deprecated.intValue() >= 0) { 317 tpi.setDeprecated(deprecated.intValue() != 0); 318 } 319 Long trial = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.TRIAL_PROP); 320 if (trial != null && trial.intValue() >= 0) { 321 tpi.setTrial(trial.intValue() != 0); 322 } 323 Long isDefault = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEFAULT_PROP); 324 if (isDefault != null && isDefault.intValue() >= 0) { 325 tpi.setDefault(isDefault.intValue() != 0); 326 } 327 tpi.setOverridden(true); 328 } 329 330 return tpi; 331 } 332 333 @Override 334 public TargetPackage getTargetPackage(String id) { 335 if (id == null) { 336 return null; 337 } 338 return getTargetPackage(packages.getTargetPackage(id)); 339 } 340 341 @Override 342 public TargetPackageInfo getTargetPackageInfo(String id) { 343 if (id == null) { 344 return null; 345 } 346 TargetPackageDescriptor desc = packages.getTargetPackage(id); 347 TargetPackageInfoImpl tpi = new TargetPackageInfoImpl(desc.getId(), desc.getName(), desc.getVersion(), 348 desc.getRefVersion(), desc.getLabel()); 349 tpi.setDescription(desc.getDescription()); 350 tpi.setStatus(desc.getStatus()); 351 tpi.setEnabled(desc.isEnabled()); 352 tpi.setReleaseDate(toDate(desc.getReleaseDate())); 353 tpi.setRestricted(desc.isRestricted()); 354 tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability())); 355 tpi.setDownloadLink(desc.getDownloadLink()); 356 tpi.setDeprecated(desc.isDeprecated()); 357 tpi.setDependencies(desc.getDependencies()); 358 return tpi; 359 } 360 361 protected TargetPackage getTargetPackage(TargetPackageDescriptor desc) { 362 if (desc == null) { 363 return null; 364 } 365 TargetPackageImpl tp = new TargetPackageImpl(desc.getId(), desc.getName(), desc.getVersion(), 366 desc.getRefVersion(), desc.getLabel()); 367 tp.setDependencies(desc.getDependencies()); 368 tp.setDeprecated(desc.isDeprecated()); 369 tp.setDescription(desc.getDescription()); 370 tp.setDownloadLink(desc.getDownloadLink()); 371 tp.setEnabled(desc.isEnabled()); 372 tp.setEndOfAvailability(toDate(desc.getEndOfAvailability())); 373 tp.setParent(getTargetPackage(desc.getParent())); 374 tp.setRefVersion(desc.getRefVersion()); 375 tp.setReleaseDate(toDate(desc.getReleaseDate())); 376 tp.setRestricted(desc.isRestricted()); 377 tp.setStatus(desc.getStatus()); 378 tp.setTypes(desc.getTypes()); 379 return tp; 380 } 381 382 @Override 383 public TargetPlatformInstance getTargetPlatformInstance(String id, List<String> packages) { 384 if (id == null) { 385 return null; 386 } 387 388 TargetPlatformInstanceImpl tpi = createTargetPlatformInstanceFromId(id); 389 390 if (packages != null) { 391 for (String pkg : packages) { 392 TargetPackage tpkg = getTargetPackage(pkg); 393 if (tpkg != null) { 394 tpi.addEnabledPackage(tpkg); 395 } else { 396 log.warn(String.format("Referenced target package '%s' not found.", pkg)); 397 } 398 } 399 } 400 401 return tpi; 402 } 403 404 @Override 405 public List<TargetPlatform> getAvailableTargetPlatforms(TargetPlatformFilter filter) { 406 List<TargetPlatform> tps = new ArrayList<>(); 407 for (TargetPlatformDescriptor desc : platforms.getTargetPlatforms()) { 408 TargetPlatform tp = getTargetPlatform(desc); 409 if (tp == null) { 410 continue; 411 } 412 if (filter != null && !filter.accepts(tp)) { 413 continue; 414 } 415 tps.add(tp); 416 } 417 // always sort for a deterministic result 418 tps.sort(Comparator.comparing(TargetInfo::getId)); 419 return tps; 420 } 421 422 @Override 423 public List<TargetPlatformInfo> getAvailableTargetPlatformsInfo(TargetPlatformFilter filter) { 424 List<TargetPlatformInfo> tps = new ArrayList<>(); 425 for (TargetPlatformDescriptor desc : platforms.getTargetPlatforms()) { 426 TargetPlatformInfo tp = getTargetPlatformInfo(desc); 427 if (tp == null) { 428 continue; 429 } 430 if (filter != null && !filter.accepts(tp)) { 431 continue; 432 } 433 tps.add(tp); 434 } 435 tps.sort(Comparator.comparing(TargetInfo::getId)); 436 return tps; 437 } 438 439 @Override 440 public void deprecateTargetPlatform(boolean deprecate, final String id) { 441 Integer val = deprecate ? Integer.valueOf(1) : Integer.valueOf(0); 442 updateOrCreateEntry(id, DirectoryUpdater.DEPRECATED_PROP, val); 443 } 444 445 @Override 446 public void enableTargetPlatform(boolean enable, final String id) { 447 Integer val = enable ? Integer.valueOf(1) : Integer.valueOf(0); 448 updateOrCreateEntry(id, DirectoryUpdater.ENABLED_PROP, val); 449 } 450 451 @Override 452 public void restrictTargetPlatform(boolean restrict, final String id) { 453 Integer val = restrict ? Integer.valueOf(1) : Integer.valueOf(0); 454 updateOrCreateEntry(id, DirectoryUpdater.RESTRICTED_PROP, val); 455 } 456 457 @Override 458 public void setTrialTargetPlatform(boolean trial, final String id) { 459 Integer val = trial ? Integer.valueOf(1) : Integer.valueOf(0); 460 updateOrCreateEntry(id, DirectoryUpdater.TRIAL_PROP, val); 461 } 462 463 @Override 464 public void setDefaultTargetPlatform(boolean isDefault, final String id) { 465 Integer val = isDefault ? Integer.valueOf(1) : Integer.valueOf(0); 466 updateOrCreateEntry(id, DirectoryUpdater.DEFAULT_PROP, val); 467 } 468 469 @Override 470 public void restoreTargetPlatform(final String id) { 471 new DirectoryUpdater(getOverrideDirectory()) { 472 @Override 473 public void run(DirectoryService service, Session session) { 474 session.deleteEntry(id); 475 } 476 }.run(); 477 } 478 479 @Override 480 public void restoreAllTargetPlatforms() { 481 new DirectoryUpdater(getOverrideDirectory()) { 482 @Override 483 public void run(DirectoryService service, Session session) { 484 for (DocumentModel entry : session.getEntries()) { 485 session.deleteEntry(entry.getId()); 486 } 487 } 488 }.run(); 489 } 490 491 protected void updateOrCreateEntry(final String id, final String prop, final Integer value) { 492 new DirectoryUpdater(getOverrideDirectory()) { 493 @Override 494 public void run(DirectoryService service, Session session) { 495 DocumentModel doc = session.getEntry(id); 496 if (doc != null) { 497 doc.setProperty(DirectoryUpdater.SCHEMA, prop, value); 498 session.updateEntry(doc); 499 } else { 500 DocumentModel entry = BaseSession.createEntryModel(null, DirectoryUpdater.SCHEMA, null, null); 501 entry.setProperty(DirectoryUpdater.SCHEMA, prop, value); 502 entry.setProperty(DirectoryUpdater.SCHEMA, "id", id); 503 session.createEntry(entry); 504 } 505 } 506 }.run(); 507 } 508 509 protected DocumentModel getDirectoryEntry(String id) { 510 Session dirSession = null; 511 try { 512 // check if entry already exists 513 DirectoryService dirService = Framework.getService(DirectoryService.class); 514 String dirName = getOverrideDirectory(); 515 dirSession = dirService.open(dirName); 516 return dirSession.getEntry(id); 517 } finally { 518 if (dirSession != null) { 519 dirSession.close(); 520 } 521 } 522 } 523 524 @Override 525 public TargetPlatformInstance getDefaultTargetPlatformInstance(boolean restricted) { 526 TargetPlatformInstance tpi = null; 527 TargetPlatformFilterImpl filter = new TargetPlatformFilterImpl(); 528 filter.setFilterRestricted(restricted); 529 TargetPlatform defaultTP = getDefaultTargetPlatform(filter); 530 if (defaultTP != null) { 531 tpi = createTargetPlatformInstanceFromId(defaultTP.getId()); 532 } 533 534 return tpi; 535 } 536 537 /** 538 * Create a TargetPlatformInstance given an id. 539 * 540 * @since 5.9.3-NXP-15602 541 */ 542 protected TargetPlatformInstanceImpl createTargetPlatformInstanceFromId(String id) { 543 TargetPlatformDescriptor desc = platforms.getTargetPlatform(id); 544 if (desc == null) { 545 return null; 546 } 547 TargetPlatformInstanceImpl tpi = new TargetPlatformInstanceImpl(id, desc.getName(), desc.getVersion(), 548 desc.getRefVersion(), desc.getLabel()); 549 tpi.setDeprecated(desc.isDeprecated()); 550 tpi.setDescription(desc.getDescription()); 551 tpi.setDownloadLink(desc.getDownloadLink()); 552 tpi.setEnabled(desc.isEnabled()); 553 tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability())); 554 tpi.setFastTrack(desc.isFastTrack()); 555 tpi.setParent(getTargetPlatform(desc.getParent())); 556 tpi.setRefVersion(desc.getRefVersion()); 557 tpi.setReleaseDate(toDate(desc.getReleaseDate())); 558 tpi.setRestricted(desc.isRestricted()); 559 tpi.setStatus(desc.getStatus()); 560 tpi.setTypes(desc.getTypes()); 561 562 DocumentModel entry = getDirectoryEntry(id); 563 if (entry != null) { 564 Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP); 565 if (enabled != null && enabled.intValue() >= 0) { 566 tpi.setEnabled(enabled.intValue() != 0); 567 } 568 Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP); 569 if (restricted != null && restricted.intValue() >= 0) { 570 tpi.setRestricted(restricted.intValue() != 0); 571 } 572 Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP); 573 if (deprecated != null && deprecated.intValue() >= 0) { 574 tpi.setDeprecated(deprecated.intValue() != 0); 575 } 576 tpi.setOverridden(true); 577 } 578 579 return tpi; 580 } 581}