/**
* This file is part of Archiv-Editor.
*
* The software Archiv-Editor serves as a client user interface for working with
* the Person Data Repository. See: pdr.bbaw.de
*
* The software Archiv-Editor was developed at the Berlin-Brandenburg Academy
* of Sciences and Humanities, Jägerstr. 22/23, D-10117 Berlin.
* www.bbaw.de
*
* Copyright (C) 2010-2013 Berlin-Brandenburg Academy
* of Sciences and Humanities
*
* The software Archiv-Editor was developed by @author: Christoph Plutte.
*
* Archiv-Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Archiv-Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Archiv-Editor.
* If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package org.bbaw.pdr.ae.export.pluggable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Vector;
import org.bbaw.pdr.ae.common.AEConstants;
import org.bbaw.pdr.ae.common.CommonActivator;
import org.bbaw.pdr.ae.export.swt.FileSelectionGroup;
import org.bbaw.pdr.ae.export.swt.IPdrWidgetStructure;
import org.bbaw.pdr.ae.export.swt.preview.PdrSelectionFilterPreview;
import org.bbaw.pdr.ae.model.PdrObject;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbenchWizard;
import org.osgi.framework.Bundle;
//FIXME: change from interface to abstract class
//TODO: doc
/**
*<p>Plugins contributing to the extension point <code>org.bbaw.pdr.ae.export.core</code>
* are required to denote a subclass that implements the methods of this abstract
* class. That subclass is meant to act as a local delegate that provides and
* customizes the API made available by the globally qualified
* {@link AeExportCoreProvider} instance.</p>
* <p>Any implementing subclasses should initialize it's respective plugin identifier
* in their constructors, for instance by calling the accordingly implemented
* method {@link #setPluginId()}. The plugin identifier is used by the core
* plugin as a key for plugin-specific information, for instance to return
* saved settings to {@link #getSettings()}</p>
* <h1>Corresponding {@link IExportWizard} implementation</h1>
* <p>The extension point <code>org.bbaw.pdr.ae.export.core</code> requires
* contributing plugins to refer to a class designated by a
* <code>org.eclipse.ui.exportWizards</code> extension. That implies that every
* plugin intended to contribute to the AE export extension point should
* also contribute to <code>org.eclipse.ui.exportWizards</code>.</p>
* <p>The wizard that has to be present in a
* <code>org.eclipse.ui.exportWizards</code> implementation has to both extend
* the abstract class {@link Wizard} and implement {@link IExportWizard}, so
* technically, it has only to implement
* {@link IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
* org.eclipse.jface.viewers.IStructuredSelection)} and {@link
* IWizard#performFinish()}, but in order to have convenient access to the
* central AE functionality, a wizard should be equipped with some additional
* information:</p>
* <p>First, any wizard that is used in the way illustrated above can be given a
* reference to the <code>AeExportUtilities</code> subclass designated
* as its complement by the
* plugin's AE export contribution by calling
* <blockquote><code>AeExportCoreProvider.getInstance().getUtilityProvider(this);
* </code></blockquote>
* Methods that could come in handy include {@link #getSettings()}
* and {@link #getLatestFiles(String)}.</p>
* <p>For putting together the wizard's GUI with little effort, there are
* ready-to-use widget structures exported by the export core plugin. These
* components are implementations of {@link IPdrWidgetStructure}. For instance:
* <ul><li>{@link PdrSelectionFilterPreview}: A tree view that autonomously obtains
* the AE's current selection of PDR objects and renders it as a interactive
* preview that allows for further filtering of export content</li>
* <li>{@link FileSelectionGroup}: A simple interface for the selection of
* resources in the local file system. Features file history, filetype extension
* filtering, file selection dialog handling and input validation.</li></ul>
*
* <h2>Addressing wizard</h2>
* <p>When an export plugin is detected, the denoted wizard class is linked
* to the corresponding export utility provider object by the core plugin.
* When that wizard is
* instantiated, it can hence let the core plugin provider look up the
* provider dedicated to it. It then notifies its provider by calling its
* {@link #setWizard(IExportWizard)} method in oder to claim that its up and
* running. This way, the utility provider has a chance to obtain a reference
* to the very wizard object it is expected to work with.</p>
*/
public abstract class AeExportUtilities {
/** Logger. */
private static ILog logger = AEConstants.ILOGGER;
/**
* <p>By calling this method, the subclass can easily
* obtain the <code>provider</code> singleton reference
* and store it in a private field.</p>
* @return provider singleton instance of {@link AeExportCoreProvider}
*/
public AeExportCoreProvider getCoreProvider() {
return AeExportCoreProvider.getInstance();
}
/**
* <p>When an implementation of an <code> {@link IExportWizard} </code> that is
* being named by a plugin contributing to the extension point
* <code>org.bbaw.ae.export.core</code> is instantiated by eclipse to open
* an export wizard dialog, that very wizard instantly calls this method
* to report its availability.</p>
* <p>This method is meant to be called only once, namely automatically during
* the wizard's instantiation. <strike>The default implementation resets the
* {@link AeExportCoreProvider#getWidgetRegistry(String)}, just in case
* there are still widgets registered from previous activation of said wizard.
* Hence, when overwriting this method, calling the superclasse's implementation
* might be worth considering:
* <code>super.setWizard(wizard);</code></strike></p>
*
* <p>In order to conversely being able to call the active wizard itself,
* any class implementing this method should keep the reference, so as to
* be on the safe side</p>
*
* @param wizard {@link IExportWizard} implementation calling to announce
* itself as being ready to serve
*/
public void setWizard(IExportWizard wizard) {
unregisterWidgets(); // FIXME wird leider nie aufgerufen
}
/**
* <p>Designed to return the {@link IExportWizard} instance that has been reported being
* responsible for the currently active export dialog via {@link
* #setWizard(IExportWizard)}.</p> <p>However, the {@link AeExportUtilities} subclass
* that is intended to serve that wizard is free to do nothing in its
* implementation of {@link #setWizard(IExportWizard)}, since said wizard
* object will always know its {@link AeExportUtilities} counterpart. </p>
* @return latest {@link IExportWizard} instance that has been passed
* with a {@link #setWizard(IExportWizard)} call.
*/
public abstract IExportWizard getWizard();
/**
* To be implemented by the subclass in order to return the implementing
* plugin's symbolic name assigned to some subclass member in
* {@link #setPluginId()}.
* @return
*/
public abstract String pluginId();
/**
* Has to be overwritten by subclass in order to initialize the subclass's
* <code>pluginId</code> field. This method is by default called during the first call
* of {@link #pluginId()}.
* <p>The subclass doesn't have much more to do than call the following line
* in its implementation:
* <blockquote><code>
* pluginId = FrameworkUtil.getBundle(getClass()).getSymbolicName();
* </code></blockquote>
* That's it.</p>
*/
public abstract void setPluginId();
//TODO doc
public PdrObject[] getPdrObjects() {
// TODO: do we even need this?
return getCoreProvider().getPdrObjects();
}
/**
* <p>The <code>IExportWizard</code> implementation might be interested in
* having an own section of {@link IDialogSettings} at its disposal,
* most likely because of the desire to set up GUI components using content
* from a previous session.</p>
* <p>To avoid confusion, the implementation of this method should call
* {@link AeExportCoreProvider#getSettings(String)} passing a unique
* identifier. On behalf of comprehensibility, the plugin name might be
* a reasonable choice:</p>
* <blockquote><code>String pluginId = FrameworkUtil.getBundle(getClass())
* .getSymbolicName();</code></blockquote>
* @return <code>IDialogSettings</code> a dialog settings section to save
* configuration data to
*/
public IDialogSettings getSettings() {
return getCoreProvider().getSettings(pluginId());
}
//TODO doc
public String[] getFiletypes(int ioFunc, String setName) {
return getCoreProvider().getPluginFiletypes(pluginId(),
(ioFunc == SWT.SAVE) ? AeExportCoreProvider.EXT_OUT :
AeExportCoreProvider.EXT_IN,
setName);
}
/**
* Returns the location of an export plugin's resource specified by
* its relative path in the bundled plugin.
* @param path relative path of desired resource within plugin file structure.
* @return absolute path to file
* @throws IOException
*/
public String staticResource(String path) throws IOException {
// http://www.eclipse.org/forums/index.php/mv/msg/45047/146049/#msg_146049
//// hilfe evtl unter:
//// http://stackoverflow.com/questions/4720214/how-to-get-the-eclipse-installation-plugins-directory-or-path
//// http://stackoverflow.com/questions/5622789/how-to-refer-a-file-from-jar-file-in-eclipse-plugin/5660242#5660242
// villeicht ueber getBundle und FileLocator.openStream?
// http://help.eclipse.org/indigo/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/IPluginDescriptor.html
// https://wiki.eclipse.org/Eclipse_Plug-in_Development_FAQ#How_do_I_read_from_a_file_that_I.27ve_included_in_my_bundle.2Fplug-in.3F
// scheint zu klappen!
Bundle bundle = Platform.getBundle(pluginId());
log(IStatus.INFO, "Bundle rel stylesheet: "+bundle.getEntry(path)
+" == "+bundle.getEntry(path).getFile());
log(IStatus.INFO, "Bundle rel resource: "+bundle.getResource(path));
URL url = FileLocator.find(bundle,
new Path(path), null);
log(IStatus.INFO, "Bundle rel url: "+url);
try { log(IStatus.INFO, "Bundle rel uri: "+url.toURI()); }
catch (URISyntaxException e){}
log(IStatus.INFO, "Bundle rel url ext: "+url.toExternalForm());
// create inputstream from bundle internal resource
InputStream stm = FileLocator.openStream(bundle, new Path(path), false);
// change slashes in case of running on windows fs
path.replaceAll("/", AEConstants.FS);
// assemble file local location for bundle resource [e.g. style sheet] installation
String extendedPath = AEConstants.AE_HOME + AEConstants.FS + "export-stylesheets"
+ AEConstants.FS +path;
logger.log(new Status(IStatus.INFO, pluginId(),
"export stylesheet location: "+extendedPath));
// look at installation location for bundle resource in question
File file = new File(extendedPath);
if (!file.exists()) {
// prepare for resource installation in case it's not present at local path
logger.log(new Status(IStatus.INFO, pluginId(),
"stylesheet not present at expected location"));
InputStream stream = this.getClass().getClassLoader().getResourceAsStream(path);
File dir = new File(AEConstants.AE_HOME + AEConstants.FS + "export-stylesheets"+ AEConstants.FS + "resources");
if (!dir.exists()) {
logger.log(new Status(IStatus.INFO, pluginId(),
"create stylesheet directory "+dir));
dir.mkdirs();
}
// set up stream to local file
OutputStream out = new FileOutputStream(file);
logger.log(new Status(IStatus.INFO, pluginId(),
"copy file "+path+" from plugin scope to stylesheet directory."));
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
int total = 0;
while ((len = stm.read(buf)) > 0) {
out.write(buf, 0, len);
total += len;
}
stream.close();
out.close();
logger.log(new Status(IStatus.INFO, pluginId(),
"copied "+total+" bytes into export stylesheet directory."));
}
logger.log(new Status(IStatus.INFO, pluginId(),
"Stylesheet location seems ok."));
return extendedPath;
// Bundle bundle = Platform.getBundle(pluginId());
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Retrieving locator for resource '"+path+"' of plugin "+pluginId()));
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Plugin location: "+bundle.getLocation()));
// // url of resource shipped with bundle
// URL url = bundle.getEntry(path); // wg moegl. probleme mit getResource
// if (url != null) {
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Resource URL: "+url));
// try {
// // URI representation of resource URL (due to possible problems using URL alone.
// // this here is how its done in AEConstants, RepositoryUpdateManager etc.)
// URI uri = FileLocator.resolve(url).toURI();
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Resolved locator: "+uri));
// // file object linking said URI
// File file = new File(uri);
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Resolved file name: "+file.getAbsolutePath()));
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Does file exist? - "+file.exists()));
// if (file.exists()) return file.getAbsolutePath();
// } catch (URISyntaxException e) {
// iLogger.log(new Status(IStatus.WARNING, CommonActivator.PLUGIN_ID,
// "Failed to transform file URL "+url+" to URI."));
// e.printStackTrace();
// // file object created using URL instead of URI
// File file = new File(FileLocator.resolve(url).toString());
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "File name resolved from URL: "+file.getAbsolutePath()));
// iLogger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
// "Does file exist? - "+file.exists()));
// if (file.exists()) return file.getAbsolutePath();
// }
// }
/*
// load via classloader
ResourceLocator locator = new ResourceLocator();
InputStream in = locator.getClass().getClassLoader().getResourceAsStream(path);*/
/*logger.log(new Status(IStatus.WARNING, CommonActivator.PLUGIN_ID,
"Failed to resolve resource path. Returning "+extendedPath));
return extendedPath;*/
}
/**
* Retrieve the default file location for a given set name.
* TODO: pluginId kein guter identifier.
* @param set
* @return
*/
public String getDefaultFilename(String set) {
String filename = getCoreProvider().getSettingsSection(pluginId(), set).get(
AeExportCoreProvider.DEF_DEF_FN);
logger.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
"Retrieve default file location for plugin "+pluginId()+", configuration '"+set+"'."));
if (filename != null) {
String absolute;
try {
absolute = staticResource(filename);
if (absolute != null)
return absolute;
} catch (IOException e) {
log(IStatus.WARNING,
"Could not resolve resource path "+filename+" for plugin "+pluginId());
}
return filename;
}
return null;
}
/**
* Logs a message.
* @param level level of message, e.g. {@link IStatus.INFO}
* @param msg
*/
public void log(int level, String msg) {
if (pluginId() != null) {
logger.log(new Status(level, pluginId(), msg));
} else {
throw new NullPointerException("pluginId has not been set!");
}
}
//TODO define abstract method export() or process() that as an argument
//TODO takes some collection of PdrObject[] arrays and returns true on
//TODO successful export
//TODO this would allow for choosing to export multiple files for multiple
//TODO pdr persons at once. The active wizard would call the AeExportUtilities
//TODO export method in performFinish() and let it do all of the processing.
//TODO The core plugin would have been informed about the choice to export
//TODO a batch instead of a single file (e.g. by the preview GUI composite)
//TODO and return a collection of PdrObject[]
//TODO sequences.
/**
* Dummy method which does nothing but returning true. Meant to be replaced
* by an abstract declaration if it seems to be of a better design to have
* the actual export/save code in the utility provider class of each export
* plugin.
* @return
*/
public boolean export() {
// TODO Auto-generated method stub: make abstract if this design
// turns out to be viable
return true;
}
/**
* Registers an {@link IPdrWidgetStructure} object for this plugin.
* @param widget
*/
public void registerWidget(IPdrWidgetStructure widget) {
Vector<IPdrWidgetStructure> widgetRegistry = getCoreProvider().getWidgetRegistry(pluginId());
if (!widgetRegistry.contains(widget)) {
widgetRegistry.add(widget);
log(IStatus.INFO,
"Plugin "+pluginId()+": register "+widget.getClass().getCanonicalName());
}
}
/**
* Unregisters {@link IPdrWidgetStructure} object from plugin's registry.
* @param widget
*/
public void unregisterWidget(IPdrWidgetStructure widget) {
// TODO: think about how to unregister widgets the most conveniently
Vector<IPdrWidgetStructure> widgetRegistry = getCoreProvider().getWidgetRegistry(pluginId());
if (widgetRegistry.contains(widget))
widgetRegistry.remove(widget);
}
/**
* Calls {@link IPdrWidgetStructure#performFinish()} for all registered widgets.
* Unregisters all widgets from registry afterwards.
* @see #registerWidget(IPdrWidgetStructure)
*/
public void terminateWidgets() {
for (IPdrWidgetStructure widget : getCoreProvider().getWidgetRegistry(pluginId())) {
log(IStatus.INFO,
"Perform termination of widget "+widget.getClass().getCanonicalName());
widget.performFinish();
}
unregisterWidgets();
}
/**
* Remove all {@link IPdrWidgetStructure}s from registry
* @see #unregisterWidget(IPdrWidgetStructure)
*/
public void unregisterWidgets() {
getCoreProvider().getWidgetRegistry(pluginId()).removeAllElements();
}
}