Java tutorial
/*--------------- Kalypso-Header -------------------------------------------------------------------- This file is part of kalypso. Copyright (C) 2004, 2005 by: Technical University Hamburg-Harburg (TUHH) Institute of River and coastal engineering Denickestr. 22 21073 Hamburg, Germany http://www.tuhh.de/wb and Bjoernsen Consulting Engineers (BCE) Maria Trost 3 56070 Koblenz, Germany http://www.bjoernsen.de This library 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 2.1 of the License, or (at your option) any later version. This library 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 this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: E-Mail: belger@bjoernsen.de schlienger@bjoernsen.de v.doemming@tuhh.de ---------------------------------------------------------------------------------------------------*/ package org.kalypso.simulation.ui.calccase; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.util.Calendar; import java.util.Date; import java.util.Map; import java.util.Properties; import java.util.TimeZone; import javax.xml.bind.DatatypeConverter; import org.apache.commons.httpclient.URIException; import org.apache.commons.io.IOUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectNature; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.variables.IStringVariableManager; import org.eclipse.core.variables.IValueVariable; import org.eclipse.core.variables.VariablesPlugin; import org.kalypso.auth.KalypsoAuthPlugin; import org.kalypso.auth.user.IKalypsoUser; import org.kalypso.commons.runtime.LogAnalyzer; import org.kalypso.contribs.eclipse.core.resources.ResourceUtilities; import org.kalypso.contribs.eclipse.core.runtime.StatusUtilities; import org.kalypso.core.KalypsoCorePlugin; import org.kalypso.ogc.gml.serialize.GmlSerializer; import org.kalypso.simulation.ui.KalypsoSimulationUIPlugin; import org.kalypso.simulation.ui.i18n.Messages; import org.kalypsodeegree.model.feature.Feature; import org.kalypsodeegree.model.feature.FeatureVisitor; import org.kalypsodeegree.model.feature.GMLWorkspace; import org.kalypsodeegree_impl.model.feature.visitors.FindPropertyByNameVisitor; /** * @author belger */ public class ModelNature implements IProjectNature, IResourceChangeListener { public static final String METADATA_KEY_CALCCASE_CONTINUE_ALLOWED = "CALCCASE_CONTINUE_ALLOWED"; //$NON-NLS-1$ private static final String STR_MODELLRECHNUNG_WIRD_DURCHGEFUEHRT = Messages .getString("org.kalypso.simulation.ui.calccase.ModelNature.0"); //$NON-NLS-1$ public static final String MODELLTYP_FOLDER = ".model"; //$NON-NLS-1$ public static final String MODELLTYP_CALCCASECONFIG_XML = MODELLTYP_FOLDER + "/" + "calcCaseConfig.xml"; //$NON-NLS-1$ //$NON-NLS-2$ public static final String ID = KalypsoSimulationUIPlugin.getID() + ".ModelNature"; //$NON-NLS-1$ private static final String METADATA_FILE = ".metadata.ini"; //$NON-NLS-1$ public static final String LOCAL_NAME = ".local"; //$NON-NLS-1$ // FIXME: move to hwv public static final String CONTROL_NAME = ".calculation"; //$NON-NLS-1$ public static final String CONTROL_TEMPLATE_NAME = ".calculation.template"; //$NON-NLS-1$ public static final String CONTROL_VIEW_PATH = MODELLTYP_FOLDER + "/.calculation.gft"; //$NON-NLS-1$ public static final String MODELLTYP_CALCWIZARD_XML = MODELLTYP_FOLDER + "/" + "calcWizard.xml"; //$NON-NLS-1$ //$NON-NLS-2$ private final Properties m_defaultMetadata = new Properties(); private final Properties m_metadata = new Properties(m_defaultMetadata); private IProject m_project; public static final String PROGNOSE_FOLDER = "Rechenvarianten"; //$NON-NLS-1$ public static final String CONTROL_TEMPLATE_GML_PATH = MODELLTYP_FOLDER + "/" + CONTROL_TEMPLATE_NAME; //$NON-NLS-1$ private static final String META_PROP_VALID_HOURS = "VALID_FORECAST_HOURS"; //$NON-NLS-1$ /** Standardddifferenz des Simulationsstarts vor dem Vorhersagezeitpunkt */ private static final String META_PROP_DEFAULT_SIMHOURS = "DEFAULT_SIMHOURS"; //$NON-NLS-1$ public ModelNature() { // Set some default value for the metadata m_defaultMetadata.put(METADATA_KEY_CALCCASE_CONTINUE_ALLOWED, Boolean.FALSE.toString()); } /** * @see org.eclipse.core.resources.IProjectNature#configure() */ @Override public void configure() { // nothing to do } public final IFolder getPrognoseFolder() { return m_project.getFolder(PROGNOSE_FOLDER); } private IFile getMetadataFile() { return m_project.getFile(new Path(METADATA_FILE)); } /** * @see org.eclipse.core.resources.IProjectNature#deconfigure() */ @Override public void deconfigure() { // nothing to do; only thing to do might be to delete the .metadata file } /** * @see org.eclipse.core.resources.IProjectNature#getProject() */ @Override public IProject getProject() { return m_project; } /** * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject) */ @Override public void setProject(final IProject project) { if (m_project != null) ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); m_project = project; if (m_project != null) { ResourcesPlugin.getWorkspace().addResourceChangeListener(this); try { reloadMetadata(); } catch (final CoreException e) { // just ignore; this will happen when a new prjoject is created } } } public void dispose() { ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); } public static String checkCanCreateCalculationCase(final IPath path) { final IWorkspaceRoot resourceRoot = ResourcesPlugin.getWorkspace().getRoot(); final IResource resource = resourceRoot.findMember(path); if (resource == null || resource == resourceRoot) return Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.1"); //$NON-NLS-1$ if (resource instanceof IFolder) { final IFolder folder = (IFolder) resource; if (isCalcCalseFolder(folder)) return Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.2"); //$NON-NLS-1$ return checkCanCreateCalculationCase(folder.getParent().getFullPath()); } else if (resource instanceof IProject) { final IProject project = (IProject) resource; try { project.isNatureEnabled(ModelNature.ID); return null; } catch (final CoreException e) { e.printStackTrace(); return e.getMessage(); } } return "???"; //$NON-NLS-1$ } /** * @deprecated Use only for HWV plugins; should be moved there. */ @Deprecated public static boolean isCalcCalseFolder(final IContainer folder) { return isCalcCalseFolder(folder, CONTROL_NAME); } public static boolean isCalcCalseFolder(final IContainer folder, final String controlPath) { final IResource calcFile = folder.findMember(controlPath); return calcFile != null && calcFile.exists() && calcFile instanceof IFile; } public IStatus launchAnt(final String progressText, final String launchName, final Map<String, Object> antProps, final IContainer folder, final IProgressMonitor monitor) throws CoreException { monitor.beginTask(progressText, 1000); //$NON-NLS-1$ final IStringVariableManager svm = VariablesPlugin.getDefault().getStringVariableManager(); IValueVariable[] userVariables = null; try { monitor.subTask(Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.3", launchName)); //$NON-NLS-1$ final IFile launchFile = getLaunchFile(launchName); final Properties userProperties = createVariablesForAntLaunch(folder); if (antProps != null) userProperties.putAll(antProps); // add user-variables to variable-manager (so they can also be used within the launch-file userVariables = registerValueVariablesFromProperties(svm, userProperties); final AntLauncher antLauncher = new AntLauncher(launchFile, userProperties); antLauncher.init(); monitor.worked(10); final IStatus status = antLauncher.execute(new SubProgressMonitor(monitor, 990)); final File logFile = antLauncher.getLogFile(); if (logFile == null) return status; final IStatus[] logStati = LogAnalyzer.logfileToStatus(logFile, Charset.defaultCharset().name()); final IStatus[] groupedStati = LogAnalyzer.groupStati(logStati); return new MultiStatus(KalypsoSimulationUIPlugin.getID(), -1, groupedStati, "Log-File was analyzed: " + logFile.getAbsolutePath(), null); //$NON-NLS-1$ } catch (final CoreException e) { throw e; } catch (final InterruptedException e) { return Status.CANCEL_STATUS; } catch (final Throwable e) { e.printStackTrace(); // sollte eigentlich nie auftreten return StatusUtilities.statusFromThrowable(e, Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.6")); //$NON-NLS-1$ } finally { // remove userVariables, to not pollute the Singleton if (userVariables != null) svm.removeVariables(userVariables); // alle resourcen des CalcCase refreshen // intern the parent of folder will used as rule /* * In order to have a good error message we need to check if the monitor is canceled (this is the case if an * exception was thrown before). */ final IProgressMonitor subMon = monitor.isCanceled() ? new NullProgressMonitor() : new SubProgressMonitor(monitor, 1000); // FIXME: This should not be necessary and is also a major performance problem, as the folder might be very big. // The ant-launch itself is responsible to refresh any changed resources. folder.refreshLocal(IResource.DEPTH_INFINITE, subMon); monitor.done(); } } private static IValueVariable[] registerValueVariablesFromProperties(final IStringVariableManager svm, final Properties properties) { final IValueVariable[] variables = new IValueVariable[properties.size()]; int count = 0; for (final Map.Entry<Object, Object> entry : properties.entrySet()) { final String name = (String) entry.getKey(); final String value = (String) entry.getValue(); final IValueVariable valueVariable = svm.newValueVariable(name, value, true, value); variables[count++] = valueVariable; try { final IValueVariable existingVariable = svm.getValueVariable(name); if (existingVariable == null) { // add each variable separatedly, because it may have allready been registered svm.addVariables(new IValueVariable[] { valueVariable }); } else { existingVariable.setValue(value); } } catch (final CoreException e) { System.out.println("Variable already exists: " + name); //$NON-NLS-1$ e.printStackTrace(); // ignore it, its already there -> ? } } return variables; } private Properties createVariablesForAntLaunch(final IContainer folder) throws CoreException { final Properties attributes = new Properties(); final IProject project = folder.getProject(); final KalypsoAuthPlugin authPlugin = KalypsoAuthPlugin.getDefault(); final IKalypsoUser currentUser = authPlugin.getCurrentUser(); final Date now = new Date(); final TimeZone kalypsoTimezone = KalypsoCorePlugin.getDefault().getTimeZone(); // auf x stunden vorher runden! hngt von der Modellspec ab final Calendar cal = Calendar.getInstance(); cal.setTimeZone(kalypsoTimezone); cal.setTime(now); attributes.setProperty("kalypso.currentTime", DatatypeConverter.printDateTime(cal)); //$NON-NLS-1$ attributes.setProperty("kalypso.timezone", kalypsoTimezone.getID()); //$NON-NLS-1$ // erstmal auf die letzte Stunde runden cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); // jetzt solange ganze stunden abziehen, bis der Wert ins // Zeitvalidierungsschema passt int count = 0; while (!validateTime(cal)) { cal.add(Calendar.HOUR_OF_DAY, -1); // nach 24h sptestens abbrechen! count++; if (count == 24) throw new CoreException(new Status(IStatus.ERROR, KalypsoSimulationUIPlugin.getID(), Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.7") + cal)); //$NON-NLS-1$ } attributes.setProperty("kalypso.startforecast", DatatypeConverter.printDateTime(cal)); //$NON-NLS-1$ // standardzeit abziehen final int simDiff = new Integer(m_metadata.getProperty(META_PROP_DEFAULT_SIMHOURS, "120")).intValue(); //$NON-NLS-1$ cal.add(Calendar.HOUR_OF_DAY, -simDiff); attributes.setProperty("kalypso.startsim", DatatypeConverter.printDateTime(cal)); //$NON-NLS-1$ attributes.setProperty("kalypso.currentUser", currentUser.getUserName()); //$NON-NLS-1$ attributes.setProperty("simulation_project_loc", project.getLocation().toPortableString()); //$NON-NLS-1$ attributes.setProperty("calc.dir", folder.getLocation().toPortableString()); //$NON-NLS-1$ attributes.setProperty("project.dir", project.getLocation().toPortableString()); //$NON-NLS-1$ attributes.setProperty("calc.path", folder.getFullPath().toPortableString()); //$NON-NLS-1$ attributes.setProperty("project.path", project.getFullPath().toPortableString()); //$NON-NLS-1$ try { attributes.setProperty("calc.url", ResourceUtilities.createURL(folder).toString()); //$NON-NLS-1$ attributes.setProperty("project.url", ResourceUtilities.createURL(project).toString()); //$NON-NLS-1$ } catch (final MalformedURLException e) { // should never happen e.printStackTrace(); } catch (final URIException e) { // TODO Auto-generated catch block e.printStackTrace(); } return attributes; } private IFile getLaunchFile(final String launchName) throws CoreException { final IFolder launchFolder = getLaunchFolder(); if (launchFolder == null) throw new CoreException(new Status(IStatus.ERROR, KalypsoSimulationUIPlugin.getID(), Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.8"))); //$NON-NLS-1$ return launchFolder.getFile(launchName + ".launch"); //$NON-NLS-1$ } private IFolder getLaunchFolder() { return getModelFolder().getFolder("launch"); //$NON-NLS-1$ } /** * Erzeugt eine neue Rechenvariante im angegebenen Ordner */ public IStatus createCalculationCaseInFolder(final IFolder folder, final Map<String, Object> antProperties, final IProgressMonitor monitor) throws CoreException { final String message = Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.9"); //$NON-NLS-1$ return launchAnt(message, "createCalcCase", antProperties, folder, monitor); //$NON-NLS-1$ } /** * Aktualisiert eine vorhandene Rechenvariante */ public IStatus updateCalcCase(final IFolder folder, final IProgressMonitor monitor) throws CoreException { final String message = Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.10"); //$NON-NLS-1$ return launchAnt(message, "updateCalcCase", null, folder, monitor); //$NON-NLS-1$ } /** * bernimmt das Modell aus der Rechenvariante in das Basismodell */ public IStatus setBasicModel(final IFolder folder, final IProgressMonitor monitor) throws CoreException { final String message = Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.11"); //$NON-NLS-1$ return launchAnt(message, "setBasicModel", null, folder, monitor); //$NON-NLS-1$ } /** * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ @Override public void resourceChanged(final IResourceChangeEvent event) { final IResourceDelta delta = event.getDelta(); final IFile metadataFile = getMetadataFile(); if (delta == null || metadataFile == null) return; final IResourceDelta metadataDelta = delta.findMember(metadataFile.getFullPath()); if (metadataDelta == null) return; switch (metadataDelta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.REMOVED: case IResourceDelta.CHANGED: { try { // always test for existence before reloading data // since project might have been deleted by the user if (getMetadataFile().exists()) reloadMetadata(); } catch (final CoreException e) { // todo: error handling? -->> als job absetzen? e.printStackTrace(); } break; } } } private void reloadMetadata() throws CoreException { InputStream contents = null; try { m_metadata.clear(); final IFile file = getMetadataFile(); contents = file.getContents(); m_metadata.load(contents); contents.close(); } catch (final IOException e) { throw new CoreException(new Status(IStatus.ERROR, KalypsoSimulationUIPlugin.getID(), 0, Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.12"), e)); //$NON-NLS-1$ } finally { IOUtils.closeQuietly(contents); } } /** * Returns a metadatum associated with this nature. * * @param key * One of the METADATA_KEY_ conmstants. * @return The value of the given key; or <code>null</code> if not set. */ public String getMetadata(final String key) { return m_metadata.getProperty(key); } private IFolder getModelFolder() { return m_project.getFolder(MODELLTYP_FOLDER); } public GMLWorkspace loadOrCreateControl(final IContainer folder, final String controlPath) throws CoreException { try { if (folder == null) throw new IllegalArgumentException(); final IFile controlFile = folder.getFile(new Path(controlPath)); final String gmlPath = controlFile.getFullPath().toString(); final URL gmlURL = new URL("platform:/resource/" + gmlPath); //$NON-NLS-1$ return GmlSerializer.createGMLWorkspace(gmlURL, null); } catch (final Exception e) { e.printStackTrace(); throw new CoreException(new Status(IStatus.ERROR, KalypsoSimulationUIPlugin.getID(), 0, Messages.getString("org.kalypso.simulation.ui.calccase.ModelNature.14") //$NON-NLS-1$ + e.getLocalizedMessage(), e)); } } /** * stellt fest, ob es sich um einen gltigen Zeitpunkt fr den Start der Prognose handelt * * @param cal * @return true when time is valid */ private boolean validateTime(final Calendar cal) { // todo: wre schner, wenn das besser parametrisiert werden knnte // z.B. ein Groovy-Skript aus der Modelspec o.. final String validHours = m_metadata.getProperty(META_PROP_VALID_HOURS, "VALID_FORECAST_HOURS=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23"); //$NON-NLS-1$ final int hour = cal.get(Calendar.HOUR_OF_DAY); return (" " + validHours + " ").indexOf(" " + hour + " ") != -1; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } public IStatus runCalculation(final IContainer calcCaseFolder, final IProgressMonitor monitor) throws CoreException { return launchAnt(STR_MODELLRECHNUNG_WIRD_DURCHGEFUEHRT, "runCalculation", null, calcCaseFolder, monitor); //$NON-NLS-1$ } /** * Load the calculation and read the value for the given property * * @param calcCase * @param propertyName * name of the property to read value for * @return value of the property to read */ public Object loadCalculationAndReadProperty(final IContainer calcCase, final String propertyName) throws CoreException { // load .calculation, dont create one if not existent final GMLWorkspace workspace = loadOrCreateControl(calcCase, CONTROL_NAME); if (workspace == null) return null; final Feature rootFeature = workspace.getRootFeature(); final FindPropertyByNameVisitor vis = new FindPropertyByNameVisitor(propertyName); workspace.accept(vis, rootFeature, FeatureVisitor.DEPTH_INFINITE); return vis.getResult(); } }