001package jmri.configurexml;
002
003import java.io.File;
004import java.net.URL;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010import jmri.InstanceManager;
011import jmri.jmrit.XmlFile;
012import jmri.jmrit.revhistory.FileHistory;
013import jmri.util.FileUtil;
014import org.jdom2.Attribute;
015import org.jdom2.Document;
016import org.jdom2.Element;
017import org.jdom2.ProcessingInstruction;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Provides the mechanisms for storing an entire layout configuration to XML.
023 * "Layout" refers to the hardware: Specific communication systems, etc.
024 *
025 * @see <a href="package-summary.html">Package summary for details of the
026 * overall structure</a>
027 * @author Bob Jacobsen Copyright (c) 2002, 2008
028 */
029public class ConfigXmlManager extends jmri.jmrit.XmlFile
030        implements jmri.ConfigureManager {
031
032    /**
033     * Define the current schema version string for the layout-config schema.
034     * See the <a href="package-summary.html#schema">Schema versioning
035     * discussion</a>. Also controls the stylesheet file version.
036     */
037    static final public String schemaVersion = "-5-5-5";
038
039    public ConfigXmlManager() {
040    }
041
042    /** {@inheritDoc} */
043    @Override
044    public void registerConfig(Object o) {
045        registerConfig(o, 50);
046    }
047
048    /** {@inheritDoc} */
049    @Override
050    public void registerPref(Object o) {
051        // skip if already present, leaving in original order
052        if (plist.contains(o)) {
053            return;
054        }
055        confirmAdapterAvailable(o);
056        // and add to list
057        plist.add(o);
058    }
059
060    /**
061     * Common check routine to confirm an adapter is available as part of
062     * registration process.
063     * <p>
064     * Note: Should only be called for debugging purposes, for example, when
065     * Log4J DEBUG level is selected, to load fewer classes at startup.
066     *
067     * @param o object to confirm XML adapter exists for
068     */
069    void confirmAdapterAvailable(Object o) {
070        if (log.isDebugEnabled()) {
071            String adapter = adapterName(o);
072            log.debug("register {} adapter {}", o, adapter);
073            if (adapter != null) {
074                try {
075                    Class.forName(adapter);
076                } catch (ClassNotFoundException | NoClassDefFoundError ex) {
077                    locateClassFailed(ex, adapter, o);
078                }
079            }
080        }
081    }
082
083    /**
084     * Handles ConfigureXml classes that have moved to a new package or been
085     * superseded.
086     *
087     * @param name name of the moved or superceded ConfigureXml class
088     * @return name of the ConfigureXml class in newer package or of superseding
089     *         class
090     */
091    static public String currentClassName(String name) {
092        return InstanceManager.getDefault(ClassMigrationManager.class).getClassName(name);
093    }
094
095    /** {@inheritDoc} */
096    @Override
097    public void removePrefItems() {
098        log.debug("removePrefItems dropped {}", plist.size());
099        plist.clear();
100    }
101
102    /** {@inheritDoc} */
103    @Override
104    public Object findInstance(Class<?> c, int index) {
105        List<Object> temp = new ArrayList<>(plist);
106        temp.addAll(clist.keySet());
107        temp.addAll(tlist);
108        temp.addAll(ulist);
109        temp.addAll(uplist);
110        for (Object o : temp) {
111            if (c.isInstance(o)) {
112                if (index-- == 0) {
113                    return o;
114                }
115            }
116        }
117        return null;
118    }
119
120    /** {@inheritDoc} */
121    @Override
122    public List<Object> getInstanceList(Class<?> c) {
123        List<Object> result = new ArrayList<>();
124
125        List<Object> temp = new ArrayList<>(plist);
126        temp.addAll(clist.keySet());
127        temp.addAll(tlist);
128        temp.addAll(ulist);
129        temp.addAll(uplist);
130        for (Object o : temp) {
131            if (c.isInstance(o)) {
132                result.add(o);
133            }
134        }
135        return result;
136    }
137
138    /** {@inheritDoc} */
139    @Override
140    public void registerConfig(Object o, int x) {
141        // skip if already present, leaving in original order
142        if (clist.containsKey(o)) {
143            return;
144        }
145        confirmAdapterAvailable(o);
146        // and add to list
147        clist.put(o, x);
148    }
149
150    /** {@inheritDoc} */
151    @Override
152    public void registerTool(Object o) {
153        // skip if already present, leaving in original order
154        if (tlist.contains(o)) {
155            return;
156        }
157        confirmAdapterAvailable(o);
158        // and add to list
159        tlist.add(o);
160    }
161
162    /**
163     * Register an object whose state is to be tracked. It is not an error if
164     * the original object was already registered.
165     *
166     * @param o The object, which must have an associated adapter class.
167     */
168    @Override
169    public void registerUser(Object o) {
170        // skip if already present, leaving in original order
171        if (ulist.contains(o)) {
172            return;
173        }
174        confirmAdapterAvailable(o);
175        // and add to list
176        ulist.add(o);
177    }
178
179    /** {@inheritDoc} */
180    @Override
181    public void registerUserPrefs(Object o) {
182        // skip if already present, leaving in original order
183        if (uplist.contains(o)) {
184            return;
185        }
186        confirmAdapterAvailable(o);
187        // and add to list
188        uplist.add(o);
189    }
190
191    /** {@inheritDoc} */
192    @Override
193    public void deregister(Object o) {
194        plist.remove(o);
195        if (o != null) {
196            clist.remove(o);
197        }
198        tlist.remove(o);
199        ulist.remove(o);
200        uplist.remove(o);
201    }
202
203    private List<Object> plist = new ArrayList<>();
204    Map<Object, Integer> clist = Collections.synchronizedMap(new LinkedHashMap<>());
205    private List<Object> tlist = new ArrayList<>();
206    private List<Object> ulist = new ArrayList<>();
207    private List<Object> uplist = new ArrayList<>();
208    private final List<Element> loadDeferredList = new ArrayList<>();
209
210    /**
211     * Find the name of the adapter class for an object.
212     *
213     * @param o object of a configurable type
214     * @return class name of adapter
215     */
216    public static String adapterName(Object o) {
217        String className = o.getClass().getName();
218        log.trace("handle object of class {}", className);
219        int lastDot = className.lastIndexOf(".");
220        if (lastDot > 0) {
221            // found package-class boundary OK
222            String result = className.substring(0, lastDot)
223                    + ".configurexml."
224                    + className.substring(lastDot + 1, className.length())
225                    + "Xml";
226            log.trace("adapter class name is {}", result);
227            return result;
228        } else {
229            // no last dot found!
230            log.error("No package name found, which is not yet handled!");
231            return null;
232        }
233    }
234
235    /**
236     * Handle failure to load adapter class. Although only a one-liner in this
237     * class, it is a separate member to facilitate testing.
238     *
239     * @param ex          the exception throw failing to load adapterName as o
240     * @param adapterName name of the adapter class
241     * @param o           adapter object
242     */
243    void locateClassFailed(Throwable ex, String adapterName, Object o) {
244        log.error("{} could not load adapter class {}", ex, adapterName);
245        log.debug("Stack trace is", ex);
246    }
247
248    protected Element initStore() {
249        Element root = new Element("layout-config");
250        root.setAttribute("noNamespaceSchemaLocation",
251                "http://jmri.org/xml/schema/layout" + schemaVersion + ".xsd",
252                org.jdom2.Namespace.getNamespace("xsi",
253                        "http://www.w3.org/2001/XMLSchema-instance"));
254        return root;
255    }
256
257    protected void addPrefsStore(Element root) {
258        for (int i = 0; i < plist.size(); i++) {
259            Object o = plist.get(i);
260            Element e = elementFromObject(o);
261            if (e != null) {
262                root.addContent(e);
263            }
264        }
265    }
266
267    protected boolean addConfigStore(Element root) {
268        boolean result = true;
269        List<Map.Entry<Object, Integer>> l = new ArrayList<>(clist.entrySet());
270        Collections.sort(l, (Map.Entry<Object, Integer> o1, Map.Entry<Object, Integer> o2) -> o1.getValue().compareTo(o2.getValue()));
271        for (int i = 0; i < l.size(); i++) {
272            try {
273                Object o = l.get(i).getKey();
274                Element e = elementFromObject(o);
275                if (e != null) {
276                    root.addContent(e);
277                }
278            } catch (Exception e) {
279                storingErrorEncountered(null, "storing to file in addConfigStore",
280                        "Exception thrown", null, null, e);
281                result = false;
282            }
283        }
284        return result;
285    }
286
287    protected boolean addToolsStore(Element root) {
288        boolean result = true;
289        for (Object o : tlist) {
290            try {
291                Element e = elementFromObject(o);
292                if (e != null) {
293                    root.addContent(e);
294                }
295            } catch (Exception e) {
296                result = false;
297                storingErrorEncountered(null, "storing to file in addToolsStore",
298                        "Exception thrown", null, null, e);
299            }
300        }
301        return result;
302    }
303
304    protected boolean addUserStore(Element root) {
305        boolean result = true;
306        for (Object o : ulist) {
307            try {
308                Element e = elementFromObject(o);
309                if (e != null) {
310                    root.addContent(e);
311                }
312            } catch (Exception e) {
313                result = false;
314                storingErrorEncountered(null, "storing to file in addUserStore",
315                        "Exception thrown", null, null, e);
316            }
317        }
318        return result;
319    }
320
321    protected void addUserPrefsStore(Element root) {
322        for (Object o : uplist) {
323            Element e = elementFromObject(o);
324            if (e != null) {
325                root.addContent(e);
326            }
327        }
328    }
329
330    protected void includeHistory(Element root, File file) {
331        // add history to end of document
332        if (InstanceManager.getNullableDefault(FileHistory.class) != null) {
333            root.addContent(jmri.jmrit.revhistory.configurexml.FileHistoryXml.storeDirectly(
334                    InstanceManager.getDefault(FileHistory.class), file.getPath()));
335        }
336    }
337
338    protected boolean finalStore(Element root, File file) {
339        try {
340            // Document doc = newDocument(root, dtdLocation+"layout-config-"+dtdVersion+".dtd");
341            Document doc = newDocument(root);
342
343            // add XSLT processing instruction
344            // <?xml-stylesheet type="text/xsl" href="XSLT/panelfile"+schemaVersion+".xsl"?>
345            java.util.Map<String, String> m = new java.util.HashMap<>();
346            m.put("type", "text/xsl");
347            m.put("href", xsltLocation + "panelfile" + schemaVersion + ".xsl");
348            ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
349            doc.addContent(0, p);
350
351            // add version at front
352            storeVersion(root);
353
354            writeXML(file, doc);
355        } catch (java.io.FileNotFoundException ex3) {
356            storingErrorEncountered(null, "storing to file " + file.getName(),
357                    "File not found " + file.getName(), null, null, ex3);
358            log.error("FileNotFound error writing file: {}", ex3.getLocalizedMessage());
359            return false;
360        } catch (java.io.IOException ex2) {
361            storingErrorEncountered(null, "storing to file " + file.getName(),
362                    "IO error writing file " + file.getName(), null, null, ex2);
363            log.error("IO error writing file: {}", ex2.getLocalizedMessage());
364            return false;
365        }
366        return true;
367    }
368
369    /** {@inheritDoc} */
370    @Override
371    public void storePrefs() {
372        storePrefs(prefsFile);
373    }
374
375    /** {@inheritDoc} */
376    @Override
377    public void storePrefs(File file) {
378        synchronized (this) {
379            Element root = initStore();
380            addPrefsStore(root);
381            finalStore(root, file);
382        }
383    }
384
385    /** {@inheritDoc} */
386    @Override
387    public void storeUserPrefs(File file) {
388        synchronized (this) {
389            Element root = initStore();
390            addUserPrefsStore(root);
391            finalStore(root, file);
392        }
393    }
394
395    /**
396     * Set location for preferences file.
397     * <p>
398     * File need not exist, but location must be writable when storePrefs()
399     * called.
400     *
401     * @param prefsFile new location for preferences file
402     */
403    public void setPrefsLocation(File prefsFile) {
404        this.prefsFile = prefsFile;
405    }
406    File prefsFile;
407
408    /** {@inheritDoc} */
409    @Override
410    public boolean storeConfig(File file) {
411        boolean result = true;
412        Element root = initStore();
413        if (!addConfigStore(root)) {
414            result = false;
415        }
416        includeHistory(root, file);
417        if (!finalStore(root, file)) {
418            result = false;
419        }
420        return result;
421    }
422
423    /** {@inheritDoc} */
424    @Override
425    public boolean storeUser(File file) {
426        boolean result = true;
427        Element root = initStore();
428        if (!addConfigStore(root)) {
429            result = false;
430        }
431        if (!addUserStore(root)) {
432            result = false;
433        }
434        includeHistory(root, file);
435        if (!finalStore(root, file)) {
436            result = false;
437        }
438        return result;
439    }
440
441    /** {@inheritDoc} */
442    @Override
443    public boolean makeBackup(File file) {
444        return makeBackupFile(FileUtil.getUserFilesPath() + "backupPanels", file);
445    }
446
447    /**
448     *
449     * @param o The object to get an XML representation of
450     * @return An XML element representing o
451     */
452    static public Element elementFromObject(Object o) {
453        return ConfigXmlManager.elementFromObject(o, true);
454    }
455
456    /**
457     *
458     * @param object The object to get an XML representation of
459     * @param shared true if the XML should be shared, false if the XML should
460     *               be per-node
461     * @return An XML element representing object
462     */
463    static public Element elementFromObject(Object object, boolean shared) {
464        String aName = adapterName(object);
465        log.debug("store using {}", aName);
466        XmlAdapter adapter = null;
467        try {
468            adapter = (XmlAdapter) Class.forName(adapterName(object)).getDeclaredConstructor().newInstance();
469        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException
470                    | NoSuchMethodException | java.lang.reflect.InvocationTargetException ex) {
471            log.error("Cannot load configuration adapter for {}", object.getClass().getName(), ex);
472        }
473        if (adapter != null) {
474            return adapter.store(object, shared);
475        } else {
476            log.error("Cannot store configuration for {}", object.getClass().getName());
477            return null;
478        }
479    }
480
481    private void storeVersion(Element root) {
482        // add version at front
483        root.addContent(0,
484                new Element("jmriversion")
485                        .addContent(new Element("major").addContent("" + jmri.Version.major))
486                        .addContent(new Element("minor").addContent("" + jmri.Version.minor))
487                        .addContent(new Element("test").addContent("" + jmri.Version.test))
488                        .addContent(new Element("modifier").addContent(jmri.Version.getModifier()))
489        );
490    }
491
492    /**
493     * Load a file.
494     * <p>
495     * Handles problems locally to the extent that it can, by routing them to
496     * the creationErrorEncountered method.
497     *
498     * @param fi file to load
499     * @return true if no problems during the load
500     * @throws jmri.configurexml.JmriConfigureXmlException if unable to load
501     *                                                     file
502     */
503    @Override
504    public boolean load(File fi) throws JmriConfigureXmlException {
505        return load(fi, false);
506    }
507
508    /** {@inheritDoc} */
509    @Override
510    public boolean load(URL url) throws JmriConfigureXmlException {
511        return load(url, false);
512    }
513
514    /**
515     * Load a file.
516     * <p>
517     * Handles problems locally to the extent that it can, by routing them to
518     * the creationErrorEncountered method.
519     *
520     * @param fi               file to load
521     * @param registerDeferred true to register objects to defer
522     * @return true if no problems during the load
523     * @throws JmriConfigureXmlException if problem during load
524     * @see jmri.configurexml.XmlAdapter#loadDeferred()
525     * @since 2.11.2
526     */
527    @Override
528    public boolean load(File fi, boolean registerDeferred) throws JmriConfigureXmlException {
529        return this.load(FileUtil.fileToURL(fi), registerDeferred);
530    }
531
532    /**
533     * Load a file.
534     * <p>
535     * Handles problems locally to the extent that it can, by routing them to
536     * the creationErrorEncountered method.
537     * <p>
538     * Always processes on Swing thread
539     *
540     * @param url              URL of file to load
541     * @param registerDeferred true to register objects to defer
542     * @return true if no problems during the load
543     * @throws JmriConfigureXmlException if problem during load
544     * @see jmri.configurexml.XmlAdapter#loadDeferred()
545     * @since 3.3.2
546     */
547    @Override
548    public boolean load(URL url, boolean registerDeferred) throws JmriConfigureXmlException {
549        log.trace("starting load({}, {})", url, registerDeferred);
550
551        // we do the actual load on the Swing thread in case it changes visible windows
552        Boolean retval = jmri.util.ThreadingUtil.runOnGUIwithReturn(() -> {
553            try {
554                Boolean ret = loadOnSwingThread(url, registerDeferred);
555                return ret;
556            } catch (Exception e) {
557                log.trace("  ending load() via JmriConfigureXmlException");
558                throw new RuntimeException(e);
559            }
560        });
561
562        log.trace("  ending load({}, {} with {})", url, registerDeferred, retval);
563        return retval;
564    }
565
566    private XmlFile.Validate validate = XmlFile.Validate.CheckDtdThenSchema;
567
568    /** {@inheritDoc} */
569    @Override
570    public void setValidate(XmlFile.Validate v) {
571        validate = v;
572    }
573
574    /** {@inheritDoc} */
575    @Override
576    public XmlFile.Validate getValidate() {
577        return validate;
578    }
579
580    // must run on GUI thread only; that's ensured at the using level.
581    private Boolean loadOnSwingThread(URL url, boolean registerDeferred) throws JmriConfigureXmlException {
582        boolean result = true;
583        Element root = null;
584        /* We will put all the elements into a load list, along with the load order
585         As XML files prior to 2.13.1 had no order to the store, beans would be stored/loaded
586         before beans that they were dependant upon had been stored/loaded
587         */
588        Map<Element, Integer> loadlist = Collections.synchronizedMap(new LinkedHashMap<>());
589
590        try {
591            setValidate(validate);
592            root = super.rootFromURL(url);
593            // get the objects to load
594            List<Element> items = root.getChildren();
595            for (Element item : items) {
596                //Put things into an ordered list
597                Attribute a = item.getAttribute("class");
598                if (a == null) {
599                    // this is an element that we're not meant to read
600                    log.debug("skipping {}", item);
601                    continue;
602                }
603                String adapterName = a.getValue();
604                log.debug("attempt to get adapter {} for {}", adapterName, item);
605                adapterName = currentClassName(adapterName);
606                XmlAdapter adapter = (XmlAdapter) Class.forName(adapterName).getDeclaredConstructor().newInstance();
607                int order = adapter.loadOrder();
608                log.debug("add {} to load list with order id of {}", item, order);
609                loadlist.put(item, order);
610            }
611
612            List<Map.Entry<Element, Integer>> l = new ArrayList<>(loadlist.entrySet());
613            Collections.sort(l, (Map.Entry<Element, Integer> o1, Map.Entry<Element, Integer> o2) -> o1.getValue().compareTo(o2.getValue()));
614
615            for (Map.Entry<Element, Integer> elementIntegerEntry : l) {
616                Element item = elementIntegerEntry.getKey();
617                String adapterName = item.getAttribute("class").getValue();
618                adapterName = currentClassName(adapterName);
619                log.debug("load {} via {}", item, adapterName);
620                XmlAdapter adapter = null;
621                try {
622                    adapter = (XmlAdapter) Class.forName(adapterName).getDeclaredConstructor().newInstance();
623
624                    // get version info
625                    // loadVersion(root, adapter);
626                    // and do it
627                    if (adapter.loadDeferred() && registerDeferred) {
628                        // register in the list for deferred load
629                        loadDeferredList.add(item);
630                        log.debug("deferred load registered for {} {}", item, adapterName);
631                    } else {
632                        boolean loadStatus = adapter.load(item, item);
633                        log.debug("load status for {} {} is {}", item, adapterName, loadStatus);
634
635                        // if any adaptor load fails, then the entire load has failed
636                        if (!loadStatus) {
637                            result = false;
638                        }
639                    }
640                } catch (Exception e) {
641                    creationErrorEncountered(adapter, "load(" + url.getFile() + ")", "Unexpected error (Exception)", null, null, e);
642
643                    result = false;  // keep going, but return false to signal problem
644                } catch (Throwable et) {
645                    creationErrorEncountered(adapter, "in load(" + url.getFile() + ")", "Unexpected error (Throwable)", null, null, et);
646
647                    result = false;  // keep going, but return false to signal problem
648                }
649            }
650
651        } catch (java.io.FileNotFoundException e1) {
652            // this returns false to indicate un-success, but not enough
653            // of an error to require a message
654            creationErrorEncountered(null, "opening file " + url.getFile(),
655                    "File not found", null, null, e1);
656            result = false;
657        } catch (org.jdom2.JDOMException e) {
658            creationErrorEncountered(null, "parsing file " + url.getFile(),
659                    "Parse error", null, null, e);
660            result = false;
661        } catch (java.io.IOException e) {
662            creationErrorEncountered(null, "loading from file " + url.getFile(),
663                    "IOException", null, null, e);
664            result = false;
665        } catch (ClassNotFoundException e) {
666            creationErrorEncountered(null, "loading from file " + url.getFile(),
667                    "ClassNotFoundException", null, null, e);
668            result = false;
669        } catch (InstantiationException e) {
670            creationErrorEncountered(null, "loading from file " + url.getFile(),
671                    "InstantiationException", null, null, e);
672            result = false;
673        } catch (IllegalAccessException e) {
674            creationErrorEncountered(null, "loading from file " + url.getFile(),
675                    "IllegalAccessException", null, null, e);
676            result = false;
677        } catch (NoSuchMethodException e) {
678            creationErrorEncountered(null, "loading from file " + url.getFile(),
679                    "NoSuchMethodException", null, null, e);
680            result = false;
681        } catch (java.lang.reflect.InvocationTargetException e) {
682            creationErrorEncountered(null, "loading from file " + url.getFile(),
683                    "InvocationTargetException", null, null, e);
684            result = false;
685        } finally {
686            // no matter what, close error reporting
687            handler.done();
688        }
689
690        // loading complete, as far as it got, make history entry
691        FileHistory r = InstanceManager.getNullableDefault(FileHistory.class);
692        if (r != null) {
693            FileHistory included = null;
694            if (root != null) {
695                Element filehistory = root.getChild("filehistory");
696                if (filehistory != null) {
697                    included = jmri.jmrit.revhistory.configurexml.FileHistoryXml.loadFileHistory(filehistory);
698                }
699            }
700            String friendlyName = url.getFile().replaceAll("%20", " ");
701            r.addOperation((result ? "Load OK" : "Load with errors"), friendlyName, included);
702        } else {
703            log.info("Not recording file history");
704        }
705        return result;
706    }
707
708    /** {@inheritDoc} */
709    @Override
710    public boolean loadDeferred(File fi) {
711        return this.loadDeferred(FileUtil.fileToURL(fi));
712    }
713
714    /** {@inheritDoc} */
715    @Override
716    public boolean loadDeferred(URL url) {
717        boolean result = true;
718        // Now process the load-later list
719        log.debug("Start processing deferred load list (size): {}", loadDeferredList.size());
720        if (!loadDeferredList.isEmpty()) {
721            for (Element item : loadDeferredList) {
722                String adapterName = item.getAttribute("class").getValue();
723                log.debug("deferred load via {}", adapterName);
724                XmlAdapter adapter = null;
725                try {
726                    adapter = (XmlAdapter) Class.forName(adapterName).getDeclaredConstructor().newInstance();
727                    boolean loadStatus = adapter.load(item, item);
728                    log.debug("deferred load status for {} is {}", adapterName, loadStatus);
729
730                    // if any adaptor load fails, then the entire load has failed
731                    if (!loadStatus) {
732                        result = false;
733                    }
734                } catch (Exception e) {
735                    creationErrorEncountered(adapter, "deferred load(" + url.getFile() + ")",
736                            "Unexpected error (Exception)", null, null, e);
737                    result = false;  // keep going, but return false to signal problem
738                } catch (Throwable et) {
739                    creationErrorEncountered(adapter, "in deferred load(" + url.getFile() + ")",
740                            "Unexpected error (Throwable)", null, null, et);
741                    result = false;  // keep going, but return false to signal problem
742                }
743            }
744        }
745        log.debug("Done processing deferred load list with result: {}", result);
746        return result;
747    }
748
749    /**
750     * Find a file by looking
751     * <ul>
752     * <li> in xml/layout/ in the preferences directory, if that exists
753     * <li> in xml/layout/ in the application directory, if that exists
754     * <li> in xml/ in the preferences directory, if that exists
755     * <li> in xml/ in the application directory, if that exists
756     * <li> at top level in the application directory
757     * </ul>
758     *
759     * @param f Local filename, perhaps without path information
760     * @return Corresponding File object
761     */
762    @Override
763    public URL find(String f) {
764        URL u = FileUtil.findURL(f, "xml/layout", "xml"); // NOI18N
765        if (u == null) {
766            this.locateFileFailed(f);
767        }
768        return u;
769    }
770
771    /**
772     * Report a failure to find a file. This is a separate member to ease
773     * testing.
774     *
775     * @param f Name of file not located.
776     */
777    void locateFileFailed(String f) {
778        log.warn("Could not locate file {}", f);
779    }
780
781    /**
782     * Invoke common handling of errors that happen during the "load" process.
783     * <p>
784     * Exceptions passed into this are absorbed.
785     *
786     * @param adapter     Object that encountered the error (for reporting), may
787     *                    be null
788     * @param operation   description of the operation being attempted, may be
789     *                    null
790     * @param description description of error encountered
791     * @param systemName  System name of bean being handled, may be null
792     * @param userName    used name of the bean being handled, may be null
793     * @param exception   Any exception being handled in the processing, may be
794     *                    null
795     */
796    static public void creationErrorEncountered(
797            XmlAdapter adapter,
798            String operation,
799            String description,
800            String systemName,
801            String userName,
802            Throwable exception) {
803        // format and log a message (note reordered from arguments)
804//        System.out.format("creationErrorEncountered: %s%n", exception.getMessage());
805//        System.out.format("creationErrorEncountered: %s, %s, %s, %s, %s, %s%n", adapter, operation, description, systemName, userName, exception == null ? null : exception.getMessage());
806        ErrorMemo e = new ErrorMemo(
807                adapter, operation, description,
808                systemName, userName, exception, "loading");
809        if (adapter != null) {
810            ErrorHandler aeh = adapter.getExceptionHandler();
811            if (aeh != null) {
812                aeh.handle(e);
813            }
814        } else {
815            handler.handle(e);
816        }
817    }
818
819    /**
820     * Invoke common handling of errors that happen during the "store" process.
821     * <p>
822     * Exceptions passed into this are absorbed.
823     *
824     * @param adapter     Object that encountered the error (for reporting), may
825     *                    be null
826     * @param operation   description of the operation being attempted, may be
827     *                    null
828     * @param description description of error encountered
829     * @param systemName  System name of bean being handled, may be null
830     * @param userName    used name of the bean being handled, may be null
831     * @param exception   Any exception being handled in the processing, may be
832     *                    null
833     */
834    static public void storingErrorEncountered(
835            XmlAdapter adapter,
836            String operation,
837            String description,
838            String systemName,
839            String userName,
840            Throwable exception) {
841        // format and log a message (note reordered from arguments)
842        ErrorMemo e = new ErrorMemo(
843                adapter, operation, description,
844                systemName, userName, exception, "storing");
845        if (adapter != null) {
846            ErrorHandler aeh = adapter.getExceptionHandler();
847            if (aeh != null) {
848                aeh.handle(e);
849            }
850        } else {
851            handler.handle(e);
852        }
853    }
854
855    private static ErrorHandler handler = new ErrorHandler();
856
857    static public void setErrorHandler(ErrorHandler handler) {
858        ConfigXmlManager.handler = handler;
859    }
860
861    /**
862     * @return the loadDeferredList
863     */
864    protected List<Element> getLoadDeferredList() {
865        return loadDeferredList;
866    }
867
868    // initialize logging
869    private final static Logger log = LoggerFactory.getLogger(ConfigXmlManager.class);
870
871}