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