001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS <http://nuxeo.com> and others 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Jean-Marc Orliaguet, Chalmers 011 * 012 * $Id$ 013 */ 014 015package org.nuxeo.theme.themes; 016 017import java.io.BufferedReader; 018import java.io.File; 019import java.io.FilenameFilter; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.InputStreamReader; 023import java.io.Reader; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedHashMap; 034import java.util.LinkedHashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038import java.util.regex.Matcher; 039import java.util.regex.Pattern; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.codehaus.plexus.util.dag.CycleDetectedException; 044import org.codehaus.plexus.util.dag.DAG; 045import org.codehaus.plexus.util.dag.TopologicalSorter; 046import org.nuxeo.common.Environment; 047import org.nuxeo.runtime.api.Framework; 048import org.nuxeo.runtime.services.event.Event; 049import org.nuxeo.runtime.services.event.EventService; 050import org.nuxeo.theme.ApplicationType; 051import org.nuxeo.theme.CustomThemeNameFilter; 052import org.nuxeo.theme.Manager; 053import org.nuxeo.theme.NegotiationDef; 054import org.nuxeo.theme.Registrable; 055import org.nuxeo.theme.Utils; 056import org.nuxeo.theme.elements.Element; 057import org.nuxeo.theme.elements.ElementFactory; 058import org.nuxeo.theme.elements.ElementFormatter; 059import org.nuxeo.theme.elements.ElementType; 060import org.nuxeo.theme.elements.PageElement; 061import org.nuxeo.theme.elements.ThemeElement; 062import org.nuxeo.theme.engines.EngineType; 063import org.nuxeo.theme.formats.Format; 064import org.nuxeo.theme.formats.FormatFactory; 065import org.nuxeo.theme.formats.FormatType; 066import org.nuxeo.theme.formats.layouts.Layout; 067import org.nuxeo.theme.formats.styles.Style; 068import org.nuxeo.theme.formats.widgets.Widget; 069import org.nuxeo.theme.fragments.Fragment; 070import org.nuxeo.theme.fragments.FragmentFactory; 071import org.nuxeo.theme.fragments.FragmentType; 072import org.nuxeo.theme.models.Info; 073import org.nuxeo.theme.models.ModelType; 074import org.nuxeo.theme.nodes.Node; 075import org.nuxeo.theme.nodes.NodeException; 076import org.nuxeo.theme.perspectives.PerspectiveManager; 077import org.nuxeo.theme.perspectives.PerspectiveType; 078import org.nuxeo.theme.properties.FieldIO; 079import org.nuxeo.theme.relations.DefaultPredicate; 080import org.nuxeo.theme.relations.DyadicRelation; 081import org.nuxeo.theme.relations.Predicate; 082import org.nuxeo.theme.relations.Relation; 083import org.nuxeo.theme.resources.ResourceBank; 084import org.nuxeo.theme.resources.ResourceManager; 085import org.nuxeo.theme.resources.ResourceType; 086import org.nuxeo.theme.templates.TemplateEngineType; 087import org.nuxeo.theme.types.Type; 088import org.nuxeo.theme.types.TypeFamily; 089import org.nuxeo.theme.types.TypeRegistry; 090import org.nuxeo.theme.uids.Identifiable; 091import org.nuxeo.theme.uids.UidManager; 092import org.nuxeo.theme.views.ViewType; 093 094public final class ThemeManager implements Registrable { 095 096 public static final String THEME_TOPIC = "org.nuxeo.theme"; 097 098 public static final String THEME_REGISTERED_EVENT_ID = "themeRegistered"; 099 100 private static final Log log = LogFactory.getLog(ThemeManager.class); 101 102 private final Map<String, Long> lastModified = new HashMap<String, Long>(); 103 104 private final Map<String, ThemeElement> themes = new HashMap<String, ThemeElement>(); 105 106 private final Map<String, PageElement> pages = new HashMap<String, PageElement>(); 107 108 private final Map<String, List<Integer>> formatsByTypeName = new LinkedHashMap<String, List<Integer>>(); 109 110 private final Map<String, ModelType> modelsByClassname = new HashMap<String, ModelType>(); 111 112 private final Map<String, Map<String, Integer>> namedObjectsByTheme = new HashMap<String, Map<String, Integer>>(); 113 114 private final Map<Integer, String> themeOfNamedObjects = new HashMap<Integer, String>(); 115 116 private final Map<String, Info> infoMap = new HashMap<String, Info>(); 117 118 private static final Predicate PREDICATE_FORMAT_INHERIT = new DefaultPredicate("_ inherits from _"); 119 120 private final Map<String, String> cachedStyles = new HashMap<String, String>(); 121 122 private final Map<String, String> cachedResources = new HashMap<String, String>(); 123 124 private final Map<String, byte[]> cachedBinaries = new HashMap<String, byte[]>(); 125 126 private final List<String> resourceOrdering = new ArrayList<String>(); 127 128 private static File THEME_DIR; 129 130 private static final FilenameFilter CUSTOM_THEME_FILENAME_FILTER = new CustomThemeNameFilter(); 131 132 private static final int DEFAULT_THEME_INDENT = 2; 133 134 private static final String COLLECTION_CSS_MARKER = "COLLECTION"; 135 136 private static final Pattern styleResourceNamePattern = Pattern.compile("(.*?)\\s\\((.*?)\\)$", Pattern.DOTALL); 137 138 public static void createThemeDir() { 139 THEME_DIR = new File(Environment.getDefault().getData(), "themes"); 140 THEME_DIR.mkdirs(); 141 } 142 143 public static File getThemeDir() { 144 if (THEME_DIR == null || !THEME_DIR.exists()) { 145 createThemeDir(); 146 } 147 return THEME_DIR; 148 } 149 150 @Override 151 public synchronized void clear() { 152 themes.clear(); 153 pages.clear(); 154 formatsByTypeName.clear(); 155 modelsByClassname.clear(); 156 namedObjectsByTheme.clear(); 157 themeOfNamedObjects.clear(); 158 infoMap.clear(); 159 cachedStyles.clear(); 160 cachedResources.clear(); 161 cachedBinaries.clear(); 162 resourceOrdering.clear(); 163 lastModified.clear(); 164 } 165 166 public Map<String, Info> getGlobalInfoMap() { 167 return infoMap; 168 } 169 170 public static boolean validateThemeName(String themeName) { 171 return (themeName.matches("^([a-zA-Z]|[a-zA-Z][a-zA-Z0-9_\\-]*?[a-zA-Z0-9])$")); 172 } 173 174 public static String getCustomThemePath(String themeName) throws ThemeIOException { 175 String themeFileName = String.format("theme-%s.xml", themeName); 176 File file = new File(getThemeDir(), themeFileName); 177 try { 178 return file.getCanonicalPath(); 179 } catch (IOException e) { 180 throw new ThemeIOException("Could not get custom theme path: " + themeName, e); 181 } 182 } 183 184 public static List<File> getCustomThemeFiles() { 185 List<File> files = new ArrayList<File>(); 186 for (File f : getThemeDir().listFiles(CUSTOM_THEME_FILENAME_FILTER)) { 187 files.add(f); 188 } 189 return files; 190 } 191 192 public static ThemeDescriptor customizeTheme(ThemeDescriptor themeDescriptor) throws ThemeException { 193 String themeName = themeDescriptor.getName(); 194 if (!themeDescriptor.isCustomizable()) { 195 throw new ThemeException("Theme : " + themeName + " cannot be customized."); 196 } 197 198 ThemeSerializer serializer = new ThemeSerializer(); 199 String xmlSource; 200 try { 201 xmlSource = serializer.serializeToXml(themeDescriptor.getSrc(), 0); 202 } catch (ThemeIOException e) { 203 throw new ThemeException("Could not serialize theme: " + themeName, e); 204 } 205 ThemeDescriptor newThemeDescriptor = createCustomTheme(themeName); 206 String newSrc = newThemeDescriptor.getSrc(); 207 try { 208 Manager.getThemeManager().loadTheme(newSrc, xmlSource); 209 } catch (ThemeIOException e) { 210 throw new ThemeException("Could not update theme: " + newSrc, e); 211 } 212 try { 213 saveTheme(newSrc); 214 } catch (ThemeIOException e) { 215 throw new ThemeException("Could not save theme: " + newSrc, e); 216 } 217 218 newThemeDescriptor.setCustomization(true); 219 return newThemeDescriptor; 220 } 221 222 public static ThemeDescriptor uncustomizeTheme(ThemeDescriptor themeDescriptor) throws ThemeException { 223 ThemeManager themeManager = Manager.getThemeManager(); 224 String themeName = themeDescriptor.getName(); 225 226 if (!themeDescriptor.isCustomization()) { 227 throw new ThemeException("Theme : " + themeName + " cannot be uncustomized."); 228 } 229 230 String themeSrc = themeDescriptor.getSrc(); 231 try { 232 themeManager.deleteTheme(themeSrc); 233 } catch (ThemeIOException e) { 234 throw new ThemeException("Could not remove theme: " + themeSrc, e); 235 } 236 237 ThemeDescriptor newThemeDescriptor = getThemeDescriptorByThemeName(themeName); 238 loadTheme(newThemeDescriptor); 239 return newThemeDescriptor; 240 } 241 242 public static ThemeDescriptor createCustomTheme(String name) throws ThemeException { 243 ThemeManager themeManager = Manager.getThemeManager(); 244 ThemeElement theme = (ThemeElement) ElementFactory.create("theme"); 245 theme.setName(name); 246 Format themeWidget = themeManager.createWidget(); 247 themeWidget.setName("theme view"); 248 ElementFormatter.setFormat(theme, themeWidget); 249 // default page 250 PageElement page = (PageElement) ElementFactory.create("page"); 251 page.setName("default"); 252 Format pageWidget = themeManager.createWidget(); 253 pageWidget.setName("page frame"); 254 Format pageLayout = themeManager.createLayout(); 255 Format pageStyle = themeManager.createStyle(); 256 ElementFormatter.setFormat(page, pageWidget); 257 ElementFormatter.setFormat(page, pageStyle); 258 ElementFormatter.setFormat(page, pageLayout); 259 try { 260 theme.addChild(page); 261 } catch (NodeException e) { 262 throw new ThemeException(e.getMessage(), e); 263 } 264 // create a theme descriptor 265 ThemeDescriptor themeDescriptor = new ThemeDescriptor(); 266 themeDescriptor.setName(name); 267 String path; 268 try { 269 path = ThemeManager.getCustomThemePath(name); 270 } catch (ThemeIOException e) { 271 throw new ThemeException("Could not get file path for theme: " + name); 272 } 273 final String src = String.format("file:///%s", path); 274 themeDescriptor.setSrc(src); 275 TypeRegistry typeRegistry = Manager.getTypeRegistry(); 276 typeRegistry.register(themeDescriptor); 277 // register the theme 278 themeManager.registerTheme(theme); 279 // save the theme 280 try { 281 ThemeManager.saveTheme(themeDescriptor.getSrc()); 282 } catch (ThemeIOException e) { 283 throw new ThemeException("Could not save theme: " + name, e); 284 } 285 return themeDescriptor; 286 } 287 288 public static void updateThemeDescriptors() { 289 Map<String, List<ThemeDescriptor>> names = new HashMap<String, List<ThemeDescriptor>>(); 290 for (ThemeDescriptor themeDescriptor : getThemeDescriptors()) { 291 String themeName = themeDescriptor.getName(); 292 if (!names.containsKey(themeName)) { 293 names.put(themeName, new ArrayList<ThemeDescriptor>()); 294 } 295 names.get(themeName).add(themeDescriptor); 296 } 297 for (List<ThemeDescriptor> themeDescriptors : names.values()) { 298 for (ThemeDescriptor themeDescriptor : themeDescriptors) { 299 themeDescriptor.setCustomized(true); 300 themeDescriptor.setCustomization(false); 301 } 302 int size = themeDescriptors.size(); 303 themeDescriptors.get(size - 1).setCustomized(false); 304 if (size > 1) { 305 themeDescriptors.get(size - 1).setCustomization(true); 306 } 307 } 308 } 309 310 public static String getDefaultTheme(final String applicationPath) { 311 return getDefaultTheme(applicationPath, null); 312 } 313 314 public static String getDefaultTheme(final String... paths) { 315 String defaultTheme = ""; 316 ApplicationType application = null; 317 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 318 application = (ApplicationType) typeRegistry.lookup(TypeFamily.APPLICATION, paths); 319 if (application != null) { 320 NegotiationDef negotiation = application.getNegotiation(); 321 if (negotiation != null) { 322 defaultTheme = negotiation.getDefaultTheme(); 323 } 324 } 325 return defaultTheme; 326 } 327 328 public static Set<String> getThemeNames(final String templateEngine) { 329 Set<String> names = new HashSet<String>(); 330 for (ThemeDescriptor themeDef : getThemeDescriptors()) { 331 // Skip customized themes 332 if (themeDef.isCustomized()) { 333 continue; 334 } 335 if (templateEngine != null && !themeDef.isCompatibleWith(templateEngine)) { 336 continue; 337 } 338 names.add(themeDef.getName()); 339 } 340 return names; 341 } 342 343 public static ThemeDescriptor getThemeDescriptorByThemeName(final String templateEngine, final String themeName) { 344 for (ThemeDescriptor themeDef : getThemeDescriptors()) { 345 // Skip customized themes 346 if (themeDef.isCustomized()) { 347 continue; 348 } 349 if (templateEngine != null && !themeDef.isCompatibleWith(templateEngine)) { 350 continue; 351 } 352 final String name = themeDef.getName(); 353 if (name != null && name.equals(themeName)) { 354 return themeDef; 355 } 356 } 357 return null; 358 } 359 360 public static ThemeDescriptor getThemeDescriptorByThemeName(final String themeName) { 361 return getThemeDescriptorByThemeName(null, themeName); 362 } 363 364 public static Set<String> getThemeNames() { 365 return getThemeNames(null); 366 } 367 368 public Set<String> getPageNames(final String themeName) { 369 final ThemeElement theme = getThemeByName(themeName); 370 final Set<String> pageNames = new LinkedHashSet<String>(); 371 if (theme != null) { 372 for (PageElement page : getPagesOf(theme)) { 373 pageNames.add(page.getName()); 374 } 375 } 376 return pageNames; 377 } 378 379 public static List<PageElement> getPagesOf(final ThemeElement theme) { 380 final List<PageElement> themePages = new ArrayList<PageElement>(); 381 for (Node node : theme.getChildren()) { 382 final PageElement page = (PageElement) node; 383 themePages.add(page); 384 } 385 return themePages; 386 } 387 388 public List<PageElement> getPagesOf(final String themeName) { 389 final ThemeElement theme = getThemeByName(themeName); 390 if (theme == null) { 391 return null; 392 } 393 return getPagesOf(theme); 394 } 395 396 public static ThemeElement getThemeOf(final Element element) { 397 ThemeElement theme = null; 398 Element current = element; 399 while (current != null) { 400 if (current instanceof ThemeElement) { 401 theme = (ThemeElement) current; 402 break; 403 } 404 current = (Element) current.getParent(); 405 } 406 return theme; 407 } 408 409 public static boolean belongToSameTheme(final Element element1, final Element element2) { 410 return getThemeOf(element1) == getThemeOf(element2); 411 } 412 413 // Object lookups by URL 414 public static EngineType getEngineByUrl(final URL url) { 415 if (url == null) { 416 return null; 417 } 418 final String[] path = url.getPath().split("/"); 419 if (path.length <= 1) { 420 return null; 421 } 422 final String engineName = path[1]; 423 return (EngineType) Manager.getTypeRegistry().lookup(TypeFamily.ENGINE, engineName); 424 } 425 426 public static String getViewModeByUrl(final URL url) { 427 if (url == null) { 428 return null; 429 } 430 final String[] path = url.getPath().split("/"); 431 if (path.length <= 2) { 432 return null; 433 } 434 return path[2]; 435 } 436 437 public static TemplateEngineType getTemplateEngineByUrl(final URL url) { 438 if (url == null) { 439 return null; 440 } 441 final String[] path = url.getPath().split("/"); 442 if (path.length <= 3) { 443 return null; 444 } 445 final String templateEngineName = path[3]; 446 return (TemplateEngineType) Manager.getTypeRegistry().lookup(TypeFamily.TEMPLATE_ENGINE, templateEngineName); 447 } 448 449 public ThemeElement getThemeBySrc(final String src) throws ThemeException { 450 ThemeDescriptor themeDef = getThemeDescriptor(src); 451 if (themeDef.isCustomized()) { 452 throw new ThemeException("Cannot access customized theme: " + src); 453 } 454 String themeName = themeDef.getName(); 455 return getThemeByName(themeName); 456 } 457 458 public ThemeElement getThemeByUrl(final URL url) { 459 String themeName = getThemeNameByUrl(url); 460 if (themeName == null) { 461 return null; 462 } 463 return getThemeByName(themeName); 464 } 465 466 public static String getThemeNameByUrl(final URL url) { 467 if (url == null) { 468 return null; 469 } 470 if (!url.getHost().equals("theme")) { 471 return null; 472 } 473 final String[] path = url.getPath().split("/"); 474 if (path.length <= 4) { 475 return null; 476 } 477 return path[4]; 478 } 479 480 public static String getPagePathByUrl(final URL url) { 481 if (url == null) { 482 return null; 483 } 484 if (!url.getHost().equals("theme")) { 485 return null; 486 } 487 final String[] path = url.getPath().split("/"); 488 if (path.length <= 5) { 489 return null; 490 } 491 final String pagePath = path[4] + '/' + path[5]; 492 return pagePath; 493 } 494 495 public PageElement getThemePageByUrl(final URL url) { 496 if (url == null) { 497 return null; 498 } 499 if (!url.getHost().equals("theme")) { 500 return null; 501 } 502 final String pagePath = getPagePathByUrl(url); 503 return getPageByPath(pagePath); 504 } 505 506 public PageElement getPageByPath(final String path) { 507 return pages.get(path); 508 } 509 510 public static String getPageNameFromPagePath(final String path) { 511 if (path.contains("/")) { 512 return path.split("/")[1]; 513 } 514 return null; 515 } 516 517 public ThemeElement getThemeByName(final String name) { 518 return themes.get(name); 519 } 520 521 public void fillScratchPage(final String themeName, final Element element) throws NodeException, ThemeException { 522 String pagePath = String.format("%s/~", themeName); 523 524 PageElement scratchPage = getPageByPath(pagePath); 525 if (scratchPage != null) { 526 destroyDescendants(scratchPage); 527 removeRelationsOf(scratchPage); 528 pages.remove(pagePath); 529 removeOrphanedFormats(); 530 } 531 532 // create a new scratch page 533 scratchPage = (PageElement) ElementFactory.create("page"); 534 Widget pageWidget = (Widget) FormatFactory.create("widget"); 535 pageWidget.setName("page frame"); 536 registerFormat(pageWidget); 537 ElementFormatter.setFormat(scratchPage, pageWidget); 538 539 UidManager uidManager = Manager.getUidManager(); 540 uidManager.register(scratchPage); 541 pages.put(pagePath, scratchPage); 542 543 scratchPage.addChild(element); 544 } 545 546 public static Element getElementByUrl(final URL url) { 547 if (url == null) { 548 return null; 549 } 550 if (!url.getHost().equals("element")) { 551 return null; 552 } 553 final String[] path = url.getPath().split("/"); 554 if (path.length < 1) { 555 return null; 556 } 557 final String uid = path[path.length - 1]; 558 return (Element) Manager.getUidManager().getObjectByUid(Integer.valueOf(uid)); 559 } 560 561 public static PerspectiveType getPerspectiveByUrl(final URL url) { 562 if (url == null) { 563 return null; 564 } 565 if (!url.getHost().equals("theme")) { 566 return null; 567 } 568 final String[] path = url.getPath().split("/"); 569 if (path.length <= 6) { 570 return null; 571 } 572 final String perspectiveName = path[6]; 573 return (PerspectiveType) Manager.getTypeRegistry().lookup(TypeFamily.PERSPECTIVE, perspectiveName); 574 } 575 576 public static String getCollectionNameByUrl(final URL url) { 577 if (url == null) { 578 return null; 579 } 580 if (!url.getHost().equals("theme")) { 581 return null; 582 } 583 final String[] path = url.getPath().split("/"); 584 if (path.length <= 7) { 585 return null; 586 } 587 final String collectionName = path[7]; 588 // TODO: check to see if the collection exists? 589 return collectionName; 590 } 591 592 public static String getUrlDescription(URL url) { 593 final String[] path = url.getPath().split("/"); 594 String host = url.getHost(); 595 String description = "[???]"; 596 if ("theme".equals(host)) { 597 description = String.format("[THEME %s, PAGE %s, ENGINE %s, TEMPLATE %s, PERSPECTIVE %s, MODE %s]", 598 path[4], path[5], path[1], path[3], path[6], path[2]); 599 } else if ("element".equals(host)) { 600 description = String.format("[ELEMENT %s, ENGINE %s, TEMPLATE %s, MODE %s]", path[4], path[1], path[3], 601 path[2]); 602 } 603 return description; 604 } 605 606 // Named objects 607 public Identifiable getNamedObject(final String themeName, final String realm, final String name) { 608 final Map<String, Integer> objectsInTheme = namedObjectsByTheme.get(themeName); 609 if (objectsInTheme == null) { 610 return null; 611 } 612 final Integer uid = objectsInTheme.get(String.format("%s/%s", realm, name)); 613 614 if (uid != null) { 615 return (Identifiable) Manager.getUidManager().getObjectByUid(uid); 616 } 617 return null; 618 } 619 620 public String getThemeNameOfNamedObject(Identifiable object) { 621 return themeOfNamedObjects.get(object.getUid()); 622 } 623 624 public void setNamedObject(final String themeName, final String realm, final Identifiable object) 625 throws ThemeException { 626 if (!namedObjectsByTheme.containsKey(themeName)) { 627 namedObjectsByTheme.put(themeName, new LinkedHashMap<String, Integer>()); 628 } 629 final Integer uid = object.getUid(); 630 final String name = object.getName(); 631 if (name == null) { 632 throw new ThemeException("Cannot register unnamed object, uid: " + uid); 633 } 634 namedObjectsByTheme.get(themeName).put(String.format("%s/%s", realm, name), uid); 635 themeOfNamedObjects.put(uid, themeName); 636 } 637 638 public List<Identifiable> getNamedObjects(final String themeName, final String realm) { 639 final List<Identifiable> objects = new ArrayList<Identifiable>(); 640 final Map<String, Integer> objectsInTheme = namedObjectsByTheme.get(themeName); 641 final String prefix = String.format("%s/", realm); 642 final UidManager uidManager = Manager.getUidManager(); 643 if (objectsInTheme != null) { 644 for (Map.Entry<String, Integer> entry : objectsInTheme.entrySet()) { 645 if (entry.getKey().startsWith(prefix)) { 646 final Identifiable object = (Identifiable) uidManager.getObjectByUid(entry.getValue()); 647 objects.add(object); 648 } 649 } 650 } 651 return objects; 652 } 653 654 public void removeNamedObject(final String themeName, final String realm, final String name) { 655 final String key = String.format("%s/%s", realm, name); 656 Identifiable object = getNamedObject(themeName, realm, name); 657 themeOfNamedObjects.remove(object.getUid()); 658 namedObjectsByTheme.get(themeName).remove(key); 659 } 660 661 public void removeNamedObjects(final String themeName) { 662 namedObjectsByTheme.remove(themeName); 663 List<Integer> toDelete = new ArrayList<Integer>(); 664 for (Map.Entry<Integer, String> entry : themeOfNamedObjects.entrySet()) { 665 if (entry.getValue().equals(themeName)) { 666 toDelete.add(entry.getKey()); 667 } 668 } 669 for (Integer key : toDelete) { 670 themeOfNamedObjects.remove(key); 671 } 672 toDelete = null; 673 } 674 675 public void makeElementUseNamedStyle(final Element element, final String inheritedName, final String themeName) 676 throws ThemeException { 677 final FormatType styleType = (FormatType) Manager.getTypeRegistry().lookup(TypeFamily.FORMAT, "style"); 678 Style style = (Style) ElementFormatter.getFormatByType(element, styleType); 679 680 if (style == null) { 681 throw new ThemeException("Element has no assigned style: " + element.computeXPath()); 682 } 683 // Make the style no longer inherits from other another style if 684 // 'inheritedName' is null 685 if (inheritedName == null) { 686 ThemeManager.removeInheritanceTowards(style); 687 } else { 688 Style inheritedStyle = (Style) getNamedObject(themeName, "style", inheritedName); 689 if (inheritedStyle == null) { 690 throw new ThemeException("Could not find named style: " + inheritedName); 691 } 692 makeFormatInherit(style, inheritedStyle); 693 } 694 } 695 696 public static void setStyleInheritance(String styleName, String ancestorStyleName, String themeName, 697 boolean allowMany) throws ThemeException { 698 699 ThemeManager themeManager = Manager.getThemeManager(); 700 ThemeDescriptor themeDescriptor = ThemeManager.getThemeDescriptorByThemeName(themeName); 701 if (themeDescriptor == null) { 702 throw new ThemeException("Theme not found: " + themeName); 703 } 704 Style style = (Style) themeManager.getNamedObject(themeName, "style", styleName); 705 if (style == null) { 706 throw new ThemeException("Could not find named style: " + styleName); 707 } 708 709 Style ancestorStyle = (Style) themeManager.getNamedObject(themeName, "style", ancestorStyleName); 710 if (ancestorStyle == null) { 711 throw new ThemeException("Could not find named style: " + ancestorStyleName); 712 } 713 if (!allowMany) { 714 ThemeManager.removeInheritanceFrom(ancestorStyle); 715 } 716 themeManager.makeFormatInherit(style, ancestorStyle); 717 } 718 719 public static void loadRemoteStyle(String resourceBankName, Style style) throws ThemeException { 720 if (!style.isNamed()) { 721 throw new ThemeException("Only named styles can be loaded from resource banks."); 722 } 723 String styleName = style.getName(); 724 final Matcher resourceNameMatcher = styleResourceNamePattern.matcher(styleName); 725 if (resourceNameMatcher.find()) { 726 String collectionName = resourceNameMatcher.group(2); 727 String resourceId = resourceNameMatcher.group(1) + ".css"; 728 String cssSource = ResourceManager.getBankResource(resourceBankName, collectionName, "style", resourceId); 729 style.setCollection(collectionName); 730 Utils.loadCss(style, cssSource, "*"); 731 } else { 732 throw new ThemeException("Incorrect remote style name: " + styleName); 733 } 734 } 735 736 // Element actions 737 public Element duplicateElement(final Element element, final boolean duplicateFormats) throws ThemeException { 738 Element duplicate; 739 final String typeName = element.getElementType().getTypeName(); 740 741 if (element instanceof Fragment) { 742 final FragmentType fragmentType = ((Fragment) element).getFragmentType(); 743 duplicate = FragmentFactory.create(fragmentType.getTypeName()); 744 } else { 745 duplicate = ElementFactory.create(typeName); 746 } 747 748 if (duplicate == null) { 749 log.warn("Could not duplicate: " + element); 750 } else { 751 // duplicate the fields 752 try { 753 FieldIO.updateFieldsFromProperties(duplicate, FieldIO.dumpFieldsToProperties(element)); 754 } catch (ThemeIOException e) { 755 log.warn("Could not copy the fields of: " + element); 756 log.debug(e.getMessage(), e); 757 } 758 759 // duplicate formats or create a relation 760 for (Format format : ElementFormatter.getFormatsFor(element)) { 761 if (duplicateFormats) { 762 format = duplicateFormat(format); 763 } 764 ElementFormatter.setFormat(duplicate, format); 765 } 766 767 // duplicate description 768 duplicate.setDescription(element.getDescription()); 769 770 // duplicate visibility 771 PerspectiveManager perspectiveManager = Manager.getPerspectiveManager(); 772 for (PerspectiveType perspective : perspectiveManager.getPerspectivesFor(element)) { 773 PerspectiveManager.setVisibleInPerspective(duplicate, perspective); 774 } 775 } 776 return duplicate; 777 } 778 779 public void destroyElement(final Element element) throws ThemeException, NodeException { 780 final Element parent = (Element) element.getParent(); 781 782 if (element instanceof ThemeElement) { 783 removeNamedStylesOf(element.getName()); 784 unregisterTheme((ThemeElement) element); 785 destroyDescendants(element); 786 removeRelationsOf(element); 787 788 } else if (element instanceof PageElement) { 789 unregisterPage((PageElement) element); 790 destroyDescendants(element); 791 removeRelationsOf(element); 792 if (parent != null) { 793 parent.removeChild(element); 794 } 795 796 } else { 797 destroyDescendants(element); 798 removeRelationsOf(element); 799 if (parent != null) { 800 parent.removeChild(element); 801 } 802 } 803 804 // Final cleanup: remove formats that are not used by any element. 805 removeOrphanedFormats(); 806 } 807 808 public void removeNamedStylesOf(String themeName) throws ThemeException { 809 ThemeManager themeManager = Manager.getThemeManager(); 810 final UidManager uidManager = Manager.getUidManager(); 811 for (Style style : themeManager.getNamedStyles(themeName)) { 812 removeNamedObject(themeName, "style", style.getName()); 813 deleteFormat(style); 814 uidManager.unregister(style); 815 } 816 } 817 818 // Formats 819 public Format duplicateFormat(final Format format) throws ThemeException { 820 final String typeName = format.getFormatType().getTypeName(); 821 final Format duplicate = FormatFactory.create(typeName); 822 registerFormat(duplicate); 823 824 duplicate.setName(format.getName()); 825 duplicate.setDescription(format.getDescription()); 826 duplicate.clonePropertiesOf(format); 827 828 final Format ancestor = getAncestorFormatOf(format); 829 if (ancestor != null) { 830 makeFormatInherit(duplicate, ancestor); 831 } 832 return duplicate; 833 } 834 835 public List<Format> listFormats() { 836 final UidManager uidManager = Manager.getUidManager(); 837 List<Format> formats = new ArrayList<Format>(); 838 for (Map.Entry<String, List<Integer>> entry : formatsByTypeName.entrySet()) { 839 for (Integer uid : entry.getValue()) { 840 Format format = (Format) uidManager.getObjectByUid(uid); 841 formats.add(format); 842 } 843 } 844 return formats; 845 } 846 847 public void registerFormat(final Format format) throws ThemeException { 848 final Integer id = format.getUid(); 849 if (id == null) { 850 throw new ThemeException("Cannot register a format without an id"); 851 } 852 final String formatTypeName = format.getFormatType().getTypeName(); 853 if (formatTypeName == null) { 854 throw new ThemeException("Cannot register a format without a type"); 855 } 856 if (!formatsByTypeName.containsKey(formatTypeName)) { 857 formatsByTypeName.put(formatTypeName, new ArrayList<Integer>()); 858 } 859 final List<Integer> ids = formatsByTypeName.get(formatTypeName); 860 if (ids.contains(id)) { 861 throw new ThemeException("Cannot register a format twice: " + id); 862 } 863 ids.add(id); 864 } 865 866 public void unregisterFormat(final Format format) throws ThemeException { 867 final Integer id = format.getUid(); 868 if (id == null) { 869 throw new ThemeException("Cannot unregister a format without an id"); 870 } 871 final String formatTypeName = format.getFormatType().getTypeName(); 872 if (formatTypeName == null) { 873 throw new ThemeException("Cannot unregister a format without a type"); 874 } 875 if (formatsByTypeName.containsKey(formatTypeName)) { 876 final List<Integer> ids = formatsByTypeName.get(formatTypeName); 877 if (!ids.contains(id)) { 878 throw new ThemeException("Format with id: " + id + " is not registered."); 879 } 880 ids.remove(id); 881 } 882 removeInheritanceTowards(format); 883 removeInheritanceFrom(format); 884 } 885 886 public Set<String> getFormatTypeNames() { 887 return new LinkedHashSet<String>(formatsByTypeName.keySet()); 888 } 889 890 public List<Format> getFormatsByTypeName(final String formatTypeName) { 891 List<Format> formats = new ArrayList<Format>(); 892 if (!formatsByTypeName.containsKey(formatTypeName)) { 893 return formats; 894 } 895 UidManager uidManager = Manager.getUidManager(); 896 for (Integer id : formatsByTypeName.get(formatTypeName)) { 897 formats.add((Format) uidManager.getObjectByUid(id)); 898 } 899 return formats; 900 } 901 902 public List<Style> getStyles() { 903 return getStyles(null); 904 } 905 906 public List<Style> getStyles(String themeName) { 907 List<Style> styles = new ArrayList<Style>(); 908 for (Format format : getFormatsByTypeName("style")) { 909 Style style = (Style) format; 910 if (themeName != null) { 911 ThemeElement theme = getThemeOfFormat(style); 912 if (theme == null) { 913 if (!style.isNamed()) { 914 log.warn("THEME inconsistency: " + style + " is not associated to any element."); 915 } 916 continue; 917 } 918 if (!themeName.equals(theme.getName())) { 919 continue; 920 } 921 } 922 styles.add(style); 923 } 924 return styles; 925 } 926 927 public List<Style> getNamedStyles(String themeName) { 928 List<Style> styles = new ArrayList<Style>(); 929 // Add named styles 930 if (themeName != null) { 931 for (Identifiable object : getNamedObjects(themeName, "style")) { 932 if (!(object instanceof Style)) { 933 log.error("Expected Style object, got instead " + object); 934 continue; 935 } 936 styles.add((Style) object); 937 } 938 } 939 return styles; 940 } 941 942 public List<Style> getSortedNamedStyles(String themeName) { 943 List<Style> namedStyles = getNamedStyles(themeName); 944 945 // sort styles to have a deterministic topological sort 946 List<String> names = new ArrayList<>(namedStyles.size()); 947 Map<String, Style> allStyles = new HashMap<>(); 948 for (Style style : namedStyles) { 949 String name = style.getName(); 950 names.add(name); 951 allStyles.put(name, style); 952 } 953 Collections.sort(names); 954 namedStyles = new ArrayList<>(namedStyles.size()); 955 for (String name : names) { 956 namedStyles.add(allStyles.get(name)); 957 } 958 959 DAG graph = new DAG(); 960 for (Style s : namedStyles) { 961 String styleName = s.getName(); 962 graph.addVertex(styleName); 963 for (Format f : listFormatsDirectlyInheritingFrom(s)) { 964 if (!f.isNamed()) { 965 continue; 966 } 967 try { 968 graph.addEdge(styleName, f.getName()); 969 } catch (CycleDetectedException e) { 970 log.error("Cycle detected in style dependencies: ", e); 971 return namedStyles; 972 } 973 } 974 } 975 976 List<Style> styles = new ArrayList<Style>(); 977 for (Object name : TopologicalSorter.sort(graph)) { 978 styles.add((Style) getNamedObject(themeName, "style", (String) name)); 979 } 980 return styles; 981 } 982 983 // Cache management 984 public Long getLastModified(String themeName) { 985 final Long date = lastModified.get(themeName); 986 if (date == null) { 987 return 0L; 988 } 989 return date; 990 } 991 992 public void setLastModified(String themeName, Long date) { 993 lastModified.put(themeName, date); 994 } 995 996 public Long getLastModified(final URL url) { 997 final String themeName = getThemeNameByUrl(url); 998 return getLastModified(themeName); 999 } 1000 1001 public void themeModified(String themeName) { 1002 setLastModified(themeName, new Date().getTime()); 1003 Manager.getResourceManager().clearGlobalCache(themeName); 1004 } 1005 1006 public void stylesModified(String themeName) { 1007 resetCachedStyles(themeName); 1008 } 1009 1010 /** 1011 * Reset cached static resources, useful for hot reload 1012 * 1013 * @since 5.6 1014 */ 1015 public void resetCachedResources() { 1016 cachedResources.clear(); 1017 } 1018 1019 // Registration 1020 public void registerTheme(final ThemeElement theme) { 1021 String themeName = theme.getName(); 1022 1023 // store to the theme 1024 themes.put(themeName, theme); 1025 1026 // store the pages 1027 for (Node node : theme.getChildren()) { 1028 PageElement page = (PageElement) node; 1029 String pagePath = String.format("%s/%s", themeName, page.getName()); 1030 pages.put(pagePath, page); 1031 } 1032 1033 // hook to notify potential listeners that the theme was registered 1034 EventService eventService = Framework.getLocalService(EventService.class); 1035 eventService.sendEvent(new Event(THEME_TOPIC, THEME_REGISTERED_EVENT_ID, this, themeName)); 1036 1037 themeModified(themeName); 1038 stylesModified(themeName); 1039 } 1040 1041 public void registerPage(final ThemeElement theme, final PageElement page) throws NodeException { 1042 theme.addChild(page); 1043 String themeName = theme.getName(); 1044 String pageName = page.getName(); 1045 pages.put(String.format("%s/%s", themeName, pageName), page); 1046 log.debug("Added page: " + pageName + " to theme: " + themeName); 1047 } 1048 1049 public void unregisterTheme(final ThemeElement theme) { 1050 String themeName = theme.getName(); 1051 // remove pages 1052 for (PageElement page : getPagesOf(theme)) { 1053 unregisterPage(page); 1054 } 1055 // remove theme 1056 themes.remove(themeName); 1057 log.debug("Removed theme: " + themeName); 1058 } 1059 1060 public void unregisterPage(PageElement page) { 1061 ThemeElement theme = (ThemeElement) page.getParent(); 1062 if (theme == null) { 1063 log.debug("Page has no parent: " + page.getUid()); 1064 return; 1065 } 1066 String themeName = theme.getName(); 1067 String pageName = page.getName(); 1068 pages.remove(String.format("%s/%s", themeName, pageName)); 1069 log.debug("Removed page: " + pageName + " from theme: " + themeName); 1070 } 1071 1072 public static void loadTheme(ThemeDescriptor themeDescriptor) { 1073 themeDescriptor.setLoadingFailed(true); 1074 String src = themeDescriptor.getSrc(); 1075 if (src == null) { 1076 log.error("Could not load theme, source not set. "); 1077 return; 1078 } 1079 try { 1080 final boolean preload = false; 1081 ThemeParser.registerTheme(themeDescriptor, preload); 1082 themeDescriptor.setLoadingFailed(false); 1083 } catch (ThemeIOException e) { 1084 log.error("Could not register theme: " + src + " " + e.getMessage()); 1085 } 1086 } 1087 1088 // Theme management 1089 public void loadTheme(String src, String xmlSource) throws ThemeIOException, ThemeException { 1090 ThemeDescriptor themeDescriptor = getThemeDescriptor(src); 1091 if (themeDescriptor == null) { 1092 throw new ThemeIOException("Theme not found: " + src); 1093 } 1094 final String oldThemeName = themeDescriptor.getName(); 1095 themeDescriptor.setLoadingFailed(true); 1096 final boolean preload = false; 1097 ThemeParser.registerTheme(themeDescriptor, xmlSource, preload); 1098 String themeName = themeDescriptor.getName(); 1099 themeDescriptor.setName(themeName); 1100 1101 themeModified(themeName); 1102 stylesModified(themeName); 1103 updateThemeDescriptors(); 1104 // remove or restore customized themes 1105 if (!themeName.equals(oldThemeName)) { 1106 themes.remove(oldThemeName); 1107 for (ThemeDescriptor themeDef : getThemeDescriptors()) { 1108 if (oldThemeName.equals(themeDef.getName()) && !themeDef.isCustomized()) { 1109 loadTheme(themeDef.getSrc()); 1110 } 1111 } 1112 } 1113 } 1114 1115 public void loadTheme(String src) throws ThemeIOException, ThemeException { 1116 loadTheme(src, null); 1117 } 1118 1119 public void deleteTheme(String src) throws ThemeIOException, ThemeException { 1120 ThemeDescriptor themeDescriptor = getThemeDescriptor(src); 1121 if (themeDescriptor.isXmlConfigured()) { 1122 throw new ThemeIOException("Themes registered as contributions cannot be deleted: " + src); 1123 } 1124 final ThemeManager themeManager = Manager.getThemeManager(); 1125 final String themeName = themeDescriptor.getName(); 1126 ThemeElement theme = themeManager.getThemeByName(themeName); 1127 if (theme == null) { 1128 throw new ThemeIOException("Theme not found: " + themeName); 1129 } 1130 1131 URL url = null; 1132 try { 1133 url = new URL(src); 1134 } catch (MalformedURLException e) { 1135 throw new ThemeIOException(e); 1136 } 1137 1138 if (!url.getProtocol().equals("file")) { 1139 throw new ThemeIOException("Theme source is not that of a file: " + src); 1140 } 1141 1142 final File file = new File(url.getFile()); 1143 if (!file.exists()) { 1144 throw new ThemeIOException("File not found: " + src); 1145 } 1146 1147 final String themeFileName = String.format("theme-%s.bak", themeName); 1148 final File backupFile = new File(getThemeDir(), themeFileName); 1149 if (backupFile.exists()) { 1150 if (!backupFile.delete()) { 1151 throw new ThemeIOException("Error while deleting backup file: " + backupFile.getPath()); 1152 } 1153 } 1154 if (!file.renameTo(backupFile)) { 1155 throw new ThemeIOException("Error while creating backup file: " + backupFile.getPath()); 1156 } 1157 1158 try { 1159 themeManager.destroyElement(theme); 1160 } catch (NodeException e) { 1161 throw new ThemeIOException("Failed to delete theme: " + themeName, e); 1162 } catch (ThemeException e) { 1163 throw new ThemeIOException("Failed to delete theme: " + themeName, e); 1164 } 1165 1166 themes.remove(themeName); 1167 deleteThemeDescriptor(src); 1168 1169 updateThemeDescriptors(); 1170 1171 for (ThemeDescriptor themeDef : getThemeDescriptors()) { 1172 if (themeName.equals(themeDef.getName()) && !themeDef.isCustomized()) { 1173 loadTheme(themeDef.getSrc()); 1174 } 1175 } 1176 } 1177 1178 public void deletePage(String path) throws ThemeIOException, ThemeException { 1179 PageElement page = getPageByPath(path); 1180 if (page == null) { 1181 throw new ThemeIOException("Failed to delete unkown page: " + path); 1182 } 1183 try { 1184 destroyElement(page); 1185 } catch (NodeException e) { 1186 throw new ThemeIOException("Failed to delete page: " + path, e); 1187 } 1188 } 1189 1190 public static void saveTheme(final String src) throws ThemeIOException, ThemeException { 1191 saveTheme(src, DEFAULT_THEME_INDENT); 1192 } 1193 1194 public static void saveTheme(final String src, final int indent) throws ThemeIOException, ThemeException { 1195 ThemeDescriptor themeDescriptor = getThemeDescriptor(src); 1196 1197 if (themeDescriptor == null) { 1198 throw new ThemeIOException("Theme not found: " + src); 1199 } 1200 1201 if (!themeDescriptor.isWritable()) { 1202 throw new ThemeIOException("Protocol does not support output: " + src); 1203 } 1204 1205 ThemeSerializer serializer = new ThemeSerializer(); 1206 final String xml = serializer.serializeToXml(src, indent); 1207 1208 // Write the file 1209 URL url = null; 1210 try { 1211 url = new URL(src); 1212 } catch (MalformedURLException e) { 1213 throw new ThemeIOException("Could not save theme to " + src, e); 1214 } 1215 try { 1216 Utils.writeFile(url, xml); 1217 } catch (IOException e) { 1218 throw new ThemeIOException("Could not save theme to " + src, e); 1219 } 1220 themeDescriptor.setLastSaved(new Date()); 1221 log.debug("Saved theme: " + src); 1222 } 1223 1224 public static void repairTheme(ThemeElement theme) throws ThemeIOException { 1225 try { 1226 ThemeRepairer.repair(theme); 1227 } catch (ThemeException e) { 1228 throw new ThemeIOException("Could not repair theme: " + theme.getName(), e); 1229 } 1230 log.debug("Repaired theme: " + theme.getName()); 1231 } 1232 1233 public static String renderElement(URL url) throws ThemeException { 1234 String result = null; 1235 InputStream is = null; 1236 try { 1237 is = url.openStream(); 1238 Reader in = null; 1239 try { 1240 in = new BufferedReader(new InputStreamReader(is)); 1241 StringBuilder rendered = new StringBuilder(); 1242 int ch; 1243 while ((ch = in.read()) > -1) { 1244 rendered.append((char) ch); 1245 } 1246 result = rendered.toString(); 1247 } catch (IOException e) { 1248 throw new ThemeException(e); 1249 } finally { 1250 if (in != null) { 1251 in.close(); 1252 } 1253 } 1254 } catch (IOException e) { 1255 throw new ThemeException(e); 1256 } finally { 1257 if (is != null) { 1258 try { 1259 is.close(); 1260 } catch (IOException e) { 1261 log.error(e, e); 1262 } finally { 1263 is = null; 1264 } 1265 } 1266 } 1267 return result; 1268 } 1269 1270 public void removeOrphanedFormats() throws ThemeException { 1271 UidManager uidManager = Manager.getUidManager(); 1272 for (Format format : listFormats()) { 1273 // Skip named formats since they are not directly associated to an 1274 // element. 1275 if (format.isNamed()) { 1276 continue; 1277 } 1278 if (ElementFormatter.getElementsFor(format).isEmpty()) { 1279 deleteFormat(format); 1280 uidManager.unregister(format); 1281 } 1282 } 1283 } 1284 1285 private static void removeRelationsOf(Element element) { 1286 UidManager uidManager = Manager.getUidManager(); 1287 PerspectiveManager perspectiveManager = Manager.getPerspectiveManager(); 1288 for (Format format : ElementFormatter.getFormatsFor(element)) { 1289 ElementFormatter.removeFormat(element, format); 1290 } 1291 perspectiveManager.setAlwaysVisible(element); 1292 uidManager.unregister(element); 1293 } 1294 1295 private static void destroyDescendants(Element element) throws NodeException { 1296 for (Node node : element.getDescendants()) { 1297 removeRelationsOf((Element) node); 1298 } 1299 element.removeDescendants(); 1300 } 1301 1302 // Format inheritance 1303 public void makeFormatInherit(Format format, Format ancestor) { 1304 if (format.equals(ancestor)) { 1305 FormatType formatType = format.getFormatType(); 1306 String formatName = formatType != null ? formatType.getTypeName() : "unknown"; 1307 log.error(String.format("A format ('%s' with type '%s') cannot inherit from itself, aborting", 1308 format.getName(), formatName)); 1309 return; 1310 } 1311 if (listAncestorFormatsOf(ancestor).contains(format)) { 1312 log.error("Cycle detected.in format inheritance, aborting."); 1313 return; 1314 } 1315 // remove old inheritance relations 1316 removeInheritanceTowards(format); 1317 // set new ancestor 1318 DyadicRelation relation = new DyadicRelation(PREDICATE_FORMAT_INHERIT, format, ancestor); 1319 Manager.getRelationStorage().add(relation); 1320 } 1321 1322 public static void removeInheritanceTowards(Format descendant) { 1323 Collection<Relation> relations = Manager.getRelationStorage().search(PREDICATE_FORMAT_INHERIT, descendant, null); 1324 Iterator<Relation> it = relations.iterator(); 1325 while (it.hasNext()) { 1326 Relation relation = it.next(); 1327 Manager.getRelationStorage().remove(relation); 1328 } 1329 } 1330 1331 public static void removeInheritanceFrom(Format ancestor) { 1332 Collection<Relation> relations = Manager.getRelationStorage().search(PREDICATE_FORMAT_INHERIT, null, ancestor); 1333 Iterator<Relation> it = relations.iterator(); 1334 while (it.hasNext()) { 1335 Relation relation = it.next(); 1336 Manager.getRelationStorage().remove(relation); 1337 } 1338 } 1339 1340 public static Format getAncestorFormatOf(Format format) { 1341 Collection<Relation> relations = Manager.getRelationStorage().search(PREDICATE_FORMAT_INHERIT, format, null); 1342 Iterator<Relation> it = relations.iterator(); 1343 if (it.hasNext()) { 1344 return (Format) it.next().getRelate(2); 1345 } 1346 return null; 1347 } 1348 1349 public static List<Format> listAncestorFormatsOf(Format format) { 1350 List<Format> ancestors = new ArrayList<Format>(); 1351 Format current = format; 1352 while (current != null) { 1353 current = getAncestorFormatOf(current); 1354 if (current == null) { 1355 break; 1356 } 1357 // cycle detected 1358 if (ancestors.contains(current)) { 1359 break; 1360 } 1361 ancestors.add(current); 1362 } 1363 return ancestors; 1364 } 1365 1366 public static List<Format> listFormatsDirectlyInheritingFrom(Format format) { 1367 List<Format> formats = new ArrayList<Format>(); 1368 Collection<Relation> relations = Manager.getRelationStorage().search(PREDICATE_FORMAT_INHERIT, null, format); 1369 Iterator<Relation> it = relations.iterator(); 1370 while (it.hasNext()) { 1371 formats.add((Format) it.next().getRelate(1)); 1372 } 1373 return formats; 1374 } 1375 1376 public void deleteFormat(Format format) throws ThemeException { 1377 ThemeManager.removeInheritanceTowards(format); 1378 ThemeManager.removeInheritanceFrom(format); 1379 unregisterFormat(format); 1380 } 1381 1382 public static List<String> getUnusedStyleViews(Style style) { 1383 List<String> views = new ArrayList<String>(); 1384 if (style.isNamed()) { 1385 return views; 1386 } 1387 for (Element element : ElementFormatter.getElementsFor(style)) { 1388 Widget widget = (Widget) ElementFormatter.getFormatFor(element, "widget"); 1389 String viewName = widget.getName(); 1390 for (String name : style.getSelectorViewNames()) { 1391 if (!name.equals(viewName)) { 1392 views.add(name); 1393 } 1394 } 1395 } 1396 return views; 1397 } 1398 1399 // Cached styles 1400 public String getCachedStyles(String themeName, String basePath, String collectionName) { 1401 String key = String.format("%s|%s|%s", themeName, basePath != null ? basePath : "", 1402 collectionName != null ? collectionName : ""); 1403 return cachedStyles.get(key); 1404 } 1405 1406 public synchronized void setCachedStyles(String themeName, String basePath, String collectionName, String css) { 1407 String key = String.format("%s|%s|%s", themeName, basePath != null ? basePath : "", 1408 collectionName != null ? collectionName : ""); 1409 cachedStyles.put(key, css); 1410 } 1411 1412 private synchronized void resetCachedStyles(String themeName) { 1413 for (String key : cachedStyles.keySet()) { 1414 if (key.startsWith(themeName)) { 1415 cachedStyles.put(key, null); 1416 } 1417 } 1418 } 1419 1420 // Resources 1421 public String getResource(String name) { 1422 return cachedResources.get(name); 1423 } 1424 1425 public synchronized void setResource(String name, String content) { 1426 cachedResources.put(name, content); 1427 } 1428 1429 public synchronized void updateResourceOrdering() { 1430 DAG graph = new DAG(); 1431 for (Type type : Manager.getTypeRegistry().getTypes(TypeFamily.RESOURCE)) { 1432 ResourceType resourceType = (ResourceType) type; 1433 String resourceName = resourceType.getName(); 1434 graph.addVertex(resourceName); 1435 for (String dependency : resourceType.getDependencies()) { 1436 try { 1437 graph.addEdge(resourceName, dependency); 1438 } catch (CycleDetectedException e) { 1439 log.error("Cycle detected in resource dependencies: ", e); 1440 return; 1441 } 1442 } 1443 } 1444 resourceOrdering.clear(); 1445 for (Object r : TopologicalSorter.sort(graph)) { 1446 resourceOrdering.add((String) r); 1447 } 1448 } 1449 1450 public List<String> getResourceOrdering() { 1451 return resourceOrdering; 1452 } 1453 1454 /** 1455 * Returns all the ordered resource names and their dependencies, given a list of resources names. 1456 * 1457 * @since 5.5 1458 * @param resourceNames 1459 */ 1460 // TODO: optimize? 1461 public List<String> getOrderedResourcesAndDeps(List<String> resourceNames) { 1462 List<String> res = new ArrayList<String>(); 1463 if (resourceNames == null) { 1464 return res; 1465 } 1466 TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1467 for (String resourceName : resourceNames) { 1468 ResourceType resource = (ResourceType) typeRegistry.lookup(TypeFamily.RESOURCE, resourceName); 1469 if (resource == null) { 1470 log.error(String.format("Resource not registered %s.", resourceName)); 1471 continue; 1472 } 1473 String[] deps = resource.getDependencies(); 1474 if (deps != null) { 1475 for (String dep : deps) { 1476 res.add(dep); 1477 } 1478 } 1479 res.add(resourceName); 1480 } 1481 List<String> orderedRes = new ArrayList<String>(); 1482 List<String> ordered = getResourceOrdering(); 1483 if (ordered != null) { 1484 for (String resource : ordered) { 1485 if (res.contains(resource)) { 1486 orderedRes.add(resource); 1487 } 1488 } 1489 } 1490 return orderedRes; 1491 } 1492 1493 public void unregisterResourceOrdering(ResourceType resourceType) { 1494 String resourceName = resourceType.getName(); 1495 if (resourceOrdering.contains(resourceName)) { 1496 resourceOrdering.remove(resourceName); 1497 } 1498 } 1499 1500 public byte[] getImageResource(String path) throws ThemeException { 1501 String key = String.format("image/%s", path); 1502 byte[] data = cachedBinaries.get(key); 1503 if (data == null) { 1504 String[] parts = path.split("/"); 1505 if (parts.length != 3) { 1506 throw new ThemeException("Incorrect image path: " + path); 1507 } 1508 String resourceBankName = parts[0]; 1509 String collectionName = parts[1]; 1510 String resourceName = parts[2]; 1511 data = ResourceManager.getBinaryBankResource(resourceBankName, collectionName, "image", resourceName); 1512 cachedBinaries.put(key, data); 1513 } 1514 return data; 1515 } 1516 1517 public static List<ViewType> getViewTypesForFragmentType(final FragmentType fragmentType) { 1518 final List<ViewType> viewTypes = new ArrayList<ViewType>(); 1519 1520 for (Type v : Manager.getTypeRegistry().getTypes(TypeFamily.VIEW)) { 1521 final ViewType viewType = (ViewType) v; 1522 final String viewName = viewType.getViewName(); 1523 1524 if ("*".equals(viewName)) { 1525 continue; 1526 } 1527 1528 // select fragment views 1529 final ElementType elementType = viewType.getElementType(); 1530 if (elementType != null && !elementType.getTypeName().equals("fragment")) { 1531 continue; 1532 } 1533 1534 // select widget view types 1535 if (!viewType.getFormatType().getTypeName().equals("widget")) { 1536 continue; 1537 } 1538 1539 // match model types 1540 final ModelType modelType = viewType.getModelType(); 1541 if (fragmentType.getModelType() != modelType) { 1542 continue; 1543 } 1544 viewTypes.add(viewType); 1545 } 1546 return viewTypes; 1547 } 1548 1549 // Resource banks 1550 public static ResourceBank getResourceBank(String name) throws ThemeException { 1551 if (name == null) { 1552 throw new ThemeException("Resource bank name not set"); 1553 } 1554 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1555 ResourceBank resourceBank = (ResourceBank) typeRegistry.lookup(TypeFamily.RESOURCE_BANK, name); 1556 if (resourceBank != null) { 1557 return resourceBank; 1558 } else { 1559 throw new ThemeException("Resource bank not found: " + name); 1560 } 1561 } 1562 1563 public static List<ResourceBank> getResourceBanks() { 1564 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1565 List<ResourceBank> resourceBanks = new ArrayList<ResourceBank>(); 1566 for (Type type : typeRegistry.getTypes(TypeFamily.RESOURCE_BANK)) { 1567 resourceBanks.add((ResourceBank) type); 1568 } 1569 return resourceBanks; 1570 } 1571 1572 // Theme descriptors 1573 public static List<ThemeDescriptor> getThemeDescriptors() { 1574 final List<ThemeDescriptor> themeDescriptors = new ArrayList<ThemeDescriptor>(); 1575 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1576 for (Type type : typeRegistry.getTypes(TypeFamily.THEME)) { 1577 if (type != null) { 1578 ThemeDescriptor themeDescriptor = (ThemeDescriptor) type; 1579 themeDescriptors.add(themeDescriptor); 1580 } 1581 } 1582 return themeDescriptors; 1583 } 1584 1585 public static ThemeDescriptor getThemeDescriptor(String src) throws ThemeException { 1586 ThemeDescriptor themeDef = (ThemeDescriptor) Manager.getTypeRegistry().lookup(TypeFamily.THEME, src); 1587 if (themeDef == null) { 1588 throw new ThemeException("Unknown theme: " + src); 1589 } 1590 return themeDef; 1591 } 1592 1593 public static void deleteThemeDescriptor(String src) throws ThemeException { 1594 ThemeDescriptor themeDef = getThemeDescriptor(src); 1595 Manager.getTypeRegistry().unregister(themeDef); 1596 } 1597 1598 // Template engines 1599 public static List<String> getTemplateEngineNames() { 1600 List<String> types = new ArrayList<String>(); 1601 for (Type type : Manager.getTypeRegistry().getTypes(TypeFamily.TEMPLATE_ENGINE)) { 1602 types.add(type.getTypeName()); 1603 } 1604 return types; 1605 } 1606 1607 public static String getTemplateEngineName(String applicationPath) { 1608 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1609 if (applicationPath == null) { 1610 return ThemeManager.getDefaultTemplateEngineName(); 1611 } 1612 final ApplicationType application = (ApplicationType) typeRegistry.lookup(TypeFamily.APPLICATION, 1613 applicationPath); 1614 1615 if (application != null) { 1616 return application.getTemplateEngine(); 1617 } 1618 return getDefaultTemplateEngineName(); 1619 } 1620 1621 public static String getDefaultTemplateEngineName() { 1622 // TODO use XML configuration 1623 return "jsf-facelets"; 1624 } 1625 1626 public static Element getElementById(final Integer id) { 1627 Object object = Manager.getUidManager().getObjectByUid(id); 1628 if (!(object instanceof Element)) { 1629 return null; 1630 } 1631 return (Element) object; 1632 } 1633 1634 public static Element getElementById(final String id) { 1635 return getElementById(Integer.valueOf(id)); 1636 } 1637 1638 public static Format getFormatById(final Integer id) { 1639 Object object = Manager.getUidManager().getObjectByUid(id); 1640 if (!(object instanceof Format)) { 1641 return null; 1642 } 1643 return (Format) object; 1644 } 1645 1646 public static Format getFormatById(final String id) { 1647 return getFormatById(Integer.valueOf(id)); 1648 } 1649 1650 public static ThemeElement getThemeOfFormat(Format format) { 1651 Collection<Element> elements = ElementFormatter.getElementsFor(format); 1652 if (elements.isEmpty()) { 1653 return null; 1654 } 1655 // Get the first element assuming all elements belong to the same 1656 // theme. 1657 Element element = elements.iterator().next(); 1658 return getThemeOf(element); 1659 } 1660 1661 public Layout createLayout() { 1662 Layout layout = null; 1663 try { 1664 layout = (Layout) FormatFactory.create("layout"); 1665 registerFormat(layout); 1666 } catch (ThemeException e) { 1667 log.error("Layout creation failed", e); 1668 } 1669 return layout; 1670 } 1671 1672 public Widget createWidget() { 1673 Widget widget = null; 1674 try { 1675 widget = (Widget) FormatFactory.create("widget"); 1676 registerFormat(widget); 1677 } catch (ThemeException e) { 1678 log.error("Widget creation failed", e); 1679 } 1680 return widget; 1681 } 1682 1683 public Style createStyle() { 1684 Style style = null; 1685 try { 1686 style = (Style) FormatFactory.create("style"); 1687 registerFormat(style); 1688 } catch (ThemeException e) { 1689 log.error("Style creation failed", e); 1690 } 1691 return style; 1692 } 1693 1694 public void registerModelByClassname(ModelType modelType) { 1695 modelsByClassname.put(modelType.getClassName(), modelType); 1696 } 1697 1698 public void unregisterModelByClassname(ModelType modelType) { 1699 modelsByClassname.remove(modelType.getClassName()); 1700 } 1701 1702 public ModelType getModelByClassname(String className) { 1703 return modelsByClassname.get(className); 1704 } 1705 1706 // Theme sets 1707 public List<ThemeSet> getThemeSets() { 1708 List<ThemeSet> themeSets = new ArrayList<ThemeSet>(); 1709 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1710 for (Type type : typeRegistry.getTypes(TypeFamily.THEMESET)) { 1711 themeSets.add((ThemeSet) type); 1712 } 1713 return themeSets; 1714 } 1715 1716 public ThemeSet getThemeSetByName(final String name) { 1717 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 1718 return (ThemeSet) typeRegistry.lookup(TypeFamily.THEMESET, name); 1719 } 1720 1721 public static String getCollectionCssMarker() { 1722 return COLLECTION_CSS_MARKER; 1723 } 1724 1725}