/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.persistence; import java.awt.Dimension; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import com.servoy.base.scripting.annotations.ServoyClientSupport; import com.servoy.base.util.DataSourceUtilsBase; import com.servoy.j2db.documentation.ServoyDocumented; import com.servoy.j2db.util.DataSourceUtils; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.FilteredIterator; import com.servoy.j2db.util.IFilter; import com.servoy.j2db.util.SortedList; import com.servoy.j2db.util.UUID; import com.servoy.j2db.util.Utils; /** * A solution also root object for meta data * * @author jblok */ @ServoyDocumented(category = ServoyDocumented.DESIGNTIME, typeCode = IRepository.SOLUTIONS) public class Solution extends AbstractRootObject implements ISupportChilds, ICloneable, ISupportUpdateableName, IMediaProvider { // iterating & changing this map's contents will happen in synchronize blocks (the easier way would // be using the ConcurrentHashMap as before, but a bug in Terracotta does not allow ConcurrentHashMaps to be serialized/deserialized); when the bug is solved // sync blocks can be reverted to using ConcurrentHashMap. See http://jira.terracotta.org/jira/browse/CDV-1377 public static final SerializableRuntimeProperty<HashMap<String, Style>> PRE_LOADED_STYLES = new SerializableRuntimeProperty<HashMap<String, Style>>() { private static final long serialVersionUID = 1L; }; /** * Store scope names when runnning in developer based in global js file names, this includes empty scopes. * When not set (like running in appserver), fallback to scopes used in objects, this does not include empty scopes. */ public static final RuntimeProperty<String[]> SCOPE_NAMES = new RuntimeProperty<String[]>() { }; /** * Key for caching default messages defined for the solution. */ public static final RuntimeProperty<Map<String, String>> DEFAULT_MESSAGES = new RuntimeProperty<Map<String, String>>() { }; public static final long serialVersionUID = 7758101764309127685L; public final static int TEXT_ORIENTATION_DEFAULT = 0; public final static int TEXT_ORIENTATION_LEFT_TO_RIGHT = 1; public final static int TEXT_ORIENTATION_RIGHT_TO_LEFT = 2; public final static int TEXT_ORIENTATION_LOCALE_SPECIFIC = 3; /* * IPersist Attributes */ Solution(IRepository repository, SolutionMetaData metaData) { super(repository, metaData); } public SolutionMetaData getSolutionMetaData() { return (SolutionMetaData)getMetaData(); } @Override public IRootObject getRootObject() { return this; } /* * _____________________________________________________________ Methods for Form handling */ public Form getForm(int id) { return selectById(getForms(null, false), id); } public Iterator<Form> getForms(Table basedOnTable, boolean sort) { return getForms(getAllObjectsAsList(), basedOnTable == null ? null : DataSourceUtils.createDBTableDataSource(basedOnTable.getServerName(), basedOnTable.getName()), sort); } public static Iterator<Form> getForms(List<IPersist> childs, final String datasource, boolean sort) { Iterator<Form> retval = new TypeIterator<Form>(childs, IRepository.FORMS, new IFilter<Form>() { public boolean match(Object f) { return datasource == null || (f instanceof Form && datasource.equals(((Form)f).getDataSource())); } }); if (sort) { return Utils.asSortedIterator(retval, NameComparator.INSTANCE); } return retval; } public Form getForm(String name) { return selectByName(getForms(null, false), name); } public Form createNewForm(IValidateName validator, Style style, String formName, String dataSource, boolean show_in_menu, Dimension size) throws RepositoryException { String name = formName == null ? "untitled" : formName; //$NON-NLS-1$ // Check if name is in use. validator.checkName(name, 0, new ValidatorSearchContext(IRepository.FORMS), false); Form f = (Form)getChangeHandler().createNewObject(this, IRepository.FORMS); // Set all the required properties. f.setName(name); f.setDataSource(dataSource); f.setShowInMenu(show_in_menu); if (size != null) f.setSize(size); if (style != null) f.setStyleName(style.getName()); addChild(f); if (getFirstFormID() == 0) setFirstFormID(f.getID()); if (getSolutionType() == SolutionMetaData.MOBILE) { f.setNavigatorID(Form.NAVIGATOR_NONE); } return f; } /* * _____________________________________________________________ Methods for Relation handling */ /** * When basedOnTable is specified it also returns global relations (based entirely on globals) */ public Iterator<Relation> getRelations(Table basedOnTable, boolean isPrimaryTable, boolean sort) throws RepositoryException { return getRelations(getRepository(), getAllObjectsAsList(), basedOnTable, isPrimaryTable, sort); } /** * Get all relations */ public Iterator<Relation> getRelations(boolean sort) { return getRelations(getAllObjectsAsList(), sort); } /** * Get all relations. * * @param childs * @param sort */ public static Iterator<Relation> getRelations(List<IPersist> childs, boolean sort) { Iterator<Relation> rels = new TypeIterator<Relation>(childs, IRepository.RELATIONS); if (sort) { return Utils.asSortedIterator(rels, NameComparator.INSTANCE); } return rels; } /** * Get all datasources for the table that are valid references to this table in all duplicate servers * @throws RepositoryException */ public static List<String> getTableDataSources(IRepository repository, ITable table) throws RepositoryException { if (table == null) { return null; } return getTableDataSources(repository, table.getDataSource()); } private static List<String> getTableDataSources(IRepository repository, String dataSource) throws RepositoryException { if (dataSource == null) return null; String[] stn = DataSourceUtilsBase.getDBServernameTablename(dataSource); List<String> dataSources = new ArrayList<String>(); if (repository == null || stn == null) // inmem or rep == null { dataSources.add(dataSource); } else { String[] serverNames; try { serverNames = repository.getDuplicateServerNames(stn[0]); } catch (RemoteException e) { throw new RepositoryException("Could not get relations", e); //$NON-NLS-1$ } if (serverNames.length == 1) { // no duplicates or an inmem table dataSources.add(dataSource); } else { // db tables with duplicate servers for (String serverName : serverNames) { dataSources.add(DataSourceUtils.createDBTableDataSource(serverName, stn[1])); } } } return dataSources; } /** * Get relations based on the table. When basedOnTable is null and isPrimaryTable is true only global relations are returned. * * @param childs * @param basedOnTable * @param isPrimaryTable * @param sort * @throws RepositoryException */ public static Iterator<Relation> getRelations(IRepository repository, List<IPersist> childs, ITable basedOnTable, boolean isPrimaryTable, boolean sort) throws RepositoryException { return getRelations(repository, childs, basedOnTable, isPrimaryTable, sort, true, false, false); } /** * Get relations based on the table. When basedOnTable is null and isPrimaryTable is true only global relations are returned. * * @param childs * @param basedOnTable * @param isPrimaryTable * @param addGlobalsAsWellWhenPrimary when isPrimaryTable == true, it will return global relations as well. * @param onlyGlobalsWhenForeign when isPrimaryTable == false, it will return only global relations that match basedOnTable. * @param onlyLiteralsWhenForeign when isPrimaryTable == false, it will return only literal relations that match basedOnTable. * @param sort * @throws RepositoryException */ public static Iterator<Relation> getRelations(IRepository repository, List<IPersist> childs, ITable basedOnTable, boolean isPrimaryTable, boolean sort, boolean addGlobalsAsWellWhenPrimary, boolean onlyGlobalsWhenForeign, boolean onlyLiteralsWhenForeign) throws RepositoryException { Iterator<Relation> retval = getRelations(childs, false); List<Relation> filtered = new ArrayList<Relation>(); List<String> dataSources = getTableDataSources(repository, basedOnTable); // null when table is null if (dataSources == null) dataSources = Collections.emptyList(); while (retval.hasNext()) { Relation r = retval.next(); if (isPrimaryTable) { if (r.isGlobal()) { if (addGlobalsAsWellWhenPrimary) filtered.add(r); } else if (dataSources.contains(r.getPrimaryDataSource())) { filtered.add(r); } } else { if (dataSources.contains(r.getForeignDataSource())) { if ((!onlyGlobalsWhenForeign && !onlyLiteralsWhenForeign) || (onlyGlobalsWhenForeign && r.isGlobal()) || (onlyLiteralsWhenForeign && r.isLiteral())) filtered.add(r); } } } if (sort) { Collections.sort(filtered, NameComparator.INSTANCE); } return filtered.iterator(); } public Relation getRelation(String name) { Iterator<Relation> it = getRelations(getAllObjectsAsList(), false); while (it.hasNext()) { Relation sm = it.next(); if (sm.getName().equals(name)) { return sm; } } return null; } public Relation createNewRelation(IValidateName validator, String relationName, int joinType) throws RepositoryException { String name = relationName == null ? "untitled" : relationName; //$NON-NLS-1$ //check if name is in use validator.checkName(name, 0, new ValidatorSearchContext(IRepository.RELATIONS), true); Relation obj = (Relation)getChangeHandler().createNewObject(this, IRepository.RELATIONS); obj.setJoinType(joinType); //set all the required properties obj.setName(name); addChild(obj); return obj; } public Relation createNewRelation(IValidateName validator, String name, String primaryDataSource, String foreignDataSource, int joinType) throws RepositoryException { Relation obj = createNewRelation(validator, name, joinType); obj.setPrimaryDataSource(primaryDataSource); obj.setForeignDataSource(foreignDataSource); return obj; } /* * _____________________________________________________________ Methods for ValueList handling */ public Iterator<ValueList> getValueLists(boolean sort) { return getValueLists(getAllObjectsAsList(), sort); } public static Iterator<ValueList> getValueLists(List<IPersist> childs, boolean sort) { Iterator<ValueList> vls = new TypeIterator<ValueList>(childs, IRepository.VALUELISTS); if (sort) { return Utils.asSortedIterator(vls, NameComparator.INSTANCE); } return vls; } public ValueList getValueList(int id) { return selectById(getValueLists(false), id); } public ValueList getValueList(String name) { return selectByName(getValueLists(false), name); } public ValueList createNewValueList(IValidateName validator, String vlName) throws RepositoryException { String name = vlName == null ? "untitled" : vlName; //$NON-NLS-1$ //check if name is in use validator.checkName(name, 0, new ValidatorSearchContext(IRepository.VALUELISTS), false); ValueList obj = (ValueList)getChangeHandler().createNewObject(this, IRepository.VALUELISTS); //set all the required properties obj.setName(name); addChild(obj); return obj; } /* * _____________________________________________________________ Methods for ScriptVariable handling */ public Iterator<ScriptVariable> getScriptVariables(boolean sort) { return getScriptVariables(getAllObjectsAsList(), null, sort); } public Iterator<ScriptVariable> getScriptVariables(String scopeName, boolean sort) { return getScriptVariables(getAllObjectsAsList(), scopeName, sort); } public static Iterator<ScriptVariable> getScriptVariables(List<IPersist> childs, final String scopeName, boolean sort) { Iterator<ScriptVariable> vars = new TypeIterator<ScriptVariable>(childs, IRepository.SCRIPTVARIABLES, new IFilter<ScriptVariable>() { public boolean match(Object o) { return scopeName == null || (o instanceof ISupportScope && scopeName.equals(((ISupportScope)o).getScopeName())); } }); if (sort) { return Utils.asSortedIterator(vars, NameComparator.INSTANCE); } return Utils.asSortedIterator(vars, LineNumberComparator.INSTANCE); } public ScriptVariable getScriptVariable(String scopeName, String name) { return selectByName(getScriptVariables(scopeName, false), name); } public ScriptVariable createNewScriptVariable(IValidateName validator, String scopeName, String varName, int variableType) throws RepositoryException { String name = varName == null ? "untitled" : varName; //$NON-NLS-1$ boolean hit = false; int[] types = Column.allDefinedTypes; for (int element : types) { if (variableType == element) { hit = true; break; } } if (!hit) { throw new RepositoryException("unknow variable type: " + variableType); //$NON-NLS-1$ } //check if name is in use validator.checkName(name, 0, new ValidatorSearchContext(scopeName, IRepository.SCRIPTVARIABLES), false); ScriptVariable obj = (ScriptVariable)getChangeHandler().createNewObject(this, IRepository.SCRIPTVARIABLES); //set all the required properties obj.setName(name); obj.setVariableType(variableType); obj.setScopeName(scopeName); addChild(obj); return obj; } /* * _____________________________________________________________ Methods for ScriptCalculation/TableNode handling */ public Iterator<ScriptCalculation> getScriptCalculations(Table basedOnTable, boolean sort) throws RepositoryException { return getScriptCalculations(getTableNodes(basedOnTable), sort).iterator(); } public static List<ScriptCalculation> getScriptCalculations(Iterator<TableNode> tablenodes, boolean sort) { List<ScriptCalculation> retval = null; if (sort) { retval = new SortedList<ScriptCalculation>(NameComparator.INSTANCE); } else { retval = new ArrayList<ScriptCalculation>(); } while (tablenodes.hasNext()) { TableNode tableNode = tablenodes.next(); Iterator<ScriptCalculation> scriptCalculations = tableNode.getScriptCalculations(); while (scriptCalculations.hasNext()) { retval.add(scriptCalculations.next()); } } return retval; } public TableNode getOrCreateTableNode(String dataSource) throws RepositoryException { Iterator<TableNode> it = getTableNodes(dataSource); if (it.hasNext()) { return it.next(); } //create return createNewTableNode(dataSource); } public ScriptCalculation createNewScriptCalculation(IValidateName validator, String dataSource, String name, String userTemplate) throws RepositoryException { return getOrCreateTableNode(dataSource).createNewScriptCalculation(validator, name, userTemplate); } public ScriptMethod createNewFoundsetMethod(IValidateName validator, String dataSource, String name, String userTemplate) throws RepositoryException { return getOrCreateTableNode(dataSource).createNewFoundsetMethod(validator, name, userTemplate); } public static List<ScriptMethod> getFoundsetMethods(Iterator<TableNode> tablenodes, boolean sort) { List<ScriptMethod> retval = null; if (sort) { retval = new SortedList<ScriptMethod>(NameComparator.INSTANCE); } else { retval = new ArrayList<ScriptMethod>(); } while (tablenodes.hasNext()) { TableNode tableNode = tablenodes.next(); Iterator<ScriptMethod> methods = tableNode.getFoundsetMethods(false); while (methods.hasNext()) { retval.add(methods.next()); } } return retval; } public AggregateVariable createNewAggregateVariable(IValidateName validator, String dataSource, String name, int aggType, String dataProviderIDToAggregate) throws RepositoryException { return getOrCreateTableNode(dataSource).createNewAggregateVariable(validator, name, aggType, dataProviderIDToAggregate); } public Iterator<AggregateVariable> getAggregateVariables(Table basedOnTable, boolean sort) throws RepositoryException { return getAggregateVariables(getTableNodes(basedOnTable), sort); } public static Iterator<AggregateVariable> getAggregateVariables(Iterator<TableNode> tableNodes, boolean sort) { List<AggregateVariable> retval = null; if (sort) { retval = new SortedList<AggregateVariable>(NameComparator.INSTANCE); } else { retval = new ArrayList<AggregateVariable>(); } while (tableNodes.hasNext()) { TableNode tableNode = tableNodes.next(); Iterator<AggregateVariable> aggregateVariables = tableNode.getAggregateVariables(); while (aggregateVariables.hasNext()) { retval.add(aggregateVariables.next()); } } return retval.iterator(); } //normally there is only one returned, but if using modules there can be multiple public Iterator<TableNode> getTableNodes(ITable table) throws RepositoryException { return getTableNodes(getRepository(), getAllObjectsAsList(), table); } public Iterator<TableNode> getTableNodes(String dataSource) { return getTableNodes(getAllObjectsAsList(), dataSource); } public static Iterator<TableNode> getTableNodes(List<IPersist> childs, final String dataSource) { if (dataSource == null) { return Collections.<TableNode> emptyList().iterator(); } return new FilteredIterator<TableNode>(new TypeIterator<TableNode>(childs, IRepository.TABLENODES), new IFilter<TableNode>() { public boolean match(Object o) { return dataSource.equals(((TableNode)o).getDataSource()); } }); } public static Iterator<TableNode> getTableNodes(IRepository repository, List<IPersist> childs, ITable table) throws RepositoryException { List<TableNode> retval = new ArrayList<TableNode>(); if (table != null) { List<String> dataSources = getTableDataSources(repository, table); Iterator<TableNode> it1 = new TypeIterator<TableNode>(childs, IRepository.TABLENODES); while (it1.hasNext()) { TableNode node = it1.next(); if (dataSources.contains(node.getDataSource())) { retval.add(node); } } } return retval.iterator(); } public static boolean areDataSourcesCompatible(IRepository repository, String dataSource1, String dataSource2) { if (Utils.stringSafeEquals(dataSource1, dataSource2)) return true; // check other datasources of the same table List<String> compatibleDataSources = null; try { compatibleDataSources = Solution.getTableDataSources(repository, dataSource2); // null when table is null } catch (RepositoryException e) { Debug.trace(e); } if (compatibleDataSources != null && compatibleDataSources.contains(dataSource1)) { return true; } return false; } public TableNode createNewTableNode(String dataSource) throws RepositoryException { TableNode obj = (TableNode)getChangeHandler().createNewObject(this, IRepository.TABLENODES); //set all the required properties obj.setDataSource(dataSource); addChild(obj); return obj; } /* * _____________________________________________________________ Methods from this class */ public int getSolutionID() { return getID(); } // do not need a set public void updateName(IValidateName validator, String arg) throws RepositoryException { validator.checkName(arg, getID(), new ValidatorSearchContext(this, IRepository.SOLUTIONS), false); //checkForNameChange(getRootObjectMetaData().getName(), arg); getRootObjectMetaData().setName(arg); } /* * _____________________________________________________________ Runtime methods from this class */ /* * _____________________________________________________________ Global Script Methods from this class */ public ScriptMethod getScriptMethod(int id) { return selectById(getScriptMethods(null, false), id); } public ScriptMethod getScriptMethod(String scopeName, String name) { return selectByName(getScriptMethods(scopeName, false), name); } public Iterator<ScriptMethod> getScriptMethods(String scopeName, boolean sort) { return getScriptMethods(getAllObjectsAsList(), scopeName, sort); } public static Iterator<ScriptMethod> getScriptMethods(List<IPersist> childs, final String scopeName, boolean sort) { Iterator<ScriptMethod> methods = new TypeIterator<ScriptMethod>(childs, IRepository.METHODS, new IFilter<ScriptMethod>() { public boolean match(Object o) { return scopeName == null || (o instanceof ISupportScope && scopeName.equals(((ISupportScope)o).getScopeName())); } }); if (sort) { return Utils.asSortedIterator(methods, NameComparator.INSTANCE); } return methods; } public ScriptMethod createNewGlobalScriptMethod(IValidateName validator, String scopeName, String scriptName) throws RepositoryException { String name = scriptName == null ? "untitled" : scriptName; //$NON-NLS-1$ validator.checkName(name, 0, new ValidatorSearchContext(scopeName, IRepository.METHODS), false); ScriptMethod obj = (ScriptMethod)getChangeHandler().createNewObject(this, IRepository.METHODS); //set all the required properties obj.setName(name); obj.setScopeName(scopeName); addChild(obj); return obj; } /*------------------------------------------------------------------------------------------------------------------------ * SECURITY * * Notes: These methods do not need to use the solution transaction to read the data. */ /** * Flag that tells if authentication is needed in order to access the solution. * If unchecked, the Smart Client will always require authentication, regardless of this setting. * If checked, authentication is required, and either a provided loginSolution or otherwise the default Servoy login mechanism will be used. * If default Servoy login mechanism is used, the "servoy.webclient.basic.authentication" setting on the Admin Page can be used to enable the use of the standard browser basic authentication. */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public boolean getMustAuthenticate() { return getSolutionMetaData().getMustAuthenticate(); } /** * Sets the mustAuthenticate. * * @return Returns a boolean */ // Needed for solution properties editing public void setMustAuthenticate(boolean mustAuthenticate) { getSolutionMetaData().setMustAuthenticate(mustAuthenticate); } /*------------------------------------------------------------------------------------------------------------------------ * LISTENERS public void iPersistChanged(IPersist persist) { getChangeHandler().fireIPersistChanged(persist); } public void iPersistCreated(IPersist persist) { getChangeHandler().fireIPersistCreated(persist); } public void iPersistRemoved(IPersist persist) { getChangeHandler().fireIPersistRemoved(persist); } */ /*------------------------------------------------------------------------------------------------------------------------ * MEDIA HANDLING */ public Media createNewMedia(IValidateName validator, String name) throws RepositoryException { return createNewMedia(validator, name, 0); } public Media createNewMedia(IValidateName validator, String name, int skip_element_id_for_name_check) throws RepositoryException { if (name == null) { throw new RepositoryException("Cannot create media without a name"); //$NON-NLS-1$ } // Check if name is in use. validator.checkName(name, skip_element_id_for_name_check, new ValidatorSearchContext(IRepository.MEDIA), false); Media m = (Media)getChangeHandler().createNewObject(this, IRepository.MEDIA); // Set all the required properties. m.setName(name); // m.setMediaData(mediaData); // m.setMimeType(mimeType); addChild(m); return m; } public Iterator<Media> getMedias(boolean sort) { return getMedias(getAllObjectsAsList(), sort); } public static Iterator<Media> getMedias(List<IPersist> childs, boolean sort) { Iterator<Media> medias = new TypeIterator<Media>(childs, IRepository.MEDIA); if (sort) { return Utils.asSortedIterator(medias, NameComparator.INSTANCE); } return medias; } public Media getMedia(int media_id) { return selectById(getMedias(false), media_id); } public Media getMedia(String name) { return selectByName(getMedias(false), name); } public String getProtectionPassword() { return getSolutionMetaData().getProtectionPassword(); } /** * @param alreadyKnownSolutions, can be null or a map (name -> solution) * @return a list of RootObjectReference */ public List<RootObjectReference> getReferencedModules(Map<String, Solution> alreadyKnownSolutions) throws RepositoryException { List<RootObjectReference> referencedModules = new ArrayList<RootObjectReference>(); String moduleNames = getModulesNames(); if (moduleNames != null) { StringTokenizer tk = new StringTokenizer(moduleNames, ";,"); //$NON-NLS-1$ int count = tk.countTokens(); if (count > 0) { while (tk.hasMoreTokens()) { try { SolutionMetaData metaData = null; int releaseNumber = -1; String moduleDescriptor = tk.nextToken(); int i = moduleDescriptor.indexOf(':'); String name = null; UUID moduleUuid = null; if (i != -1) { releaseNumber = Integer.parseInt(moduleDescriptor.substring(i + 1)); moduleDescriptor = moduleDescriptor.substring(0, i); } if (alreadyKnownSolutions == null || !alreadyKnownSolutions.containsKey(moduleDescriptor)) { if (moduleDescriptor.indexOf('-') != -1) { // A uuid reference. moduleUuid = UUID.fromString(moduleDescriptor); metaData = (SolutionMetaData)getRepository().getRootObjectMetaData(moduleUuid); if (metaData != null) name = metaData.getName(); } else { // A module name; for backwards compatibility. name = moduleDescriptor; metaData = (SolutionMetaData)getRepository().getRootObjectMetaData(name, IRepository.SOLUTIONS); if (metaData != null) moduleUuid = metaData.getRootObjectUuid(); } referencedModules.add(new RootObjectReference(name, moduleUuid, metaData, releaseNumber)); } } catch (Exception e) { throw new RepositoryException(e); } } } } return referencedModules; } public Map<String, Solution> getReferencedModulesRecursive(Map<String, Solution> result) throws RepositoryException { List<RootObjectReference> referencedModules = getReferencedModules(null); Iterator<RootObjectReference> iterator = referencedModules.iterator(); while (iterator.hasNext()) { RootObjectReference moduleReference = iterator.next(); RootObjectMetaData metaData = moduleReference.getMetaData(); if (metaData != null) { try { Solution module = (Solution)getRepository().getActiveRootObject(metaData.getRootObjectId()); if (!result.containsKey(metaData.getName())) { result.put(metaData.getName(), module); module.getReferencedModulesRecursive(result); } } catch (RemoteException e) { throw new RepositoryException(e); } } } return result; } /** * Add the login solution as module by first removing exiting login solutions from the modules list * @throws RepositoryException */ public void setLoginSolutionName(String loginSolutionName) throws RepositoryException { List<RootObjectReference> referencedModules = getReferencedModules(null); List<String> newModules = new ArrayList<String>(); for (RootObjectReference moduleReference : referencedModules) { RootObjectMetaData metaData = moduleReference.getMetaData(); if (metaData instanceof SolutionMetaData && ((SolutionMetaData)metaData).getSolutionType() != SolutionMetaData.LOGIN_SOLUTION) { newModules.add(((SolutionMetaData)metaData).getName()); } } if (loginSolutionName != null) newModules.add(loginSolutionName); String newModulesNames = null; if (newModules.size() > 0) { StringBuilder sb = new StringBuilder(); for (String module : newModules) { if (sb.length() > 0) { sb.append(','); } sb.append(module); } newModulesNames = sb.toString(); } setModulesNames(newModulesNames); } /** * Get the first module that is also a login solution. * @throws RepositoryException */ public String getLoginSolutionName() throws RepositoryException { List<RootObjectReference> referencedModules = getReferencedModules(null); for (RootObjectReference moduleReference : referencedModules) { RootObjectMetaData metaData = moduleReference.getMetaData(); if (metaData instanceof SolutionMetaData && ((SolutionMetaData)metaData).getSolutionType() == SolutionMetaData.LOGIN_SOLUTION) { return metaData.getName(); } } return null; } /** * The custom CSS used by the solution (a MEDIA lib entry). It can reference other media resources (even additional .css through relative '@import' statements). * For NGClient - this CSS will be available directly in the browser. */ @ServoyClientSupport(ng = true, mc = false, wc = false, sc = false) public int getStyleSheetID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_STYLESHEET).intValue(); } public void setStyleSheetID(int styleSheetMediaID) { setTypedProperty(StaticContentSpecLoader.PROPERTY_STYLESHEET, Integer.valueOf(styleSheetMediaID)); } /** * Get all scope names defined in this solution, includes globals scope. */ public Collection<String> getScopeNames() { String[] runtimeScopeNames = getRuntimeProperty(Solution.SCOPE_NAMES); if (runtimeScopeNames != null) { // set from developer based on global js file names return Arrays.asList(runtimeScopeNames); } // scope names not set from developer, use scope names found in solution objects Set<String> scopeNames = new HashSet<String>(); scopeNames.add(ScriptVariable.GLOBAL_SCOPE); for (IPersist persist : getAllObjectsAsList()) { if (persist instanceof ISupportScope) { String scopeName = ((ISupportScope)persist).getScopeName(); if (scopeName != null) { scopeNames.add(scopeName); } } } return scopeNames; } /** * Require authentication if mustAuthenticate flag is set or a loginSolution is defined. */ public boolean requireAuthentication() { try { return getMustAuthenticate() || getLoginSolutionName() != null; } catch (RepositoryException e) { Debug.error(e); return true; // on the safe side } } /** * The first form that loads when a solution is deployed. * * NOTE: If the Login form is specified, then the firstForm is the first form that will load next after the loginForm. */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public int getFirstFormID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_FIRSTFORMID).intValue(); } /** * The method that is executed when a solution closes. The default is -none-. * * @templatedescription Callback method for when solution is closed, force boolean argument tells if this is a force (not stoppable) close or not. * @templatename onSolutionClose * @templatetype Boolean * @templateparam Boolean force if false then solution close can be stopped by returning false * @templateaddtodo * @templatecode * return true */ public int getOnCloseMethodID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_ONCLOSEMETHODID).intValue(); } /** * The method that is executed when a solution opens. The default is -none-. * * @templatedescription Callback method for when solution is opened * When deeplinking into solutions, the argument part of the deeplink url will be passed in as the first argument * All query parameters + the argument of the deeplink url will be passed in as the second argument * For more information on deeplinking, see the chapters on the different Clients in the Deployment Guide. * @templatename onSolutionOpen * @templateaddtodo * @templateparam String arg startup argument part of the deeplink url with which the Client was started * @templateparam Object<Array<String>> queryParams all query parameters of the deeplink url with which the Client was started */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public int getOnOpenMethodID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_ONOPENMETHODID).intValue(); } /** * The menu bar title of a solution. */ public String getTitleText() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_TITLETEXT); } /** * @param i */ public void setLoginFormID(int i) { setTypedProperty(StaticContentSpecLoader.PROPERTY_LOGINFORMID, i); } /** * The name of the login form that loads when a solution is deployed. */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public int getLoginFormID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_LOGINFORMID).intValue(); } /** * @param i */ public void setOnErrorMethodID(int i) { setTypedProperty(StaticContentSpecLoader.PROPERTY_ONERRORMETHODID, i); } /** * The method that is executed when a solution opens and an error occurs. The default is -none. * * @templatedescription Callback method for when an error occurred (the error can be a JavaScript or Servoy Java error). * @templatename onError * @templatetype Boolean * @templateparam * ex exception to handle * @templateaddtodo * @templatecode * application.output('Exception Object: '+ex) * application.output('MSG: '+ex.getMessage()) * if (ex instanceof ServoyException) * { * /** @type {ServoyException} */ * var servoyException = ex; * application.output("is a ServoyException") * application.output("Errorcode: "+servoyException.getErrorCode()) * //var trace = servoyException.getStackTrace(); * if (servoyException.getErrorCode() == ServoyException.SAVE_FAILED) * { * plugins.dialogs.showErrorDialog( 'Error', 'It seems you did not fill in a required field', 'OK'); * //Get the failed records after a save * var array = databaseManager.getFailedRecords() * for( var i = 0 ; i < array.length ; i++ ) * { * var record = array[i]; * application.output(record.exception); * if (record.exception instanceof DataException) * { * /** @type {DataException} */ * var dataException = record.exception; * application.output('SQL: '+dataException.getSQL()) * application.output('SQLState: '+dataException.getSQLState()) * application.output('VendorErrorCode: '+dataException.getVendorErrorCode()) * } * } * return false * } * } * //if returns false or no return, error is not reported to client; if returns true error is reported * //by default error report means logging the error, in smart client an error dialog will also show up * return true */ public int getOnErrorMethodID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_ONERRORMETHODID).intValue(); } /** * @param i */ public void setFirstFormID(int i) { setTypedProperty(StaticContentSpecLoader.PROPERTY_FIRSTFORMID, i); } /** * @param i */ public void setOnCloseMethodID(int i) { setTypedProperty(StaticContentSpecLoader.PROPERTY_ONCLOSEMETHODID, i); } /** * @param i */ public void setOnOpenMethodID(int i) { setTypedProperty(StaticContentSpecLoader.PROPERTY_ONOPENMETHODID, i); } /** * @param text */ public void setTitleText(String string) { String text = string; if (text != null && (text.equals("<default>") || text.equals(""))) //$NON-NLS-1$//$NON-NLS-2$ { text = null; } setTypedProperty(StaticContentSpecLoader.PROPERTY_TITLETEXT, text); } /** * The i18n database server connection and database table that stores the i18n keys for a solution. */ @ServoyClientSupport(mc = true, wc = true, sc = true) public String getI18nDataSource() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_I18NDATASOURCE); } public void setI18nDataSource(String dataSource) { setTypedProperty(StaticContentSpecLoader.PROPERTY_I18NDATASOURCE, dataSource); } public String getI18nServerName() { String[] stn = DataSourceUtilsBase.getDBServernameTablename(getI18nDataSource()); return stn == null ? null : stn[0]; } public void setI18nServerName(String serverName) { setI18nDataSource(DataSourceUtils.createDBTableDataSource("".equals(serverName) ? null : serverName, getI18nTableName())); //$NON-NLS-1$ } public String getI18nTableName() { String[] stn = DataSourceUtilsBase.getDBServernameTablename(getI18nDataSource()); return stn == null ? null : stn[1]; } public void setI18nTableName(String tableName) { setI18nDataSource(DataSourceUtils.createDBTableDataSource(getI18nServerName(), "".equals(tableName) ? null : tableName)); //$NON-NLS-1$ } /** * The type of a solution; can be "Normal" (non-module), "Module", "Web client only", "Smart client only", * "Login", "Authenticator", "Pre-import hook module", "Post-import hook module", "Mobile". * These constants are defined in SolutionMetaData class. */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public int getSolutionType() { return getSolutionMetaData().getSolutionType(); } // Needed for solution properties editing public void setSolutionType(int type) { getSolutionMetaData().setSolutionType(type); } /** * The list of modules that have been added to a solution. */ @ServoyClientSupport(ng = true, mc = true, wc = true, sc = true) public String getModulesNames() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_MODULESNAMES); } public void setModulesNames(String arg) { setTypedProperty(StaticContentSpecLoader.PROPERTY_MODULESNAMES, arg); } /** * The direction that text is displayed. * * Options include: * DEFAULT * left to right * right to left * locale specific */ @ServoyClientSupport(ng = true, wc = true, sc = true) public int getTextOrientation() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_TEXTORIENTATION).intValue(); } public void setTextOrientation(int o) { setTypedProperty(StaticContentSpecLoader.PROPERTY_TEXTORIENTATION, o); } /** * Method that is executed when data broadcast occurs. The default is -none-. * * @templatedescription Callback method for data broadcast * @templatename onDataBroadcast * @templateparam String dataSource table data source * @templateparam Number action see SQL_ACTION_TYPES constants * @templateparam JSDataSet pks affected primary keys * @templateparam Boolean cached data was cached * @templateaddtodo */ public int getOnDataBroadcastMethodID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_ONDATABROADCASTMETHODID).intValue(); } public void setOnDataBroadcastMethodID(int arg) { setTypedProperty(StaticContentSpecLoader.PROPERTY_ONDATABROADCASTMETHODID, arg); } public int getOnInitMethodID() { return getTypedProperty(StaticContentSpecLoader.PROPERTY_ONINITMETHODID).intValue(); } public void setOnInitMethodID(int arg) { setTypedProperty(StaticContentSpecLoader.PROPERTY_ONINITMETHODID, arg); } }