Package org.geoserver.config

Source Code of org.geoserver.config.GeoServerLoader

/* Copyright (c) 2001 - 2008 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.config;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogFactory;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.catalog.Wrapper;
import org.geoserver.catalog.impl.CatalogImpl;
import org.geoserver.catalog.util.LegacyCatalogImporter;
import org.geoserver.catalog.util.LegacyCatalogReader;
import org.geoserver.catalog.util.LegacyFeatureTypeInfoReader;
import org.geoserver.config.impl.GeoServerInfoImpl;
import org.geoserver.config.util.LegacyConfigurationImporter;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.config.util.XStreamPersisterFactory;
import org.geoserver.config.util.XStreamServiceLoader;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geotools.util.logging.Logging;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.vfny.geoserver.global.GeoserverDataDirectory;

/**
* Initializes GeoServer configuration and catalog on startup.
* <p>
* This class post processes the singleton beans {@link Catalog} and {@link GeoServer}, populating
* them from stored configuration.
* </p>
* @author Justin Deoliveira, The Open Planning Project
*
*/
public abstract class GeoServerLoader {

    static Logger LOGGER = Logging.getLogger( "org.geoserver" );
   
    protected GeoServerResourceLoader resourceLoader;
    GeoServer geoserver;
    XStreamPersisterFactory xpf = new XStreamPersisterFactory();
   
    //JD: this is a hack for the moment, it is used only to maintain tests since the test setup relies
    // on the old data directory structure, once the tests have been ported to the new structure
    // this ugly hack can die
    static boolean legacy = false;
   
    public GeoServerLoader( GeoServerResourceLoader resourceLoader ) {
        this.resourceLoader = resourceLoader;
    }
   
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        GeoserverDataDirectory.init((WebApplicationContext)applicationContext);
    }
   
    public void setXStreamPeristerFactory(XStreamPersisterFactory xpf) {
        this.xpf = xpf;
    }
   
    public static void setLegacy(boolean legacy) {
        GeoServerLoader.legacy = legacy;
    }
   
    public final Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    public final Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if ( bean instanceof Catalog ) {
            //ensure this is not a wrapper but the real deal
            if ( bean instanceof Wrapper && ((Wrapper) bean).isWrapperFor(Catalog.class) ) {
                return bean;
            }
           
            //load
            try {
                Catalog catalog = (Catalog) bean;
                XStreamPersister xp = xpf.createXMLPersister();
                xp.setCatalog( catalog );
                loadCatalog( catalog, xp );
               
                //initialize styles
                initializeStyles(catalog, xp);
            }
            catch (Exception e) {
                throw new RuntimeException( e );
            }
        }
       
        if ( bean instanceof GeoServer ) {
            geoserver = (GeoServer) bean;
            try {
                loadGeoServer( geoserver, xpf.createXMLPersister() );
               
                //load initializers
                loadInitializers(geoserver);
            }
            catch (Exception e) {
                throw new RuntimeException( e );
            }
            //initialize();
        }
       
        return bean;
    }

    protected abstract void loadCatalog(Catalog catalog, XStreamPersister xp) throws Exception;

    protected abstract void loadGeoServer(final GeoServer geoServer, XStreamPersister xp) throws Exception;

    protected void loadInitializers(GeoServer geoServer) throws Exception {
        //load initializer extensions
        List<GeoServerInitializer> initializers = GeoServerExtensions.extensions( GeoServerInitializer.class );
        for ( GeoServerInitializer initer : initializers ) {
            try {
                initer.initialize( geoServer );
            }
            catch( Throwable t ) {
                //TODO: log this
                t.printStackTrace();
            }
        }
    }
   
    /**
     * Does some post processing on the catalog to ensure that the "well-known" styles
     * are always around.
     */
    protected void initializeStyles( Catalog catalog, XStreamPersister xp) throws IOException {
        if ( catalog.getStyleByName( StyleInfo.DEFAULT_POINT ) == null ) {
            initializeStyle( catalog, StyleInfo.DEFAULT_POINT, "default_point.sld" );
        }
        if ( catalog.getStyleByName( StyleInfo.DEFAULT_LINE ) == null ) {
            initializeStyle( catalog, StyleInfo.DEFAULT_LINE, "default_line.sld" );
        }
        if ( catalog.getStyleByName( StyleInfo.DEFAULT_POLYGON ) == null ) {
            initializeStyle( catalog, StyleInfo.DEFAULT_POLYGON, "default_line.sld" );
        }
        if ( catalog.getStyleByName( StyleInfo.DEFAULT_RASTER ) == null ) {
            initializeStyle( catalog, StyleInfo.DEFAULT_RASTER, "default_raster.sld" );
        }
    }
   
    /**
     * Copies a well known style out to the data directory and adds a catalog entry for it.
     */
    void initializeStyle( Catalog catalog, String styleName, String sld ) throws IOException {
       
        //copy the file out to the data directory if necessary
        if ( resourceLoader.find( "styles", sld ) == null ) {
            FileUtils.copyURLToFile(GeoServerLoader.class.getResource(sld),
                new File( resourceLoader.findOrCreateDirectory("styles" ), sld) );
        }
       
        //create a style for it
        StyleInfo s = catalog.getFactory().createStyle();
        s.setName( styleName );
        s.setFilename( sld );
        catalog.add( s );
    }
   
    public void reload() throws Exception {
        destroy();
       
        //reload catalog, make sure we reload the underlying catalog, not any wrappers
        Catalog catalog = geoserver.getCatalog();
        if ( catalog instanceof Wrapper ) {
            catalog = ((Wrapper)geoserver.getCatalog()).unwrap(Catalog.class);
        }
       
        XStreamPersister xp = xpf.createXMLPersister();
        xp.setCatalog( catalog );
       
        loadCatalog( catalog, xp );
        loadGeoServer( geoserver, xp);
    }

    protected void readCatalog(Catalog catalog, XStreamPersister xp) throws Exception {
        //look for catalog.xml, if it exists assume we are dealing with
        // an old data directory
        File f = resourceLoader.find( "catalog.xml" );
        if ( f == null ) {
            //assume 2.x style data directory
            CatalogImpl catalog2 = (CatalogImpl) readCatalog( xp );
            ((CatalogImpl)catalog).sync( catalog2 );
        } else {
            // import old style catalog, register the persister now so that we start
            // with a new version of the catalog
            CatalogImpl catalog2 = (CatalogImpl) readLegacyCatalog( f, xp );
            ((CatalogImpl)catalog).sync( catalog2 );
        }
    }
   
    /**
     * Reads the catalog from disk.
     */
    Catalog readCatalog( XStreamPersister xp ) throws Exception {
        Catalog catalog = new CatalogImpl();
        catalog.setResourceLoader(resourceLoader);
        xp.setCatalog( catalog );
       
        CatalogFactory factory = catalog.getFactory();
      
        //styles
        File styles = resourceLoader.find( "styles" );
        for ( File sf : list(styles,new SuffixFileFilter(".xml") ) ) {
            try {
                //handle the .xml.xml case
                if (new File(styles,sf.getName()+".xml").exists()) {
                    continue;
                }
               
                StyleInfo s = depersist( xp, sf, StyleInfo.class );
                catalog.add( s );
               
                LOGGER.info( "Loaded style '" + s.getName() + "'" );
            }
            catch( Exception e ) {
                LOGGER.log( Level.WARNING, "Failed to load style from file '" + sf.getName() + "'" , e );
            }
        }
       
        //workspaces, stores, and resources
        File workspaces = resourceLoader.find( "workspaces" );
        if ( workspaces != null ) {
            //do a first quick scan over all workspaces, setting the default
            File dws = new File(workspaces, "default.xml");
            WorkspaceInfo defaultWorkspace = null;
            if (dws.exists()) {
                try {
                    defaultWorkspace = depersist(xp, dws, WorkspaceInfo.class);
                    LOGGER.info("Loaded default workspace " + defaultWorkspace.getName());
                }
                catch( Exception e ) {
                    LOGGER.log(Level.WARNING, "Failed to load default workspace", e);
                }
            }
            else {
                LOGGER.warning("No default workspace was found.");
            }
           
            for ( File wsd : list(workspaces, DirectoryFileFilter.INSTANCE ) ) {
                File f = new File( wsd, "workspace.xml");
                if ( !f.exists() ) {
                    continue;
                }
               
                WorkspaceInfo ws = null;
                try {
                    ws = depersist( xp, f, WorkspaceInfo.class );
                    catalog.add( ws );   
                }
                catch( Exception e ) {
                    LOGGER.log( Level.WARNING, "Failed to load workspace '" + wsd.getName() + "'" , e );
                    continue;
                }
               
                LOGGER.info( "Loaded workspace '" + ws.getName() +"'");
               
                //load the namespace
                File nsf = new File( wsd, "namespace.xml" );
                NamespaceInfo ns = null;
                if ( nsf.exists() ) {
                    try {
                        ns = depersist( xp, nsf, NamespaceInfo.class );
                        catalog.add( ns );
                    }
                    catch( Exception e ) {
                        LOGGER.log( Level.WARNING, "Failed to load namespace for '" + wsd.getName() + "'" , e );
                    }
                }
               
                //set the default workspace, this value might be null in the case of coming from a
                // 2.0.0 data directory. See http://jira.codehaus.org/browse/GEOS-3440
                if (defaultWorkspace != null ) {
                    if (ws.getName().equals(defaultWorkspace.getName())) {
                        catalog.setDefaultWorkspace(ws);
                        if (ns != null) {
                            catalog.setDefaultNamespace(ns);
                        }
                    }
                }
                else {
                    //create the default.xml file
                    defaultWorkspace = catalog.getDefaultWorkspace();
                    if (defaultWorkspace != null) {
                        try {
                            persist(xp, defaultWorkspace, dws);   
                        }
                        catch( Exception e ) {
                            LOGGER.log( Level.WARNING, "Failed to persist default workspace '" +
                                wsd.getName() + "'" , e );
                        }
                       
                    }
                }
               
            }
           
            for ( File wsd : list(workspaces, DirectoryFileFilter.INSTANCE ) ) {
               
                //load the stores for this workspace
                for ( File sd : list(wsd, DirectoryFileFilter.INSTANCE) ) {
                    File f = new File( sd, "datastore.xml");
                    if ( f.exists() ) {
                        //load as a datastore
                        DataStoreInfo ds = null;
                        try {   
                            ds = depersist( xp, f, DataStoreInfo.class );
                            catalog.add( ds );
                           
                            LOGGER.info( "Loaded data store '" + ds.getName() +"'");
                           
                            if (ds.isEnabled()) {
                                //connect to the datastore to determine if we should disable it
                                try {
                                    ds.getDataStore(null);
                                }
                                catch( Throwable t ) {
                                    LOGGER.warning( "Error connecting to '" + ds.getName() + "'. Disabling." );
                                    LOGGER.log( Level.INFO, "", t );
                                   
                                    ds.setError(t);
                                    ds.setEnabled(false);
                                }
                            }
                        }
                        catch( Exception e ) {
                            LOGGER.log( Level.WARNING, "Failed to load data store '" + sd.getName() +"'", e);
                            continue;
                        }
                       
                        //load feature types
                        for ( File ftd : list(sd,DirectoryFileFilter.INSTANCE) ) {
                            f = new File( ftd, "featuretype.xml" );
                            if( f.exists() ) {
                                FeatureTypeInfo ft = null;
                                try {
                                    ft = depersist(xp,f,FeatureTypeInfo.class);
                                }
                                catch( Exception e ) {
                                    LOGGER.log( Level.WARNING, "Failed to load feature type '" + ftd.getName() +"'", e);
                                    continue;
                                }
                               
                                catalog.add( ft );
                               
                                LOGGER.info( "Loaded feature type '" + ds.getName() +"'");
                               
                                f = new File( ftd, "layer.xml" );
                                if ( f.exists() ) {
                                    try {
                                        LayerInfo l = depersist(xp, f, LayerInfo.class );
                                        catalog.add( l );
                                       
                                        LOGGER.info( "Loaded layer '" + l.getName() + "'" );
                                    }
                                    catch( Exception e ) {
                                        LOGGER.log( Level.WARNING, "Failed to load layer for feature type '" + ft.getName() +"'", e);
                                    }
                                }
                            }
                            else {
                                LOGGER.warning( "Ignoring feature type directory " + ftd.getAbsolutePath() );
                            }
                        }
                    } else {
                        //look for a coverage store
                        f = new File( sd, "coveragestore.xml" );
                        if ( f.exists() ) {
                            CoverageStoreInfo cs = null;
                            try {
                                cs = depersist( xp, f, CoverageStoreInfo.class );
                                catalog.add( cs );
                           
                                LOGGER.info( "Loaded coverage store '" + cs.getName() +"'");
                            }
                            catch( Exception e ) {
                                LOGGER.log( Level.WARNING, "Failed to load coverage store '" + sd.getName() +"'", e);
                                continue;
                            }
                           
                            //load coverages
                            for ( File cd : list(sd,DirectoryFileFilter.INSTANCE) ) {
                                f = new File( cd, "coverage.xml" );
                                if( f.exists() ) {
                                    CoverageInfo c = null;
                                    try {
                                        c = depersist(xp,f,CoverageInfo.class);
                                        catalog.add( c );
                                       
                                        LOGGER.info( "Loaded coverage '" + cs.getName() +"'");
                                    }
                                    catch( Exception e ) {
                                        LOGGER.log( Level.WARNING, "Failed to load coverage '" + cd.getName() +"'", e);
                                        continue;
                                    }
                                   
                                    f = new File( cd, "layer.xml" );
                                    if ( f.exists() ) {
                                        try {
                                            LayerInfo l = depersist(xp, f, LayerInfo.class );
                                            catalog.add( l );
                                           
                                            LOGGER.info( "Loaded layer '" + l.getName() + "'" );
                                        }
                                        catch( Exception e ) {
                                            LOGGER.log( Level.WARNING, "Failed to load layer coverage '" + c.getName() +"'", e);
                                        }
                                    }
                                }
                                else {
                                    LOGGER.warning( "Ignoring coverage directory " + cd.getAbsolutePath() );
                                }
                            }
                        } else {
                            f = new File( sd, "wmsstore.xml" );
                            if(f.exists()) {
                                WMSStoreInfo wms = null;
                                try {
                                    wms = depersist( xp, f, WMSStoreInfo.class );
                                    catalog.add( wms );
                               
                                    LOGGER.info( "Loaded wmsstore '" + wms.getName() +"'");
                                } catch( Exception e ) {
                                    LOGGER.log( Level.WARNING, "Failed to load wms store '" + sd.getName() +"'", e);
                                    continue;
                                }
                               
                                //load wms layers
                                for ( File cd : list(sd,DirectoryFileFilter.INSTANCE) ) {
                                    f = new File( cd, "wmslayer.xml" );
                                    if( f.exists() ) {
                                        WMSLayerInfo wl = null;
                                        try {
                                            wl = depersist(xp,f,WMSLayerInfo.class);
                                            catalog.add( wl );
                                           
                                            LOGGER.info( "Loaded wms layer'" + wl.getName() +"'");
                                        }
                                        catch( Exception e ) {
                                            LOGGER.log( Level.WARNING, "Failed to load wms layer '" + cd.getName() +"'", e);
                                            continue;
                                        }
                                       
                                        f = new File( cd, "layer.xml" );
                                        if ( f.exists() ) {
                                            try {
                                                LayerInfo l = depersist(xp, f, LayerInfo.class );
                                                catalog.add( l );
                                               
                                                LOGGER.info( "Loaded layer '" + l.getName() + "'" );
                                            }
                                            catch( Exception e ) {
                                                LOGGER.log( Level.WARNING, "Failed to load cascaded wms layer '" + wl.getName() +"'", e);
                                            }
                                        }
                                    }
                                    else {
                                        LOGGER.warning( "Ignoring coverage directory " + cd.getAbsolutePath() );
                                    }
                                }
                            } else {
                                LOGGER.warning( "Ignoring store directory '" + sd.getName() "'");
                                continue;
                            }
                        }
                    }
                }
            }
        }
        else {
            LOGGER.warning( "No 'workspaces' directory found, unable to load any stores." );
        }

        //namespaces
       
        //layergroups
        File layergroups = resourceLoader.find( "layergroups" );
        if ( layergroups != null ) {
            for ( File lgf : list( layergroups, new SuffixFileFilter( ".xml" ) ) ) {
                try {
                    LayerGroupInfo lg = depersist( xp, lgf, LayerGroupInfo.class );
                    if(lg.getLayers() == null || lg.getLayers().size() == 0) {
                        LOGGER.warning("Skipping empty layer group '" + lg.getName() + "', it is invalid");
                        continue;
                    }
                    catalog.add( lg );
                   
                    LOGGER.info( "Loaded layer group '" + lg.getName() + "'" );   
                }
                catch( Exception e ) {
                    LOGGER.log( Level.WARNING, "Failed to load layer group '" + lgf.getName() + "'", e );
                }
            }
        }
               
        return catalog;
    }
   
    /**
     * Reads the legacy (1.x) catalog from disk.
     */
    Catalog readLegacyCatalog(File f, XStreamPersister xp) throws Exception {
        Catalog catalog2 = new CatalogImpl();
        catalog2.setResourceLoader(resourceLoader);
       
        //add listener now as a converter which will convert from the old style
        // data directory to the new
        GeoServerPersister p = new GeoServerPersister( resourceLoader, xp );
        if ( !legacy ) {
            catalog2.addListener( p );
        }
       
        LegacyCatalogImporter importer = new LegacyCatalogImporter(catalog2);
        importer.setResourceLoader(resourceLoader);
        importer.imprt(resourceLoader.getBaseDirectory());
       
        if ( !legacy ) {
            catalog2.removeListener( p );
        }
       
        if ( !legacy ) {
            //copy files from old feature type directories to new
            File featureTypesDir = resourceLoader.find( "featureTypes" );
            if ( featureTypesDir != null ) {
                LegacyCatalogReader creader = new LegacyCatalogReader();
                creader.read( f );
                Map<String,Map<String,Object>> dataStores = creader.dataStores();
               
                for ( File featureTypeDir : featureTypesDir.listFiles() ) {
                    if ( !featureTypeDir.isDirectory() ) {
                        continue;
                    }
                   
                    File featureTypeInfo = new File( featureTypeDir, "info.xml" );
                    if ( !featureTypeInfo.exists() )  {
                        continue;
                    }
                   
                    LegacyFeatureTypeInfoReader reader = new LegacyFeatureTypeInfoReader();
                    reader.read( featureTypeInfo );
                   
                    Map<String,Object> dataStore = dataStores.get( reader.dataStore() );
                    if ( dataStore == null ) {
                        continue;
                    }
                   
                    String namespace = (String) dataStore.get( "namespace" );
                    File destFeatureTypeDir =
                        resourceLoader.find( "workspaces", namespace, reader.dataStore(), reader.name() );
                    if ( destFeatureTypeDir != null ) {
                        //copy all the files over
                        for ( File file : featureTypeDir.listFiles() ) {
                            if ( file.isFile() && !featureTypeInfo.equals( file ) ) {
                                FileUtils.copyFile( file, new File( destFeatureTypeDir, file.getName() ) ) ;
                            }
                        }
                    }
                }
            }
           
            //rename catalog.xml
            f.renameTo( new File( f.getParentFile(), "catalog.xml.old" ) );
        }
       
        return catalog2;
    }
   
    protected void readConfiguration(GeoServer geoServer, XStreamPersister xp) throws Exception {
        //look for services.xml, if it exists assume we are dealing with
        // an old data directory
        File f = resourceLoader.find( "services.xml" );
        if ( f == null ) {
            //assume 2.x style
            f = resourceLoader.find( "global.xml");
            if ( f != null ) {
                BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) );
                try {
                    GeoServerInfoImpl global =
                        (GeoServerInfoImpl) xpf.createXMLPersister().load( in, GeoServerInfo.class );
                    geoServer.setGlobal( global );
                }
                finally {
                    in.close();
                }
            }
           
            //load logging
            f = resourceLoader.find( "logging.xml" );
            if ( f != null ) {
                BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) );
                try {
                    LoggingInfo logging = xpf.createXMLPersister().load( in, LoggingInfo.class );
                    geoServer.setLogging( logging );
                }
                finally {
                    in.close();
                }
            }
            //load services
            final List<XStreamServiceLoader> loaders =
                GeoServerExtensions.extensions( XStreamServiceLoader.class );
            for ( XStreamServiceLoader<ServiceInfo> l : loaders ) {
                try {
                    ServiceInfo s = l.load( geoServer );
                    geoServer.add( s );
                   
                    LOGGER.info( "Loaded service '" +  s.getId() + "', " + (s.isEnabled()?"enabled":"disabled") );
                }
                catch( Throwable t ) {
                    //TODO: log this
                    t.printStackTrace();
                }
            }
        } else {
            //add listener now as a converter which will convert from the old style
            // data directory to the new
            GeoServerPersister p = new GeoServerPersister( resourceLoader, xp );
            geoServer.addListener( p );
           
            //import old style services.xml
            new LegacyConfigurationImporter(geoServer).imprt(resourceLoader.getBaseDirectory());
           
            geoServer.removeListener( p );
           
            //rename the services.xml file
            f.renameTo( new File( f.getParentFile(), "services.xml.old" ) );
        }
       
    }
   
    /**
     * Helper method which uses xstream to persist an object as xml on disk.
     */
    void persist( XStreamPersister xp, Object obj, File f ) throws Exception {
        BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( ) );
        xp.save( obj, out );   
        out.flush();
        out.close();
    }
   
    /**
     * Helper method which uses xstream to depersist an object as xml from disk.
     */
    <T> T depersist( XStreamPersister xp, File f , Class<T> clazz ) throws IOException {
        BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) );
        T obj = xp.load( in, clazz );

        in.close();
        return obj;
    }
   
    /**
     * Helper method for listing files in a directory.
     */
    Collection<File> list( File d, IOFileFilter filter ) {
        if (d == null) {
            return Collections.EMPTY_LIST;
        }
        ArrayList<File> files = new ArrayList();
        for ( File f : d.listFiles() ) {
            if ( filter.accept( f ) ) {
                files.add( f );
            }
        }
        return files;
    }
   
    public void destroy() throws Exception {
        //dispose
        geoserver.dispose();
    }
}
TOP

Related Classes of org.geoserver.config.GeoServerLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.