001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.dataAccess;
019
020import io.stallion.Context;
021import io.stallion.dataAccess.db.DB;
022import io.stallion.dataAccess.file.JsonFilePersister;
023import io.stallion.exceptions.ConfigException;
024import io.stallion.exceptions.UsageException;
025import org.apache.commons.lang3.StringUtils;
026
027import static io.stallion.utils.Literals.empty;
028
029public class DataAccessRegistration {
030    private Class<? extends ModelController> controllerClass;
031    private Class<? extends Model> modelClass;
032    private Class<? extends Persister> persisterClass;
033    private Class<? extends Stash> stashClass;
034    private boolean syncAllToMemory = true;
035    private DynamicModelDefinition dynamicModelDefinition;
036    private String path;
037    private String relativePath;
038    private String absolutePath;
039    private String tableName;
040
041    private boolean writable = false;
042    private boolean shouldWatch = true;
043    private String nameSpace;
044    private boolean useDataFolder = true;
045    private String templatePath = "";
046    private String bucket = null;
047    private boolean multiplePerFile = false;
048    private String itemArrayName = "";
049    private boolean databaseBacked = false;
050
051    /**
052     * Ensure all required fields are set and valid, hydrate any other fields with
053     * defaults.
054     *
055     * @param appTargetPath
056     * @return
057     */
058    public DataAccessRegistration build(String appTargetPath) {
059        if (controllerClass == null) {
060            throw new ConfigException("You must choose a controller class for every DalRegistration");
061        }
062        ModelController controller;
063        try {
064            controller = controllerClass.newInstance();
065        } catch (InstantiationException e) {
066            throw new RuntimeException(e);
067        } catch (IllegalAccessException e) {
068            throw new RuntimeException(e);
069        }
070        if (modelClass == null) {
071            modelClass = controller.getModelClass();
072        }
073        if (modelClass == null) {
074            throw new ConfigException("You must either set the DalRegistration modelClass property, or the controller must return the model class");
075        }
076        if (persisterClass == null) {
077            if (DB.available()) {
078                persisterClass = DB.instance().getDefaultPersisterClass();
079            } else {
080                persisterClass = JsonFilePersister.class;
081            }
082        }
083        if (stashClass == null) {
084            if (syncAllToMemory) {
085                stashClass = LocalMemoryStash.class;
086            } else {
087                stashClass = NoStash.class;
088            }
089        }
090
091        if (empty(tableName)) {
092            tableName = getBucket();
093        }
094        if (empty(getPath())) {
095            setPath(getBucket());
096        }
097
098        hydratePaths(appTargetPath);
099
100
101        return this;
102    }
103
104    /**
105     * Parse the target path and hydrate the absolute and relative path fields.
106     * @param appTargetPath
107     */
108    public void hydratePaths(String appTargetPath) {
109        if (empty(getPath()) && empty(getTableName())) {
110            throw new UsageException("DalRegistration must have a non-empty path or tableName");
111        } else if (empty(getPath())) {
112            return;
113        }
114        if (getPath().contains("|")) {
115            String[] parts = getPath().split("\\|", 2);
116            setPath(parts[0]);
117            setItemArrayName(parts[1]);
118            setMultiplePerFile(true);
119        }
120
121        if (getPath().startsWith("/")) {
122            setAbsolutePath(getPath());
123            setRelativePath(getAbsolutePath().replace(appTargetPath, ""));
124            if (relativePath.startsWith("/")) {
125                relativePath = relativePath.substring(1);
126            }
127        } else {
128            if (!StringUtils.isEmpty(nameSpace)) {
129                path = nameSpace + "-" + path;
130            }
131            if (isUseDataFolder()) {
132                setAbsolutePath(Context.getSettings().getDataDirectory() + "/" + getPath());
133            } else {
134                setAbsolutePath(appTargetPath + "/" + getPath());
135            }
136            setRelativePath(getPath());
137        }
138    }
139
140    public Class<? extends ModelController> getControllerClass() {
141        return controllerClass;
142    }
143
144    /**
145     * The ModelController subclass to be associated with this registration
146     * @return
147     */
148    public DataAccessRegistration setControllerClass(Class<? extends ModelController> controllerClass) {
149        this.controllerClass = controllerClass;
150        return this;
151    }
152
153    public Class<? extends Model> getModelClass() {
154        return modelClass;
155    }
156
157    /**
158     * The data model to be used with this registration
159     * @return
160     */
161    public DataAccessRegistration setModelClass(Class<? extends Model> modelClass) {
162        this.modelClass = modelClass;
163        return this;
164    }
165
166    public String getPath() {
167        return path;
168    }
169
170    /**
171     * For file based registrations, the path to the folder with the data. Should be relative.
172     *
173     * @return
174     */
175    public DataAccessRegistration setPath(String path) {
176        this.path = path;
177        return this;
178    }
179
180    public String getRelativePath() {
181        return relativePath;
182    }
183
184    /**
185     * The path to the data, relative to the Settings.targetPath or relative to the
186     * app-data path, if isUseDataFolder() is true.
187     * @return
188     */
189    DataAccessRegistration setRelativePath(String relativePath) {
190        this.relativePath = relativePath;
191        return this;
192    }
193
194    public String getAbsolutePath() {
195        return absolutePath;
196    }
197
198    /**
199     * The absolute file system path to the data
200     * @return
201     */
202    DataAccessRegistration setAbsolutePath(String absolutePath) {
203        this.absolutePath = absolutePath;
204        return this;
205    }
206
207    public String getTableName() {
208        return tableName;
209    }
210
211    /**
212     * The database table for the data, for DB backed registrations
213     * @return
214     */
215    public DataAccessRegistration setTableName(String tableName) {
216        this.tableName = tableName;
217        return this;
218    }
219
220    public Class<? extends Persister> getPersisterClass() {
221        return persisterClass;
222    }
223
224    /**
225     * The Persister subclass to use for this registration
226     * @return
227     */
228    public DataAccessRegistration setPersisterClass(Class<? extends Persister> persisterClass) {
229        this.persisterClass = persisterClass;
230        return this;
231    }
232
233    public boolean isWritable() {
234        return writable;
235    }
236
237    /**
238     * Is Stallion allowed to write to the datastore, or is it read only?
239     * @param writable
240     * @return
241     */
242    public DataAccessRegistration setWritable(boolean writable) {
243        this.writable = writable;
244        return this;
245    }
246
247    public boolean isShouldWatch() {
248        return shouldWatch;
249    }
250
251    /**
252     * Should the file system be watched for changes, and reload changes automatically?
253     *
254     * @return
255     */
256    public DataAccessRegistration setShouldWatch(boolean shouldWatch) {
257        this.shouldWatch = shouldWatch;
258        return this;
259    }
260
261    public String getNameSpace() {
262        return nameSpace;
263    }
264
265    public DataAccessRegistration setNameSpace(String nameSpace) {
266        this.nameSpace = nameSpace;
267        return this;
268    }
269
270    /**
271     * The bucket is the shorthand by which the controller will be accessed from templates,
272     * from DalRegistry.get(), etc. Usually this should be the table name or the folder name,
273     * but you can set it to a custom value. It should be unique among all registrations.
274     *
275      * @return
276     */
277    public String getBucket() {
278        if (!empty(bucket)) {
279            return bucket;
280        } else if (!StringUtils.isEmpty(getTableName())) {
281            return getTableName();
282        } else {
283            return getRelativePath();
284        }
285    }
286
287    public String getTemplatePath() {
288        return templatePath;
289    }
290
291    /**
292     * For displayable registrations, the default template to use for rendering the object.
293     *
294     * @return
295     */
296    public DataAccessRegistration setTemplatePath(String templatePath) {
297        this.templatePath = templatePath;
298        return this;
299    }
300
301    public boolean isUseDataFolder() {
302        return useDataFolder;
303    }
304
305    /**
306     * If true, relative path will be relative to the app-data folder, not the project folder.
307     * @param useDataFolder
308     * @return
309     */
310    public DataAccessRegistration setUseDataFolder(boolean useDataFolder) {
311        this.useDataFolder = useDataFolder;
312        return this;
313    }
314
315    /**
316     * The bucket is the shorthand by which the controller will be accessed from templates,
317     * from DalRegistry.get(), etc. Usually this should be the table name or the folder name,
318     * but you can set it to a custom value. It should be unique among all registrations.
319     *
320     * @return
321     */
322    public DataAccessRegistration setBucket(String bucket) {
323        this.bucket = bucket;
324        return this;
325    }
326
327    public DynamicModelDefinition getDynamicModelDefinition() {
328        return dynamicModelDefinition;
329    }
330
331
332    /**
333     * Used for defining a model via Javascript
334     *
335     * @param dynamicModelDefinition
336     * @return
337     */
338    public DataAccessRegistration setDynamicModelDefinition(DynamicModelDefinition dynamicModelDefinition) {
339        this.dynamicModelDefinition = dynamicModelDefinition;
340        return this;
341    }
342
343    public boolean isMultiplePerFile() {
344        return multiplePerFile;
345    }
346
347    /**
348     * True for file-based data stores if there are multiple objects per file, instead of one file per object
349     * @return
350     */
351    public DataAccessRegistration setMultiplePerFile(boolean multiplePerFile) {
352        this.multiplePerFile = multiplePerFile;
353        return this;
354    }
355
356    public String getItemArrayName() {
357        return itemArrayName;
358    }
359
360    /**
361     * If isMultiplePerFile() is true, this is the name of the array of the objects.
362     * @param itemArrayName
363     * @return
364     */
365    public DataAccessRegistration setItemArrayName(String itemArrayName) {
366        this.itemArrayName = itemArrayName;
367        return this;
368    }
369
370    /**
371     * The class to use for the data stash. Default is LocalMemoryStash, which syncs
372     * everything into local memory. Use NoStash to avoid syncing into memory.
373     * @return
374     */
375    public Class<? extends Stash> getStashClass() {
376        return stashClass;
377    }
378
379    public DataAccessRegistration setStashClass(Class<? extends Stash> stashClass) {
380        this.stashClass = stashClass;
381        return this;
382    }
383
384    public boolean isSyncAllToMemory() {
385        return syncAllToMemory;
386    }
387
388    /**
389     * Should all objects from the data store be synced to local memory? Defaults to true.
390     * @return
391     */
392    public DataAccessRegistration setSyncAllToMemory(boolean syncAllToMemory) {
393        this.syncAllToMemory = syncAllToMemory;
394        return this;
395    }
396
397
398    public boolean isDatabaseBacked() {
399        return databaseBacked;
400    }
401
402    /**
403     * True if the registration should use the database, false if it should use the
404     * file system.
405     *
406     * @param databaseBacked
407     * @return
408     */
409    public DataAccessRegistration setDatabaseBacked(boolean databaseBacked) {
410        this.databaseBacked = databaseBacked;
411        return this;
412    }
413
414}
415