> # The Melati Project

> ### Release Notes

> #### last updated 29/01/2007 - timp

> #### Release 0.7.5-RC6

> #### 29/01/2007

> Major refactoring of org.melati.utils such that all dependencies are now on poem.utils if the particular util is used in POEM at all.
> The main work is in moving StringUtils, which is used extensivley in and outside of POEM.
> The other gotcha is the move of org.melati.util.MelatiLocale to org.melati.poem.PoemLocale, so check your templates as well as your code.
> The transaction api has moved to org.melati.poem.transaction.
> org.melati.LogicalDatabase now relies upon org.melati.poem.PoemDatabaseFactory.
> The enclosed class Persistent.Saved has been removed.
> The LogSQL, LogCommits and AddConstaints properties are applied from the properties file.

> #### Release 0.7.5

> #### 06/02/2005

> Support for command line apps.

> Additional support for Oracle and HSQLDB.

> You will need to change references to org.melati.servlet.MelatiContext to org.melati.PoemContext and remove direct access to its members, use the setters and getters. Eg in templates $melati.Context.logicalDatabase should now be $melati.PoemContext.LogicalDatabase

> #### Release 0.7.4

> #### 10/01/2005

> Addition of support for Oracle.

> #### Release 0.7.3

> #### 22/12/2004

> Fixes to TemplateServlet to allow reading templates from jars.

> Separation of JUnit tests into own tree.

> You will need to change `melati-static` to point to `/dist/melati/src/main`.

> #### Release 0.7.2

> #### 26/02/2004

> Upgrade of Velocity jars.

> Fixes to statictrees.js to accomodate variations in EACMA script.

> Addition of a JSP handling servlet.

> Better setting of content-type to reflect Accept-charset.

> Support for canselect columns and filtering out of rows for which the user does not have the capability.

> Support for other joins and use of Persistent to represent selection criteria on joins.

> Fixed bug affecting rollback of Persistent objects.

> Persistent.postEdit() hook called by Admin system to allow processing rows after user creation/edit.

> #### Release 0.7.1

> #### 11/07/2003

> A new PoemType BigDecimal has been added.

> The preprocessor has been modified to generate checkstyle compliant javadoc which has been retrofitted to existing POEM classes.

> An additional method on TemplateEngine now allows exception templates to be discovered on the classpath in addition to the relevant templet directory.

> #### Release 0.7.0

> #### 04/06/2003

> In Table.appendWhereClause() Column names are now qualified with the table name to allow rows of the table to be selected using joins.

> Webmacro 1.1 is now assumed.

> New DSD method added to Admin.

> The source tree has been massively cleaned up, due to the use of Eclipse and Maven highlighting silly format issues.

> Trees can be rendered using Velocity as well as webmacro.

> User Locale is now inferred from the user session.

> #### Release 0.6.1

> #### 11/06/2002

> - Support for MckoiDB
> - Support for HSQLDB
> - Bundling of jetty and hsqldb
> - Website improvements
> - Ant build improvements

> The template path convention has been changed, so that there is no need to have `org/melati` in your classpath. This is because both WebMacro and Velocity now default to a classpath template loader. This change should make no difference to existing installation.

> the major change is the bundling of Jetty and HSQLDB with the distribution as a step towards a Melati Developer's Kit.

> #### Release 0.6.0

> #### 19/04/2002

> - Support for MySQL

> #### Release 0.55.6

> #### 01/03/2002

> - Add Tree rendering to Admin
> - Tidy JSStatic tree
> - Incidently added support for MySQL, but this requires testing prior to 0.6.0 release

> #### Release 0.55.5

> #### 15/02/2002

> - Improve Admin frame handling
> - Add edit links for Reference Type objects in Admin
> - Add JSStatic tree

> #### Release 0.55.4

> #### 17/12/2001

> - Improve connection pending error message

> ## Release 0.55.3

> #### 18/09/2001

> - Add javascript email validation
> - when extracting fields, trim whitespace from values. This allows users to enter '19 ' in an integer field, and not generate an error.
> - fix trailing / for upload in ConfigServletTest
> - allow melati to run from an IP address under jserv

> ##

> ## Release 0.55.2

> #### 17/08/2001

> - Adds a 'remember me' feature to login. This stores the user's password in the cookie.

> ## Release 0.55.1

> #### 25/07/2001

> This is a minor maintenance release that fixes problems with uploading items for objects that don't yet exist.

> ##

> ##

> #### 12/07/2001

> - support for JSDK 2.2, specifically Tomcat
> - modifications to the upload process, allowing access to the Melati / MelatiContext
> - Improvements to error handling

> ### JSDK 2.2 / Tomcat

> If you only want to install one melati, but would like to run several contexts (analog of servlet zone), you should copy or make softlink of melati dir to WEB-INF/classes for each context (or melati jar to WEB-INF/lib/).

> ### Upload Process

> A limitation of the Upload Process was that the Melati / MelatiContext was not set up before the file upload begun. This meant that the location where the file is stored could not be manipulated depending on the User/Object/Field currently in use. Additionally no Access check was made, potentilly causing security problems. This has now been fixed.

> ### Error Handling

> You can now avoid Red Stacktrace Of Death effect when the user creates duplicate keys

> If you have specialised org/melati/error.wm for your site, you will have to rename your version to `org/melati/template/webmacro/templets/html/error/java.lang.Exception.wm`

> org.melati.servlet.TemplateServlet has been changed to handle exception conditions by searching for a templet named after the exception or a superclass, prefixed by "error/". In general this winds up as

> `org/melati/template/webmacro/templets/html/error/java.lang.Exception.wm`

> and that file has superseded the top level "error.wm" (now deleted from CVS). The immediate purpose of this is to enable us to supply a specialised file

> `org/melati/template/webmacro/templets/html/error/org.melati.poem.DuplicateKeySQLPoemException.wm`

> for handling the case in which the user inserts or modifies a record in a way which breaks index uniqueness (e.g. duplicate user name) without making it seem as if the world has ended.

> The implementation leverages a new feature of (ClassName)TempletLoader: you can optionally supply a "purpose" parameter, in this case "error", to direct it to look in a subdirectory of the standard templet repository corresponding to the purpose for which you want the templet. This makes it possible to use class names for templets without messing up the namespace when you want to do different things with the same class.

> ## Release 0.54.2

> #### 03/07/2001

> - fix to default rendering of html elements - \n is translated to <br> for elements that are displayed, but not if they are 'rendered for input'.
> - please use WebMacro 0.98, as 0.97 has a bug which menas that AccessExceptions do not always return a login page

> ## Release 0.54.1

> #### 20/06/2001

> - fixes to templates and templets required for Webmacro 0.97.
> - change to default rendering of html elements - \n is now translated to <br>.

> ## Release 0.54

> #### 11/06/2001

> - support for Webmacro 0.97. Previous versions of webmacro are no longer supported.
> - support for Velocity 1.1

> ### WebMacro 0.97

> 1. Cleaning up. We have removed all 'hacked' webmacro classes from Melati. If you have any org.webmacro.* classes hanging arround, please ensure you delete them.
> 2. FastWriter. FastWriter is now constructed with a Broker. So, you will need to replace any construction (eg):
>    `MelatiFastWriter(OutputStream output, String encoding)` with
>    `MelatiFastWriter(Broker broker, OutputStream output, String encoding)`
> 3. Exception Handling in Templates. Melati sets a `PropagateExceptionHandler` by default (forcing a logon page on encountering a `AccessPoemException).` If you want to use a `PassbackExceptionHandler`, you must set it in your template:
>    `$melati.setVariableExceptionHandler($melati.PassbackVariableExceptionHandler).` Please ensure you check your Templates.
> 4. Be careful with your templates (WM0.97 is not fully backward compatible). Please see http://www.webmacro.org/SyntaxChanges. Run this command to see if you have problems; grep -H -n "\\$\(\\w*\\.*\)*;" *.wm`
>    `

> ### Velocity

> Melati now supports Velocity 1.1.

> 1. At this stage, we don't ship any Velocity specific templates, instead we translate the webmacro templates 'on the fly'. To do this, we override the Velocity `file.resource.loader.class` with our own `org.melati.template.velocity.WebMacroFileResourceLoader.` If a .vm template with the correct name is found it will be used, otherwise, the .wm template will be loaded and translated.
> 2. The template path for Velocity is set in `org.melati.MelatiServlet.properties.` It may be a good idea to do this for WebMacro as well, but it wasn't obvious how to do this, so it has been left for the time being.

> ###

> ## Release 0.53.6

> #### 09/04/2001

> - Minor housekeeping release.

> ## Release 0.53.5

> #### 23/03/2001

> 0.53.5 introduces the following changes.

> - DSDs can now be imported into other DSDs

> ### Importing DSDs into other DSDs

> You can now import DSDs into other DSDs. For instance, in my ImportBoards.dsd I have a line:

>      import org.paneris.melati.boards.model.boards.dsd;

> This adds all the tables defined in the `boards.dsd` file into my ImportBoardsDatabase in such a way that you can access them in the same way as any tables defined in ImportBoards.dsd. In particular, the objects in these tables are instances of org.paneris.melati.boards.model.boards.* (i.e. any custom code is available).

> All the imported tables are included into the new database. Note that all Tables/Persistents are cast to the "base" table/persistent with the same name - you will have to manually cast them to the correct type. Here "base" means the oldest (nearest the root) base class. E.g. Poem.dsd defines User. boards.dsd defines a new persistent, org.paneris.melati.boards.model.User (and ImportBoards.dsd defines a org.paneris.melati.playing.importboards.User). If you call getUserTable() on any of these databases you will get a org.melati.poem.User object (which can be cast to org.paneris.melati.boards.User or org.paneris.melati.playing.importboards.User).

> #### Changes required to projects:

> 1) We no longer provide a getXxxxDatabase() function in each Table and Persistent. Instead you should either just use getDatabase() or you can use getXxxxDatabaseTables() to access tables specific to your project. In particular, getPoemDatabase() is no longer available in the Poem tables/persisents. So if you call getPoemDatabase() in your code, you should change it one of getDatabase() or getPoemDatabaseTables(). Similarly, if you re-run DSD on your project Xxxx then you will need to handle any calls to getXxxxDatabase(). You can either change them to getXxxxDatabaseTables(), getDatabase(), or you can add the old function into your non-generated files: e.g. in MyTable.java add:

>      XxxxDatabase getXxxxDatabase() {

>         (XxxxDatabase)getDatabase();

>      }

> 2) I have tested this on several databases and although I am fairly confident that it should work, IF YOU RE-RUN DSD ON YOUR OWN DATABASES YOU MAY EXPERIENCE PROBLEMS.

> 3) Your .dsd file must live in the same directory as your compiled code because it is loaded as a Resource.

> ## Release 0.53.4

> #### 13/03/2001

> 0.53.4 introduces the following changes.

> - Support for Webmacro 0.95b3. Previous versions of webmacro are no longer supported.
> - Improved support for MS SQLServer

> This release patches the memory problems experienced with Webmacro previously.

> ##

> ## Release 0.53.3

> #### 26/02/2001

> 0.53.3 introduces the following changes.

> - Support for Webmacro 2000-12-06. Previous versions of webmacro are no longer supported.
> - PoemTransaction monitoring, please see `
>   http://localhost/<servlet zone>/org.melati.poem.SessionAnalysisServlet`

> Please note, this is an experimental release. we have found problems with Webmacro 2000-12-06 eating memory.

> ##

> ## Release 0.53.2.1

> #### 01/03/2001

> Provides a workarround for the bug where Webmacro0.94 Fastwriter silently failed to write context of > 512 chars

> ## Release 0.53

> #### 13/02/2001

> 0.53 introduces the following changes.

> - support for Webmacro 0.94. Previous versions of webmacro are no longer supported.
> - improved file uploading (streaming to file, preventing "out of memory" errors).
> - cascading deletion in POEM

> ### Webmacro 0.94

> Webmacro 0.94 uses a FastWriter which writes to the underlying OutputStream. It no longer uses a PrintWriter. So; Melati now has the following new classes:

> `abstract class MelatiWriter` - for classes writing direct to the Servlet (or another) OutputStream / Writer

> `class MelatiStringWriter extends MelatiWriter` - for writing to a String `
> class MelatiSimpleWriter extends MelatiWriter` - writes to a given Writer`
> class MelatiBufferedWriter extends MelatiWriter` - writes to a given Writer (with buffering)

> `
> ` for using melati with webmacro: `
> class MelatiFastWriter extends MelatiWriter` - writes to a FastWriter `
> class MelatiBufferedFastWriter extends MelatiFastWriter` - writes to a FastWriter (with full buffering, output also available as a String)

> `melati.getWriter()` will ask your template engine (if you have one) for an appropiate writer, otherwise it will give you a`SimpleMelatiWriter` or a `BufferedMelatiWriter` depending on if you have got buffering on or off. `MelatiWriter` will also start a `Flusher` if appropiate.

> The only API change you need to be aware of is the alternate constructor for melati (for use in 'stand alone' mode) is now:

> `public Melati(MelatiConfig config, MelatiWriter writer)`

> If melati can't get an encoding from `response.getCharacterEncoding()`, it uses the default "UFT8".

> ### Improved Uploading

> Access properties of an uploaded file (or "normal" form fields) through MultipartFormField. Some of these properties are supplied by FormDataAdaptors which save each field somehow (e.g. to memory or to a file).

> You can handle uploaded files how you want by supplying a FormDataAdaptorFactory to MultipartDataDecoder. If you are using TemplateServlet then this is done automatically for you: It uses the FormDataAdaptorFactory set in the MelatiConfig (which can be set in org.melati.MelatiServlet.properties). You can override the default in your TemplateServlet by doing something like:

>      protected MelatiConfig melatiConfig() throws MelatiException {

>          MelatiConfig config = super.melatiConfig();

>          DefaultFileDataAdaptorFactory factory = new DefaultFileDataAdaptorFactory();

>          factory.setUploadDir("/tmp");

>          config.setFormDataAdaptorFactory(factory);

>          return config;

>      }

> For an example of how to parse an uploaded file manually, see org.melati.test.ConfigServletTest. org.melati.test.TemplateServletTestWM also allows you to upload files.

> If you are using UploadURLPoemType.wm for the renderinfo for a field (so you can upload files in Admin) then you need to make sure your FormDataAdaptorFactory implements getURL (e.g. use PoemFileDataAdaptorFactory)

> In summary, then, the important classes (in org.melati.servlet) are:

> `MultipartDataDecoder` - parses a Multipart form into
> `MultipartFormField`
> `MultipartFormField` - allows access to properties of uploaded fields (normal form fields and uploaded files)

> `FormDataAdaptor` - tell us how "save" an uploaded field:
> `MemoryDataAdaptor` - save the data in a byte array in memory
> `TemporyFileDataAdaptor` - save the data to a temporary file
> `DefaultFileDataAdaptor` - save the data in a (unique) file in the given directory

> `FormDataAdaptorFactory` - allows us to implement a policy for saving uploaded files (not normal form fields)
> `MemoryDataAdaptorFactory` - save all files in memory
> `TemporyFileDataAdaptorFactory` - save all files to a temporary file on disk
> `DefaultFileDataAdaptorFactory` - save all files in a (unique) file in the given directory
> `PoemFileDataAdaptorFactory` - save all files to in a directory specified in the database's setting table

> ### Cascading Deletion in POEM

> 0.53 has the following improvements to Melati's object deletion mechanism:

> - Facility for deleting referring objects, or NULLing out referring links, rather than aborting the delete in order to maintain referential integrity.
> - Intra-transation delete (which can be rolled back): handier than deleteAndCommit.

> #### Action required/risk analysis

> These changes have only been lightly tested, so administrators are advised to try the updated system out before deploying it.

> In principle, no action is required unless you are a programmer and your code overrides `Persistent.deleteAndCommit()'. In that case you should change it to override the new `Persistent.delete(java.util.Map)' instead.

> If you wish to use `Persistent.delete()' in your code in future, you should recreate the generated/*.java files from your application's DSD and recompile. Otherwise there is an unlocked race condition which might cause loss of referential integrity. Existing code which only calls `Persistent.deleteAndCommit()', and does not override it, does not need to be recompiled.

> A new field is introduced into the `columninfo' table, but POEM's unification mechanism will handle that automatically. (The field is nullable, so does not need initialising.)

> Adding support for the intra-transaction delete method involved introducing additional record locking in the case that a POEM thread creates a link to an object but does not examine its contents. Previously this would not read-lock the object; now it does. This is most unlikely to cause problems.

> #### Detailed description

> Flexible integrity fixes

> Previously, if there were objects in the database containing references to the object to be deleted, the deletion would simply fail. That is still the default, but two alternative behaviours are now also available: NULL out the offending links, or delete the linking objects too. The policy followed is configurable for each referring column in the database, so that ephemeral records associated with a master object can be set selectively to auto-delete rather than inappropriately obstruct deletion of the parent.

> To make a link delete its source when its target is deleted o in the DSD:

> - use "(integrityfix = delete)". For instance,
>   `table BookStocking (description = "A record that a bookshop stocks a book") {
>   (primary) Integer id; Bookshop shop (description = "The bookshop");
>   Book book (description = "The stocked book") (integrityfix = delete); ...
>   }`
>   The BookStocking record will be removed transparently if its `book' is deleted, but will (defaulting to the standard behaviour) prevent deletion of its `shop'.
> - via the admin system: find the column in the `Column' metadata table, and set its `Integrity fix' field to `delete'
> - via POEM directly: column.setIntegrityFix(StandardIntegrityFix.delete);

> To make the link instead NULL itself out, use `clear' rather than `delete'.

> Applications programmers are also able to override these settings for each individual delete operation, by passing an optional argument to the `Persistent.delete' method.

> #### Cancellable intra-transaction deletes

> The new `Persistent.delete' method differs from the existing `Persistent.deleteAndCommit' in that (a) it does not wait for exclusive access to the database before attempting to delete the object, and (b) it does not perform a `commit' (`rollback') immediately upon (un)successful completion. This means that it can be rolled back later if necessary, and makes it more like the normal get/set/create operations. Both methods will remain for now, and both support the new, more flexible integrity maintenance scheme.

> ## Release 0.52.1

> #### 21//01/2001

> The main motivation for this release is to seperate the Java doc from the rest of the code so that each can be downloaded seperately. The JavaDocs are no longer in CVS, but are available on the website for browsing and download.

> Additionally, the following changes are included.

> - ability to override Melati Config settings.
> - support for BINARY types in POEM

> ## Release 0.52

> #### 18/01/2001

> ### Introduction

> This release involves a major overhaul of the Melati servlet API and templet API.

> This has been done to provide the following:

> - a cleaner Servlet API that allows more flexible interaction with Melati, notable the ability to use Melati outside of a POEM Session
> - a cleaner relationship with Webmacro, allowing us to plug in other Template Engines, and to prepare the code to provide support the latest WebMacro release (0.95). Note, this release still only supports the July 19, 2000 WebMacro snapshot.

> This release does not introduce any significant new functionality, but does provide an API against which this can be done. Some minor new functaionality has been added:

> - File upload
> - Melati Test suite

> ### Warning

> - You existing 0.50 applications will not work with this release

> ### The new Servlet API

> `MelatiServlet` and `MelatiWMServlet` have been removed, and have been replaced by the following hierarchy:

> `org.melati.servlet.ConfigServlet extends HttpServlet
> org.melati.servlet.PoemServlet extends ConfigServlet
> ` `org.melati.servlet.TemplateServlet extends PoemServlet
> org.melati.template.webmacro.WebmacroMelatiServlet extends TemplateServlet
> `

> These are explained in more detail here:

> ####

> #### `org.melati.servlet.ConfigServlet`

> This simply creates a `Melati` using the configuration given in `org.melati.MelatiServlet.properties`, and a `MelatiContext.` The PathInfo is parsed to provide a 'Method'. Extending ConfigServlet is useful when writing servlets that do no want to run entirely within a POEM Session. Eg, a servlet that does a lengthy task not associated with POEM (like streaming a download) could tie up POEM Transactions for a long time unnecessarily.

> To use ConfigServlet, implement:

> `protected abstract void doConfiguredRequest(Melati melati) throws Exception;`

> `org.melati.servlet.PoemServlet`

> This does further parsing of the PathInfo into MelatiContext in order to provide a `LogicalDatabase`, `Table` and `Troid`. It then sets up a POEM Session (using the configuration given in `org.melati.LogicalDatabase.properties`.). PoemServlet is useful when you want to do build servlets without a Template Engine getting in the way. Eg, you want direct access to the Servlet Request, or want control over the response (using say Response.OutputStream).

> To use PoemServlet, implement:

> `protected abstract void doPoemRequest(Melati melati) throws Exception;`

> `org.melati.servlet.TemplateServlet`

> This initialises the Template Engine (given in `org.melati.MelatiServlet.properties).` TemplateServlet is used as the base servlet for generic Melati Servlets eg Admin, Login.

> To use TemplateServlet, implement:

> `protected abstract String doTemplateRequest(Melati melati, TemplateContext context) throws Exception;`

> You should return the template name without the extension which is added by the `TemplateServlet`

> `org.melati.template.webmacro.WebmacroTemplateServlet`

> This is provided for convenience. It simply provides an API for using melati with WebMacro. This API is a little different from the 0.50 `MelatiServlet` API. to use

> `public abstract String handle( Melati melati, WebContext webContext ) throws Exception;`

> So, instead of returning a template, simply return the template name (including the extension).

> ### Changes to the Packages

> Some stuff has been moved, and new packages have been created:

> `org.melati.template`

> This now contains the Template Engine Interface code (see below). It also contains `org.melati.template.webmacro and org.melati.template.jtemplater.` Package `org.melati.template` also now contains `MarkupLanguage` and associated classes.

> `org.melati.templet`

> Has gone. Everything has been moved to `org.melati.template`. It was basically too confusing to have packages with such similar names, and they are very intertwined anyway.

> `org.melati.servlet`

> Has the generic classes that implement `HttpServlet`. Also now contains `MelatiContext`.

> `org.melati.test`

> Has some nice servlets that allow you to test your installation. Bloody useful.

> ### Key Objects

> The objects used by Melati have changed a bit, here are the important ones

> `org.melati.MelatiConfig`

> This loads in `org.melati.Melatiservlet.properties.` Importantly it allows you to get a `Melati` ;

> `public Melati getContext(HttpServletRequest request, HttpServletResponse response) throws MelatiException`

> `org.melati.Melati`

> This is where everything happens, it does all the usual stuff (access to the servlet API) and much of what was in `org.melati.Melati``.` Importantly, is also has:

> `public Writer getWriter() throws IOException;`

> This give you access to the output stream at all times. By default, this is fully buffered, but you can turn this off completely or use a `ThrowingPrintWriter` which periodically flushes. Templets are now expanded directly to the output stream without haveing to be buffered in strings first.

> `org.melati.MelatiContext`

> This is unchanged.

> `org.melati.template.TemplateContext`

> This is the interface to the context against which your templates / templets are expanded. It provides `get` and `set` methods that (in the case of WebMacro) map directly onto the `WebContext`

> ### Changes to .properties

> ###

> You will need to add the following line to`org.melati.Melatiservlet.properties`

> `org.melati.MelatiServlet.templateEngine=org.melati.template.webmacro.WebmacroTemplateEngine`

> Also, if you have an entry for TempletLoader, it should now read:

> `org.melati.MelatiServlet.templetLoader=org.melati.template.ClassNameTempletLoader`

> And javascriptLibraryURL should read

> `org.melati.MelatiServlet.javascriptLibraryURL=/melati-static/template/webmacro/templets/html`

> (although it is not clear that it belongs here)

> ### Exception Handling

> I believe that exceptions are now presented better. Any exception that is not part of the Initialisation process should now be presented with a full stack trace to the browser, and logged with a full stack trace to `System.err.` If you get a Server Error, it means you have a problem in the initialisation and you need to review the error_log.

> ### What has not been done

> - source code documentation (some has been done)
> - complete seperation from WebMacro (we are still using the hacked Variable stuff), but this should be possible when we introduce WebMacro 0.95

> ### Installation Instructions

> Follow the usual instructions and then open:

> `http://localhost/<servlet zone>/org.melati.test.ConfigServletTest/`

> This should lead you through testing Melati and allow you to debug your installation easily.

> ## Release 0.50

> ### 23/11/2000

> This is the first public beta release of Melati.

> Melati has been used as Alpha software for over 9 months, and there are several sites in production.

> This beta release offers the following improvements over Alpha versions of Melati:

> - Testing and numerous bug fixes
> - Now available as a downloadable zip / tar.gz.
> - Precompiled melati-snapshot.jar allowing easier install.
> - Ant build script
> - Mailing list hosted on SourceForge
> - More documentation
> - Revised licensing - Dual ASF / GPL licensing.

> See the LICENSE.txt file for details about your rights and obligations with respect to the software, plus legal disclaimers, and so forth.

> #### Upgrading from Melati Alpha Software

> Please follow the Installation instructions.