001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.space;
020
021import org.jpos.util.ConcurrentUtil;
022import org.jpos.util.NameRegistrar;
023
024import java.util.StringTokenizer;
025import java.util.concurrent.ScheduledThreadPoolExecutor;
026
027/**
028 * Creates a space based on a space URI.
029 *
030 * <p>A space URI has three parts:
031 *  <ul>
032 *   <li>scheme
033 *   <li>name
034 *   <li>optional parameter
035 *  </ul>
036 *
037 * <p>Examples:
038 *
039 * <pre>
040 *   // default unnamed space (tspace:default)
041 *   Space sp = SpaceFactory.getSpace ();
042 *
043 *   // transient space named "test"
044 *   Space sp = SpaceFactory.getSpace ("transient:test");
045 *
046 *   // lspace (Loom-optimized) named "test"
047 *   Space sp = SpaceFactory.getSpace ("lspace:test");
048 *
049 *   // persistent space named "test"
050 *   Space sp = SpaceFactory.getSpace ("persistent:test");
051 *
052 *   // jdbm space named test
053 *   Space sp = SpaceFactory.getSpace ("jdbm:test");
054 *
055 *   // jdbm space named test, storage located in /tmp/test
056 *   Space sp = SpaceFactory.getSpace ("jdbm:test:/tmp/test");
057 * </pre>
058 *
059 */
060public class SpaceFactory {
061    /** Default constructor; no instance state to initialise. */
062    public SpaceFactory() {}
063    /** Scheme constant for transient (in-memory) spaces. */
064    public static final String TSPACE     = "tspace";
065    /** Scheme constant for L-space (Loom-optimized) transient spaces. */
066    public static final String LSPACE     = "lspace";
067    /** Scheme alias for {@link #TSPACE}. */
068    public static final String TRANSIENT  = "transient";
069    /** Scheme constant for persistent (jdbm-backed) spaces. */
070    public static final String PERSISTENT = "persistent";
071    /** Scheme constant used to look up an externally-registered spacelet. */
072    public static final String SPACELET   = "spacelet";
073    /** Scheme constant for JDBM-backed spaces. */
074    public static final String JDBM       = "jdbm";
075    /** Scheme constant for Berkeley DB (JE) backed spaces. */
076    public static final String JE         = "je";
077    /** Default name used for unnamed spaces. */
078    public static final String DEFAULT    = "default";
079    private static ScheduledThreadPoolExecutor gcExecutor = ConcurrentUtil.newScheduledThreadPoolExecutor();
080
081    /**
082     * Returns the default transient space (equivalent to {@code tspace:default}).
083     *
084     * @return the default TransientSpace
085     */
086    public static Space getSpace () {
087        return getSpace (TSPACE, DEFAULT, null);
088    }
089
090    /**
091     * Resolves a space URI of the form {@code scheme:name[:param]}.
092     *
093     * @param spaceUri space URI; {@code null} returns the default space
094     * @return Space for given URI or null
095     */
096    public static Space getSpace (String spaceUri) {
097        if (spaceUri == null)
098            return getSpace ();
099
100        String scheme = null;
101        String name   = null;
102        String param  = null;
103
104        StringTokenizer st = new StringTokenizer (spaceUri, ":");
105        int count = st.countTokens();
106        if (count == 0) {
107            scheme = TSPACE;
108            name   = DEFAULT;
109        }
110        else if (count == 1) {
111            scheme = TSPACE;
112            name   = st.nextToken ();
113        }
114        else {
115            scheme = st.nextToken ();
116            name   = st.nextToken ();
117        }
118        if (st.hasMoreTokens()) {
119            param  = st.nextToken ();
120        }
121        return getSpace (scheme, name, param);
122    }
123    /**
124     * Resolves the space identified by {@code scheme}, {@code name}, and optional {@code param},
125     * registering a newly-created space in {@link NameRegistrar} on first use.
126     *
127     * @param scheme space scheme (one of the {@code TSPACE}/{@code LSPACE}/... constants)
128     * @param name space name
129     * @param param optional scheme-specific parameter (e.g. file path for {@code jdbm:})
130     * @return the resolved space
131     * @throws SpaceError if the scheme is unknown or registration fails
132     */
133    public static Space getSpace (String scheme, String name, String param) {
134        Space sp = null;
135        String uri = normalize (scheme, name, param);
136        synchronized (SpaceFactory.class) {
137            try {
138                sp = (Space) NameRegistrar.get (uri);
139            } catch (NameRegistrar.NotFoundException e) {
140                if (SPACELET.equals (scheme) || "rspace".equals(scheme))
141                    throw new SpaceError (uri + " not found.");
142
143                sp = createSpace (scheme, name, param);
144                NameRegistrar.register (uri, sp);
145            }
146        }
147        if (sp == null) {
148            throw new SpaceError ("Invalid space: " + uri);
149        }
150        return sp;
151    }
152    /**
153     * Returns the shared executor used by spaces to run lease-expiry/GC tasks.
154     *
155     * @return the shared GC executor
156     */
157    public static ScheduledThreadPoolExecutor getGCExecutor() {
158        return gcExecutor;
159    }
160    private static Space createSpace (String scheme, String name, String param)
161    {
162        Space sp = null;
163        if (TSPACE.equals (scheme) || TRANSIENT.equals (scheme)) {
164            sp = new TSpace();
165        } else if (LSPACE.equals (scheme)) {
166            sp = new LSpace();
167        } else if (JDBM.equals (scheme) || PERSISTENT.equals (scheme)) {
168            if (param != null)
169                sp = JDBMSpace.getSpace (name, param);
170            else
171                sp = JDBMSpace.getSpace (name);
172        } else if (JE.equals (scheme)) {
173            if (param != null)
174                sp = JESpace.getSpace (name, param);
175            else
176                sp = JESpace.getSpace (name);
177        }
178        return sp;
179    }
180    private static String normalize (String scheme, String name, String param) {
181        StringBuilder sb = new StringBuilder (scheme);
182        sb.append (':');
183        sb.append (name);
184        if (param != null) {
185            sb.append (':');
186            sb.append (param);
187        }
188        return sb.toString();
189    }
190}
191