OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 67 → Rev 73

/trunk/OpenConcerto/src/org/openconcerto/sql/mapping.xml
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/PropsConfiguration.java
19,6 → 19,7
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBStructureItem;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.FieldMapper;
import org.openconcerto.sql.model.HierarchyLevel;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLDataSource;
27,7 → 28,9
import org.openconcerto.sql.model.SQLServer;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.LogUtils;
36,6 → 39,7
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.StreamUtils;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
58,8 → 62,11
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.ResourceBundle.Control;
import java.util.Set;
import java.util.logging.FileHandler;
import java.util.logging.Level;
153,6 → 160,7
private SQLServer server;
private DBSystemRoot sysRoot;
private DBRoot root;
private UserRightsManager urMngr;
// rest
private ProductInfo productInfo;
private ShowAs showAs;
176,6 → 184,7
// SSL
private Session conn;
private boolean isUsingSSH;
private FieldMapper fieldMapper;
 
public PropsConfiguration() throws IOException {
this(new File("fwk_SQL.properties"), DEFAULTS);
210,6 → 219,8
if (this.server != null) {
this.server.destroy();
}
if (this.urMngr != null)
UserRightsManager.clearInstanceIfSame(this.urMngr);
if (this.directoryListener != null)
this.directory.removeListener(this.directoryListener);
}
308,6 → 319,11
throw new NullPointerException("no rootname");
}
 
// return null, if none desired
protected UserRightsManager createUserRightsManager(final DBRoot root) {
return UserRightsManager.setInstanceIfNone(root);
}
 
public String getRootName() {
return this.getProperty("base.root");
}
445,6 → 461,7
this.conn.setConfig(config);
// wait no more than 6 seconds for TCP connection
this.conn.connect(6000);
afterSSLConnect(this.conn);
 
isAuthenticated = true;
} catch (final Exception e) {
454,6 → 471,9
throw new IllegalStateException("Authentication failed.");
}
 
protected void afterSSLConnect(Session conn) {
}
 
public String getSSLUserName() {
return this.getProperty("server.wan.user");
}
512,7 → 532,9
protected void initDS(final SQLDataSource ds) {
ds.setCacheEnabled(true);
// supported by postgreSQL from 9.1-901, see also Connection#setClientInfo
ds.addConnectionProperty("ApplicationName", getAppID());
final String appID = getAppID();
if (appID != null)
ds.addConnectionProperty("ApplicationName", appID);
propIterate(new IClosure<String>() {
@Override
public void executeChecked(final String propName) {
625,9 → 647,6
}
System.setErr(new PrintStream(new BufferedOutputStream(err, 128), true));
System.setOut(new PrintStream(new BufferedOutputStream(out, 128), true));
} catch (final IOException e) {
throw new IllegalStateException("unable to write to log file", e);
}
// Takes about 350ms so run it async
new Thread(new Runnable() {
@Override
640,6 → 659,9
}
}
}).start();
} catch (final Exception e) {
ExceptionHandler.handle("Redirection des sorties standards impossible", e);
}
} else {
System.out.println("Standard streams not redirected to file");
}
658,7 → 680,7
final FileHandler fh = new FileHandler(logFile.getPath(), 5 * 1024 * 1024, 2, true);
fh.setFormatter(new SimpleFormatter());
Logger.getLogger("").addHandler(fh);
} catch (final IOException e) {
} catch (final Exception e) {
ExceptionHandler.handle("Enregistrement du Logger désactivé", e);
}
 
722,7 → 744,7
 
// items will be passed to #getStream(String)
protected List<String> getMappings() {
return Arrays.asList("mapping.xml", "mapping-" + this.getProperty("customer") + ".xml");
return Arrays.asList("mapping", "mapping-" + this.getProperty("customer"));
}
 
protected SQLFieldTranslator createTranslator() {
730,16 → 752,41
if (mappings.size() == 0)
throw new IllegalStateException("empty mappings");
 
final SQLFieldTranslator trns = new SQLFieldTranslator(this.getRoot(), PropsConfiguration.class.getResourceAsStream("mapping.xml"), this.getDirectory());
final SQLFieldTranslator trns = new SQLFieldTranslator(this.getRoot(), null, this.getDirectory());
// perhaps listen to UserProps (as in TM)
return loadTranslations(trns, this.getRoot(), mappings);
}
 
protected final SQLFieldTranslator loadTranslations(final SQLFieldTranslator trns, final DBRoot root, final List<String> mappings) {
final Locale locale = TM.getInstance().getTranslationsLocale();
final Control cntrl = TranslationManager.getControl();
boolean found = false;
// better to have a translation in the correct language than a translation for the correct
// customer in the wrong language
final String fakeBaseName = "";
for (Locale targetLocale = locale; targetLocale != null && !found; targetLocale = cntrl.getFallbackLocale(fakeBaseName, targetLocale)) {
final List<Locale> langs = cntrl.getCandidateLocales(fakeBaseName, targetLocale);
// SQLFieldTranslator overwrite, so we need to load from general to specific
final ListIterator<Locale> listIterator = CollectionUtils.getListIterator(langs, true);
while (listIterator.hasNext()) {
final Locale lang = listIterator.next();
found |= loadTranslations(trns, PropsConfiguration.class.getResourceAsStream(cntrl.toBundleName("mapping", lang) + ".xml"), root);
for (final String m : mappings) {
// do not force to have one mapping for each client
final InputStream in = this.getStream(m);
if (in != null)
trns.load(this.getRoot(), in);
found |= loadTranslations(trns, this.getStream(cntrl.toBundleName(m, lang) + ".xml"), root);
}
}
}
return trns;
}
 
private final boolean loadTranslations(final SQLFieldTranslator trns, final InputStream in, final DBRoot root) {
final boolean res = in != null;
// do not force to have one mapping for each client and each locale
if (res)
trns.load(root, in);
return res;
}
 
protected File createWD() {
return new File(this.getProperty("wd"));
}
891,7 → 938,11
 
@Override
public final String getAppName() {
return this.getProductInfo().getName();
final ProductInfo productInfo = this.getProductInfo();
if (productInfo != null)
return productInfo.getName();
else
return this.getProperty("app.name");
}
 
@Override
917,6 → 968,8
 
private final void setRoot(final DBRoot root) {
this.root = root;
// be sure to try to set a manager to avoid giving all permissions to everyone
this.urMngr = createUserRightsManager(root);
}
 
private final void setShowAs(final ShowAs showAs) {
934,4 → 987,12
private final void setWD(final File dir) {
this.wd = dir;
}
 
public FieldMapper getFieldMapper() {
return fieldMapper;
}
 
public void setFieldMapper(FieldMapper fieldMapper) {
this.fieldMapper = fieldMapper;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/mapping_en.xml
New file
0,0 → 1,25
<?xml version="1.0" encoding="UTF-8" ?>
<ROOT>
<TABLE name="USER_COMMON">
<FIELD name="NOM" label="Last Name" />
<FIELD name="PRENOM" label="First name" />
<FIELD name="PASSWORD" label="Password" />
<FIELD name="PASSWORD_CONFIRM" label="Confirm" />
<FIELD name="LOGIN" label="Login" />
<FIELD name="SURNOM" label="Nickname" />
<FIELD name="ADMIN" label="Administrator" titlelabel="Admin." />
<FIELD name="ID_USER_RIGHT_COMMON" label="User rights" />
<FIELD name="MAIL" label="E-Mail" />
</TABLE>
<TABLE name="RIGHT">
<FIELD name="CODE" label="Code" />
<FIELD name="NOM" label="Name" titlelabel="Name of right" />
<FIELD name="DESCRIPTION" label="Description" titlelabel="Desc." />
</TABLE>
<TABLE name="USER_RIGHT">
<FIELD name="ID_USER_COMMON" label="User" />
<FIELD name="ID_RIGHT" label="Right" />
<FIELD name="OBJECT" label="Object" />
<FIELD name="HAVE_RIGHT" label="Right granted" titlelabel="Granted" />
</TABLE>
</ROOT>
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/Login.java
22,7 → 22,6
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.utils.Base64;
import org.openconcerto.utils.CollectionUtils;
46,7 → 45,9
/**
* A reason returned by {@link #connectClear(String, String)}.
*/
public static final String UNKNOWN_USER = "Utilisateur inconnu";
public static final String UNKNOWN_USER = "unknownUser";
public static final String MORE_THAN_ONE_USER = "multipleUser";
public static final String WRONG_PASSWORD = "wrongPass";
private final DBRoot root;
private final SQLTable userT;
 
112,9 → 113,9
final String pass = passwords.get(i);
encPass = this.connect(userRow, pass, encoded);
}
res = Tuple2.create(encPass == null ? "Mot de passe erronné" : null, encPass);
res = Tuple2.create(encPass == null ? WRONG_PASSWORD : null, encPass);
} else if (users.size() > 1) {
res = Tuple2.create("Plusieurs utilisateurs nommés " + login, null);
res = Tuple2.create(MORE_THAN_ONE_USER, null);
} else {
assert users.size() == 0;
res = Tuple2.create(UNKNOWN_USER, null);
130,7 → 131,7
}
 
private final List<SQLRow> findUser(final String login) {
final SQLSelect selUser = new SQLSelect(this.userT.getBase());
final SQLSelect selUser = new SQLSelect();
selUser.addSelect(this.userT.getField("ID"));
selUser.addSelect(this.userT.getField("PASSWORD"));
 
153,8 → 154,6
if (dbPass == null || dbPass.equals(encPass) || dbPass.equals(encodePassword(""))) {
// --->Connexion
UserManager.getInstance().setCurrentUser(userRow.getID());
// Preload right to avoid request in AWT later
UserRightsManager.getInstance().preloadRightsForUserId(userRow.getID());
// Authentification OK
res = encPass;
assert res != null;
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/InfoPanel.java
13,6 → 13,7
package org.openconcerto.sql.ui;
 
import org.openconcerto.sql.TM;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.SystemInfoPanel;
 
33,17 → 34,17
final GridBagConstraints c = new DefaultGridBagConstraints();
c.weightx = 1;
c.weighty = 1;
this.add(createTitle("Logiciel"), c);
this.add(createTitle("infoPanel.softwareTitle"), c);
c.gridy++;
this.add(new SoftwareInfoPanel(), c);
c.gridy++;
this.add(createTitle("Informations système"), c);
this.add(createTitle("infoPanel.systemTitle"), c);
c.gridy++;
this.add(new SystemInfoPanel(), c);
}
 
private JLabel createTitle(final String text) {
final JLabel res = new JLabel(text);
final JLabel res = new JLabel(TM.tr(text));
final Font font = res.getFont();
res.setFont(font.deriveFont(font.getSize2D() * 1.2f).deriveFont(Font.BOLD));
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/RadioButtons.java
22,6 → 22,9
import org.openconcerto.sql.sqlobject.itemview.RowItemViewComponent;
import org.openconcerto.ui.component.JRadioButtons;
import org.openconcerto.utils.SwingWorker2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.Transformer;
import org.openconcerto.utils.i18n.TM;
 
import java.util.LinkedHashMap;
import java.util.Map;
36,6 → 39,7
 
private final String colName;
private SQLField field;
private ITransformer<? super String, String> tm;
 
public RadioButtons() {
this("LABEL");
44,8 → 48,31
public RadioButtons(String colName) {
super();
this.colName = colName;
this.tm = Transformer.nopTransformer();
}
 
public final RadioButtons initLocalization(final TM tm) {
return this.initLocalization(new ITransformer<String, String>() {
@Override
public String transformChecked(String input) {
return tm.translate(input);
}
});
}
 
/**
* Allow to localize choices.
*
* @param tm will be passed the column value.
* @return this.
*/
public final RadioButtons initLocalization(final ITransformer<? super String, String> tm) {
if (this.isInited())
throw new IllegalStateException("Already inited");
this.tm = tm;
return this;
}
 
private final Map<Integer, String> createChoices() {
final Map<Integer, String> res = new LinkedHashMap<Integer, String>();
 
62,7 → 89,7
 
for (final SQLRow choice : SQLRowListRSH.execute(sel)) {
final String choiceLabel = choice.getString(this.colName);
res.put(choice.getID(), choiceLabel);
res.put(choice.getID(), this.tm.transformChecked(choiceLabel));
}
 
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/StringWithId.java
New file
0,0 → 1,82
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.ui;
 
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
 
public class StringWithId implements Externalizable {
private long id;
// value is always trimed
private String value;
 
public StringWithId() {
}
 
public StringWithId(long id, String value) {
this.id = id;
this.value = value.trim();
}
 
public StringWithId(String condensedValue) {
int index = condensedValue.indexOf(',');
if (index <= 0) {
throw new IllegalArgumentException("invalid condensed value " + condensedValue);
}
id = Long.parseLong(condensedValue.substring(0, index));
value = condensedValue.substring(index + 1).trim();
}
 
public long getId() {
return id;
}
 
public String getValue() {
return value;
}
 
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(id);
out.writeUTF(value);
}
 
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readLong();
value = in.readUTF().trim();
}
 
@Override
public String toString() {
return this.value;
}
 
@Override
public boolean equals(Object obj) {
StringWithId o = (StringWithId) obj;
return o.getId() == this.getId() && o.getValue().endsWith(this.getValue());
}
 
@Override
public int hashCode() {
return (int) id + value.hashCode();
}
 
public String toCondensedString() {
return id + "," + value;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/ConnexionPanel.java
13,7 → 13,9
package org.openconcerto.sql.ui;
 
import static org.openconcerto.sql.TM.getTM;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.preferences.UserProps;
21,6 → 23,8
import org.openconcerto.sql.sqlobject.IComboModel;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
import org.openconcerto.sql.sqlobject.SQLRequestComboBox;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.ReloadPanel;
import org.openconcerto.ui.valuewrapper.EmptyValueWrapper;
118,12 → 122,12
private final ReloadPanel reloadPanel;
private boolean isConnecting = false;
private String connectionAllowed;
private final JCheckBox saveCheckBox = new JCheckBox("Mémoriser le mot de passe");
private final JButton buttonConnect = new JButton("Connexion");
private String adminLogin = "Administrateur";
private final JLabel loginLabel = new JLabel("Identifiant");
private final JLabel passwordLabel = new JLabel("Mot de passe");
private final JLabel companyLabel = new JLabel("Société");
private final JCheckBox saveCheckBox = new JCheckBox(getTM().translate("loginPanel.storePass"));
private final JButton buttonConnect = new JButton(getTM().translate("loginPanel.loginAction"));
private String adminLogin = getTM().translate("loginPanel.adminLogin");
private final JLabel loginLabel = new JLabel(getTM().translate("loginPanel.loginLabel"));
private final JLabel passwordLabel = new JLabel(getTM().translate("loginPanel.passLabel"));
private final JLabel companyLabel = new JLabel(getTM().translate("loginPanel.companyLabel"));
private String localeBaseName = null;
private final List<Locale> localesToDisplay = new ArrayList<Locale>();
private final JButton langButton = new JButton(Locale.ROOT.getLanguage());
357,6 → 361,7
this.localeBaseName = baseName;
this.localesToDisplay.addAll(toDisplay);
this.setUILanguage(UserProps.getInstance().getLocale());
TM.getInstance();
}
 
private void checkValidity() {
441,17 → 446,18
}
 
private void connect() {
final String userName = this.textLogin.getValue();
final Tuple2<String, String> loginRes;
// if the user has not typed anything and there was a stored pass
if (this.clearPassword == null)
loginRes = this.login.connectEnc(this.textLogin.getValue(), this.encryptedPassword);
loginRes = this.login.connectEnc(userName, this.encryptedPassword);
else
// handle legacy passwords
loginRes = this.login.connectClear(this.textLogin.getValue(), this.clearPassword, "\"" + this.clearPassword + "\"");
loginRes = this.login.connectClear(userName, this.clearPassword, "\"" + this.clearPassword + "\"");
 
if (loginRes.get0() == null) {
// --->Connexion
UserProps.getInstance().setLastLoginName(this.textLogin.getValue());
UserProps.getInstance().setLastLoginName(userName);
if (this.societeSelector) {
UserProps.getInstance().setLastSocieteID(this.comboSociete.getSelectedId());
}
461,6 → 467,10
UserProps.getInstance().setEncryptedStoredPassword(null);
UserProps.getInstance().store();
 
// Preload right to avoid request in AWT later
if (UserRightsManager.getInstance() != null)
UserRightsManager.getInstance().preloadRightsForUserId(UserManager.getUserID());
 
// Fermeture des frames et execution du Runnable
this.r.run();
// only dispose the panel after r has run so that there's always something on screen for
467,16 → 477,16
// the user to see
SwingUtilities.getWindowAncestor(this).dispose();
} else {
unlockUIOnError(loginRes.get0());
unlockUIOnError(loginRes.get0(), userName);
}
}
 
private void unlockUIOnError(final String error) {
private void unlockUIOnError(final String error, final String userName) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ConnexionPanel.this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
JOptionPane.showMessageDialog(ConnexionPanel.this, error);
JOptionPane.showMessageDialog(ConnexionPanel.this, TM.getTM().translate("loginPanel." + error, userName));
// Guillaume wants this for the Nego
if (Login.UNKNOWN_USER.equals(error))
ConnexionPanel.this.textLogin.setValue(ConnexionPanel.this.adminLogin);
490,7 → 500,7
}
 
protected void setUILanguage(Locale locale) {
final ResourceBundle bundle = ResourceBundle.getBundle(this.localeBaseName, locale, TranslationManager.CONTROL);
final ResourceBundle bundle = ResourceBundle.getBundle(this.localeBaseName, locale, TranslationManager.getControl());
this.adminLogin = bundle.getString("adminLogin");
this.loginLabel.setText(bundle.getString("loginLabel"));
this.passwordLabel.setText(bundle.getString("passwordLabel"));
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/SoftwareInfoPanel.java
15,10 → 15,16
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.users.User;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRights;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.ui.FormLayouter;
import org.openconcerto.ui.SystemInfoPanel;
import org.openconcerto.ui.component.HTMLTextField;
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.i18n.I18nUtils;
 
import javax.swing.JLabel;
import javax.swing.JPanel;
32,6 → 38,17
public class SoftwareInfoPanel extends JPanel {
 
public SoftwareInfoPanel() {
final FormLayouter l = new FormLayouter(this, 1);
 
final UserRightsManager userRightsManager = UserRightsManager.getInstance();
l.add(TM.tr("infoPanel.rights"), new JLabel(org.openconcerto.utils.i18n.TM.tr(I18nUtils.getYesNoKey(userRightsManager != null))));
final User user = UserManager.getUser();
if (user != null) {
final UserRights userRights = UserRightsManager.getCurrentUserRights();
final String userS = user.toString() + (userRights.isSuperUser() ? " (superuser)" : "");
l.add(org.openconcerto.utils.i18n.TM.tr("user"), new JLabel(userS));
}
 
final Configuration conf = Configuration.getInstance();
final PropsConfiguration propsConf;
final ProductInfo productInfo;
43,22 → 60,21
productInfo = ProductInfo.getInstance();
}
 
final FormLayouter l = new FormLayouter(this, 1);
final String name, version;
if (productInfo == null) {
name = "inconnu";
version = "inconnue";
name = TM.tr("infoPanel.noAppName");
version = TM.tr("infoPanel.noVersion");
} else {
name = productInfo.getName();
version = productInfo.getProperty(ProductInfo.VERSION, "inconnue");
version = productInfo.getProperty(ProductInfo.VERSION, TM.tr("infoPanel.noVersion"));
}
l.add("Nom de l'application", new JLabel(name));
l.add("Version de l'application", new JLabel(version));
l.add(TM.tr("infoPanel.appName"), new JLabel(name));
l.add(TM.tr("infoPanel.version"), new JLabel(version));
if (propsConf != null && propsConf.isUsingSSH()) {
l.add("Liaison sécurisée", new JLabel(propsConf.getWanHostAndPort()));
l.add(TM.tr("infoPanel.secureLink"), new JLabel(propsConf.getWanHostAndPort()));
}
l.add("URL de base de données", new JLabel(conf.getSystemRoot().getDataSource().getUrl()));
final String logs = propsConf == null ? "" : " ; " + SystemInfoPanel.getLink("Journaux", propsConf.getLogDir().toURI());
l.add("Dossiers", new HTMLTextField(SystemInfoPanel.getLink("Documents", conf.getWD().toURI()) + logs));
l.add(TM.tr("infoPanel.dbURL"), new JLabel(conf.getSystemRoot().getDataSource().getUrl()));
final String logs = propsConf == null ? "" : " ; " + SystemInfoPanel.getLink(TM.tr("infoPanel.logs"), propsConf.getLogDir().toURI());
l.add(TM.tr("infoPanel.dirs"), new HTMLTextField(SystemInfoPanel.getLink(TM.tr("infoPanel.docs"), conf.getWD().toURI()) + logs));
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightUIDescriptorProvider.java
New file
0,0 → 1,21
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.ui.light;
 
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.ui.light.LightUIDescriptor;
 
public interface LightUIDescriptorProvider {
LightUIDescriptor getUIDescriptor(PropsConfiguration configuration);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightUIDescriptorFiller.java
New file
0,0 → 1,110
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.ui.light;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.sqlobject.ElementComboBoxUtils;
import org.openconcerto.sql.ui.StringWithId;
import org.openconcerto.ui.light.LightUIDescriptor;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUILine;
 
import java.util.Calendar;
import java.util.List;
 
/**
* Fill value from default or database
* */
public class LightUIDescriptorFiller {
private final LightUIDescriptor desc;
 
public LightUIDescriptorFiller(LightUIDescriptor desc) {
this.desc = desc;
}
 
public void fillWithDefaultValues() {
final int lineCount = desc.getSize();
for (int i = 0; i < lineCount; i++) {
final LightUILine l = desc.getLine(i);
final int elementCount = l.getSize();
for (int j = 0; j < elementCount; j++) {
final LightUIElement element = l.getElement(j);
if (element.getType() == LightUIElement.TYPE_DATE) {
// Set date to current server date
element.setValue(String.valueOf(System.currentTimeMillis()));
}
}
 
}
}
 
public void fillFromId(PropsConfiguration configuration, SQLTable table, long id) {
System.err.println("LightUIDescriptorFiller.fillFromId() " + id);
final SQLRow row = table.getRow((int) id);
 
final int lineCount = desc.getSize();
for (int i = 0; i < lineCount; i++) {
final LightUILine l = desc.getLine(i);
final int elementCount = l.getSize();
for (int j = 0; j < elementCount; j++) {
final LightUIElement element = l.getElement(j);
final SQLField field = configuration.getFieldMapper().getSQLFieldForItem(element.getId());
 
int type = element.getType();
if (type == LightUIElement.TYPE_TEXT_FIELD) {
 
if (field == null) {
Log.get().severe("No field found for text field : " + element.getId());
continue;
}
element.setValue(row.getString(field.getName()));
} else if (type == LightUIElement.TYPE_COMBOBOX_ELEMENT) {
// send: id,value
SQLTable foreignTable = field.getForeignTable();
final List<SQLField> fieldsToFetch = configuration.getDirectory().getElement(foreignTable).getComboRequest().getFields();
 
final Where where = new Where(foreignTable.getKey(), "=", row.getLong(field.getName()));
List<SQLRowValues> fetchedRows = ElementComboBoxUtils.fetchRows(configuration, foreignTable, fieldsToFetch, where);
if (fetchedRows.size() > 1) {
throw new IllegalStateException("multiple rows fetched for id " + id + " on table " + table.getName());
}
for (final SQLRowValues vals : fetchedRows) {
StringWithId s = ElementComboBoxUtils.createItem(configuration, foreignTable, vals, fieldsToFetch);
element.setValue(s.toCondensedString());
}
 
} else if (type == LightUIElement.TYPE_CHECKBOX) {
if (row.getBoolean(field.getName())) {
element.setValue("true");
} else {
element.setValue("false");
}
} else if (type == LightUIElement.TYPE_DATE) {
Calendar date = row.getDate(field.getName());
if (date != null) {
element.setValue(String.valueOf(row.getDate(field.getName()).getTimeInMillis()));
}
}
 
}
 
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/GroupToLightUIConvertor.java
New file
0,0 → 1,185
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.ui.light;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.model.FieldMapper;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.ui.group.Group;
import org.openconcerto.ui.group.Item;
import org.openconcerto.ui.group.LayoutHints;
import org.openconcerto.ui.light.CustomEditorProvider;
import org.openconcerto.ui.light.LightUIDescriptor;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUILine;
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.awt.Color;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
public class GroupToLightUIConvertor {
private final int maxColumnCount;
private PropsConfiguration configuration;
private Map<String, CustomEditorProvider> customEditorProviders = new HashMap<String, CustomEditorProvider>();
 
public GroupToLightUIConvertor(PropsConfiguration conf) {
this(conf, 4);
}
 
public GroupToLightUIConvertor(PropsConfiguration conf, int columns) {
this.maxColumnCount = columns;
this.configuration = conf;
}
 
public LightUIDescriptor convert(Group group) {
final LightUIDescriptor desc = new LightUIDescriptor(group.getId());
append(desc, group);
return desc;
}
 
private void append(LightUIDescriptor desc, Item item) {
if (item instanceof Group) {
Group gr = (Group) item;
int size = gr.getSize();
for (int i = 0; i < size; i++) {
Item it = gr.getItem(i);
append(desc, it);
}
} else {
LayoutHints localHint = item.getLocalHint();
LightUILine currentLine = desc.getLastLine();
if (localHint.isSeparated()) {
if (currentLine.getWidth() > 0) {
currentLine = new LightUILine();
desc.addLine(currentLine);
}
}
if (localHint.fillHeight()) {
currentLine.setFillHeight(true);
}
 
if (localHint.largeHeight()) {
currentLine.setWeightY(1);
}
 
if (currentLine.getWidth() >= maxColumnCount) {
currentLine = new LightUILine();
desc.addLine(currentLine);
}
LightUIElement elementLabel = null;
if (localHint.showLabel()) {
elementLabel = new LightUIElement();
elementLabel.setId(item.getId());
elementLabel.setType(LightUIElement.TYPE_LABEL);
String label = TranslationManager.getInstance().getTranslationForItem(item.getId());
if (label == null) {
label = item.getId();
elementLabel.setColor(Color.ORANGE);
elementLabel.setToolTip("No translation for " + item.getId());
Log.get().warning("No translation for " + item.getId());
}
 
elementLabel.setLabel(label);
if (localHint.isSplit()) {
elementLabel.setGridWidth(4);
} else {
elementLabel.setGridWidth(1);
}
 
currentLine.add(elementLabel);
}
LightUIElement elementEditor = this.getCustomEditor(item.getId());
if (elementEditor == null) {
elementEditor = new LightUIElement();
 
elementEditor.setId(item.getId());
 
FieldMapper fieldMapper = configuration.getFieldMapper();
if (fieldMapper == null) {
throw new IllegalStateException("null field mapper");
}
 
SQLField field = fieldMapper.getSQLFieldForItem(item.getId());
if (field != null) {
Class<?> javaType = field.getType().getJavaType();
if (field.isKey()) {
elementEditor.setType(LightUIElement.TYPE_COMBOBOX_ELEMENT);
elementEditor.setMinInputSize(20);
} else if (javaType.equals(String.class)) {
elementEditor.setType(LightUIElement.TYPE_TEXT_FIELD);
elementEditor.setValue("");
elementEditor.setMinInputSize(10);
} else if (javaType.equals(Date.class)) {
elementEditor.setType(LightUIElement.TYPE_DATE);
} else {
elementEditor.setType(LightUIElement.TYPE_TEXT_FIELD);
Log.get().warning("unsupported type " + javaType.getName());
elementEditor.setValue("unsupported type " + javaType.getName());
}
} else {
elementEditor.setType(LightUIElement.TYPE_TEXT_FIELD);
elementEditor.setMinInputSize(10);
elementEditor.setToolTip("No field attached to " + item.getId());
Log.get().warning("No field attached to " + item.getId());
if (elementLabel != null) {
elementLabel.setColor(Color.ORANGE);
elementLabel.setToolTip("No field attached to " + item.getId());
}
}
}
if (localHint.isSplit()) {
if (currentLine.getWidth() > 0) {
currentLine = new LightUILine();
desc.addLine(currentLine);
}
}
 
if (localHint.isSplit()) {
elementEditor.setGridWidth(4);
} else if (localHint.largeWidth()) {
 
if (localHint.showLabel()) {
elementEditor.setGridWidth(3);
} else {
elementEditor.setGridWidth(4);
}
} else {
elementEditor.setGridWidth(1);
}
elementEditor.setFillWidth(localHint.fillWidth());
currentLine.add(elementEditor);
 
}
 
}
 
private LightUIElement getCustomEditor(String id) {
final CustomEditorProvider customEditorProvider = this.customEditorProviders.get(id);
if (customEditorProvider != null) {
LightUIElement element = customEditorProvider.createUIElement(id);
if (element.getId() == null) {
throw new IllegalStateException("Null id for custom editor for id: " + id);
}
return element;
}
return null;
}
 
public void setCustomEditorProvider(String id, CustomEditorProvider provider) {
this.customEditorProviders.put(id, provider);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/mapping_fr.xml
New file
0,0 → 1,25
<?xml version="1.0" encoding="UTF-8" ?>
<ROOT>
<TABLE name="USER_COMMON">
<FIELD name="NOM" label="Nom" titlelabel="Nom" />
<FIELD name="PRENOM" label="Prénom" titlelabel="Prénom" />
<FIELD name="PASSWORD" label="Mot de passe" titlelabel="Mot de passe" />
<FIELD name="PASSWORD_CONFIRM" label="Confirmation" />
<FIELD name="LOGIN" label="Identifiant" titlelabel="Identifiant" />
<FIELD name="SURNOM" label="Surnom" titlelabel="Surnom" />
<FIELD name="ADMIN" label="Administrateur" titlelabel="Admin." />
<FIELD name="ID_USER_RIGHT_COMMON" label="Droits utilisateur" titlelabel="Droits utilisateur" />
<FIELD name="MAIL" label="E-Mail" titlelabel="E-Mail" />
</TABLE>
<TABLE name="RIGHT">
<FIELD name="CODE" label="Code" titlelabel="Code" />
<FIELD name="NOM" label="Nom" titlelabel="Nom du droit" />
<FIELD name="DESCRIPTION" label="Description" titlelabel="Desc." />
</TABLE>
<TABLE name="USER_RIGHT">
<FIELD name="ID_USER_COMMON" label="Utilisateur" titlelabel="Utilis." />
<FIELD name="ID_RIGHT" label="Droit" />
<FIELD name="OBJECT" label="Objet" />
<FIELD name="HAVE_RIGHT" label="Droit accordé" titlelabel="Accordé" />
</TABLE>
</ROOT>
/trunk/OpenConcerto/src/org/openconcerto/sql/UserPropsTM.java
New file
0,0 → 1,46
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql;
 
import org.openconcerto.sql.preferences.UserProps;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
/**
* Translation manager, listen to locale of {@link UserProps} and load the corresponding bundle.
*
* @author Sylvain
*/
public class UserPropsTM extends org.openconcerto.utils.i18n.TM {
 
protected UserPropsTM() {
}
 
@Override
protected void init() {
final UserProps userProps = UserProps.getInstance();
userProps.addListener(UserProps.LOCALE, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
updateLocale((UserProps) evt.getSource());
}
});
updateLocale(userProps);
}
 
protected void updateLocale(final UserProps userProps) {
this.setLocale(userProps.getLocale());
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/IComboSelectionItem.java
92,8 → 92,9
if (o instanceof IComboSelectionItem) {
final IComboSelectionItem item = (IComboSelectionItem) o;
// have to test also the label, otherwise when a row is modified (and obviously don't
// change id) Swing won't update it.
result = item.getId() == getId() && CompareUtils.equals(item.getLabel(), this.getLabel());
// change id) Swing won't update it. Likewise if the where change an item might only
// change flag.
result = item.getId() == getId() && item.getFlag() == getFlag() && CompareUtils.equals(item.getLabel(), this.getLabel());
} else {
result = false;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/SQLRequestComboBox.java
13,7 → 13,8
package org.openconcerto.sql.sqlobject;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.RIVPanel;
import org.openconcerto.sql.element.SQLComponentItem;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLTable;
20,7 → 21,6
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.request.SQLForeignRowItemView;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.sqlobject.itemview.RowItemViewComponent;
import org.openconcerto.sql.view.search.SearchSpec;
import org.openconcerto.ui.FontUtils;
import org.openconcerto.ui.component.ComboLockedMode;
34,10 → 34,11
import org.openconcerto.utils.checks.EmptyListener;
import org.openconcerto.utils.checks.EmptyObj;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidObject;
import org.openconcerto.utils.checks.ValidState;
import org.openconcerto.utils.model.DefaultIMutableListModel;
import org.openconcerto.utils.model.NewSelection;
 
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.HierarchyEvent;
45,6 → 46,7
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
 
import javax.accessibility.Accessible;
68,7 → 70,7
* @author Sylvain CUAZ
* @see #uiInit(ComboSQLRequest)
*/
public class SQLRequestComboBox extends JPanel implements SQLForeignRowItemView, ValueWrapper<Integer>, EmptyObj, TextComponent, Pulseable, RowItemViewComponent {
public class SQLRequestComboBox extends JPanel implements SQLForeignRowItemView, ValueWrapper<Integer>, EmptyObj, TextComponent, Pulseable, SQLComponentItem {
 
public static final String UNDEFINED_STRING = "----- ??? -----";
 
121,6 → 123,9
this.stringStuff = "123456789012345678901234567890";
 
this.combo = new ISearchableCombo<IComboSelectionItem>(ComboLockedMode.LOCKED, 1, this.stringStuff.length());
// it's this.req which handles the selection so the graphical combo should never pick a new
// selection by itself
this.combo.setOnRemovingOrReplacingSelection(NewSelection.NO);
// ComboSQLRequest return items with children at the start (e.g. Room <| Building <| Site)
this.combo.setForceDisplayStart(true);
this.combo.setIncludeEmpty(addUndefined);
147,13 → 152,15
}
 
@Override
public void init(SQLRowItemView v) {
final SQLTable foreignTable = v.getField().getDBSystemRoot().getGraph().getForeignTable(v.getField());
if (!this.hasModel())
this.uiInit(Configuration.getInstance().getDirectory().getElement(foreignTable).getComboRequest());
else if (this.getRequest().getPrimaryTable() != foreignTable)
public void added(RIVPanel sqlComp, SQLRowItemView v) {
final SQLTable foreignTable = v.getField().getForeignTable();
if (!this.hasModel()) {
this.uiInit(sqlComp.getDirectory().getElement(foreignTable).getComboRequest());
} else {
if (this.getRequest().getPrimaryTable() != foreignTable)
throw new IllegalArgumentException("Tables are different " + getRequest().getPrimaryTable().getSQLName() + " != " + foreignTable.getSQLName());
}
}
 
/**
* Init de l'interface graphique.
188,6 → 195,18
modelValueChanged();
}
});
this.req.addListener("wantedID", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
SQLRequestComboBox.this.supp.fireValueChange();
}
});
this.req.addEmptyListener(new EmptyListener() {
@Override
public void emptyChange(EmptyObj src, boolean newValue) {
SQLRequestComboBox.this.emptySupp.fireEmptyChange(newValue);
}
});
 
// remove listeners to allow this to be gc'd
this.addHierarchyListener(new HierarchyListener() {
245,6 → 264,13
 
// getValidSate() depends on this.req
this.supp.fireValidChange();
// and on this.combo.getValidState()
this.combo.addValidListener(new ValidListener() {
@Override
public void validChange(ValidObject src, ValidState newValue) {
SQLRequestComboBox.this.supp.fireValidChange();
}
});
 
this.uiLayout();
 
403,9 → 429,9
}
 
private final void comboValueChanged() {
// since we used NewSelection.NO for this.combo it never generates spurious events
// i.e. this method is only called by user action
this.req.setValue(this.combo.getValue());
this.supp.fireValueChange();
this.emptySupp.fireEmptyChange(this.isEmpty());
}
 
/**
458,14 → 484,21
return this.getClass().getName() + " " + this.req;
}
 
@Override
public final boolean isEmpty() {
return this.req == null || this.req.isEmpty();
}
 
@Override
public final void addEmptyListener(EmptyListener l) {
this.emptySupp.addEmptyListener(l);
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
this.emptySupp.removeEmptyListener(l);
}
 
public final void addValueListener(PropertyChangeListener l) {
this.supp.addValueListener(l);
}
495,7 → 528,6
 
@Override
public ValidState getValidState() {
// OK, since we fire every time the combo does (see our ctor)
// we are valid if we can return a value and getValue() needs this.req
return ValidState.getNoReasonInstance(hasModel()).and(this.combo.getValidState());
}
535,8 → 567,9
return this.combo.getTextComp();
}
 
public Component getPulseComponent() {
return this.combo;
@Override
public Collection<JComponent> getPulseComponents() {
return Arrays.<JComponent> asList(this.combo);
}
 
// *** search
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ElementComboBoxUtils.java
New file
0,0 → 1,121
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.sqlobject;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TransfFieldExpander;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.ui.StringWithId;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class ElementComboBoxUtils {
public static final SQLRowValues getGraphToFetch(Configuration conf, SQLTable table, List<SQLField> fieldsToFetch) {
if (fieldsToFetch == null)
return null;
 
final SQLRowValues vals = new SQLRowValues(table);
for (final SQLField f : fieldsToFetch) {
vals.put(f.getName(), null);
}
// keep order field in graph (not only in graphToFetch) so that a debug column is created
for (final Path orderP : Collections.singletonList(new Path(table))) {
final SQLRowValues orderVals = vals.followPath(orderP);
if (orderVals != null && orderVals.getTable().isOrdered()) {
orderVals.put(orderVals.getTable().getOrderField().getName(), null);
}
}
getShowAs(conf).expand(vals);
return vals;
}
 
public static final StringWithId createItem(Configuration conf, SQLTable primaryTable, final SQLRowValues rs, List<SQLField> fields) {
final String desc;
if (rs.getID() == primaryTable.getUndefinedID())
desc = "?";
else
desc = CollectionUtils.join(getShowAs(conf).expandGroupBy(fields), " ◄ ", new ITransformer<Tuple2<Path, List<FieldPath>>, Object>() {
public Object transformChecked(Tuple2<Path, List<FieldPath>> ancestorFields) {
final List<String> filtered = CollectionUtils.transformAndFilter(ancestorFields.get1(), new ITransformer<FieldPath, String>() {
// no need to keep this Transformer in an attribute
// even when creating one per line it's the same speed
public String transformChecked(FieldPath input) {
return input.getString(rs);
}
}, IPredicate.notNullPredicate(), new ArrayList<String>());
return CollectionUtils.join(filtered, " ");
}
});
// don't store the whole SQLRowValues to save some memory
final StringWithId res = new StringWithId(rs.getID(), desc);
 
return res;
}
 
public static final List<SQLRowValues> fetchRows(final Configuration conf, final SQLTable foreignTable, List<SQLField> fieldsToFetch, final Where where) {
// TODO: a cleaner par Sylvain
SQLRowValues graphToFetch = ElementComboBoxUtils.getGraphToFetch(conf, foreignTable, fieldsToFetch);
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(graphToFetch, false);
final String tableName = foreignTable.getName();
BaseFillSQLRequest.setupForeign(fetcher);
final ITransformer<SQLSelect, SQLSelect> origSelTransf = fetcher.getSelTransf();
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect sel) {
if (origSelTransf != null)
sel = origSelTransf.transformChecked(sel);
boolean lockSelect = true;
if (lockSelect) {
sel.addWaitPreviousWriteTXTable(tableName);
}
for (final Path orderP : Collections.singletonList(new Path(foreignTable))) {
sel.addOrder(sel.assurePath(tableName, orderP), false);
}
if (where != null) {
sel.andWhere(where);
}
return sel;
}
});
 
final SQLRowValuesListFetcher comboSelect = fetcher.freeze();
final List<SQLRowValues> fetchedRows = comboSelect.fetch();
return fetchedRows;
}
 
public static final TransfFieldExpander getShowAs(final Configuration conf) {
final TransfFieldExpander exp = new TransfFieldExpander(new ITransformer<SQLField, List<SQLField>>() {
@Override
public List<SQLField> transformChecked(SQLField fk) {
final SQLTable foreignTable = fk.getDBSystemRoot().getGraph().getForeignTable(fk);
return conf.getDirectory().getElement(foreignTable).getComboRequest().getFields();
}
});
return exp;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/itemview/BaseRowItemView.java
37,6 → 37,7
this.fields = new ArrayList<SQLField>();
}
 
@Override
public final void init(String sqlName, Set<SQLField> fields) {
this.sqlName = sqlName;
this.fields.addAll(fields);
45,8 → 46,6
 
if (this.getComp() instanceof RowItemViewComponent)
((RowItemViewComponent) this.getComp()).init(this);
 
this.resetValue();
}
 
protected abstract void init();
55,17 → 54,19
return this.fields;
}
 
@Override
public String toString() {
return this.getClass().getName() + " on " + this.getFields();
}
 
// *** by default, insert is the same as update
@Override
public void insert(SQLRowValues vals) {
this.update(vals);
}
 
@Override
public final String getSQLName() {
return this.sqlName;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/itemview/VWRowItemView.java
77,11 → 77,17
this.getWrapper().resetValue();
}
 
public void show(SQLRowAccessor r) {
if (r.getFields().contains(this.getField().getName())) {
@SuppressWarnings("unchecked")
public void show(SQLRowAccessor r) {
if (r.getFields().contains(this.getField().getName()))
this.getWrapper().setValue((T) r.getObject(this.getField().getName()));
final T object = (T) r.getObject(this.getField().getName());
try {
this.getWrapper().setValue(object);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot set value of " + this.getWrapper() + " to " + object + " (from " + this.getField() + ")", e);
}
}
}
 
public void update(SQLRowValues vals) {
vals.put(this.getField().getName(), this.isEmpty() ? SQLRowValues.SQL_DEFAULT : this.getWrapper().getValue());
98,14 → 104,21
 
// *** emptyObj
 
@Override
public final boolean isEmpty() {
return this.helper.isEmpty();
}
 
@Override
public final void addEmptyListener(EmptyListener l) {
this.helper.addEmptyListener(l);
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
this.helper.removeEmptyListener(l);
}
 
// *** validObj
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ISQLElementWithCodeSelector.java
216,7 → 216,7
c.weightx = 1;
this.add(this.textOpt, c);
 
this.addButton = new JButton("Créer " + this.element.getSingularName());
this.addButton = new JButton(EditFrame.getCreateMessage(this.element));
this.addButton.addActionListener(this);
c.fill = GridBagConstraints.NONE;
c.weightx = 0;
271,6 → 271,12
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
// TODO no longer implements EmptyObject (just EmptyObj)
throw new UnsupportedOperationException();
}
 
@Override
public void addValueListener(PropertyChangeListener l) {
this.supp.addPropertyChangeListener(l);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ElementComboBox.java
13,11 → 13,11
package org.openconcerto.sql.sqlobject;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.RIVPanel;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.view.EditFrame;
28,6 → 28,7
import org.openconcerto.sql.view.ListeAddPanel;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.FrameUtil;
import org.openconcerto.ui.component.ITextCombo;
import org.openconcerto.ui.list.selection.BaseListStateModel;
import org.openconcerto.utils.cc.ITransformer;
 
148,15 → 149,12
}
 
@Override
public void init(SQLRowItemView v) {
final SQLTable foreignTable = v.getField().getDBSystemRoot().getGraph().getForeignTable(v.getField());
if (foreignTable == null) {
throw new IllegalArgumentException("No foreign table for " + v.getField().getFullName());
}
public void added(RIVPanel sqlComp, SQLRowItemView v) {
final SQLElement foreignElement = sqlComp.getDirectory().getElement(v.getField().getForeignTable());
if (this.getElement() == null)
this.init(Configuration.getInstance().getDirectory().getElement(foreignTable));
else if (this.getElement().getTable() != foreignTable)
throw new IllegalArgumentException("Tables are different " + getElement().getTable().getSQLName() + " != " + foreignTable.getSQLName());
this.init(foreignElement);
else if (this.getElement() != foreignElement)
throw new IllegalArgumentException("Elements are different " + getElement() + " != " + foreignElement);
}
 
public final SQLElement getElement() {
222,7 → 220,7
DefaultGridBagConstraints.lockMinimumSize(this.viewButton);
add(this.viewButton, c);
c.gridx++;
this.listButton.setToolTipText("Lister les " + this.element.getPluralName());
this.listButton.setToolTipText(TM.getInstance().trM("combo.list", "element", this.element.getName()));
DefaultGridBagConstraints.lockMinimumSize(this.listButton);
add(this.listButton, c);
 
230,7 → 228,7
this.addButton.setPreferredSize(new Dimension(24, 16));
this.addButton.setIcon(getAddIcon());
IListButton.initButton(this.addButton);
this.addButton.setToolTipText("Créer " + this.element.getSingularName());
this.addButton.setToolTipText(EditFrame.getCreateMessage(this.element));
c.gridx++;
DefaultGridBagConstraints.lockMinimumSize(this.addButton);
add(this.addButton, c);
241,15 → 239,11
this.listButton.addActionListener(this);
this.addValueListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
// don't change our frames' selection when we reload
// (furthermore our listener on the list will change idToSelect to the
// temporary one, thus rending it ineffective)
if (!isUpdating())
valueChanged();
}
});
 
if (Boolean.getBoolean("org.openconcerto.ui.simpleTraversal")) {
if (Boolean.getBoolean(ITextCombo.SIMPLE_TRAVERSAL)) {
this.viewButton.setFocusable(false);
this.listButton.setFocusable(false);
this.addButton.setFocusable(false);
265,10 → 259,10
updateViewBtn();
if (this.viewFrame != null) {
// changer la frame du détail
this.viewFrame.selectionId(this.getSelectedId());
this.viewFrame.selectionId(this.getWantedID());
}
if (this.listFrame != null) {
this.listFrame.getPanel().getListe().selectID(this.getSelectedId());
this.listFrame.getPanel().getListe().selectID(this.getWantedID());
}
}
 
298,7 → 292,7
// dispose since if we change canModif, the old frame will be orphaned
this.viewFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
this.viewFrame.selectionId(getSelectedId());
this.viewFrame.selectionId(getWantedID());
FrameUtil.show(this.viewFrame);
}
} else if (e.getSource() == this.addButton) {
315,7 → 309,7
setValue(newID == BaseListStateModel.INVALID_ID ? null : newID);
}
});
this.listFrame.getPanel().getListe().selectID(getSelectedId());
this.listFrame.getPanel().getListe().selectID(getWantedID());
}
FrameUtil.show(this.listFrame);
}
368,7 → 362,7
 
private void updateViewBtn() {
final boolean modif, enabled;
if (getEnabled() == ComboMode.DISABLED || this.getSelectedId() < SQLRow.MIN_VALID_ID) {
if (getEnabled() == ComboMode.DISABLED || this.getWantedID() < SQLRow.MIN_VALID_ID) {
// disabled
modif = this.canModif;
enabled = false;
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/IComboModel.java
15,6 → 15,7
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
22,6 → 23,7
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.view.search.SearchSpec;
import org.openconcerto.sql.view.search.SearchSpecUtils;
import org.openconcerto.ui.SwingThreadUtils;
import org.openconcerto.ui.component.combo.Log;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.SwingWorker2;
31,6 → 33,7
import org.openconcerto.utils.checks.EmptyObj;
import org.openconcerto.utils.checks.MutableValueObject;
import org.openconcerto.utils.model.DefaultIMutableListModel;
import org.openconcerto.utils.model.NewSelection;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
46,6 → 49,8
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
 
import net.jcip.annotations.GuardedBy;
 
/**
* A model that takes its values from a {@link ComboSQLRequest}. It listens to table changes, but
* can also be reloaded by calling {@link #fillCombo()}. It can be searched using
79,6 → 84,7
// index des éléments par leurs IDs
private Map<Integer, IComboSelectionItem> itemsByID;
 
@GuardedBy("this")
private SearchSpec search;
 
private PropertyChangeListener filterListener;
106,7 → 112,8
this.running = false;
 
this.setSelectOnAdd(false);
this.setSelectOnRm(false);
// we always change the selection after changing the items
this.setOnRemovingOrReplacingSelection(NewSelection.NO);
 
this.uiInit();
}
366,7 → 373,14
this.itemsByID.put(item.getId(), item);
}
 
private void removeItem(final int id) {
final IComboSelectionItem removed = this.itemsByID.remove(id);
if (removed != null)
this.getComboModel().removeElement(removed);
}
 
private IComboSelectionItem getComboItem(int id) {
assert SwingUtilities.isEventDispatchThread();
return this.itemsByID.get(id);
}
 
376,15 → 390,14
}
 
// refresh, delete or add the passed row
private void reloadComboItem(int id) {
private void reloadComboItem(final int id, final IComboSelectionItem nItem) {
assert SwingUtilities.isEventDispatchThread();
if (nItem == null) {
removeItem(id);
} else {
final IComboSelectionItem item = this.getComboItem(id);
// does this combo currently displays id
if (item != null) {
final IComboSelectionItem nItem = this.req.getComboItem(id);
if (nItem == null) {
this.getComboModel().removeElement(item);
this.itemsByID.remove(item.getId());
} else {
// before replace() which empties the selection
final boolean selectedID = this.getSelectedId() == id;
this.getComboModel().replace(item, nItem);
393,12 → 406,12
// selectedItem is NOT part of the items, even for non-editable combos
this.setValue(id);
}
}
} else {
// don't know if and where to put the new item, so call fillCombo()
this.fillCombo();
}
}
}
 
private final void itemsChanged() {
final List<IComboSelectionItem> newVal = this.getList();
529,10 → 542,17
} else if (id == SQLRow.NONEXISTANT_ID) {
this.setSelectedItem(null);
log("NONEXISTANT_ID: setSelectedItem(null)");
} else if (id != this.getSelectedId()) {
log("id != this.getSelectedId() : " + this.getSelectedId());
} else {
final IComboSelectionItem item = this.getComboItem(id);
log("item: " + item);
log("valid id : " + id + " item: " + item);
// * setSelectedItem() use IComboSelectionItem.equals() so it must compare the ID and
// the flag since even if ID doesn't change the combo might get refreshed and the
// selected row :
// 1. get removed : in that case we want to add the "warning" item
// 2. get added : in that case remove the "warning"
// * ATTN item being null means id isn't in the result set, getSelectedValue() being
// null means nothing is selected. For example if the current selection is empty and
// now we want ID 34 but it isn't returned by the request, both will be null.
if (item == null && this.addMissingItem()) {
// si l'ID voulu n'est pas la, essayer d'aller le chercher directement dans la base
// sans respecter le filtre
614,6 → 634,11
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
this.emptySupp.removeEmptyListener(l);
}
 
@Override
public final void addValueListener(PropertyChangeListener l) {
this.addListener("value", l);
}
653,23 → 678,43
 
// *** une table que nous affichons a changé
 
private boolean tableOnlyOnce() {
final SQLRowValues graphToFetch = this.req.getGraphToFetch();
for (final SQLRowValues item : graphToFetch.getGraph().getItems()) {
if (item != graphToFetch && item.getTable() == graphToFetch.getTable()) {
return false;
}
}
return true;
}
 
@Override
public void tableModified(SQLTableEvent evt) {
final int id = evt.getId();
if (id >= SQLRow.MIN_VALID_ID && this.getForeignTable().equals(evt.getTable())) {
this.reloadComboItem(id);
} else
if (id >= SQLRow.MIN_VALID_ID && this.getForeignTable().equals(evt.getTable()) && tableOnlyOnce()) {
// MAYBE SwingWorker à la fillCombo()
final IComboSelectionItem nItem = SearchSpecUtils.filterOne(this.req.getComboItem(id), getSearch());
SwingThreadUtils.invoke(new Runnable() {
@Override
public void run() {
reloadComboItem(id, nItem);
}
});
} else {
// if multiple rows were changed or if one row can affect multiple combo items (e.g.
// displaying mission and its previous date)
this.fillCombo();
}
}
 
// *** search
 
public final void search(SearchSpec spec) {
public synchronized final void search(SearchSpec spec) {
this.search = spec;
this.fillCombo();
}
 
private SearchSpec getSearch() {
private synchronized SearchSpec getSearch() {
return this.search;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/Configuration.java
76,12 → 76,23
 
public abstract File getWD();
 
public String getAppName() {
return "unnamed application";
}
// abstract :
// - we can't return a default name as we don't know how to localize it
// - avoid that 2 different application share the same name (and perhaps configuration)
public abstract String getAppName();
 
/**
* A string that should be unique to an application and this configuration. E.g. allow to store
* different settings for different uses of a same application.
*
* @return a string beginning with {@link #getAppName()}, <code>null</code> if appName is
* <code>null</code> or empty.
*/
public final String getAppID() {
return this.getAppName() + getAppIDSuffix();
final String appName = this.getAppName();
if (appName == null || appName.length() == 0)
return null;
return appName + getAppIDSuffix();
}
 
protected String getAppIDSuffix() {
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLCache.java
29,6 → 29,8
*/
public final class SQLCache<K, V> extends ICache<K, V, SQLData> {
 
private final boolean clearAfterTx;
 
public SQLCache() {
this(60);
}
50,7 → 52,14
* @throws IllegalArgumentException if size is 0.
*/
public SQLCache(int delay, int size, final String name) {
// clearAfterTx = true : READ_COMMITTED but even for the current transaction. To use cache
// inside a transaction, another instance must be used (as in SQLDataSource).
this(delay, size, name, true);
}
 
public SQLCache(int delay, int size, final String name, final boolean clearAfterTx) {
super(delay, size, name);
this.clearAfterTx = clearAfterTx;
this.setWatcherFactory(new CacheWatcherFactory<K, SQLData>() {
public SQLCacheWatcher<K> createWatcher(ICache<K, ?, SQLData> cache, SQLData o) {
final SQLCache<K, ?> c = (SQLCache<K, ?>) cache;
59,4 → 68,7
});
}
 
public final boolean isClearedAfterTransaction() {
return this.clearAfterTx;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/RowItemDesc.java
13,18 → 13,21
package org.openconcerto.sql.request;
 
import net.jcip.annotations.ThreadSafe;
 
/**
* A container to hold information on how to refer to a particular item of a row.
*
* @author ilm
*/
@ThreadSafe
public class RowItemDesc {
 
private static final String EMPTY_DOC = "";
 
private String label;
private String titlelabel;
private String documentation;
private final String label;
private final String titlelabel;
private final String documentation;
 
public RowItemDesc(String label, String title) {
this(label, title, EMPTY_DOC);
47,5 → 50,4
public String getDocumentation() {
return this.documentation;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ComboSQLRequest.java
195,6 → 195,7
}
 
cache.put(cacheKey, result, this.getTables());
 
return result;
} catch (RuntimeException exn) {
// don't use finally, otherwise we'll do both put() and rmRunning()
237,7 → 238,7
if (this.undefLabel != null && rs.getID() == getPrimaryTable().getUndefinedID())
desc = this.undefLabel;
else
desc = CollectionUtils.join(this.exp.expandGroupBy(this.getFields()), SEP_CHILD, new ITransformer<Tuple2<Path, List<FieldPath>>, Object>() {
desc = CollectionUtils.join(this.getShowAs().expandGroupBy(this.getFields()), SEP_CHILD, new ITransformer<Tuple2<Path, List<FieldPath>>, Object>() {
public Object transformChecked(Tuple2<Path, List<FieldPath>> ancestorFields) {
final List<String> filtered = CollectionUtils.transformAndFilter(ancestorFields.get1(), new ITransformer<FieldPath, String>() {
// no need to keep this Transformer in an attribute
/trunk/OpenConcerto/src/org/openconcerto/sql/request/UpdateBuilder.java
16,15 → 16,21
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.TableRef;
import org.openconcerto.sql.model.Where;
import org.openconcerto.utils.Tuple3.List3;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
 
/**
38,6 → 44,9
private final Map<String, String> fields;
private final List<String> tables;
private Where where;
// alias -> definition, t field, field of the joined table
private final Map<String, List3<String>> virtualJoins;
private final Map<String, Boolean> virtualJoinsOptimized;
 
public UpdateBuilder(SQLTable t) {
super();
44,6 → 53,8
this.t = t;
this.fields = new LinkedHashMap<String, String>();
this.tables = new ArrayList<String>();
this.virtualJoins = new HashMap<String, List3<String>>(4);
this.virtualJoinsOptimized = new HashMap<String, Boolean>(4);
}
 
public final SQLTable getTable() {
50,14 → 61,63
return this.t;
}
 
private final void checkField(final String field) {
if (!this.getTable().contains(field))
throw new IllegalArgumentException("unknown " + field + " in " + this.getTable().getSQLName());
}
 
public final UpdateBuilder set(final String field, final String value) {
if (this.getTable().contains(field))
this.checkField(field);
this.fields.put(field, value);
else
throw new IllegalArgumentException("unknown " + field + " in " + this.getTable().getSQLName());
return this;
}
 
private final boolean isJoinVirtual(final String alias) {
if (!this.virtualJoins.containsKey(alias))
throw new IllegalArgumentException("Not a join " + alias);
return getTable().getServer().getSQLSystem() == SQLSystem.H2 || this.virtualJoinsOptimized.get(alias) == Boolean.FALSE;
}
 
/**
* Set the passed field to the value of a field from a virtual join.
*
* @param field a field in the {@link #getTable() table} to update.
* @param joinAlias the alias of the virtual join.
* @param joinedTableField a field from the joined table.
* @return this.
* @see #setFromVirtualJoin(String, String, String)
*/
public final UpdateBuilder setFromVirtualJoinField(final String field, final String joinAlias, final String joinedTableField) {
return this.setFromVirtualJoin(field, joinAlias, new SQLName(joinAlias, joinedTableField).quote());
}
 
/**
* Set the passed field to the passed SQL value from a virtual join.
*
* @param field a field in the {@link #getTable() table} to update.
* @param joinAlias the alias of the virtual join.
* @param value the SQL, e.g. a quoted field from the joined table or an arbitrary expression.
* @return this.
* @see #setFromVirtualJoinField(String, String, String)
* @see #addVirtualJoin(TableRef, String)
*/
public final UpdateBuilder setFromVirtualJoin(final String field, final String joinAlias, final String value) {
final String val;
if (this.isJoinVirtual(joinAlias)) {
final List3<String> virtualJoin = this.virtualJoins.get(joinAlias);
val = "( select " + value + " from " + virtualJoin.get0() + " where " + getWhere(joinAlias, virtualJoin) + " )";
} else {
val = value;
}
return this.set(field, val);
}
 
private final String getWhere(final String joinAlias, final List3<String> virtualJoin) {
assert this.virtualJoins.get(joinAlias) == virtualJoin;
final SQLName joinedTableFieldName = new SQLName(joinAlias, virtualJoin.get2());
return getTable().getField(virtualJoin.get1()).getSQLNameUntilDBRoot(false) + " = " + joinedTableFieldName.quote();
}
 
public final Set<String> getFieldsNames() {
return this.fields.keySet();
}
98,10 → 158,65
this.tables.add(definition + (rawAlias == null ? "" : " " + rawAlias));
}
 
public final void addVirtualJoin(final TableRef t, final String joinedTableField) {
this.addVirtualJoin(t.getSQL(), t.getAlias(), true, joinedTableField, getTable().getKey().getName());
}
 
public final void addVirtualJoin(final String definition, final String alias, final String joinedTableField) {
this.addVirtualJoin(definition, alias, false, joinedTableField, getTable().getKey().getName());
}
 
public final void addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String field) {
this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, field, true);
}
 
/**
* Add a virtual join to this UPDATE. Some systems don't support
* {@link #addRawTable(String, String) multiple tables}, this method is virtual in the sense
* that it emulates the behaviour using sub-queries.
*
* @param definition the definition of a table, e.g. simply "root"."t" or a VALUES expression.
* @param alias the alias, cannot be <code>null</code>.
* @param aliasAlreadyDefined if <code>true</code> the <code>alias</code> won't be appended to
* the <code>definition</code>. Needed for
* {@link SQLSyntax#getConstantTable(List, String, List) constant tables} since the alias
* is already inside the definition, e.g. ( VALUES ... ) as "constTable"(field1, ...) .
* @param joinedTableField the field in the joined table that will match <code>field</code> of
* the update {@link #getTable() table}.
* @param field the field in the update {@link #getTable() table}.
* @param optimize if <code>true</code> and if the system supports it, the virtual join will use
* the multiple table support.
*/
public final void addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String field, final boolean optimize) {
if (alias == null)
throw new NullPointerException("No alias");
if (this.virtualJoins.containsKey(alias))
throw new IllegalStateException("Alias already exists : " + alias);
this.checkField(field);
final String completeDef = aliasAlreadyDefined ? definition : definition + ' ' + SQLBase.quoteIdentifier(alias);
this.virtualJoins.put(alias, new List3<String>(completeDef, field, joinedTableField));
this.virtualJoinsOptimized.put(alias, optimize);
}
 
public final String asString() {
final String w = this.where == null ? "" : "\nWHERE " + this.where.getClause();
return "UPDATE " + this.getTable().getServer().getSQLSystem().getSyntax().getUpdate(this.getTable(), unmodifiableList(this.tables), unmodifiableMap(this.fields)) + w;
// add tables and where for virtual joins
Where computedWhere = this.where;
final List<String> computedTables = new ArrayList<String>(this.tables);
for (final Entry<String, List3<String>> e : this.virtualJoins.entrySet()) {
final String joinAlias = e.getKey();
final List3<String> virtualJoin = e.getValue();
final Where w;
if (this.isJoinVirtual(joinAlias)) {
w = Where.createRaw(SQLBase.quoteIdentifier(virtualJoin.get1()) + " in ( select " + SQLBase.quoteIdentifier(virtualJoin.get2()) + " from " + virtualJoin.get0() + " )");
} else {
w = Where.createRaw(getWhere(joinAlias, virtualJoin));
computedTables.add(virtualJoin.get0());
}
computedWhere = w.and(computedWhere);
}
final String w = computedWhere == null ? "" : "\nWHERE " + computedWhere.getClause();
return "UPDATE " + this.getTable().getServer().getSQLSystem().getSyntax().getUpdate(this.getTable(), unmodifiableList(computedTables), unmodifiableMap(this.fields)) + w;
}
 
@Override
public String toString() {
/trunk/OpenConcerto/src/org/openconcerto/sql/request/FilteredFillSQLRequest.java
26,7 → 26,6
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.Tuple2;
 
import java.util.Collection;
51,9 → 50,16
return Tuple2.create(filterTable, ids);
}
 
static protected final SQLFilter getDefaultFilter() {
final Configuration conf = Configuration.getInstance();
return conf == null ? null : conf.getFilter();
}
 
// never null (but can be <null, null>)
private Tuple2<Set<SQLRow>, Path> filterInfo;
private boolean filterEnabled;
private final SQLFilter filter;
// initially false since we don't listen to filter before calling setFilterEnabled()
private boolean filterEnabled = false;
private final SQLFilterListener filterListener;
 
{
67,6 → 73,7
 
public FilteredFillSQLRequest(final SQLTable primaryTable, Where w) {
super(primaryTable, w);
this.filter = getDefaultFilter();
this.filterInfo = Tuple2.create(null, null);
this.setFilterEnabled(true);
}
73,22 → 80,29
 
public FilteredFillSQLRequest(FilteredFillSQLRequest req) {
super(req);
this.filter = req.filter;
this.filterInfo = req.filterInfo;
this.setFilterEnabled(req.filterEnabled);
}
 
protected final SQLFilter getFilter() {
return Configuration.getInstance().getFilter();
return this.filter;
}
 
public final void setFilterEnabled(boolean b) {
final SQLFilter filter = this.getFilter();
b = filter == null ? false : b;
if (this.filterEnabled != b) {
// since filter is final and filterEnabled is initially false
assert filter != null;
this.filterEnabled = b;
if (this.filterEnabled)
this.getFilter().addWeakListener(this.filterListener);
filter.addWeakListener(this.filterListener);
else
this.getFilter().rmListener(this.filterListener);
filter.rmListener(this.filterListener);
updateFilterWhere();
}
}
 
private void updateFilterWhere() {
if (this.filterEnabled) {
103,7 → 117,7
if (w == null || p == null) {
this.filterInfo = Tuple2.create(null, null);
} else {
this.filterInfo = Tuple2.create(CopyUtils.copy(w), new Path(p));
this.filterInfo = Tuple2.create((Set<SQLRow>) new HashSet<SQLRow>(w), new Path(p));
}
fireWhereChange();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLCacheWatcher.java
16,7 → 16,7
import org.openconcerto.sql.model.SQLData;
import org.openconcerto.sql.model.SQLDataListener;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.SQLTable.ListenerAndConfig;
import org.openconcerto.utils.cache.CacheWatcher;
 
/**
27,15 → 27,15
*/
public class SQLCacheWatcher<K> extends CacheWatcher<K, SQLData> {
 
private final SQLTableModifiedListener listener;
private final ListenerAndConfig listener;
 
SQLCacheWatcher(final SQLCache<K, ?> c, final SQLData t) {
super(c, t);
this.listener = t.createTableListener(new SQLDataListener() {
this.listener = new ListenerAndConfig(t.createTableListener(new SQLDataListener() {
public void dataChanged() {
clearCache();
}
});
}), c.isClearedAfterTransaction());
this.getTable().addPremierTableModifiedListener(this.listener);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ListSQLRequest.java
14,7 → 14,7
package org.openconcerto.sql.request;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.ShowAs;
import org.openconcerto.sql.FieldExpander;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
86,8 → 86,9
}
 
@Override
protected ShowAs getShowAs() {
return Configuration.getInstance().getShowAs();
protected FieldExpander getShowAs() {
final Configuration conf = Configuration.getInstance();
return conf == null ? FieldExpander.getEmpty() : conf.getShowAs();
}
 
/*
/trunk/OpenConcerto/src/org/openconcerto/sql/request/BaseFillSQLRequest.java
31,22 → 31,33
 
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
 
private static boolean DEFAULT_SELECT_LOCK = true;
 
/**
* Whether to use "FOR SHARE" in list requests (preventing roles with just SELECT right from
* seeing the list).
*/
public static final boolean lockSelect = !Boolean.getBoolean("org.openconcerto.sql.noSelectLock");
public static final boolean getDefaultLockSelect() {
return DEFAULT_SELECT_LOCK;
}
 
public static final void setDefaultLockSelect(final boolean b) {
DEFAULT_SELECT_LOCK = b;
}
 
static public void setupForeign(final SQLRowValuesListFetcher fetcher) {
// include rows having NULL (not undefined ID) foreign keys
fetcher.setFullOnly(false);
// treat the same way tables with or without undefined ID
fetcher.setIncludeForeignUndef(false);
// be predictable
fetcher.setReferentsOrdered(true, true);
}
 
private final SQLTable primaryTable;
private Where where;
private ITransformer<SQLSelect, SQLSelect> selTransf;
private boolean lockSelect;
 
private SQLRowValues graph;
private SQLRowValues graphToFetch;
60,6 → 71,7
this.primaryTable = primaryTable;
this.where = w;
this.selTransf = null;
this.lockSelect = getDefaultLockSelect();
this.graph = null;
this.graphToFetch = null;
}
69,6 → 81,7
this.primaryTable = req.getPrimaryTable();
this.where = req.where;
this.selTransf = req.selTransf;
this.lockSelect = req.lockSelect;
// TODO copy
// use methods since they're both lazy
this.graph = req.getGraph();
138,6 → 151,7
protected final SQLRowValuesListFetcher setupFetcher(final SQLRowValuesListFetcher fetcher, final Where w) {
final String tableName = getPrimaryTable().getName();
setupForeign(fetcher);
fetcher.setOrder(getOrder());
final ITransformer<SQLSelect, SQLSelect> origSelTransf = fetcher.getSelTransf();
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
145,11 → 159,8
if (origSelTransf != null)
sel = origSelTransf.transformChecked(sel);
sel = transformSelect(sel);
if (lockSelect)
if (isLockSelect())
sel.addWaitPreviousWriteTXTable(tableName);
for (final Path orderP : getOrder()) {
sel.addOrder(sel.assurePath(tableName, orderP), false);
}
return sel.andWhere(getWhere()).andWhere(w);
}
});
169,6 → 180,14
return this.where;
}
 
public final void setLockSelect(boolean lockSelect) {
this.lockSelect = lockSelect;
}
 
public final boolean isLockSelect() {
return this.lockSelect;
}
 
@Override
public final Collection<SQLField> getAllFields() {
// don't rely on the expansion of our fields, since our fetcher can be arbitrary modified
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLFieldTranslator.java
27,10 → 27,12
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.SQLKey;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.io.FileInputStream;
62,7 → 64,8
*/
public class SQLFieldTranslator {
 
private static final RowItemDesc NULL_DESC = new RowItemDesc(null, null);
// OK since RowItemDesc is immutable
public static final RowItemDesc NULL_DESC = new RowItemDesc(null, null);
 
private static final String METADATA_TABLENAME = SQLSchema.FWK_TABLENAME_PREFIX + "RIV_METADATA";
private static final String ELEM_FIELDNAME = "ELEMENT_CODE";
94,6 → 97,29
return root.getTable(METADATA_TABLENAME);
}
 
public static RowItemDesc getDefaultDesc(SQLField f) {
String name = f.getName(), label = null;
if (f.isPrimaryKey())
label = "ID";
else if (f.getTable().getForeignKeys().contains(f))
name = name.startsWith(SQLKey.PREFIX) ? name.substring(SQLKey.PREFIX.length()) : name;
if (label == null)
label = cleanupName(name);
return new RowItemDesc(label, label);
}
 
private static String cleanupName(final String name) {
return StringUtils.firstUpThenLow(name).replace('_', ' ');
}
 
public static RowItemDesc getDefaultDesc(SQLTable t, final String name) {
if (t.contains(name))
return getDefaultDesc(t.getField(name));
 
final String label = cleanupName(name);
return new RowItemDesc(label, label);
}
 
// Instance members
 
// { SQLTable -> { compCode, variant, item -> RowItemDesc }}
213,7 → 239,8
if (table == null && this.dir != null && this.dir.getElement(tableName) != null)
table = this.dir.getElement(tableName).getTable();
if (table == null) {
Log.get().info("unknown table " + tableName);
// allow to supply the union all tables and ignore those that aren't in a given base
Log.get().config("Ignore loading of inexistent table " + tableName);
} else {
for (final Element elem : getChildren(tableElem)) {
final String elemName = elem.getName().toLowerCase();
351,25 → 378,31
}
 
public RowItemDesc getDescFor(SQLTable t, String compCode, String name) {
return getDescFor(t, compCode, Collections.<String> emptyList(), name);
final SQLElement element = this.dir == null ? null : this.dir.getElement(t);
final List<String> variants = element == null ? Collections.<String> emptyList() : element.getMDPath();
return getDescFor(t, compCode, variants, name);
}
 
public RowItemDesc getDescFor(final String elementCode, String compCode, String name) {
return this.getDescFor(elementCode, compCode, Collections.<String> emptyList(), name);
return this.getDescFor(elementCode, compCode, null, name);
}
 
public RowItemDesc getDescFor(final String elementCode, String compCode, List<String> variants, String name) {
return this.getDescFor(this.dir.getElementForCode(elementCode).getTable(), compCode, variants, name);
final SQLElement elem = this.dir.getElementForCode(elementCode);
if (variants == null)
variants = elem.getMDPath();
return this.getDescFor(elem.getTable(), compCode, variants, name);
}
 
public RowItemDesc getDescFor(SQLTable t, String compCodeArg, List<String> variants, String name) {
RowItemDesc labeledField = this.getTranslation(t, compCodeArg, variants, name);
// if nothing found, re-fetch from the DB
if (labeledField == null) {
if (labeledField == null && this.dir.getElement(t) != null) {
this.fetchAndPut(this.table, Collections.singleton(this.dir.getElement(t).getCode()));
labeledField = this.getTranslation(t, compCodeArg, variants, name);
}
if (labeledField == null) {
// we didn't find a requested item
Log.get().info("unknown item " + name + " in " + t);
return NULL_DESC;
} else {
378,7 → 411,7
}
 
private RowItemDesc getDescFor(SQLField f) {
return this.getDescFor(f.getTable(), SQLElement.DEFAULT_COMP_ID, this.dir.getElement(f.getTable()).getMDPath(), f.getName());
return this.getDescFor(f.getTable(), f.getName());
}
 
public String getLabelFor(SQLField f) {
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementNamesMap.java
New file
0,0 → 1,77
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.utils.i18n.Phrase;
 
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
 
import net.jcip.annotations.ThreadSafe;
 
@ThreadSafe
public abstract class SQLElementNamesMap<K> extends SQLElementNames {
 
static public class ByClass extends SQLElementNamesMap<Class<? extends SQLElement>> {
public ByClass(Locale locale) {
super(locale);
}
 
@Override
protected Class<? extends SQLElement> getKey(SQLElement elem) {
return elem.getClass();
}
}
 
static public class ByCode extends SQLElementNamesMap<String> {
public ByCode(Locale locale) {
super(locale);
}
 
@Override
protected String getKey(SQLElement elem) {
return elem.getCode();
}
}
 
private final Map<K, Phrase> names;
 
public SQLElementNamesMap(Locale locale) {
super(locale);
this.names = new HashMap<K, Phrase>();
}
 
@Override
protected final Phrase _getName(SQLElement elem) {
return this.handleGetName(getKey(elem));
}
 
/**
* Use this map only, i.e. the key is not an {@link SQLElement} and no recursion occurs.
*
* @param key the key.
* @return the name if present, <code>null</code> otherwise.
* @see #getName(SQLElement)
*/
public final synchronized Phrase handleGetName(K key) {
return this.names.get(key);
}
 
protected abstract K getKey(SQLElement elem);
 
protected final synchronized void put(K key, Phrase value) {
this.names.put(key, value);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementNames.java
New file
0,0 → 1,68
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.utils.i18n.Phrase;
 
import java.util.Locale;
 
import net.jcip.annotations.ThreadSafe;
 
/**
* Allow to find out the name of an {@link SQLElement}.
*
* @author Sylvain
* @see #getName(SQLElement)
*/
@ThreadSafe
public abstract class SQLElementNames {
 
private final Locale locale;
private SQLElementNames parent;
 
public SQLElementNames(Locale locale) {
super();
this.locale = locale;
}
 
public final Locale getLocale() {
return this.locale;
}
 
public final synchronized void setParent(SQLElementNames parent) {
this.parent = parent;
}
 
public final synchronized SQLElementNames getParent() {
return this.parent;
}
 
/**
* Return the name of the passed instance. If the name isn't found in this instance, then the
* search recursively continues with {@link #getParent()}.
*
* @param elem the element.
* @return the name of <code>elem</code>.
*/
public final Phrase getName(final SQLElement elem) {
final Phrase res = this._getName(elem);
final SQLElementNames parent = this.getParent();
if (res == null && parent != null)
return parent.getName(elem);
else
return res;
}
 
protected abstract Phrase _getName(final SQLElement elem);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementDirectory.java
14,6 → 14,7
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.DBStructureItemNotFound;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLTable;
20,7 → 21,12
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.i18n.LocalizedInstances;
import org.openconcerto.utils.i18n.Phrase;
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
28,9 → 34,12
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
 
import org.jdom.JDOMException;
 
/**
* Directory of SQLElement by table.
*
38,6 → 47,29
*/
public final class SQLElementDirectory {
 
public static final String BASENAME = SQLElementNames.class.getSimpleName();
private static final LocalizedInstances<SQLElementNames> LOCALIZED_INSTANCES = new LocalizedInstances<SQLElementNames>(SQLElementNames.class, TranslationManager.getControl()) {
@Override
protected SQLElementNames createInstance(String bundleName, Locale candidate, Class<?> cl) throws IOException {
final InputStream ins = cl.getResourceAsStream('/' + getControl().toResourceName(bundleName, "xml"));
if (ins == null)
return null;
final SQLElementNamesFromXML res = new SQLElementNamesFromXML(candidate);
try {
res.load(ins);
} catch (JDOMException e) {
throw new IOException("Invalid XML", e);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
} finally {
ins.close();
}
return res;
}
};
 
private final Map<SQLTable, SQLElement> elements;
private final CollectionMap<String, SQLTable> tableNames;
private final CollectionMap<String, SQLTable> byCode;
44,6 → 76,9
private final CollectionMap<Class<? extends SQLElement>, SQLTable> byClass;
private final List<DirectoryListener> listeners;
 
private String phrasesPkgName;
private final Map<String, SQLElementNames> elementNames;
 
public SQLElementDirectory() {
this.elements = new HashMap<SQLTable, SQLElement>();
// to mimic elements behaviour, if we add twice the same table
53,6 → 88,9
this.byClass = new CollectionMap<Class<? extends SQLElement>, SQLTable>(HashSet.class);
 
this.listeners = new ArrayList<DirectoryListener>();
 
this.phrasesPkgName = null;
this.elementNames = new HashMap<String, SQLElementNames>();
}
 
private static <K> SQLTable getSoleTable(CollectionMap<K, SQLTable> m, K key) throws IllegalArgumentException {
107,6 → 145,7
for (final DirectoryListener dl : this.listeners) {
dl.elementAdded(elem);
}
elem.setDirectory(this);
}
 
public synchronized final boolean contains(SQLTable t) {
191,6 → 230,53
return elem;
}
 
public synchronized final void initL18nPackageName(final String baseName) {
if (this.phrasesPkgName != null)
throw new IllegalStateException("Already initialized : " + this.getL18nPackageName());
this.phrasesPkgName = baseName;
}
 
public synchronized final String getL18nPackageName() {
return this.phrasesPkgName;
}
 
protected synchronized final SQLElementNames getElementNames(final String pkgName, final Locale locale, final Class<?> cl) {
if (pkgName == null)
return null;
final char sep = ' ';
final String key = pkgName + sep + locale.toString();
assert pkgName.indexOf(sep) < 0 : "ambiguous key : " + key;
SQLElementNames res = this.elementNames.get(key);
if (res == null) {
final List<SQLElementNames> l = LOCALIZED_INSTANCES.createInstances(pkgName + "." + BASENAME, locale, cl).get1();
if (!l.isEmpty()) {
for (int i = 1; i < l.size(); i++) {
l.get(i - 1).setParent(l.get(i));
}
res = l.get(0);
}
this.elementNames.put(key, res);
}
return res;
}
 
/**
* Search a name for the passed instance and the {@link TM#getTranslationsLocale() current
* locale}. Search for {@link SQLElementNames} using {@link LocalizedInstances} and
* {@link SQLElementNamesFromXML} first in {@link SQLElement#getL18nPackageName()} then in
* {@link #getL18nPackageName()}. E.g. this could load SQLElementNames_en.class and
* SQLElementNames_en_UK.xml.
*
* @param elem the element.
* @return the name if found, <code>null</code> otherwise.
*/
public final Phrase getName(final SQLElement elem) {
final String elemBaseName = elem.getL18nPackageName();
final String pkgName = elemBaseName == null ? getL18nPackageName() : elemBaseName;
final SQLElementNames elementNames = getElementNames(pkgName, TM.getInstance().getTranslationsLocale(), elem.getClass());
return elementNames == null ? null : elementNames.getName(elem);
}
 
public synchronized final void addListener(DirectoryListener dl) {
this.listeners.add(dl);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/GroupSQLComponent.java
13,6 → 13,7
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.sql.request.RowItemDesc;
49,7 → 50,6
 
private final Group group;
private final int columns = 2;
private final boolean forceViewOnly = true;
private final Map<String, JComponent> labels = new HashMap<String, JComponent>();
private final Map<String, JComponent> editors = new HashMap<String, JComponent>();
 
56,14 → 56,11
public GroupSQLComponent(final SQLElement element, final Group group) {
super(element);
this.group = group;
 
}
 
@Override
protected void addViews() {
this.group.dumpOneColumn();
this.setLayout(new GridBagLayout());
this.group.sort();
final GridBagConstraints c = new DefaultGridBagConstraints();
c.fill = GridBagConstraints.NONE;
layout(this.group, 0, 0, 0, c);
72,6 → 69,10
public void layout(final Item currentItem, final Integer order, int x, final int level, final GridBagConstraints c) {
final String id = currentItem.getId();
final LayoutHints size = currentItem.getLocalHint();
if (!size.isVisible()) {
return;
}
 
if (size.isSeparated()) {
x = 0;
c.gridx = 0;
78,20 → 79,24
c.gridy++;
}
if (currentItem instanceof Group) {
Group currentGroup = (Group) currentItem;
final Group currentGroup = (Group) currentItem;
final int stop = currentGroup.getSize();
if (size.showLabel() && getLabel(id) != null) {
x = 0;
c.gridy++;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.weightx = 1;
c.gridwidth = 4;
this.add(getLabel(id), c);
c.gridy++;
}
for (int i = 0; i < stop; i++) {
final Item subGroup = currentGroup.getItem(i);
final Integer subGroupOrder = currentGroup.getOrder(i);
 
layout(subGroup, subGroupOrder, x, level + 1, c);
 
}
 
} else {
System.out.print(" (" + x + ")");
 
System.out.print(order + " " + id + "[" + size + "]");
c.gridwidth = 1;
if (size.showLabel()) {
c.weightx = 0;
136,8 → 141,12
c.gridwidth = this.columns * 2 - 1;
}
} else {
if (size.showLabel() && !size.isSeparated()) {
c.gridwidth = 1;
} else {
c.gridwidth = 2;
}
}
if (c.gridx % 2 == 1) {
c.weightx = 1;
}
145,7 → 154,7
try {
this.addView(editor, id);
} catch (final Exception e) {
System.err.println(e.getMessage());
Log.get().warning(e.getMessage());
}
 
if (size.largeWidth()) {
168,9 → 177,7
}
 
public JComponent createEditor(final String id) {
 
if (id.startsWith("(") && id.endsWith(")*")) {
 
try {
final String table = id.substring(1, id.length() - 2).trim();
final String idEditor = GlobalMapper.getInstance().getIds(table).get(0) + ".editor";
180,7 → 187,6
} catch (final Exception e) {
e.printStackTrace();
}
 
}
 
final SQLField field = this.getTable().getFieldRaw(id);
198,11 → 204,7
jLabel.setToolTipText(t);
return jLabel;
}
// if (/* this.getMode().equals(Mode.VIEW) || */forceViewOnly) {
// final JLabel jLabel = new JLabel();
// jLabel.setForeground(Color.gray);
// return jLabel;
// }
 
final SQLType type = field.getType();
 
final JComponent comp;
233,7 → 235,6
}
 
return comp;
 
}
 
protected JComponent createLabel(final String id) {
/trunk/OpenConcerto/src/org/openconcerto/sql/element/ElementSQLObject.java
16,15 → 16,13
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLForeignRowItemView;
import org.openconcerto.utils.checks.EmptyChangeSupport;
import org.openconcerto.utils.checks.EmptyListener;
import org.openconcerto.utils.checks.EmptyObject;
import org.openconcerto.utils.checks.EmptyObjectHelper;
import org.openconcerto.utils.checks.ValidChangeSupport;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidObject;
33,10 → 31,7
import java.awt.Component;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Set;
 
import org.apache.commons.collections.Predicate;
 
/**
* A SQLObject editing a private foreignKey. It handles the creation, deletion and updating of the
* database row.
43,7 → 38,7
*
* @author Sylvain CUAZ
*/
public abstract class ElementSQLObject extends BaseSQLObject implements EmptyObject, SQLForeignRowItemView {
public abstract class ElementSQLObject extends BaseSQLObject implements SQLForeignRowItemView {
 
protected boolean required;
private final SQLComponent parent;
53,7 → 48,7
 
private final PropertyChangeSupport supp;
private final ValidChangeSupport validSupp;
private EmptyObjectHelper helper;
private EmptyChangeSupport helper;
 
/**
* Create a new instance.
62,8 → 57,10
* @param comp the component to edit, eg OBSERVATION.
*/
public ElementSQLObject(SQLComponent parent, SQLComponent comp) {
// MAYBE remove firePropertyChange() in ValidListener and listen to every item of comp
this.supp = new PropertyChangeSupport(this);
this.validSupp = new ValidChangeSupport(this);
this.helper = new EmptyChangeSupport(this);
this.parent = parent;
this.comp = comp;
this.required = false;
132,6 → 129,7
else
this.setCreatePanel();
 
this.helper.fireEmptyChange(this.isEmpty());
fireValidChange();
this.supp.firePropertyChange("value", null, null);
}
141,15 → 139,6
return this.created;
}
 
public void init(String sqlName, Set<SQLField> fields) {
super.init(sqlName, fields);
this.helper = new EmptyObjectHelper(this, new Predicate() {
public boolean evaluate(Object object) {
return !getSQLChild().isInited() || !getSQLChild().getValidState().isValid();
}
});
}
 
public void setValue(SQLRowAccessor r) {
// a row with no ID is displayable but not the undefined row
final boolean displayableRow = r != null && r.getID() != r.getTable().getUndefinedID();
195,31 → 184,21
this.setValue((SQLRowAccessor) null);
}
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.sql.SQLObject#getStringValue()
*/
public String getStringValue() {
return this.getUncheckedValue().toString();
@Override
public boolean isEmpty() {
return !this.isCreated();
}
 
public Object getValue() {
return this.helper.getValue();
@Override
public void addEmptyListener(EmptyListener l) {
this.helper.addEmptyListener(l);
}
 
public Object getUncheckedValue() {
return new Integer(getCurrentID());
@Override
public void removeEmptyListener(EmptyListener l) {
this.helper.removeEmptyListener(l);
}
 
public boolean isEmpty() {
return this.helper.isEmpty();
}
 
public void addEmptyListener(EmptyListener l) {
this.helper.addListener(l);
}
 
public final void addValueListener(PropertyChangeListener l) {
this.supp.addPropertyChangeListener(l);
}
276,7 → 255,7
public ValidState getValidState() {
final ValidState res;
if (isCreated()) {
res = this.comp.getValidState();
res = this.getSQLChild().getValidState();
} else {
res = ValidState.getTrueInstance();
}
316,7 → 295,7
}
 
private void fillRowValues(SQLRowValues vals) {
vals.put(this.getField().getName(), this.getCurrentID() == SQLRow.NONEXISTANT_ID ? SQLRowValues.SQL_EMPTY_LINK : this.getUncheckedValue());
vals.put(this.getField().getName(), this.getCurrentID() == SQLRow.NONEXISTANT_ID ? SQLRowValues.SQL_EMPTY_LINK : this.getCurrentID());
}
 
public void show(SQLRowAccessor r) {
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SharedSQLElement.java
23,11 → 23,11
public class SharedSQLElement extends ConfSQLElement {
 
public SharedSQLElement(String tableName) {
super(tableName, tableName, tableName);
super(tableName);
}
 
public SharedSQLElement(SQLTable table) {
super(table, table.getName(), table.getName());
super(table);
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLComponentItem.java
New file
0,0 → 1,29
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.request.SQLRowItemView;
 
/**
* If the component of a SQLRowItemView implement that, it will be passed its {@link SQLComponent}
* and its initialized {@link SQLRowItemView}.
*
* @author Sylvain CUAZ
* @see SQLRowItemView#getComp()
*/
public interface SQLComponentItem {
 
void added(RIVPanel comp, SQLRowItemView v);
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementNamesFromXML.java
New file
0,0 → 1,151
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.Log;
import org.openconcerto.utils.i18n.Grammar;
import org.openconcerto.utils.i18n.NounClass;
import org.openconcerto.utils.i18n.Phrase;
import org.openconcerto.utils.i18n.VariantKey;
import org.openconcerto.xml.JDOMUtils;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.regex.Pattern;
 
import net.jcip.annotations.ThreadSafe;
 
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
 
/**
* Parses XML to create phrases.
*
* <pre>
* &lt;element refid="elementCode">
* &lt;name base="elemName" nounClass="masculine">
* &lt;variant refids="singular,plural" value="elemNameBothSingularAndPlural" />
* &lt;variant idPattern="(singular|plural)" value="elemNameBothSingularAndPlural" />
* &lt;/name>
* &lt;/element>
* </pre>
*
* @author Sylvain
*
*/
@ThreadSafe
public class SQLElementNamesFromXML extends SQLElementNamesMap.ByCode {
 
static public final Pattern SPLIT_PATTERN = Pattern.compile("\\s*,\\s*");
 
public SQLElementNamesFromXML(Locale locale) {
super(locale);
}
 
public final void load(final InputStream ins) throws JDOMException, IOException {
final Grammar gr = Grammar.getInstance(getLocale());
final Document doc = new SAXBuilder().build(ins);
@SuppressWarnings("unchecked")
final List<Element> elements = doc.getRootElement().getChildren("element");
for (final Element elem : elements)
this.load(gr, elem);
}
 
public final Entry<String, Phrase> createPhrase(final Element elem) throws IOException {
return this.createPhrase(Grammar.getInstance(getLocale()), elem);
}
 
private Entry<String, Phrase> createPhrase(final Grammar gr, final Element elem) throws IOException {
final String refid = elem.getAttributeValue("refid");
if (refid == null)
throw new IOException("No refid attribute");
 
final Element nameElem = elem.getChild("name");
final boolean hasChild = nameElem != null;
final String nameAttr = elem.getAttributeValue("name");
if (!hasChild && nameAttr == null) {
Log.get().warning("No name for code : " + refid);
return null;
}
if (hasChild && nameAttr != null) {
Log.get().warning("Ignoring attribute : " + nameAttr);
}
 
final String base = hasChild ? nameElem.getAttributeValue("base") : nameAttr;
if (base == null)
throw new IOException("No base for the name of " + refid);
final String nounClassName = hasChild ? nameElem.getAttributeValue("nounClass") : elem.getAttributeValue("nameClass");
final NounClass nounClass = nounClassName == null ? null : gr.getNounClass(nounClassName);
 
final Phrase res = new Phrase(gr, base, nounClass);
if (!hasChild) {
// most languages have at most 2 grammatical number
final String plural = elem.getAttributeValue("namePlural");
if (plural != null)
res.putVariant(Grammar.PLURAL, plural);
} else {
@SuppressWarnings("unchecked")
final List<Element> variantElems = nameElem.getChildren("variant");
for (final Element variantElem : variantElems) {
final String value = variantElem.getAttributeValue("value");
if (value == null) {
warning(refid, variantElem, "No value");
continue;
}
final String variantIDs = variantElem.getAttributeValue("refids");
final String variantPattern = variantElem.getAttributeValue("idPattern");
if (variantIDs == null && variantPattern == null) {
warning(refid, variantElem, "No ID");
} else if (variantIDs != null) {
if (variantPattern != null) {
warning(refid, variantElem, "Ignorig pattern " + variantPattern);
}
for (final String variantID : SPLIT_PATTERN.split(variantIDs)) {
final VariantKey variantKey = gr.getVariantKey(variantID);
if (variantKey == null) {
warning(refid, variantElem, "Ignorig " + variantID);
} else {
res.putVariant(variantKey, value);
}
}
} else {
assert variantIDs == null && variantPattern != null;
final Pattern p = Pattern.compile(variantPattern);
for (final VariantKey vk : gr.getVariantKeys()) {
if (p.matcher(vk.getID()).matches()) {
res.putVariant(vk, value);
}
}
}
}
}
return new SimpleEntry<String, Phrase>(refid, res);
}
 
private void load(final Grammar gr, final Element elem) throws IOException {
final Entry<String, Phrase> entry = this.createPhrase(gr, elem);
if (entry != null)
this.put(entry.getKey(), entry.getValue());
}
 
private void warning(final String refid, final Element variantElem, final String msg) {
Log.get().warning(msg + " for variant of " + refid + " : " + JDOMUtils.output(variantElem));
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/DefaultElementSQLObject.java
16,7 → 16,9
*/
package org.openconcerto.sql.element;
 
import static org.openconcerto.sql.TM.getTM;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidObject;
import org.openconcerto.utils.checks.ValidState;
100,7 → 102,7
});
 
this.supprBtn = new JButton(new ImageIcon(this.getClass().getResource("delete.png")));
this.supprBtn.setToolTipText("Supprimer");
this.supprBtn.setToolTipText(getTM().translate("remove"));
this.supprBtn.setOpaque(false);
if (isPlastic)
this.supprBtn.setBorder(null);
112,10 → 114,11
}
 
private boolean confirm() {
return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(DefaultElementSQLObject.this, "Voulez-vous vraiment supprimer cet élément ?", "Suppression", JOptionPane.YES_NO_OPTION);
return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(DefaultElementSQLObject.this, getTM().trM("elementSQLObject.delete", "element", getSQLChild().getElement().getName()),
getTM().trA("sqlElement.confirmDelete"), JOptionPane.YES_NO_OPTION);
}
});
this.createBtn = new JButton("Créer " + this.getSQLChild().getElement().getSingularName());
this.createBtn = new JButton(EditFrame.getCreateMessage(this.getSQLChild().getElement()));
// false leaves only a line for the button under Plastic3DLookAndFeel
this.createBtn.setOpaque(isPlastic);
this.createBtn.addActionListener(new ActionListener() {
/trunk/OpenConcerto/src/org/openconcerto/sql/element/DeletionMode.java
16,7 → 16,7
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.Where;
 
import java.sql.SQLException;
60,13 → 60,13
 
public abstract void fireChange(SQLRowAccessor row);
 
protected final String getUpdateClause(SQLElement elem, boolean archive) {
final String newVal;
protected final SQLRowValues getUpdateClause(SQLElement elem, boolean archive) {
final Object newVal;
if (Boolean.class.equals(elem.getTable().getArchiveField().getType().getJavaType()))
newVal = archive + "";
newVal = archive;
else
newVal = archive ? "1" : "0";
return SQLSelect.quote("UPDATE %f SET %n=" + newVal, elem.getTable(), elem.getTable().getArchiveField());
newVal = archive ? 1 : 0;
return new SQLRowValues(elem.getTable()).put(elem.getTable().getArchiveField().getName(), newVal);
}
 
protected final String getWhereClause(SQLElement elem, int id) {
106,9 → 106,7
 
@Override
protected void updateDB(SQLElement elem, int id) throws SQLException {
String req = this.getUpdateClause(elem, true);
req += getWhereClause(elem, id);
elem.getTable().getBase().getDataSource().execute(req);
this.getUpdateClause(elem, true).update(id);
}
 
@Override
133,7 → 131,7
@Override
protected void updateDB(SQLElement elem, int id) throws SQLException {
// Supression d'un enregistrement de la table.
String req = SQLSelect.quote("DELETE FROM %f ", elem.getTable()) + getWhereClause(elem, id);
final String req = "DELETE FROM " + elem.getTable().getSQLName().quote() + getWhereClause(elem, id);
elem.getTable().getBase().getDataSource().execute(req);
}
 
158,9 → 156,7
 
@Override
protected void updateDB(SQLElement elem, int id) throws SQLException {
String req = this.getUpdateClause(elem, false);
req += getWhereClause(elem, id);
elem.getTable().getBase().getDataSource().execute(req);
this.getUpdateClause(elem, false).update(id);
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElement.java
13,8 → 13,10
package org.openconcerto.sql.element;
 
import static org.openconcerto.sql.TM.getTM;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.DBStructureItemNotFound;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldsSet;
44,7 → 46,6
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cache.CacheResult;
import org.openconcerto.utils.cc.IClosure;
51,6 → 52,10
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.change.ListChangeIndex;
import org.openconcerto.utils.change.ListChangeRecorder;
import org.openconcerto.utils.i18n.Grammar;
import org.openconcerto.utils.i18n.Grammar_fr;
import org.openconcerto.utils.i18n.NounClass;
import org.openconcerto.utils.i18n.Phrase;
 
import java.awt.Component;
import java.lang.reflect.Constructor;
74,6 → 79,8
import javax.swing.JOptionPane;
import javax.swing.text.JTextComponent;
 
import net.jcip.annotations.GuardedBy;
 
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.iterators.EntrySetMapIterator;
89,6 → 96,26
static final private Set<String> computingFF = Collections.unmodifiableSet(new HashSet<String>());
static final private Set<SQLField> computingRF = Collections.unmodifiableSet(new HashSet<SQLField>());
 
private static Phrase createPhrase(String singular, String plural) {
final NounClass nounClass;
final String base;
if (singular.startsWith("une ")) {
nounClass = NounClass.FEMININE;
base = singular.substring(4);
} else if (singular.startsWith("un ")) {
nounClass = NounClass.MASCULINE;
base = singular.substring(3);
} else {
nounClass = null;
base = singular;
}
final Phrase res = new Phrase(Grammar_fr.getInstance(), base, nounClass);
if (nounClass != null)
res.putVariant(Grammar.INDEFINITE_ARTICLE_SINGULAR, singular);
res.putVariant(Grammar.PLURAL, plural);
return res;
}
 
// from the most loss of information to the least.
public static enum ReferenceAction {
/** If a referenced row is archived, empty the foreign field */
100,14 → 127,19
}
 
static final public String DEFAULT_COMP_ID = "default component code";
/**
* If this value is passed to the constructor, {@link #createCode()} will only be called the
* first time {@link #getCode()} is. This allow the method to use objects passed to the
* constructor of a subclass.
*/
static final public String DEFERRED_CODE = new String("deferred code");
 
// must contain the article "a stone" / "an elephant"
private final String singular;
// no article "stones" / "elephants"
private final String plural;
private SQLElementDirectory directory;
private String l18nPkgName;
private Phrase name;
private final SQLTable primaryTable;
// used as a key in SQLElementDirectory so it should be immutable
private final String code;
private String code;
private ComboSQLRequest combo;
private ListSQLRequest list;
private SQLTableModelSourceOnline tableSrc;
128,20 → 160,30
 
private final Map<String, JComponent> additionalFields;
private final List<SQLTableModelColumn> additionalListCols;
@GuardedBy("this")
private List<String> mdPath;
 
@Deprecated
public SQLElement(String singular, String plural, SQLTable primaryTable) {
this(singular, plural, primaryTable, null);
this(primaryTable, createPhrase(singular, plural));
}
 
public SQLElement(String singular, String plural, SQLTable primaryTable, final String code) {
public SQLElement(SQLTable primaryTable) {
this(primaryTable, null);
}
 
public SQLElement(final SQLTable primaryTable, final Phrase name) {
this(primaryTable, name, null);
}
 
public SQLElement(final SQLTable primaryTable, final Phrase name, final String code) {
super();
this.singular = singular;
this.plural = plural;
if (primaryTable == null) {
throw new DBStructureItemNotFound("table is null for " + this);
throw new DBStructureItemNotFound("table is null for " + this.getClass());
}
this.primaryTable = primaryTable;
this.l18nPkgName = null;
this.setDefaultName(name);
this.code = code == null ? createCode() : code;
this.combo = null;
this.list = null;
184,6 → 226,10
this.otherRF = null;
}
 
protected synchronized final boolean areRelationshipsInited() {
return this.sharedFF != null;
}
 
private void checkSelfCall(boolean check, final String methodName) {
assert check : this + " " + methodName + "() is calling itself, and thus the caller will only see a partial state";
}
190,7 → 236,7
 
private synchronized void initFF() {
checkSelfCall(this.sharedFF != computingFF, "initFF");
if (this.sharedFF != null)
if (areRelationshipsInited())
return;
this.sharedFF = computingFF;
 
316,6 → 362,22
this.childRF = tmpChildRF;
}
 
final void setDirectory(final SQLElementDirectory directory) {
// since this method should only be called at the end of SQLElementDirectory.addSQLElement()
assert directory.getElement(this.getTable()) == this;
synchronized (this) {
if (this.directory != directory) {
if (this.areRelationshipsInited())
this.resetRelationships();
this.directory = directory;
}
}
}
 
protected final SQLElementDirectory getDirectory() {
return this.directory;
}
 
final SQLElement getElement(SQLTable table) {
final SQLElement res = getElementLenient(table);
if (res == null)
324,8 → 386,10
}
 
final SQLElement getElementLenient(SQLTable table) {
return Configuration.getInstance().getDirectory().getElement(table);
synchronized (this) {
return this.getDirectory().getElement(table);
}
}
 
public final SQLElement getForeignElement(String foreignField) {
try {
339,12 → 403,56
return this.getTable().getBase().getGraph().getForeignTable(this.getTable().getField(foreignField));
}
 
public final synchronized String getL18nPackageName() {
return this.l18nPkgName;
}
 
public final void setL18nPackageName(Class<?> clazz) {
this.setL18nPackageName(clazz.getPackage().getName());
}
 
public final synchronized void setL18nPackageName(String name) {
this.l18nPkgName = name;
}
 
/**
* Set the default name, used if no translations could be found.
*
* @param name the default name, if <code>null</code> the {@link #getTable() table} name will be
* used.
*/
public final synchronized void setDefaultName(Phrase name) {
this.name = name != null ? name : Phrase.getInvariant(getTable().getName());
}
 
/**
* The default name.
*
* @return the default name, never <code>null</code>.
*/
public final synchronized Phrase getDefaultName() {
return this.name;
}
 
/**
* The name of this element in the current locale.
*
* @return the name of this, {@link #getDefaultName()} if there's no {@link #getDirectory()
* directory} or if it hasn't a name for this.
* @see SQLElementDirectory#getName(SQLElement)
*/
public final Phrase getName() {
final SQLElementDirectory dir = this.getDirectory();
final Phrase res = dir == null ? null : dir.getName(this);
return res == null ? this.getDefaultName() : res;
}
 
public String getPluralName() {
return this.plural;
return this.getName().getVariant(Grammar.PLURAL);
}
 
public String getSingularName() {
return this.singular;
return this.getName().getVariant(Grammar.INDEFINITE_ARTICLE_SINGULAR);
}
 
public CollectionMap<String, String> getShowAs() {
372,7 → 480,7
 
private final SQLCache<SQLRowAccessor, Object> getModelCache() {
if (this.modelCache == null)
this.modelCache = new SQLCache<SQLRowAccessor, Object>(60, -1, "modelObjects of " + this.getSingularName());
this.modelCache = new SQLCache<SQLRowAccessor, Object>(60, -1, "modelObjects of " + this.getCode());
return this.modelCache;
}
 
626,7 → 734,13
return this.primaryTable;
}
 
public final String getCode() {
public synchronized final String getCode() {
if (this.code == DEFERRED_CODE) {
final String createCode = this.createCode();
if (createCode == DEFERRED_CODE)
throw new IllegalStateException("createCode() returned DEFERRED_CODE");
this.code = createCode;
}
return this.code;
}
 
886,7 → 1000,10
return new ComboSQLRequest(this.getTable(), this.getComboFields());
}
 
abstract protected List<String> getComboFields();
// not all elements need to be displayed in combos so don't make this method abstract
protected List<String> getComboFields() {
return this.getListFields();
}
 
public final synchronized ListSQLRequest getListRequest() {
if (this.list == null) {
1194,7 → 1311,7
this.check(row);
 
final SQLRowValues copy = new SQLRowValues(this.getTable());
copy.loadAllSafe(row);
this.loadAllSafe(copy, row);
 
for (final String privateName : this.getPrivateForeignFields()) {
final SQLElement privateElement = this.getPrivateElement(privateName);
1216,6 → 1333,23
return copy;
}
 
/**
* Load all values that can be safely copied (shared by multiple rows). This means all values
* except private, primary, order and archive.
*
* @param vals the row to modify.
* @param row the row to be loaded.
*/
public final void loadAllSafe(final SQLRowValues vals, final SQLRow row) {
check(vals);
check(row);
vals.setAll(row.getAllValues());
vals.load(row, this.getNormalForeignFields());
if (this.getParentForeignField() != null)
vals.put(this.getParentForeignField(), row.getObject(this.getParentForeignField()));
vals.load(row, this.getSharedForeignFields());
}
 
// *** getRows
 
/**
1430,25 → 1564,32
return new SQLElementRowR(this, row).equals(new SQLElementRowR(this, row2));
}
 
@Override
public final boolean equals(Object obj) {
if (obj instanceof SQLElement) {
final SQLElement o = (SQLElement) obj;
final boolean parentEq = CompareUtils.equals(this.getParentForeignField(), o.getParentForeignField());
return this.getTable().equals(o.getTable()) && this.getSharedForeignFields().equals(o.getSharedForeignFields()) && parentEq
&& this.getPrivateForeignFields().equals(o.getPrivateForeignFields()) && this.getChildrenReferentFields().equals(o.getChildrenReferentFields());
// MAYBE also check getPrivateElement(String foreignField);
} else
// don't need to compare SQLField computed by initFF() & initRF() since they're function
// of this.table (and by extension its graph) & this.directory
final boolean parentEq = CompareUtils.equals(this.getParentFFName(), o.getParentFFName());
return this.getTable().equals(o.getTable()) && CompareUtils.equals(this.getDirectory(), o.getDirectory()) && parentEq && this.getPrivateFields().equals(o.getPrivateFields())
&& this.getChildren().equals(o.getChildren());
} else {
return false;
}
}
 
public final int hashCode() {
// ne pas mettre getParent car des fois null
return this.getTable().hashCode() + this.getSharedForeignFields().hashCode() + this.getPrivateForeignFields().hashCode();
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.primaryTable.hashCode();
result = prime * result + ((this.directory == null) ? 0 : this.directory.hashCode());
return result;
}
 
@Override
public String toString() {
return this.getClass().getName() + " '" + this.plural + "'";
return this.getClass().getName() + " " + this.getTable().getSQLName();
}
 
// *** gui
1512,19 → 1653,23
*/
protected abstract SQLComponent createComponent();
 
public final void addToMDPath(String mdVariant) {
public final void addToMDPath(final String mdVariant) {
if (mdVariant == null)
throw new NullPointerException();
synchronized (this) {
final LinkedList<String> newL = new LinkedList<String>(this.mdPath);
newL.addFirst(mdVariant);
this.mdPath = Collections.unmodifiableList(newL);
}
}
 
public final void removeFromMDPath(String mdVariant) {
public synchronized final void removeFromMDPath(final String mdVariant) {
final LinkedList<String> newL = new LinkedList<String>(this.mdPath);
if (newL.remove(mdVariant))
this.mdPath = Collections.unmodifiableList(newL);
}
 
public final List<String> getMDPath() {
public synchronized final List<String> getMDPath() {
return this.mdPath;
}
 
1578,10 → 1723,9
*/
public boolean askArchive(final Component comp, final Collection<? extends Number> ids) {
boolean shouldArchive = false;
if (ids.isEmpty())
final int rowCount = ids.size();
if (rowCount == 0)
return true;
final boolean plural = ids.size() > 1;
final String lines = plural ? "ces " + ids.size() + " lignes" : "cette ligne";
try {
if (!UserRightsManager.getCurrentUserRights().canDelete(getTable()))
throw new SQLException("forbidden");
1588,29 → 1732,32
final TreesOfSQLRows trees = TreesOfSQLRows.createFromIDs(this, ids);
final CollectionMap<SQLTable, SQLRowAccessor> descs = trees.getDescendantsByTable();
final SortedMap<SQLField, Integer> externRefs = trees.getExternReferencesCount();
if (descs.size() + externRefs.size() > 0) {
String msg = "";
if (descs.size() > 0)
msg = StringUtils.firstUpThenLow(lines) + (plural ? " sont utilisées" : " est utilisée") + " par : \n" + toString(descs);
if (externRefs.size() > 0) {
msg += descs.size() > 0 ? "\n\nDe plus les" : "Les";
msg += " liens suivant vont être IRREMEDIABLEMENT détruit :\n" + toStringExtern(externRefs);
}
 
int i = askSerious(comp, msg + "\n\nVoulez vous effacer " + lines + " ainsi que toutes les lignes liées ?", "Confirmation d'effacement");
final String confirmDelete = getTM().trA("sqlElement.confirmDelete");
final Map<String, Object> map = new HashMap<String, Object>();
map.put("rowCount", rowCount);
final int descsSize = descs.size();
final int externsSize = externRefs.size();
if (descsSize + externsSize > 0) {
final String descsS = descsSize > 0 ? toString(descs) : null;
final String externsS = externsSize > 0 ? toStringExtern(externRefs) : null;
map.put("descsSize", descsSize);
map.put("descs", descsS);
map.put("externsSize", externsSize);
map.put("externs", externsS);
map.put("times", "once");
int i = askSerious(comp, getTM().trM("sqlElement.deleteRef.details", map) + getTM().trM("sqlElement.deleteRef", map), confirmDelete);
if (i == JOptionPane.YES_OPTION) {
msg = "";
if (externRefs.size() > 0)
msg = "Les liens suivant vont être IRREMEDIABLEMENT détruit, ils ne pourront pas être 'désarchivés' :\n" + toStringExtern(externRefs) + "\n\n";
i = askSerious(comp, msg + "Voulez vous VRAIMENT effacer " + lines + " ainsi que toutes les lignes liées ?", "Confirmation d'effacement");
map.put("times", "twice");
final String msg = externsSize > 0 ? getTM().trM("sqlElement.deleteRef.details2", map) : "";
i = askSerious(comp, msg + getTM().trM("sqlElement.deleteRef", map), confirmDelete);
if (i == JOptionPane.YES_OPTION) {
shouldArchive = true;
} else {
JOptionPane.showMessageDialog(comp, "Aucune ligne effacée.", "Information", JOptionPane.INFORMATION_MESSAGE);
JOptionPane.showMessageDialog(comp, getTM().trA("sqlElement.noLinesDeleted"), getTM().trA("sqlElement.noLinesDeletedTitle"), JOptionPane.INFORMATION_MESSAGE);
}
}
} else {
int i = askSerious(comp, "Voulez vous effacer " + lines + " ?", "Confirmation d'effacement");
int i = askSerious(comp, getTM().trM("sqlElement.deleteNoRef", map), confirmDelete);
if (i == JOptionPane.YES_OPTION) {
shouldArchive = true;
}
1621,7 → 1768,7
} else
return false;
} catch (SQLException e) {
ExceptionHandler.handle(comp, "Impossible d'archiver " + this + " IDs " + ids, e);
ExceptionHandler.handle(comp, TM.tr("sqlElement.archiveError", this, ids), e);
return false;
}
}
1640,8 → 1787,7
}
 
private static final String elemToString(int count, SQLElement elem) {
// don't use count for 1 as the article is in singularName
return "- " + (count == 1 ? elem.getSingularName() : count + " " + elem.getPluralName());
return "- " + elem.getName().getNumeralVariant(count, Grammar.INDEFINITE_NUMERAL);
}
 
// traduire TRANSFO.ID_ELEMENT_TABLEAU_PRI -> {TRANSFO[5], TRANSFO[12]}
1648,16 → 1794,16
// en 2 transformateurs vont perdre leurs champs 'Circuit primaire'
private final String toStringExtern(SortedMap<SQLField, Integer> externRef) {
final List<String> l = new ArrayList<String>();
final Map<String, Object> map = new HashMap<String, Object>(4);
for (final Map.Entry<SQLField, Integer> entry : externRef.entrySet()) {
final SQLField foreignKey = entry.getKey();
final int count = entry.getValue();
final String end;
final String label = Configuration.getTranslator(foreignKey.getTable()).getLabelFor(foreignKey);
if (count > 1)
end = " vont perdre leurs champs '" + label + "'";
else
end = " va perdre son champ '" + label + "'";
l.add(elemToString(count, getElement(foreignKey.getTable())) + end);
final SQLElement elem = getElement(foreignKey.getTable());
map.put("elementName", elem.getName());
map.put("count", count);
map.put("linkName", label);
l.add(getTM().trM("sqlElement.linksWillBeCut", map));
}
return CollectionUtils.join(l, "\n");
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/BaseSQLComponent.java
18,6 → 18,7
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
26,6 → 27,7
import org.openconcerto.sql.request.MutableRowItemView;
import org.openconcerto.sql.request.RowItemDesc;
import org.openconcerto.sql.request.RowNotFound;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.sql.request.SQLForeignRowItemView;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.request.SQLRowView;
71,6 → 73,7
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
82,6 → 85,7
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.text.JTextComponent;
 
93,7 → 97,7
*
* @author ilm
*/
public abstract class BaseSQLComponent extends SQLComponent implements Scrollable {
public abstract class BaseSQLComponent extends SQLComponent implements Scrollable, RIVPanel {
protected static final String REQ = "required";
protected static final String DEC = "notdecorated";
protected static final String SEP = "noseparator";
283,7 → 287,7
final String s = (String) t;
final boolean ok = s == null || s.length() <= type.getSize();
// only compute string if needed
return ok ? ValidState.getTrueInstance() : ValidState.create(ok, "La valeur fait " + (s.length() - type.getSize()) + " caractère(s) de trop");
return ok ? ValidState.getTrueInstance() : ValidState.create(ok, TM.tr("sqlComp.stringValueTooLong", s.length() - type.getSize()));
}
});
// other numeric SQL types are fixed size like their java counterparts
290,7 → 294,7
} else if (BigDecimal.class.isAssignableFrom(fieldClass)) {
final Integer decimalDigits = type.getDecimalDigits();
final int intDigits = type.getSize() - decimalDigits;
final String reason = "Nombre trop grand, il doit faire moins de " + intDigits + " chiffre(s) avant la virgule (" + decimalDigits + " après)";
final String reason = TM.tr("sqlComp.bdTooHigh", intDigits, decimalDigits);
res = ValidatedValueWrapper.add(res, new ITransformer<T, ValidState>() {
@Override
public ValidState transformChecked(T t) {
330,7 → 334,7
 
private final <R extends MutableRowItemView> R initRIV(R rowItemView, String fields) {
final List<String> fieldListS = SQLRow.toList(fields);
final Set<SQLField> fieldList = new HashSet<SQLField>(fieldListS.size());
final Set<SQLField> fieldList = new LinkedHashSet<SQLField>(fieldListS.size());
for (final String fieldName : fieldListS) {
fieldList.add(this.getField(fieldName));
}
349,13 → 353,24
 
// ParentForeignField is always required
final String fieldName = v.getField().getName();
if (spec.isRequired() || fieldName.equals(getElement().getParentForeignField()) || this.getRequiredNames() == null || this.getRequiredNames().contains(v.getSQLName())) {
final Set<String> reqNames = this.getRequiredNames();
if (spec.isRequired() || fieldName.equals(getElement().getParentForeignField()) || reqNames == null || reqNames.contains(v.getSQLName())) {
this.required.add(v);
if (v instanceof ElementSQLObject)
((ElementSQLObject) v).setRequired(true);
if (reqNames != null)
this.requiredNames.add(v.getSQLName());
}
this.getRequest().add(v);
 
if (v.getComp() instanceof SQLComponentItem) {
((SQLComponentItem) v.getComp()).added(this, v);
}
// reset just before adding to the UI :
// - avoid updating value while displayed
// - but still wait the longest to be sure the RIV is initialized (SQLComponentItem)
v.resetValue();
 
if (!this.hide.contains(v.getField())) {
if (spec.isAdditional()) {
if (this.additionalFieldsPanel == null)
487,16 → 502,14
for (final SQLRowItemView obj : this.getRequest().getViews()) {
final ValidState state = obj.getValidState();
if (!state.isValid()) {
String explanation = "'" + getDesc(obj) + "' n'est pas valide";
final String txt = state.getValidationText();
if (txt != null)
explanation += " (" + txt + ")";
final String explanation = TM.tr("sqlComp.invalidItem", "'" + getDesc(obj) + "'", txt != null ? 1 : 0, txt);
pbs.add(explanation);
res = false;
// ne regarder si vide que pour les valides (souvent les non-valides sont vides car
// il ne peuvent renvoyer de valeur)
} else if (this.getRequired().contains(obj) && obj.isEmpty()) {
pbs.add("'" + getDesc(obj) + "' est vide");
pbs.add(TM.tr("sqlComp.emptyItem", "'" + getDesc(obj) + "'"));
res = false;
}
}
504,14 → 517,23
}
 
protected final String getDesc(final SQLRowItemView obj) {
return getDesc(obj.getSQLName(), getRIVDesc(obj.getSQLName())).get0();
return getLabelFor(obj.getSQLName());
}
 
static protected final Tuple2<String, Boolean> getDesc(final String itemName, final RowItemDesc desc) {
public final String getLabelFor(String itemName) {
return getDesc(itemName, getRIVDesc(itemName)).get0();
}
 
// not public since desc could be different from getRIVDesc(itemName)
protected final Tuple2<String, Boolean> getDesc(final String itemName, final RowItemDesc desc) {
final boolean emptyLabel = desc.getLabel() == null || desc.getLabel().trim().length() == 0;
return Tuple2.create(emptyLabel ? itemName : desc.getLabel(), !emptyLabel);
return Tuple2.create(emptyLabel ? itemName : getLabel(itemName, desc), !emptyLabel);
}
 
protected String getLabel(final String itemName, final RowItemDesc desc) {
return desc.getLabel();
}
 
/*
* (non-Javadoc)
*
555,7 → 577,7
else
return this.getRequest().insert(order).getID();
} catch (SQLException e) {
ExceptionHandler.handle(this, "Impossible d'insérer", e);
ExceptionHandler.handle(this, TM.tr("sqlComp.insertError"), e);
return -1;
}
}
587,9 → 609,9
} catch (RowNotFound e) {
// l'id demandé n'existe pas : prévenir tout le monde
this.getTable().fireRowDeleted(e.getRow().getID());
ExceptionHandler.handle(this, "La ligne n'est plus dans la base : " + e.getRow(), e);
ExceptionHandler.handle(this, TM.tr("sqlComp.deletedRow", e.getRow()), e);
} catch (IllegalStateException e) {
ExceptionHandler.handle(this, "Impossible de sélectionner " + r, e);
ExceptionHandler.handle(this, TM.tr("sqlComp.selectError", r), e);
}
}
 
631,6 → 653,7
}
 
public int getSelectedID() {
assert (SwingUtilities.isEventDispatchThread());
return this.getRequest().getSelectedID();
}
 
640,7 → 663,7
throw new SQLException("forbidden");
this.getRequest().update();
} catch (SQLException e) {
ExceptionHandler.handle(this, "Impossible de mettre à jour", e);
ExceptionHandler.handle(this, TM.tr("sqlComp.updateError"), e);
}
}
 
655,7 → 678,7
// } else
this.getElement().archive(this.getSelectedID());
} catch (SQLException e) {
ExceptionHandler.handle(this, "Impossible d'archiver " + this + ": ", e);
ExceptionHandler.handle(this, TM.tr("sqlComp.archiveError", this), e);
}
}
 
665,8 → 688,13
return this.required;
}
 
/**
* The SQL names that are required.
*
* @return the required SQL names, <code>null</code> meaning all of them.
*/
protected final Set<String> getRequiredNames() {
return this.requiredNames;
return this.requiredNames == null ? null : Collections.unmodifiableSet(this.requiredNames);
}
 
/**
690,12 → 718,12
this.alwaysEditable = alwaysEditable;
}
 
public final String getLabelFor(String field) {
return getDesc(field, getRIVDesc(field)).get0();
}
 
public final RowItemDesc getRIVDesc(String field) {
return Configuration.getInstance().getTranslator().getDescFor(this.getTable(), getCode(), getElement().getMDPath(), field);
final Configuration conf = Configuration.getInstance();
if (conf == null)
return SQLFieldTranslator.NULL_DESC;
else
return conf.getTranslator().getDescFor(this.getTable(), getCode(), getElement().getMDPath(), field);
}
 
public final void setRIVDesc(String itemName, RowItemDesc desc) {
703,7 → 731,7
Configuration.getTranslator(this.getTable()).storeDescFor(this.getTable(), getCode(), itemName, desc);
updateUI(itemName, desc);
} catch (SQLException e) {
ExceptionHandler.handle(this, "Impossible d'enregistrer la documentation de " + itemName, e);
ExceptionHandler.handle(this, TM.tr("sqlComp.saveDocError", itemName), e);
}
}
 
710,11 → 738,11
protected void updateUI(final String itemName, final RowItemDesc desc) {
}
 
static protected void updateUI(final String itemName, final JComponent label, final RowItemDesc desc) {
protected final void updateUI(final String itemName, final JComponent label, final RowItemDesc desc) {
updateUI(itemName, label, desc, null);
}
 
static protected void updateUI(final String itemName, final JComponent label, final RowItemDesc desc, final Color emptyLabelColor) {
protected final void updateUI(final String itemName, final JComponent label, final RowItemDesc desc, final Color emptyLabelColor) {
label.setToolTipText(desc.getDocumentation().trim().length() == 0 ? null : desc.getDocumentation());
final Tuple2<String, Boolean> tuple = getDesc(itemName, desc);
final String s = tuple.get0();
816,6 → 844,16
}
}
 
@Override
public SQLElementDirectory getDirectory() {
return this.getElement().getDirectory();
}
 
@Override
public SQLComponent getSQLComponent() {
return this;
}
 
// *** scrollable
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/element/ConfSQLElement.java
24,18 → 24,33
*/
public class ConfSQLElement extends SQLElement {
 
@Deprecated
public ConfSQLElement(String tableName, String singular, String plural) {
this(Configuration.getInstance(), tableName, singular, plural);
}
 
@Deprecated
public ConfSQLElement(Configuration conf, String tableName, String singular, String plural) {
this(conf.getRoot().findTable(tableName), singular, plural);
}
 
@Deprecated
public ConfSQLElement(SQLTable table, String singular, String plural) {
super(singular, plural, table);
}
 
public ConfSQLElement(String tableName) {
this(Configuration.getInstance(), tableName);
}
 
public ConfSQLElement(Configuration conf, String tableName) {
this(conf.getRoot().findTable(tableName));
}
 
public ConfSQLElement(SQLTable table) {
super(table);
}
 
@Override
protected List<String> getComboFields() {
return Collections.emptyList();
/trunk/OpenConcerto/src/org/openconcerto/sql/element/UISQLComponent.java
16,6 → 16,7
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.TM;
import org.openconcerto.sql.request.RowItemDesc;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.ui.FormLayouter;
76,9 → 77,7
 
protected void addToUI(final SQLRowItemView obj, String where) {
final RowItemDesc rivDesc = getRIVDesc(obj.getSQLName());
String desc = rivDesc.getLabel();
if (this.getRequired().contains(obj))
desc += REQ_SUFFIX;
final String desc = getLabel(obj.getSQLName(), rivDesc);
if (where == null)
where = this.getDefaultWhere(obj);
final JComponent added;
98,7 → 97,7
this.labels.put(obj.getSQLName(), added);
updateUI(obj.getSQLName(), rivDesc);
final JPopupMenu menu = new JPopupMenu();
menu.add(new AbstractAction("Modifier la documentation") {
menu.add(new AbstractAction(TM.tr("sqlComp.modifyDoc")) {
@Override
public void actionPerformed(ActionEvent e) {
final DocumentationEditorFrame frame = new DocumentationEditorFrame(UISQLComponent.this, obj.getSQLName());
115,6 → 114,12
}
 
@Override
protected String getLabel(final String itemName, final RowItemDesc desc) {
final String res = super.getLabel(itemName, desc);
return this.getRequiredNames().contains(itemName) ? res + REQ_SUFFIX : res;
}
 
@Override
protected void updateUI(String itemName, RowItemDesc desc) {
super.updateUI(itemName, desc);
updateUI(itemName, this.labels.get(itemName), desc);
/trunk/OpenConcerto/src/org/openconcerto/sql/element/RIVPanel.java
New file
0,0 → 1,33
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
 
import org.openconcerto.sql.request.SQLRowItemView;
 
/**
* Allow a panel to provide context to {@link SQLRowItemView}.
*
* @author Sylvain
* @see SQLComponentItem
*/
public interface RIVPanel {
/**
* Return the SQL component, if possible.
*
* @return the SQL component, or <code>null</code> if it's another panel.
*/
public SQLComponent getSQLComponent();
 
public SQLElementDirectory getDirectory();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLComponent.java
80,6 → 80,9
private IFactory<SQLRowValues> defaults;
 
protected SQLComponent(SQLElement element) {
if (element == null) {
throw new IllegalArgumentException("null element");
}
// Doit être opaque sinon bug avec L&F Nimbus
this.setOpaque(true);
this.parent = null;
/trunk/OpenConcerto/src/org/openconcerto/sql/FieldExpander.java
29,6 → 29,18
import java.util.Set;
 
public abstract class FieldExpander {
 
static private final FieldExpander EMPTY = new FieldExpander() {
@Override
protected List<SQLField> expandOnce(SQLField field) {
return Collections.emptyList();
}
};
 
public static FieldExpander getEmpty() {
return EMPTY;
}
 
// eg |TABLEAU.ID_OBSERVATION| -> [[DESIGNATION], []]
private final Map<IFieldPath, List<FieldPath>> cache;
private final Map<List<? extends IFieldPath>, List<Tuple2<Path, List<FieldPath>>>> cacheGroupBy;
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/BrowserStateManager.java
50,7 → 50,7
try {
saveState();
} catch (IOException exn) {
ExceptionHandler.handle(getSrc(), "Impossible de sauvegarder la taille des colonnes", exn);
ExceptionHandler.handle(getSrc(), org.openconcerto.ui.TM.tr("saveColumnsWidth"), exn);
}
}
};
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/SQLBrowserColumn.java
111,23 → 111,8
focusChanged(hasFocus);
}
};
this.uiInit();
this.list.setCellRenderer(new DefaultListCellRenderer() {
@SuppressWarnings("unchecked")
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final JLabel comp = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
render(comp, (T) value);
return comp;
}
 
});
this.list.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
navigate(e);
}
});
}
 
abstract protected String getHeaderName();
 
protected void render(final JLabel comp, T value) {
165,7 → 150,7
this.list.requestFocusInWindow();
}
 
private void uiInit() {
protected final void uiInit() {
UIManager.put("List.background", Color.WHITE);
// UIManager.put("List.selectionBackground", new Color(100, 100, 120));
UIManager.put("List.selectionForeground", Color.WHITE);
196,7 → 181,14
 
this.list = new JList(this.getModel());
this.list.setSelectionModel(new ReSelectionModel());
// this.list.setCellRenderer(new ListCellRenderDecorated());
this.list.setCellRenderer(new DefaultListCellRenderer() {
@SuppressWarnings("unchecked")
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final JLabel comp = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
render(comp, (T) value);
return comp;
}
});
this.list.setFont(fontText);
this.list.setSelectionMode(this.getSelectionMode());
JScrollPane scrollPane = new JScrollPane(this.list);
229,6 → 221,12
}
}
});
this.list.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
navigate(e);
}
});
 
this.getModel().addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/RowsSQLBrowserColumn.java
13,7 → 13,6
/*
* Créé le 21 mai 2005
*
*/
package org.openconcerto.sql.navigator;
 
52,6 → 51,7
public RowsSQLBrowserColumn(SQLElement element, boolean searchable) {
super(new RowsSQLListModel(element), searchable);
this.children = new ArrayList<SQLField>();
this.uiInit();
this.state = ListSelectionState.manage(this.list.getSelectionModel(), new ListStateModel(this.getModel()));
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/ElementsSQLBrowserColumn.java
13,11 → 13,11
/*
* Créé le 28 mai 2005
*
*/
package org.openconcerto.sql.navigator;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
36,6 → 36,8
public ElementsSQLBrowserColumn(SQLElement parent, List<SQLElement> elements) {
super(new ElementsSQLListModel(elements), false);
this.parent = parent;
// uiInit() eventually needs this.parent (in getHeaderName())
this.uiInit();
}
 
protected int getSelectionMode() {
68,7 → 70,7
if (this.parent == null) {
res = "Selection";
} else {
final String name = "Contenu des " + this.parent.getPluralName();
final String name = TM.getInstance().trM("browserCol.content", "element", this.parent.getName());
// Mets la 1ere lettre en majuscule
res = name.substring(0, 1).toUpperCase() + name.substring(1);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/SQLElementNames_en.xml
New file
0,0 → 1,13
<translations>
<element refid="ilm.sql.users.UserCommonSQLElement-USER_COMMON">
<name base="user" />
</element>
<element refid="ilm.sql.users.rights.RightSQLElement-RIGHT">
<name base="right" />
</element>
<element refid="ilm.sql.users.rights.UserRightSQLElement-USER_RIGHT">
<name base="user right">
<variant refids="plural" value="users rights" />
</name>
</element>
</translations>
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/SQLElementNames_fr.xml
New file
0,0 → 1,13
<translations>
<element refid="ilm.sql.users.UserCommonSQLElement-USER_COMMON">
<name base="utilisateur" nounClass="masculine" />
</element>
<element refid="ilm.sql.users.rights.RightSQLElement-RIGHT">
<name base="droit" nounClass="masculine" />
</element>
<element refid="ilm.sql.users.rights.UserRightSQLElement-USER_RIGHT">
<name base="droit utilisateur" nounClass="masculine">
<variant refids="plural" value="droits utilisateurs" />
</name>
</element>
</translations>
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_en.properties
New file
0,0 → 1,150
init.error=Initialization error
add=Add
saveModifications=Save modifications
modify=Modify
delete=Delete
remove=Remove
close=Close
cancel=Cancel
search=Search
open=Open
save=Save
backup=Backup
export=Export
noSelection=No selection
duplicate=Duplicate
duplication=Duplication
location=Location
choose=Choose
toApply=Apply
 
all=All
toReverse=Reverse
 
contains=Contains
contains.exactly=Contains exactly
isLessThan=Is less than
isEqualTo=Is equal to
isExactlyEqualTo=Is exactly equal to
isGreaterThan=Is greater than
isEmpty=Is empty
 
clone.newPlace=New location (optional) :
 
saveError=Error while saving
 
loginPanel.storePass=Store password
loginPanel.loginAction=Log in
loginPanel.adminLogin=Administrator
loginPanel.loginLabel=Login
loginPanel.passLabel=Password
loginPanel.companyLabel=Company
loginPanel.unknownUser=Unknown user
loginPanel.multipleUser=Multiple users named "{0}"
loginPanel.wrongPass=Wrong password
 
noRightToAdd=You''re not allowed to add
noRightToModify=You''re not allowed to modify
noRightToDel=You''re not allowed to delete
noRightToClone=You''re not allowed to duplicate
noRightToReorder=You''re not allowed to change order
 
editPanel.keepOpen=don''t close the window
editPanel.inexistentElement=this item doesn't exist
editPanel.cancelError=Error while canceling
editPanel.modifyError=Error while modifying
editPanel.addError=Error while adding
editPanel.deleteError=Error while deleting
editPanel.invalidContent=Input fields aren''t filled correctly.\n\nYou cannot save modifications :
editPanel.invalidContent.unknownReason= they aren''t valid
 
editAction.name=Create {element__singularIndefiniteArticle}
editFrame.create=Create {element__singularIndefiniteArticle}
editFrame.modify=Modify {element__singularIndefiniteArticle}
editFrame.look=Details of {element__singularIndefiniteArticle}
 
listPanel.cloneToolTip=<html>Allow to duplicate a row.<br>Hold CTRL down to also duplicate the content<br>Hold Shift down to change the location.</html>
listPanel.cloneRows=Do you want to clone {0,choice,1#this row'{1,choice,0#|1# and its content}'|1<these {0,number,integer} rows'{1,choice,0#|1# and their contents}'} ?
listPanel.noSelectionOrSort=No selection or list sorted
listPanel.export=List export
listPanel.save=Save the list
listPanel.wholeList=the whole list
listPanel.selection=the selection
listPanel.duplicationError=Couldn''t duplicate {0}
 
listAction.name=Manage {element__pluralDefiniteArticle}
element.list=List of {element__plural}
 
ilist.setColumnsWidth=Adjust columns widths
ilist.metadata={0,choice,0#Modified|1#Created}{1,choice,0#|1# by {2} {3}}{4,choice,0#|1#, {5,date,long} at {5,time,medium}}
 
sqlComp.stringValueTooLong=The value is {0} character{0,choice,1#|1<s} too long
sqlComp.bdTooHigh=Number too high, it must have at most {0} digit{0,choice,1#|1<s} before the decimal point ({1} after)
sqlComp.invalidItem={0} is invalid{1,choice,0#|1# ({2})}
sqlComp.emptyItem={0} is empty
sqlComp.insertError=Error while inserting
sqlComp.selectError=Error while displaying {0}
sqlComp.updateError=Error while updating
sqlComp.archiveError=Error while archiving {0}
sqlComp.saveDocError=Error while saving documentation of {0}
sqlComp.modifyDoc=Modify the documentation
sqlComp.deletedRow=The row is no longer in the database : {0}
 
elementSQLObject.delete=Do you really want to delete {element__singularDemonstrative} ?
 
sqlElement.archiveError=Error while archiving {0} IDs {1}
sqlElement.confirmDelete=Confirm deletion
sqlElement.deleteNoRef=Do you want to delete {rowCount, plural, one {this row} other {these # rows}} ?
sqlElement.deleteRef.details= {descsSize, plural, =0 {} other\
{{rowCount, plural, one {This row is used} other {These rows are used}} by :\n\
{descs}}}\
{externsSize, plural, =0 {} other {{descsSize, plural, =0 {The} other {\n\nFurther the}} following links will be IRRETRIEVABLY cut : \n\
{externs}}}\n\n
sqlElement.deleteRef.details2=The following links will be IRRETRIEVABLY cut, they couldn\u2019t be 'unarchived' :\n\
{externs}\n\n
sqlElement.deleteRef=Do you{times, select, once {} other { REALLY}} want to delete {rowCount, plural, one {this row} other {these # rows}} and all linked ones ?
sqlElement.noLinesDeleted=No lines deleted.
sqlElement.noLinesDeletedTitle=Information
sqlElement.linksWillBeCut=- {elementName__indefiniteNumeral} {count, plural, one {will loose its} other {will loose their}} "{linkName}"
 
user.passwordsDontMatch=Passwords don\u2019t match
user.passwordsDontMatch.short=Passwords don\u2019t match
 
infoPanel.rights=Rights enabled
infoPanel.appName=Application name
infoPanel.noAppName=unknown
infoPanel.version=Application version
infoPanel.noVersion=unknown
infoPanel.secureLink=Secure link
infoPanel.dbURL=Database URL
infoPanel.dirs=Folders
infoPanel.logs=Logs
infoPanel.docs=Documents
 
infoPanel.softwareTitle=Software
infoPanel.systemTitle=System information
 
backupPanel.backup=Backup
backupPanel.createFolderError=Couldn't create destination folder. Backup canceled !
backupPanel.folderRightsError=Insufficient rights on destination folder. Backup canceled !
backupPanel.errorsOnLastBackup=Errors occurred on last backup. Please contact IT.
backupPanel.lastBackup=Last backup {date, date, long} at {date, time, short}\non {destination}
backupPanel.differentDisks=Please backup on different disks !
backupPanel.progress=Backup progress
backupPanel.inProgress=Backup in progress
backupPanel.closingIn=Closing in {0}s
backupPanel.endFail=Backup ended with errors !
backupPanel.endSuccess=Backup ended successfully
backupPanel.failed=Backup failed
 
rights=Rights
rightsPanel.defaultRights=Default rights
 
combo.list=List {element__pluralDefiniteArticle}
 
browserCol.content=Content of {element__pluralDefiniteArticle}
 
addNewLine=Add a new line
insertNewLine=Insert a line
duplicateLine=Duplicate selected lines
deleteLine=Remove selected lines
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_fr.properties
New file
0,0 → 1,150
init.error=Erreur d''initialisation
add=Ajouter
saveModifications=Enregistrer les modifications
modify=Modifier
delete=Effacer
remove=Supprimer
close=Fermer
cancel=Annuler
search=Rechercher
open=Ouvrir
save=Sauver
backup=Sauvegarder
export=Exporter
noSelection=Aucune sélection
duplicate=Dupliquer
duplication=Duplication
location=Emplacement
choose=Sélectionner
toApply=Appliquer
 
all=Tout
toReverse=inverser
 
contains=Contient
contains.exactly=Contient exactement
isLessThan=Est inférieur à
isEqualTo=Est égal à
isExactlyEqualTo=Est exactement égal à
isGreaterThan=Est supérieur à
isEmpty=Est vide
 
clone.newPlace=Nouvel emplacement (optionnel) :
 
saveError=Erreur pendant la sauvegarde
 
loginPanel.storePass=Mémoriser le mot de passe
loginPanel.loginAction=Connexion
loginPanel.adminLogin=Administrateur
loginPanel.loginLabel=Identifiant
loginPanel.passLabel=Mot de passe
loginPanel.companyLabel=Société
loginPanel.unknownUser=Utilisateur inconnu
loginPanel.multipleUser=Plusieurs utilisateurs nommés "{0}"
loginPanel.wrongPass=Mot de passe erronné
 
noRightToAdd=Vous n''avez pas le droit d'ajouter
noRightToModify=Vous n''avez pas le droit de modifier
noRightToDel=Vous n''avez pas le droit de supprimer
noRightToClone=Vous n''avez pas le droit de dupliquer
noRightToReorder=Vous n''avez pas le droit de changer l'ordre
 
editPanel.keepOpen=ne pas fermer la fenêtre
editPanel.inexistentElement=cet élément n'existe pas
editPanel.cancelError=Erreur pendant l''annulation
editPanel.modifyError=Erreur pendant la modification
editPanel.addError=Erreur pendant l''ajout
editPanel.deleteError=Erreur pendant la suppression
editPanel.invalidContent=Les champs de saisie ne sont pas remplis correctement.\n\nVous ne pouvez enregistrer les modifications :
editPanel.invalidContent.unknownReason= elles ne sont pas valides
 
editAction.name=Créer {element__singularIndefiniteArticle}
editFrame.create=Créer {element__singularIndefiniteArticle}
editFrame.modify=Modifier {element__singularIndefiniteArticle}
editFrame.look=Détail {element__de__singularIndefiniteArticle}
 
listPanel.cloneToolTip=<html>Permet de dupliquer une ligne.<br>Maintenir CTRL enfoncé pour dupliquer également le contenu<br>Maintenir Maj. enfoncé pour changer d'emplacement.</html>
listPanel.cloneRows=Voulez-vous cloner {0,choice,1#cette ligne'{1,choice,0#|1# et son contenu}'|1<ces {0,number,integer} lignes'{1,choice,0#|1# et leurs contenus}'} ?
listPanel.noSelectionOrSort=Pas de sélection ou liste triée
listPanel.export=Export de la liste
listPanel.save=Sauver la liste
listPanel.wholeList=l''ensemble de la liste
listPanel.selection=la sélection
listPanel.duplicationError=Impossible de dupliquer {0}
 
listAction.name=Gérer {element__pluralDefiniteArticle}
element.list=Liste {element__de__pluralDefiniteArticle}
 
ilist.setColumnsWidth=Ajuster la largeur des colonnes
ilist.metadata={0,choice,0#Modifiée|1#Créée}{1,choice,0#|1# par {2} {3}}{4,choice,0#|1# le {5,date,long} à {5,time,medium}}
 
sqlComp.stringValueTooLong=La valeur fait {0} caractère{0,choice,1#|1<s} de trop
sqlComp.bdTooHigh=Nombre trop grand, il doit faire moins de {0} chiffre{0,choice,1#|1<s} avant la virgule ({1} après)
sqlComp.invalidItem={0} n''est pas valide{1,choice,0#|1# ({2})}
sqlComp.emptyItem={0} est vide
sqlComp.insertError=Impossible d''insérer
sqlComp.selectError=Impossible d''afficher {0}
sqlComp.updateError=Impossible de mettre à jour
sqlComp.archiveError=Impossible d''archiver {0}
sqlComp.saveDocError=Impossible d''enregistrer la documentation de {0}
sqlComp.modifyDoc=Modifier la documentation
sqlComp.deletedRow=La ligne n'est plus dans la base : {0}
 
elementSQLObject.delete=Voulez-vous vraiment supprimer {element__singularDemonstrative} ?
 
sqlElement.archiveError=Impossible d''archiver {0} IDs {1}
sqlElement.confirmDelete=Confirmation d'effacement
sqlElement.deleteNoRef=Voulez vous effacer {rowCount, plural, one {cette ligne} other {ces # lignes}} ?
sqlElement.deleteRef.details= {descsSize, plural, =0 {} other\
{{rowCount, plural, one {Cette ligne est utilisée} other {Ces # lignes sont utilisées}} par :\n\
{descs}}}\
{externsSize, plural, =0 {} other {{descsSize, plural, =0 {Les} other {\n\nDe plus les}} liens suivant vont être IRREMEDIABLEMENT détruit : \n\
{externs}}}\n\n
sqlElement.deleteRef.details2=Les liens suivant vont être IRREMEDIABLEMENT détruits, ils ne pourront pas être 'désarchivés' :\n\
{externs}\n\n
sqlElement.deleteRef=Voulez vous{times, select, once {} other { VRAIMENT}} effacer {rowCount, plural, one {cette ligne} other {ces # lignes}} ainsi que toutes les lignes liées ?
sqlElement.noLinesDeleted=Aucune ligne effacée.
sqlElement.noLinesDeletedTitle=Information
sqlElement.linksWillBeCut=- {elementName__indefiniteNumeral} {count, plural, one {va perdre son champ} other {vont perdre leurs champs}} "{linkName}"
 
user.passwordsDontMatch=Les mots de passe ne correspondent pas
user.passwordsDontMatch.short=Confirmation incorrecte
 
infoPanel.rights=Gestion des droits
infoPanel.appName=Nom de l''application
infoPanel.noAppName=inconnu
infoPanel.version=Version de l''application
infoPanel.noVersion=inconnue
infoPanel.secureLink=Liaison sécurisée
infoPanel.dbURL=URL de base de données
infoPanel.dirs=Dossiers
infoPanel.logs=Journaux
infoPanel.docs=Documents
 
infoPanel.softwareTitle=Logiciel
infoPanel.systemTitle=Informations système
 
backupPanel.backup=Sauvegarde
backupPanel.createFolderError=Impossible de créer le dossier de destination. Sauvegarde annulée !
backupPanel.folderRightsError=Droits insuffisants sur le dossier de destination. Sauvegarde annulée !
backupPanel.errorsOnLastBackup=Des erreurs sont survenues lors de la dernière sauvegarde. Veuillez contacter le service technique.
backupPanel.lastBackup=Derniére sauvegarde effectuée le {date, date, dd/MM/yyyy 'à' HH:mm}\nsur {destination}
backupPanel.differentDisks=Pensez à effectuer vos sauvegardes sur différents disques !
backupPanel.progress=Progression de la sauvegarde
backupPanel.inProgress=Sauvegarde en cours
backupPanel.closingIn=Fermeture dans {0}s
backupPanel.endFail=Sauvegarde terminée avec erreurs !
backupPanel.endSuccess=Sauvegarde terminée avec succès
backupPanel.failed=Echec de la sauvegarde
 
rights=Droits
rightsPanel.defaultRights=Droits par défaut
 
combo.list=Lister {element__pluralDefiniteArticle}
 
browserCol.content=Contenu {element__de__pluralDefiniteArticle}
 
addNewLine=Ajouter une ligne
insertNewLine=Insérer une ligne
duplicateLine=Dupliquer une ligne
deleteLine=Supprimer la sélection
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRow.java
349,22 → 349,12
return this.getValues().get(field);
}
 
public BigDecimal getOrder() {
return (BigDecimal) this.getObject(this.getTable().getOrderField().getName());
}
 
/**
* The free order just after or before this row.
*
* @param after whether to look before or after this row.
* @return a free order, or <code>null</code> if there's no room left.
*/
public final BigDecimal getOrder(boolean after) {
public final SQLRow getRow(boolean after) {
final SQLTable t = this.getTable();
final BigDecimal destOrder = this.getOrder();
final int diff = (!after) ? -1 : 1;
 
final SQLSelect sel = new SQLSelect(t.getBase());
final SQLSelect sel = new SQLSelect();
// undefined must not move
sel.setExcludeUndefined(true);
// unique index prend aussi en compte les archivés
371,16 → 361,33
sel.setArchivedPolicy(SQLSelect.BOTH);
sel.addSelect(t.getKey());
sel.addSelect(t.getOrderField());
if (t.isArchivable())
sel.addSelect(t.getArchiveField());
sel.setWhere(new Where(t.getOrderField(), diff < 0 ? "<" : ">", destOrder));
sel.addFieldOrder(t.getOrderField(), diff < 0 ? Order.desc() : Order.asc());
sel.setLimit(1);
 
final BigDecimal otherOrder;
final SQLDataSource ds = t.getBase().getDataSource();
@SuppressWarnings("unchecked")
final Map<String, Object> otherMap = ds.execute1(sel.asString());
if (otherMap != null) {
final SQLRow otherRow = new SQLRow(t, otherMap);
return new SQLRow(t, otherMap);
} else {
return null;
}
}
 
/**
* The free order just after or before this row.
*
* @param after whether to look before or after this row.
* @return a free order, or <code>null</code> if there's no room left.
*/
public final BigDecimal getOrder(boolean after) {
final BigDecimal destOrder = this.getOrder();
final SQLRow otherRow = this.getRow(after);
final BigDecimal otherOrder;
if (otherRow != null) {
otherOrder = otherRow.getOrder();
} else if (after) {
// dernière ligne de la table
/trunk/OpenConcerto/src/org/openconcerto/sql/model/FieldMapper.java
New file
0,0 → 1,154
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
import org.openconcerto.utils.Log;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
 
/**
* Map ids to fields
*
* */
public class FieldMapper {
 
private final List<Class<?>> classes = new ArrayList<Class<?>>();
// sales.invoice.label <=> SAISIE_VENTE_FACTURE.NOM
private Map<String, String> itemMapping = new HashMap<String, String>();
private Map<String, String> tableMapping = new HashMap<String, String>();
private DBRoot root;
 
public FieldMapper(DBRoot root) {
this.root = root;
}
 
public synchronized void addMapperStreamFromClass(Class<?> c) {
this.classes.add(c);
loadMapping(c);
}
 
public void setTableMapping(String tableId, String tableName) {
tableMapping.put(tableId, tableName);
}
 
public SQLField getSQLFieldForItem(String id) {
final String fieldName = this.itemMapping.get(id);
if (fieldName == null) {
return null;
}
final SQLField field = this.root.getField(fieldName);
return field;
}
 
public SQLTable getSQLTableForItem(String id) {
final String tableName = this.tableMapping.get(id);
if (tableName == null) {
return null;
}
final SQLTable table = this.root.getTable(tableName);
return table;
}
 
public void setTranslationForItem(String id, String tableName, String fieldName) {
if (id == null)
throw new NullPointerException("null id");
if (tableName == null)
throw new NullPointerException("null tableName");
if (fieldName == null)
throw new NullPointerException("null fieldName");
this.itemMapping.put(id, tableName + "." + fieldName);
}
 
public void loadAllMapping() {
this.itemMapping.clear();
if (this.classes.size() == 0) {
Log.get().warning("FieldMapper has no resources to load for root " + this.root.getName());
}
for (Class<?> c : this.classes) {
loadMapping(c);
}
}
 
private InputStream findStream(final Class<?> c) {
final String baseName = c.getPackage().getName();
final String resourcePath = baseName.replace('.', '/') + "/fieldmapping.xml";
final InputStream ins = c.getClassLoader().getResourceAsStream(resourcePath);
if (ins == null) {
Log.get().warning("No ressource " + resourcePath + " found");
} else {
Log.get().info("Using ressource " + resourcePath);
}
return ins;
}
 
private void loadMapping(Class<?> c) {
final InputStream stream = findStream(c);
loadMapping(stream);
}
 
private void loadMapping(final InputStream input) {
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
 
try {
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
final Document doc = dBuilder.parse(input);
final NodeList tableChildren = doc.getElementsByTagName("table");
final int size = tableChildren.getLength();
for (int i = 0; i < size; i++) {
final Element elementTable = (Element) tableChildren.item(i);
final String tableId = elementTable.getAttributeNode("id").getValue();
final String tableName = elementTable.getAttributeNode("name").getValue();
this.tableMapping.put(tableId, tableName);
final NodeList fieldChildren = elementTable.getElementsByTagName("field");
final int size2 = fieldChildren.getLength();
for (int j = 0; j < size2; j++) {
final Element elementField = (Element) fieldChildren.item(j);
final String fieldId = elementField.getAttributeNode("id").getValue();
final String fieldName = elementField.getAttributeNode("name").getValue();
if (this.itemMapping.containsKey(fieldId)) {
throw new IllegalStateException("Duplicate mm translation entry for " + fieldId + " (" + fieldName + " - " + itemMapping.get(fieldId) + ")");
}
final String field = tableName + "." + fieldName;
this.itemMapping.put(fieldId, field);
// Add fied name to field mapping for compatibility
this.itemMapping.put(field, field);
}
}
 
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
try {
if (input != null) {
input.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/UndefinedRowValuesCache.java
13,6 → 13,8
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.request.MultipleSQLSelectExecutor;
import org.openconcerto.utils.ExceptionHandler;
 
35,6 → 37,10
 
private final Map<SQLTable, SQLRowValues> map = new HashMap<SQLTable, SQLRowValues>();
 
private final SQLElementDirectory getDirectory() {
return Configuration.getInstance().getDirectory();
}
 
public SQLRowValues getDefaultRowValues(final SQLTable t) {
SQLRowValues rv = this.map.get(t);
if (rv == null) {
42,7 → 48,7
final SQLRow undefRow = t.getRow(t.getUndefinedID());
if (undefRow == null)
throw new IllegalStateException(t.getSQLName() + " doesn't contain undef ID " + t.getUndefinedID());
rv.loadAllSafe(undefRow);
getDirectory().getElement(t).loadAllSafe(rv, undefRow);
this.map.put(t, rv);
}
return rv;
72,7 → 78,7
final List<SQLRow> rows = l.get(i);
if (rows.size() > 0) {
final SQLRowValues rv = new SQLRowValues(sqlTable);
rv.loadAllSafe(rows.get(0));
getDirectory().getElement(sqlTable).loadAllSafe(rv, rows.get(0));
this.map.put(sqlTable, rv);
} else {
System.err.println("Warning: no undefined row in table: " + sqlTable.getName() + " id: " + sqlTable.getUndefinedID());
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSystem.java
91,6 → 91,9
void removeRootsToIgnore(Set<String> s) {
super.removeRootsToIgnore(s);
s.remove("mysql");
s.remove("performance_schema");
// before 5.5.8
s.remove("PERFORMANCE_SCHEMA");
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTableEvent.java
30,19 → 30,36
 
public enum Mode {
/**
* A row was inserted in the db
* A row was inserted in the DB
*/
ROW_ADDED,
ROW_ADDED {
@Override
public Mode opposite() {
return ROW_DELETED;
}
},
/**
* A row was deleted in the db
* A row was deleted in the DB
*/
ROW_DELETED,
ROW_DELETED {
@Override
public Mode opposite() {
return ROW_ADDED;
}
},
/**
* Some columns of a row were updated
*/
ROW_UPDATED
ROW_UPDATED {
@Override
public Mode opposite() {
return this;
}
};
 
public abstract Mode opposite();
}
 
private final SQLTable table;
private final SQLRow row;
private final Mode mode;
96,6 → 113,10
}
}
 
final SQLTableEvent opposite() {
return new SQLTableEvent(this.table, this.row == null ? null : new SQLRow(this.table, this.row.getID()), this.mode.opposite(), this.fieldNames);
}
 
public final List<SQLField> getFields() {
return Collections.unmodifiableList(this.fields);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRequestLog.java
13,6 → 13,7
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Log;
import org.openconcerto.utils.ExceptionUtils;
 
import java.awt.BorderLayout;
24,6 → 25,7
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
30,6 → 32,7
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
 
import javax.swing.JButton;
import javax.swing.JFrame;
114,6 → 117,18
log(query, comment, 0, starAtMs, startTime, startTime, startTime, startTime, startTime, startTime);
}
 
public static void log(PreparedStatement pStmt, String comment, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
// only call potentially expensive and/or exceptions throwing methods if necessary
if (enabled) {
try {
log(pStmt.toString(), comment, pStmt.getConnection(), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime);
} catch (Exception e) {
// never propagate exceptions
Log.get().log(Level.WARNING, "Couldn't log " + pStmt, e);
}
}
}
 
public static void log(String query, String comment, Connection conn, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
log(query, comment, System.identityHashCode(conn), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/TransactionListener.java
New file
0,0 → 1,18
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
public interface TransactionListener {
void transactionEnded(TransactionPoint point);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMySQL.java
35,6 → 35,7
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.math.BigDecimal;
import java.sql.Blob;
203,17 → 204,17
if (!newNullable && newDef != null && newDef.trim().toUpperCase().equals("NULL"))
newDef = null;
 
return Collections.singletonList(SQLSelect.quote("MODIFY COLUMN %n " + getFieldDecl(newType, newDef, newNullable), f));
return Collections.singletonList("MODIFY COLUMN " + f.getQuotedName() + " " + getFieldDecl(newType, newDef, newNullable));
}
 
@Override
public String getDropRoot(String name) {
return SQLSelect.quote("DROP DATABASE IF EXISTS %i ;", name);
return "DROP DATABASE IF EXISTS " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
public String getCreateRoot(String name) {
return SQLSelect.quote("CREATE DATABASE %i ;", name);
return "CREATE DATABASE " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
230,12 → 231,13
// MySQL dumps strings in binary, so fields must be consistent otherwise the
// file is invalid
throw new IllegalArgumentException(t + " has more than on character set : " + charsets);
final SQLBase base = t.getBase();
// if no string cols there should only be values within ASCII (eg dates, ints, etc)
final String charset = charsets.size() == 0 ? "UTF8" : charsets.keySet().iterator().next();
final String cols = CollectionUtils.join(t.getOrderedFields(), ",", new ITransformer<SQLField, String>() {
@Override
public String transformChecked(SQLField input) {
return SQLBase.quoteStringStd(input.getName());
return base.quoteString(input.getName());
}
});
try {
242,20 → 244,31
final File tmp = File.createTempFile(SQLSyntaxMySQL.class.getSimpleName() + "storeData", ".txt");
// mysql cannot overwrite files
tmp.delete();
final SQLSelect sel = new SQLSelect(t.getBase(), true).addSelectStar(t);
final SQLSelect sel = new SQLSelect(true).addSelectStar(t);
// store the data in the temp file
t.getBase().getDataSource().execute(t.getBase().quote("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE %s " + getDATA_OPTIONS(t) + ";", tmp.getAbsolutePath()));
base.getDataSource().execute("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE " + base.quoteString(tmp.getAbsolutePath()) + " " + getDATA_OPTIONS(base) + ";");
// then read it to remove superfluous escape char and convert to utf8
final BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(tmp), charset));
final Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF8"));
normalizeData(r, w, 1000 * 1024);
r.close();
w.close();
tmp.delete();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
 
// remove superfluous escape character
static void normalizeData(final Reader r, final Writer w, final int bufferSize) throws IOException {
int count;
final char[] buf = new char[1000 * 1024];
final char[] buf = new char[bufferSize];
int offset = 0;
final char[] wbuf = new char[buf.length];
boolean wasBackslash = false;
while ((count = r.read(buf, offset, buf.length - offset)) != -1) {
int wbufLength = 0;
for (int i = 0; i < count; i++) {
for (int i = 0; i < offset + count; i++) {
final char c = buf[i];
// MySQL escapes the field delimiter (which other systems do as well)
// but also "LINES TERMINATED BY" which others don't understand
266,7 → 279,8
wbuf[wbufLength++] = c;
wasBackslash = c == '\\';
}
// the read buffer ends with a backslash
// the read buffer ends with a backslash, don't let it be written to w as we might
// want to remove it
if (wasBackslash) {
// restore state one char before
wbufLength--;
273,20 → 287,15
wasBackslash = wbuf[wbufLength - 1] == '\\';
buf[0] = '\\';
offset = 1;
} else
} else {
offset = 0;
}
w.write(wbuf, 0, wbufLength);
}
r.close();
w.close();
tmp.delete();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
 
private static String getDATA_OPTIONS(DBStructureItem<?> i) {
return i.getAnc(SQLBase.class).quote("FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY %s LINES TERMINATED BY '\n' ", "\\");
private static String getDATA_OPTIONS(final SQLBase b) {
return "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY " + b.quoteString("\\") + " LINES TERMINATED BY '\n' ";
}
 
@Override
310,7 → 319,7
throw new IllegalStateException("the database charset is not utf8 and this version doesn't support specifying another one : " + dbCharset);
}
}
ds.execute(t.getBase().quote("LOAD DATA LOCAL INFILE %s INTO TABLE %f " + charsetClause + getDATA_OPTIONS(t) + " IGNORE 1 LINES;", f.getAbsolutePath(), t));
ds.execute(t.getBase().quote("LOAD DATA LOCAL INFILE %s INTO TABLE %f ", f.getAbsolutePath(), t) + charsetClause + getDATA_OPTIONS(t.getBase()) + " IGNORE 1 LINES;");
return null;
}
});
333,7 → 342,6
return "NOT (" + nullSafe + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "DATE_FORMAT(" + sqlTS + ", " + SQLBase.quoteStringStd(basic ? "%Y%m%dT%H%i%s.%f" : "%Y-%m-%dT%H:%i:%s.%f") + ")";
450,7 → 458,7
 
@Override
public String getDropTrigger(Trigger t) {
return SQLBase.quoteStd("DROP TRIGGER %i", new SQLName(t.getTable().getSchema().getName(), t.getName()));
return "DROP TRIGGER " + new SQLName(t.getTable().getSchema().getName(), t.getName()).quote();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLInjector.java
13,54 → 13,140
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.graph.SQLKey;
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.AlterTable;
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.view.list.SQLTableModelSource;
import org.openconcerto.sql.view.list.SQLTableModelSourceOnline;
import org.openconcerto.utils.cc.ITransformer;
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
public class SQLInjector {
 
final private SQLTable tableSrc, tableDest;
final private ArrayList<SQLField> from = new ArrayList<SQLField>();
final private ArrayList<SQLField> to = new ArrayList<SQLField>();
private Map<SQLField, Object> values = new HashMap<SQLField, Object>();
private static Map<SQLTable, Map<SQLTable, SQLInjector>> injectors = new HashMap<SQLTable, Map<SQLTable, SQLInjector>>();
private final SQLTable tableSrc, tableDest;
private final ArrayList<SQLField> from = new ArrayList<SQLField>();
private final ArrayList<SQLField> to = new ArrayList<SQLField>();
private final Map<SQLField, Object> values = new HashMap<SQLField, Object>();
private final static Map<DBRoot, Map<SQLTable, Map<SQLTable, SQLInjector>>> allRegisteredInjectors = new HashMap<DBRoot, Map<SQLTable, Map<SQLTable, SQLInjector>>>();
 
public SQLInjector(final DBRoot r, final String src, final String dest) {
this(r.findTable(src), r.findTable(dest));
private boolean storeTransfer;
// maps of injectors that store transfer
private static Map<DBRoot, Map<SQLTable, Map<SQLTable, SQLInjector>>> injectors = new HashMap<DBRoot, Map<SQLTable, Map<SQLTable, SQLInjector>>>();
 
public SQLInjector(final DBRoot r, final String src, final String dest, boolean storeTransfer) {
this(r.findTable(src), r.findTable(dest), storeTransfer);
}
 
public SQLInjector(SQLTable src, SQLTable dest) {
public SQLInjector(SQLTable src, SQLTable dest, boolean storeTransfer) {
this.tableDest = dest;
this.tableSrc = src;
Map<SQLTable, SQLInjector> srcs = injectors.get(src);
this.storeTransfer = storeTransfer;
final DBRoot dbRoot = src.getDBRoot();
Map<SQLTable, Map<SQLTable, SQLInjector>> inj = allRegisteredInjectors.get(dbRoot);
if (inj == null) {
inj = new HashMap<SQLTable, Map<SQLTable, SQLInjector>>();
allRegisteredInjectors.put(dbRoot, inj);
}
Map<SQLTable, SQLInjector> srcs = inj.get(src);
if (srcs == null) {
srcs = new HashMap<SQLTable, SQLInjector>();
injectors.put(src, srcs);
inj.put(src, srcs);
}
srcs.put(dest, this);
 
if (storeTransfer) {
// Register only SQLInjector that store transfer
inj = injectors.get(dbRoot);
if (inj == null) {
inj = new HashMap<SQLTable, Map<SQLTable, SQLInjector>>();
injectors.put(dbRoot, inj);
}
srcs = inj.get(src);
if (srcs == null) {
srcs = new HashMap<SQLTable, SQLInjector>();
inj.put(src, srcs);
}
srcs.put(dest, this);
}
}
 
public SQLRowValues createRowValuesFrom(int idSrc) {
return createRowValuesFrom(getSource().getRow(idSrc));
public synchronized SQLRowValues createRowValuesFrom(int idSrc) {
final List<SQLRowAccessor> srcRows = new ArrayList<SQLRowAccessor>(1);
srcRows.add(new SQLImmutableRowValues(getSource().getRow(idSrc).asRowValues()));
return createRowValuesFrom(srcRows);
}
 
private static final SQLSystem dbSystem = Configuration.getInstance().getBase().getServer().getSQLSystem();
public synchronized SQLRowValues createRowValuesFrom(final SQLRow srcRow) {
final SQLRowValues rowVals = new SQLRowValues(getDestination());
if (!srcRow.getTable().equals(getSource()))
throw new IllegalArgumentException("Row not from source table : " + srcRow);
merge(srcRow, rowVals);
return rowVals;
}
 
public SQLRowValues createRowValuesFrom(final SQLRowAccessor srcRow) {
public synchronized SQLRowValues createRowValuesFrom(final List<? extends SQLRowAccessor> srcRows) {
final SQLRowValues rowVals = new SQLRowValues(getDestination());
for (SQLRowAccessor srcRow : srcRows) {
if (!srcRow.getTable().equals(getSource()))
throw new IllegalArgumentException("Row not from source table : " + srcRow);
SQLRowValues rowVals = new SQLRowValues(getDestination());
merge(srcRow, rowVals);
}
return rowVals;
}
 
public void commitTransfert(final List<? extends SQLRowAccessor> srcRows, int destId) throws SQLException {
 
if (storeTransfer) {
System.err.println("SQLInjector.commitTransfert() : transfert from " + this.getSource().getName() + " to " + this.getDestination().getName());
// Transfert
final SQLTable tableTransfert = getSource().getDBRoot().getTable(getTableTranferName());
if (tableTransfert == null) {
throw new IllegalStateException("No table transfer for " + getSource().getName());
}
 
for (SQLRowAccessor srcRow : srcRows) {
 
final SQLRowValues rowTransfer = new SQLRowValues(tableTransfert);
 
final Set<SQLField> foreignKeysSrc = tableTransfert.getForeignKeys(getSource());
final Set<SQLField> foreignKeysDest = tableTransfert.getForeignKeys(getDestination());
if (foreignKeysSrc.isEmpty()) {
throw new IllegalStateException("No foreign (src) to " + getSource().getName() + " in " + tableTransfert.getName());
}
if (foreignKeysDest.isEmpty()) {
throw new IllegalStateException("No foreign (dest) to " + getDestination().getName() + " in " + tableTransfert.getName());
}
rowTransfer.put(foreignKeysSrc.iterator().next().getName(), srcRow.getIDNumber());
rowTransfer.put(foreignKeysDest.iterator().next().getName(), destId);
// TODO: commit in one shot
rowTransfer.commit();
 
}
}
 
}
 
private String getTableTranferName() {
return "TR_" + getSource().getName();
}
 
protected void merge(SQLRowAccessor srcRow, SQLRowValues rowVals) {
for (SQLField field : this.values.keySet()) {
 
rowVals.put(field.getName(), this.values.get(field));
}
final SQLSystem dbSystem = srcRow.getTable().getDBSystemRoot().getServer().getSQLSystem();
final int size = getFrom().size();
for (int i = 0; i < size; i++) {
 
for (int i = 0; i < getFrom().size(); i++) {
 
final SQLField sqlFieldFrom = getFrom().get(i);
final SQLField sqlFieldTo = getTo().get(i);
final Object o = srcRow.getObject(sqlFieldFrom.getName());
67,18 → 153,19
 
// Probleme avec H2 Primary Key en Long et foreignKey en Int
if (dbSystem == SQLSystem.H2 && sqlFieldFrom.getType().getJavaType() == Long.class && sqlFieldTo.getType().getJavaType() == Integer.class) {
rowVals.put(sqlFieldTo.getName(), ((Long) o).intValue());
merge(sqlFieldTo, ((Long) o).intValue(), rowVals);
} else {
 
rowVals.put(sqlFieldTo.getName(), o);
merge(sqlFieldTo, o, rowVals);
}
}
}
 
return rowVals;
protected void merge(SQLField field, Object value, SQLRowValues rowVals) {
rowVals.put(field.getName(), value);
}
 
public SQLRow insertFrom(final SQLRowAccessor srcRow) throws SQLException {
return createRowValuesFrom(srcRow).insert();
public synchronized SQLRow insertFrom(final SQLRowAccessor srcRow) throws SQLException {
return createRowValuesFrom(Arrays.asList(srcRow)).insert();
}
 
// TODO gettable()..getName()..equalsIgnoreCase( by .getTable().equals(
88,7 → 175,7
* @param fieldDest
* @param defaultValue
*/
protected final void mapDefaultValues(SQLField fieldDest, Object defaultValue) {
protected synchronized final void mapDefaultValues(SQLField fieldDest, Object defaultValue) {
if (fieldDest.getTable().getName().equalsIgnoreCase(this.tableDest.getName())) {
this.values.put(fieldDest, defaultValue);
} else {
96,7 → 183,7
}
}
 
protected final void map(SQLField from, SQLField to) throws IllegalArgumentException {
protected synchronized final void map(SQLField from, SQLField to) throws IllegalArgumentException {
// Verification de la validité des SQLField
if (!from.getTable().getName().equalsIgnoreCase(this.tableSrc.getName())) {
throw new IllegalArgumentException("SQLField " + from + " is not a field of table " + this.tableSrc);
115,7 → 202,7
}
}
 
protected final void remove(SQLField from, SQLField to) throws IllegalArgumentException {
protected synchronized final void remove(SQLField from, SQLField to) throws IllegalArgumentException {
// Verification de la validité des SQLField
if (!from.getTable().getName().equalsIgnoreCase(this.tableSrc.getName())) {
throw new IllegalArgumentException("SQLField " + from + " is not a field of table " + this.tableSrc);
136,7 → 223,7
* Créer l'association entre les champs portant le nom dans les deux tables
*
*/
public void createDefaultMap() {
public synchronized void createDefaultMap() {
for (SQLField field : this.tableSrc.getContentFields()) {
 
if (this.tableDest.contains(field.getName())) {
145,24 → 232,14
}
}
 
public ArrayList<SQLField> getFrom() {
public synchronized ArrayList<SQLField> getFrom() {
return this.from;
}
 
public ArrayList<SQLField> getTo() {
public synchronized ArrayList<SQLField> getTo() {
return this.to;
}
 
public static SQLInjector getInjector(SQLTable src, SQLTable dest) {
 
Map<SQLTable, SQLInjector> m = injectors.get(src);
if (m != null) {
return m.get(dest);
} else {
return null;
}
}
 
/**
* Creer un SQLInjector par défaut si aucun n'est déja défini
*
170,22 → 247,155
* @param dest
* @return un SQLInjector par défaut si aucun n'est déja défini
*/
public static SQLInjector createDefaultInjector(SQLTable src, SQLTable dest) {
if (getInjector(src, dest) == null) {
public static synchronized SQLInjector getInjector(SQLTable src, SQLTable dest) {
SQLInjector injector = getRegistrereddInjector(src, dest);
if (injector == null) {
injector = createDefaultInjector(src, dest);
}
return injector;
}
 
public static synchronized SQLInjector getRegistrereddInjector(SQLTable src, SQLTable dest) {
final Map<SQLTable, Map<SQLTable, SQLInjector>> map = allRegisteredInjectors.get(src.getDBRoot());
if (map == null) {
return null;
}
Map<SQLTable, SQLInjector> m = map.get(src);
if (m != null) {
return m.get(dest);
}
return null;
}
 
private static synchronized SQLInjector createDefaultInjector(SQLTable src, SQLTable dest) {
System.err.println("No SQLInjector defined for " + src + " , " + dest + ". SQLInjector created automatically.");
SQLInjector injector = new SQLInjector(src, dest);
SQLInjector injector = new SQLInjector(src, dest, false);
injector.createDefaultMap();
return injector;
} else {
return getInjector(src, dest);
}
}
 
public SQLTable getDestination() {
public synchronized SQLTable getDestination() {
return this.tableDest;
}
 
public SQLTable getSource() {
public synchronized SQLTable getSource() {
return this.tableSrc;
}
 
public synchronized static void createTransferTables(DBRoot root) throws SQLException {
Map<SQLTable, Map<SQLTable, SQLInjector>> map = injectors.get(root);
if (root == null) {
System.err.println("No SQLInjector for root " + root);
return;
}
 
final Set<SQLTable> srcTables = map.keySet();
if (srcTables.isEmpty()) {
System.err.println("No SQLInjector for root " + root);
return;
}
 
final List<SQLCreateTable> createTablesQueries = new ArrayList<SQLCreateTable>();
// Create table if needed
for (SQLTable sqlTable : srcTables) {
final String trTableName = "TR_" + sqlTable.getName();
if (root.getTable(trTableName) == null) {
final SQLCreateTable createTable = new SQLCreateTable(root, trTableName);
createTable.setPlain(false);
// createTable.addColumn(SQLSyntax.ID_NAME,
// createTable.getSyntax().getPrimaryIDDefinition());
createTable.addForeignColumn(SQLKey.PREFIX + sqlTable.getName(), sqlTable);
createTablesQueries.add(createTable);
}
}
if (createTablesQueries.size() > 0) {
root.createTables(createTablesQueries);
}
 
// Create transfer fields if needed
final List<AlterTable> alterTablesQueries = new ArrayList<AlterTable>();
final TablesMap toRefresh = new TablesMap();
for (SQLTable srcTable : srcTables) {
final String trTableName = "TR_" + srcTable.getName();
final SQLTable transfertTable = root.getTable(trTableName);
final AlterTable alter = new AlterTable(transfertTable);
final Set<SQLTable> destTables = map.get(srcTable).keySet();
for (SQLTable destTable : destTables) {
final String fk = SQLKey.PREFIX + destTable.getName();
if (!transfertTable.contains(fk)) {
alter.addForeignColumn(fk, destTable);
}
}
if (!alter.isEmpty()) {
alterTablesQueries.add(alter);
toRefresh.add(alter.getRootName(), alter.getName());
}
}
for (final String q : ChangeTable.cat(alterTablesQueries)) {
root.getDBSystemRoot().getDataSource().execute(q);
}
root.getSchema().updateVersion();
root.getDBSystemRoot().refresh(toRefresh, false);
 
}
 
public void setOnlyTransfered(SQLTableModelSourceOnline tableSource) {
// needed for distinct
tableSource.getReq().setLockSelect(false);
 
tableSource.getReq().setSelectTransf(new ITransformer<SQLSelect, SQLSelect>() {
 
@Override
public SQLSelect transformChecked(SQLSelect input) {
 
final SQLTable tableTR = getSource().getTable(getTableTranferName());
// FIXME: preprocess TR_ .. content before join : group by id_src
final SQLSelectJoin j = input.addBackwardJoin("INNER", null, tableTR.getForeignKeys(getSource()).iterator().next(), null);
j.setWhere(new Where(tableTR.getForeignKeys(getDestination()).iterator().next(), "!=", getDestination().getUndefinedID()));
input.setDistinct(true);
 
System.err.println(input.asString());
return input;
}
});
}
 
public void setOnlyNotTransfered(SQLTableModelSourceOnline tableSource) {
tableSource.getReq().setSelectTransf(new ITransformer<SQLSelect, SQLSelect>() {
 
@Override
public SQLSelect transformChecked(SQLSelect input) {
final SQLTable tableTR = getSource().getTable(getTableTranferName());
 
final Where w = new Where(tableTR.getForeignKeys(getSource()).iterator().next(), "=", input.getAlias(getSource().getKey()));
input.addJoin("LEFT", tableTR, w);
final Where w2 = new Where(tableTR.getForeignKeys(getDestination()).iterator().next(), "IS", (Object) null);
input.setWhere(w2);
 
System.err.println(input.asString());
return input;
}
});
}
 
/**
* register manually a transfer, use with caution
*
* @throws SQLException
* */
public void addTransfert(int idFrom, int idTo) throws SQLException {
System.err.println("SQLInjector.addTransfert() " + idFrom + " -> " + idTo);
final SQLTable tableTransfert = getSource().getTable(getTableTranferName());
final SQLRowValues rowTransfer = new SQLRowValues(tableTransfert);
 
final Set<SQLField> foreignKeysSrc = tableTransfert.getForeignKeys(getSource());
final Set<SQLField> foreignKeysDest = tableTransfert.getForeignKeys(getDestination());
 
rowTransfer.put(foreignKeysSrc.iterator().next().getName(), idFrom);
rowTransfer.put(foreignKeysDest.iterator().next().getName(), idTo);
 
rowTransfer.commit();
 
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSelect.java
16,6 → 16,7
import org.openconcerto.sql.model.Order.Direction;
import org.openconcerto.sql.model.Order.Nulls;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.Step;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.ITransformer;
 
452,7 → 453,7
* @param s une collection de nom de champs, eg "NOM".
* @return this pour pouvoir chaîner.
*/
public SQLSelect addAllSelect(SQLTable t, Collection<String> s) {
public SQLSelect addAllSelect(TableRef t, Collection<String> s) {
for (final String fieldName : s) {
this.addSelect(t.getField(fieldName));
}
505,10 → 506,10
return this.addRawSelect(function + "(*)", null);
}
 
public SQLSelect addSelectStar(SQLTable table) {
this.select.add(SQLBase.quoteIdentifier(table.getName()) + ".*");
public SQLSelect addSelectStar(TableRef table) {
this.select.add(SQLBase.quoteIdentifier(table.getAlias()) + ".*");
this.from.add(this.declaredTables.add(table));
this.selectFields.addAll(table.getOrderedFields());
this.selectFields.addAll(table.getTable().getOrderedFields());
return this;
}
 
794,16 → 795,16
 
TableRef current = firstTableRef;
for (int i = 0; i < p.length(); i++) {
final Set<SQLField> step = p.getStepFields(i);
final Step step = p.getStep(i);
// |BATIMENT.ID_SITE|
final SQLField ff = step.getSingleField();
// TODO handle multi-link:
// JOIN test.article art ON obs.ID_ARTICLE_1 = art.ID or obs.ID_ARTICLE_2 = art.ID
if (step.size() != 1)
if (ff == null)
throw new IllegalArgumentException(p + " has more than 1 link at index " + i);
// |BATIMENT.ID_SITE|
final SQLField ff = step.iterator().next();
final SQLSelectJoin j;
// are we currently at the start of the foreign field or at the destination
final boolean forward = current.getTable() == ff.getTable();
final boolean forward = step.getDirection() == org.openconcerto.sql.model.graph.Link.Direction.FOREIGN;
if (forward) {
// bat.ID_SITE
j = this.getJoin(current.getField(ff.getName()));
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxH2.java
92,7 → 92,7
}
 
protected String setNullable(SQLField f, boolean b) {
return SQLSelect.quote("ALTER COLUMN %n SET " + (b ? "" : "NOT") + " NULL", f);
return "ALTER COLUMN " + f.getQuotedName() + " SET " + (b ? "" : "NOT") + " NULL";
}
 
@Override
102,7 → 102,7
// MAYBE implement AlterTableAlterColumn.CHANGE_ONLY_TYPE
final String newDef = toAlter.contains(Properties.DEFAULT) ? defaultVal : getDefault(f, type);
final boolean newNullable = toAlter.contains(Properties.NULLABLE) ? nullable : getNullable(f);
res.add(SQLSelect.quote("ALTER COLUMN %n " + getFieldDecl(type, newDef, newNullable), f));
res.add("ALTER COLUMN " + f.getQuotedName() + " " + getFieldDecl(type, newDef, newNullable));
} else {
if (toAlter.contains(Properties.DEFAULT))
res.add(this.setDefault(f, defaultVal));
116,12 → 116,12
 
@Override
public String getDropRoot(String name) {
return SQLSelect.quote("DROP SCHEMA IF EXISTS %i ;", name);
return "DROP SCHEMA IF EXISTS " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
public String getCreateRoot(String name) {
return SQLSelect.quote("CREATE SCHEMA %i ;", name);
return "CREATE SCHEMA " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
144,14 → 144,16
@Override
public void _loadData(final File f, final SQLTable t) {
checkServerLocalhost(t);
t.getDBSystemRoot().getDataSource().execute(SQLSelect.quote("insert into %f select * from CSVREAD(%s, NULL, 'UTF8', ',', '\"', '\\', '\\N') ;", t, f.getAbsolutePath()));
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
t.getDBSystemRoot().getDataSource().execute("insert into " + t.getSQLName().quote() + " select * from CSVREAD(" + quotedPath + ", NULL, 'UTF8', ',', '\"', '\\', '\\N') ;");
}
 
@Override
protected void _storeData(final SQLTable t, final File f) {
checkServerLocalhost(t);
final SQLSelect sel = SQLSyntaxPG.selectAll(t);
t.getBase().getDataSource().execute(SQLSelect.quote("CALL CSVWRITE(%s, %s, 'UTF8', ',', '\"', '\\', '\\N', '\n');", f.getAbsolutePath(), sel.asString()));
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
final String quotedSel = t.getBase().quoteString(SQLSyntaxPG.selectAll(t).asString());
t.getBase().getDataSource().execute("CALL CSVWRITE(" + quotedPath + ", " + quotedSel + ", 'UTF8', ',', '\"', '\\', '\\N', '\n');");
}
 
@Override
256,6 → 258,6
 
@Override
public String getDropTrigger(Trigger t) {
return SQLBase.quoteStd("DROP TRIGGER %i", new SQLName(t.getTable().getSchema().getName(), t.getName()));
return "DROP TRIGGER " + new SQLName(t.getTable().getSchema().getName(), t.getName()).quote();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesListFetcher.java
31,6 → 31,7
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
205,7 → 206,8
private final Path descendantPath;
private ITransformer<SQLSelect, SQLSelect> selTransf;
private Integer selID;
private boolean ordered;
private Set<Path> ordered;
private boolean descendantsOrdered;
private SQLRowValues minGraph;
private boolean includeForeignUndef;
private SQLSelect frozen;
307,7 → 309,8
 
this.selTransf = null;
this.selID = null;
this.ordered = false;
this.ordered = Collections.<Path> emptySet();
this.descendantsOrdered = false;
this.minGraph = null;
this.includeForeignUndef = false;
this.frozen = null;
422,15 → 425,57
* @return this.
*/
public final SQLRowValuesListFetcher setOrdered(final boolean b) {
this.setOrder(b ? Collections.singleton(new Path(getGraph().getTable())) : Collections.<Path> emptySet(), true);
this.setReferentsOrdered(b, false);
return this;
}
 
public final SQLRowValuesListFetcher setOrder(final List<Path> order) {
return this.setOrder(order, false);
}
 
private final SQLRowValuesListFetcher setOrder(final Collection<Path> order, final boolean safeVal) {
this.checkFrozen();
this.ordered = b;
for (final Path p : order)
if (this.getGraph().followPath(p) == null)
throw new IllegalArgumentException("Path not in this " + p);
this.ordered = safeVal ? (Set<Path>) order : Collections.unmodifiableSet(new LinkedHashSet<Path>(order));
return this;
}
 
public final boolean isOrdered() {
public final Set<Path> getOrder() {
return this.ordered;
}
 
/**
* Whether to order referent rows in this fetcher.
*
* @param b <code>true</code> to order referent rows starting from the primary node, e.g. if the
* graph is
*
* <pre>
* *SITE* <- BATIMENT <- LOCAL
* </pre>
*
* then this will cause ORDER BY BATIMENT.ORDRE, LOCAL.ORDRE.
* @param rec if grafts should also be changed.
* @return this.
*/
public final SQLRowValuesListFetcher setReferentsOrdered(final boolean b, final boolean rec) {
this.descendantsOrdered = b;
if (rec) {
for (final Map<Path, SQLRowValuesListFetcher> m : this.grafts.values()) {
for (final SQLRowValuesListFetcher f : m.values())
f.setReferentsOrdered(b, rec);
}
}
return this;
}
 
public final boolean areReferentsOrdered() {
return this.descendantsOrdered;
}
 
public final SQLRowValuesListFetcher graft(final SQLRowValuesListFetcher other) {
return this.graft(other, new Path(getGraph().getTable()));
}
525,7 → 570,7
return this.frozen;
 
final SQLTable t = this.getGraph().getTable();
final SQLSelect sel = new SQLSelect(t.getBase());
final SQLSelect sel = new SQLSelect();
 
if (this.includeForeignUndef) {
sel.setExcludeUndefined(false);
532,8 → 577,6
sel.setExcludeUndefined(true, t);
}
 
if (this.isOrdered() && t.isOrdered())
sel.addFieldOrder(t.getOrderField());
walk(sel, new ITransformer<State<SQLSelect>, SQLSelect>() {
@Override
public SQLSelect transformChecked(State<SQLSelect> input) {
545,10 → 588,6
if (input.isBackwards()) {
// eg LEFT JOIN loc on loc.ID_BATIMENT = BATIMENT.ID
input.getAcc().addBackwardJoin(joinType, alias, input.getFrom(), aliasPrev);
// order is only meaningfull for backwards
// as going forward there's at most 1 row for each row in t
if (isOrdered())
input.getAcc().addOrderSilent(alias);
} else {
input.getAcc().addJoin(joinType, new AliasedField(input.getFrom(), aliasPrev), alias);
}
561,6 → 600,16
}
 
});
for (final Path p : this.getOrder())
sel.addOrder(sel.followPath(t.getName(), p), false);
// after getOrder() since it can specify more precise order
if (this.areReferentsOrdered()) {
final int descSize = this.descendantPath.length();
for (int i = 1; i <= descSize; i++) {
sel.addOrder(sel.followPath(t.getName(), this.descendantPath.subPath(0, i)), false);
}
}
 
if (this.selID != null)
sel.andWhere(new Where(t.getKey(), "=", this.selID));
return this.getSelTransf() == null ? sel : this.getSelTransf().transformChecked(sel);
632,7 → 681,7
}
 
public final boolean isBackwards() {
return !this.from.isForeign(this.from.getSingleField());
return !this.from.isForeign();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMS.java
136,7 → 136,7
}
 
protected String setNullable(SQLField f, boolean b) {
return SQLSelect.quote("ALTER COLUMN %n SET " + (b ? "" : "NOT") + " NULL", f);
return "ALTER COLUMN " + f.getQuotedName() + " SET " + (b ? "" : "NOT") + " NULL";
}
 
// FIXME
147,7 → 147,7
// MAYBE implement AlterTableAlterColumn.CHANGE_ONLY_TYPE
final String newDef = toAlter.contains(Properties.DEFAULT) ? defaultVal : getDefault(f, type);
final boolean newNullable = toAlter.contains(Properties.NULLABLE) ? nullable : getNullable(f);
res.add(SQLSelect.quote("ALTER COLUMN %n " + getFieldDecl(type, newDef, newNullable), f));
res.add("ALTER COLUMN " + f.getQuotedName() + " " + getFieldDecl(type, newDef, newNullable));
} else {
if (toAlter.contains(Properties.NULLABLE))
res.add(this.setNullable(f, nullable));
161,12 → 161,12
public String getDropRoot(String name) {
// FIXME
// http://ranjithk.com/2010/01/31/script-to-drop-all-objects-of-a-schema/
return SQLSelect.quote("exec CleanUpSchema %s, 'w' ;", name);
return "exec CleanUpSchema " + SQLBase.quoteStringStd(name) + ", 'w' ;";
}
 
@Override
public String getCreateRoot(String name) {
return SQLSelect.quote("CREATE SCHEMA %i ;", name);
return "CREATE SCHEMA " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
289,7 → 289,7
 
@Override
public String getDropTrigger(Trigger t) {
return SQLBase.quoteStd("DROP TRIGGER %i", new SQLName(t.getTable().getSchema().getName(), t.getName()));
return "DROP TRIGGER " + new SQLName(t.getTable().getSchema().getName(), t.getName()).quote();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/StructureSource.java
280,12 → 280,14
*/
public final void fillTables() throws E {
assert Thread.holdsLock(this.getBase().getDBSystemRoot().getTreeMutex());
// externalStruct was created before this, so it must be applied first
// (e.g. it might have an old schema version, while we have one up to date)
mutateTo(this.externalStruct);
if (!this.isPreVerify())
this.fillTablesP();
else {
mutateTo(this.newStructure);
}
mutateTo(this.externalStruct);
}
 
private final void mutateTo(final Map<String, SQLSchema> m) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/HandlersStack.java
13,22 → 13,36
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.request.SQLCache;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.LinkedList;
import java.util.List;
 
class HandlersStack {
private final SQLDataSource ds;
private Connection conn;
private final LinkedList<ConnectionHandler<?, ?>> stack;
private boolean changeAllowed;
// list of transaction points, i.e. first a transaction start and then any number of save
// points. The list is thus empty in auto-commit mode.
private final LinkedList<TransactionPoint> txPoints;
// the cache for each point, items can be null if no cache should be used
private final LinkedList<SQLCache<List<?>, Object>> caches;
 
HandlersStack(final Connection conn, final ConnectionHandler<?, ?> handler) {
HandlersStack(final SQLDataSource ds, final Connection conn, final ConnectionHandler<?, ?> handler) {
super();
if (conn == null)
throw new NullPointerException("null connection");
this.ds = ds;
this.changeAllowed = false;
this.conn = conn;
this.stack = new LinkedList<ConnectionHandler<?, ?>>();
this.push(handler);
this.txPoints = new LinkedList<TransactionPoint>();
this.caches = new LinkedList<SQLCache<List<?>, Object>>();
}
 
public final Connection getConnection() throws IllegalStateException {
56,6 → 70,105
return this.stack.isEmpty();
}
 
final void addTxPoint(TransactionPoint txPoint) {
if (txPoint.getConn() != this.conn)
throw new IllegalArgumentException("Different connections");
// the first point should be a transaction start
assert this.stack.size() > 0 || txPoint.getSavePoint() == null;
this.addCache();
this.txPoints.add(txPoint);
}
 
private void addCache() {
final SQLCache<List<?>, Object> previous = this.getCache();
final SQLCache<List<?>, Object> current = this.ds.createCache(this);
this.caches.add(current);
if (current != null) {
if (previous != null) {
current.setParent(previous);
} else {
// MAYBE if transaction start, set parent to DS cache for READ_COMMITTED, copy of DS
// for REPEATABLE_READ and SERIALIZABLE
}
}
}
 
private final void removeLastCache() {
// throws NoSuchElementException, i.e. if last is null it's because the item was null
final SQLCache<List<?>, Object> last = this.caches.removeLast();
if (last != null)
last.clear();
}
 
void updateCache() {
final int size = this.txPoints.size();
// cache only needed for transactions
if (size > 0) {
clearCache();
for (int i = 0; i < size - 1; i++) {
this.caches.add(null);
}
this.addCache();
}
assert size == this.caches.size();
}
 
private final void clearCache() {
while (!this.caches.isEmpty()) {
removeLastCache();
}
}
 
final TransactionPoint getLastTxPoint() {
return this.txPoints.peekLast();
}
 
final SQLCache<List<?>, Object> getCache() {
return this.caches.peekLast();
}
 
private final TransactionPoint removeFirstTxPoint() {
return this.txPoints.pollFirst();
}
 
private final TransactionPoint removeLastTxPoint() {
return this.txPoints.pollLast();
}
 
void commit() throws SQLException {
TransactionPoint txPoint = this.removeFirstTxPoint();
while (txPoint != null) {
txPoint.fire(true);
txPoint = this.removeFirstTxPoint();
}
// don't bother trying to merge the cache
clearCache();
}
 
void rollback() throws SQLException {
TransactionPoint txPoint = this.removeLastTxPoint();
while (txPoint != null) {
txPoint.fire(false);
txPoint = this.removeLastTxPoint();
}
// discard the cache
clearCache();
}
 
void rollback(Savepoint savepoint) throws SQLException {
TransactionPoint txPoint = this.removeLastTxPoint();
while (txPoint.getSavePoint() != savepoint) {
txPoint.fire(false);
// discard the cache
removeLastCache();
txPoint = this.removeLastTxPoint();
}
txPoint.fire(false);
// discard the cache
removeLastCache();
assert this.txPoints.size() == this.caches.size();
}
 
public final boolean isChangeAllowed() {
return this.changeAllowed;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLBase.java
41,8 → 41,8
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
565,7 → 565,7
*/
String getFwkMetadata(String schema, String name, final boolean shouldTestForTable) {
final SQLName tableName = new SQLName(this.getName(), schema, SQLSchema.METADATA_TABLENAME);
final String sel = SQLSelect.quote("SELECT \"VALUE\" FROM " + tableName.quote() + " WHERE \"NAME\"= " + this.quoteString(name));
final String sel = "SELECT \"VALUE\" FROM " + tableName.quote() + " WHERE \"NAME\"= " + this.quoteString(name);
// In postgreSQL once there's an error the transaction is aborted and further queries throw
// an exception. In H2 and MySQL, the transaction is *not* aborted.
final SQLSystem system = getServer().getSQLSystem();
750,6 → 750,8
return quote(this, pattern, params);
}
 
// since Strings might not be quoted correctly
@Deprecated
public final static String quoteStd(final String pattern, Object... params) {
return quote(null, pattern, params);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/TransactionPoint.java
New file
0,0 → 1,159
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
import org.openconcerto.utils.CompareUtils;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.ArrayList;
import java.util.List;
 
import net.jcip.annotations.ThreadSafe;
 
/**
* The start of a transaction, or a {@link Savepoint}.
*
* @author Sylvain
* @see #addListener(TransactionListener)
*/
@ThreadSafe
public class TransactionPoint {
 
private Boolean committed;
private final Connection conn;
private final Savepoint savepoint;
private final boolean namedSavePoint;
private final String savePointID;
 
private final List<TransactionListener> listeners;
 
public TransactionPoint(Connection conn) throws SQLException {
this(conn, null, false);
}
 
public TransactionPoint(Connection conn, Savepoint savepoint, boolean namedSavePoint) throws SQLException {
super();
this.committed = null;
if (conn == null)
throw new NullPointerException("Null connection");
this.conn = conn;
this.savepoint = savepoint;
this.namedSavePoint = namedSavePoint;
if (savepoint == null)
this.savePointID = null;
else if (namedSavePoint)
this.savePointID = savepoint.getSavepointName();
else
this.savePointID = String.valueOf(savepoint.getSavepointId());
this.listeners = new ArrayList<TransactionListener>();
}
 
/**
* How this transaction point ended.
*
* @return <code>null</code> if it's ongoing, <code>true</code> if it was committed,
* <code>false</code> if aborted.
*/
public synchronized final Boolean getCommitted() {
return this.committed;
}
 
final Connection getConn() {
return this.conn;
}
 
/**
* The save point.
*
* @return the save point, <code>null</code> for the start of a transaction.
*/
public final Savepoint getSavePoint() {
return this.savepoint;
}
 
public final boolean isNamedSavePoint() {
return this.namedSavePoint;
}
 
/**
* The name or ID of the save point.
*
* @return <code>null</code> if {@link #getSavePoint()} is, {@link Savepoint#getSavepointName()}
* if {@link #isNamedSavePoint()} else {@link Savepoint#getSavepointId()}.
*/
protected final String getSavePointID() {
return this.savePointID;
}
 
/**
* To be notified when this point is either committed or aborted.
*
* @param l a listener.
*/
public synchronized final void addListener(TransactionListener l) {
checkActive();
this.listeners.add(l);
}
 
private synchronized void checkActive() {
if (this.committed != null)
throw new IllegalStateException("Transaction point inactive");
}
 
/**
* Remove listener, *not* to be called in the callback. Indeed, since a transaction point is
* either committed or aborted once and only once, the listeners are cleared automatically.
*
* @param l a listener.
*/
public synchronized final void removeListener(TransactionListener l) {
checkActive();
this.listeners.remove(l);
}
 
final void fire(boolean committed) {
final List<TransactionListener> ls;
synchronized (this) {
this.committed = Boolean.valueOf(committed);
ls = this.listeners;
}
for (final TransactionListener l : ls) {
l.transactionEnded(this);
}
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.conn == null) ? 0 : this.conn.hashCode());
result = prime * result + (this.namedSavePoint ? 1231 : 1237);
result = prime * result + ((this.savePointID == null) ? 0 : this.savePointID.hashCode());
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final TransactionPoint other = (TransactionPoint) obj;
return this.conn == other.conn && this.namedSavePoint == other.namedSavePoint && CompareUtils.equals(this.savePointID, other.savePointID);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLIdentifier.java
37,6 → 37,10
 
// ** names
 
public final String getQuotedName() {
return SQLBase.quoteIdentifier(this.getName());
}
 
/**
* The complete name from the root.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/OrderComparator.java
20,7 → 20,7
*
* @author Sylvain
*/
public class OrderComparator implements Comparator<SQLRow> {
public class OrderComparator implements Comparator<SQLRowAccessor> {
 
public static final OrderComparator INSTANCE = new OrderComparator();
 
29,7 → 29,7
}
 
@Override
public int compare(SQLRow r1, SQLRow r2) {
public int compare(SQLRowAccessor r1, SQLRowAccessor r2) {
// handle two nulls (but the next line throws an exception if only one is null)
// MAYBE NULLS FIRST/LAST
if (r1 == r2)
/trunk/OpenConcerto/src/org/openconcerto/sql/model/JDBCStructureSource.java
175,12 → 175,7
final DatabaseMetaData metaData = conn.getMetaData();
// getColumns() only supports pattern (eg LIKE) so we must make multiple calls
for (final String s : newSchemas.keySet()) {
final Set<String> tablesToRefresh = newSchemas.get(s);
assert tablesToRefresh != null : "Null should have been resolved in getNames()";
if (tablesToRefresh.isEmpty())
continue;
final SQLSchema schema = getNewSchema(s);
final ResultSet rs = metaData.getColumns(this.getBase().getMDName(), s, getTablePattern(IncludeExclude.getNormalized(tablesToRefresh)), null);
 
// always fetch version to record in tables since we might decide to use cache later
String schemaVers = SQLSchema.getVersion(getBase(), s);
194,9 → 189,19
if (this.getTablesToRefresh(s) == null)
schema.setFullyRefreshedVersion(schemaVers);
 
// ATTN don't continue the for till we called setFullyRefreshedVersion() : if we have an
// out-of-date schema, with all its tables up-to-date, and the database contains no new
// tables, tablesToRefresh will be empty. But we have to record that the schema is in
// fact up-to-date.
final Set<String> tablesToRefresh = newSchemas.get(s);
assert tablesToRefresh != null : "Null should have been resolved in getNames()";
if (tablesToRefresh.isEmpty())
continue;
 
// handle tables becoming empty (possible in pg)
final Set<SQLName> tablesWithColumns = new HashSet<SQLName>();
 
final ResultSet rs = metaData.getColumns(this.getBase().getMDName(), s, getTablePattern(IncludeExclude.getNormalized(tablesToRefresh)), null);
boolean hasNext = rs.next();
while (hasNext) {
final String schemaName = rs.getString("TABLE_SCHEM");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFilter.java
84,6 → 84,10
this.listeners = Collections.emptyList();
}
 
public final SQLElementDirectory getDirectory() {
return this.dir;
}
 
/**
* The path from the passed table to the filtered row.
*
177,7 → 181,7
connectedSet = this.filterGraph.getAllTables();
else {
// getFieldRaw since it can be inexistant
final String parentForeignField = this.dir.getElement(table).getParentForeignField();
final String parentForeignField = this.getDirectory().getElement(table).getParentForeignField();
// 5x faster than getElement(table).getDescendantTables()
connectedSet = this.filterGraph.getDescTables(table, table.getFieldRaw(parentForeignField));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValues.java
13,8 → 13,6
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
35,6 → 33,7
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IdentitySet;
import org.openconcerto.utils.cc.LinkedIdentitySet;
import org.openconcerto.utils.cc.TransformedMap;
import org.openconcerto.utils.convertor.NumberConvertor;
146,6 → 145,8
return checkValidity;
}
 
private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
 
private final Map<String, Object> values;
private final Map<String, SQLRowValues> foreigns;
private final CollectionMap<SQLField, SQLRowValues> referents;
507,7 → 508,7
 
private Object getContainedObject(String fieldName) throws IllegalArgumentException {
if (!this.values.containsKey(fieldName))
throw new IllegalArgumentException("Field not present in this : " + this.getFields());
throw new IllegalArgumentException("Field " + fieldName + " not present in this : " + this.getFields());
return this.values.get(fieldName);
}
 
970,8 → 971,9
/**
* Return the row at the end of passed path.
*
* @param p the path to follow, eg SITE,SITE.ID_CONTACT_CHEF.
* @return the row at the end or <code>null</code> if none exists, eg SQLRowValues on /CONTACT/.
* @param p the path to follow, e.g. SITE,SITE.ID_CONTACT_CHEF.
* @return the row at the end or <code>null</code> if none exists, e.g. SQLRowValues on
* /CONTACT/.
*/
public final SQLRowValues followPath(final Path p) {
return this.followPath(p, false);
978,7 → 980,21
}
 
private final SQLRowValues followPath(final Path p, final boolean create) {
final Collection<SQLRowValues> res = followPath(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, true);
return followPathToOne(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, DEFAULT_ALLOW_BACKTRACK);
}
 
/**
* Follow path to at most one row.
*
* @param p the path to follow.
* @param create if and how to create new rows.
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
* @return the destination row or <code>null</code> if none exists and <code>create</code> was
* {@link CreateMode#CREATE_NONE}
* @see #followPath(Path, CreateMode, boolean, boolean)
*/
public final SQLRowValues followPathToOne(final Path p, final CreateMode create, final boolean allowBackTrack) {
final Collection<SQLRowValues> res = this.followPath(p, create, true, allowBackTrack);
// since we passed onlyOne=true
assert res.size() <= 1;
return CollectionUtils.getSole(res);
996,6 → 1012,26
}
 
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne) {
return followPath(p, create, onlyOne, DEFAULT_ALLOW_BACKTRACK);
}
 
/**
* Follow path through the graph.
*
* @param p the path to follow.
* @param create if and how to create new rows.
* @param onlyOne <code>true</code> if this method should return at most one row.
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
* @return the destination rows, can be empty.
* @throws IllegalArgumentException if <code>p</code> doesn't start with this table.
* @throws IllegalStateException if <code>onlyOne</code> and there's more than one row on the
* path.
*/
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final boolean allowBackTrack) throws IllegalArgumentException, IllegalStateException {
return followPath(p, create, onlyOne, allowBackTrack ? null : new LinkedIdentitySet<SQLRowValues>());
}
 
private final IdentitySet<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final IdentitySet<SQLRowValues> beenThere) {
if (p.getFirst() != this.getTable())
throw new IllegalArgumentException("path " + p + " doesn't start with us " + this);
if (p.length() > 0) {
1010,11 → 1046,12
// _____-- rapport -> CONTACT
final Set<SQLRowValues> next = new LinkedIdentitySet<SQLRowValues>();
final Step firstStep = p.getStep(0);
final Set<SQLField> ffs = firstStep.getFields();
for (final SQLField ff : ffs) {
if (firstStep.isForeign(ff)) {
final Set<Link> ffs = firstStep.getLinks();
for (final Link l : ffs) {
final SQLField ff = l.getLabel();
if (firstStep.isForeign(l)) {
final Object fkValue = this.getObject(ff.getName());
if (fkValue instanceof SQLRowValues) {
if (fkValue instanceof SQLRowValues && (beenThere == null || !beenThere.contains(fkValue))) {
next.add((SQLRowValues) fkValue);
} else if (create == CreateMode.CREATE_ONE || create == CreateMode.CREATE_MANY) {
assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() <= 1);
1032,8 → 1069,15
}
} else {
final Set<SQLRowValues> referentRows = this.getReferentRows(ff);
if (referentRows.size() > 0) {
next.addAll(referentRows);
final Set<SQLRowValues> validReferentRows;
if (beenThere == null || beenThere.size() == 0) {
validReferentRows = referentRows;
} else {
validReferentRows = new LinkedIdentitySet<SQLRowValues>(referentRows);
validReferentRows.removeAll(beenThere);
}
if (validReferentRows.size() > 0) {
next.addAll(validReferentRows);
} else if (create == CreateMode.CREATE_ONE || create == CreateMode.CREATE_MANY) {
assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() <= 1);
final SQLRowValues nextOne;
1051,13 → 1095,21
throw new IllegalStateException("more than one row and onlyOne=true : " + next);
 
// see comment above for IdentitySet
final Set<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
final IdentitySet<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
for (final SQLRowValues n : next) {
res.addAll(n.followPath(p.minusFirst(), create, onlyOne));
final IdentitySet<SQLRowValues> newBeenThere;
if (beenThere == null) {
newBeenThere = null;
} else {
newBeenThere = new LinkedIdentitySet<SQLRowValues>(beenThere);
final boolean added = newBeenThere.add(this);
assert added;
}
res.addAll(n.followPath(p.minusFirst(), create, onlyOne, newBeenThere));
}
return res;
} else {
return Collections.singleton(this);
return CollectionUtils.createIdentitySet(this);
}
}
 
1111,21 → 1163,6
this.putAll(m);
}
 
/**
* Load all values that can be safely copied (shared by multiple rows). This means all values
* except private, primary, order and archive.
*
* @param row the row to be loaded.
*/
public void loadAllSafe(SQLRow row) {
this.setAll(row.getAllValues());
final SQLElement elem = Configuration.getInstance().getDirectory().getElement(this.getTable());
this.load(row, elem.getNormalForeignFields());
if (elem.getParentForeignField() != null)
this.put(elem.getParentForeignField(), row.getObject(elem.getParentForeignField()));
this.load(row, elem.getSharedForeignFields());
}
 
// *** modify
 
void checkValidity() {
1267,7 → 1304,12
@Override
public List<String> handle(SQLDataSource ds) throws SQLException {
final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
final long timeMs = System.currentTimeMillis();
final long time = System.nanoTime();
pStmt.get0().executeUpdate();
final long afterExecute = System.nanoTime();
// logging after closing fails to get the connection info
SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
pStmt.get0().close();
return pStmt.get1();
}
1584,7 → 1626,12
* @throws SQLException if the insertion fails.
*/
static public final Number insert(final PreparedStatement pStmt, final SQLTable table) throws SQLException {
final long timeMs = System.currentTimeMillis();
 
final long time = System.nanoTime();
pStmt.execute();
final long afterExecute = System.nanoTime();
 
final Number newID;
if (table.isRowable()) {
final ResultSet rs;
1605,6 → 1652,9
} else {
newID = null;
}
final long afterHandle = System.nanoTime();
SQLRequestLog.log(pStmt, "rowValues.insert()", timeMs, time, afterExecute, afterExecute, afterExecute, afterHandle, System.nanoTime());
 
return newID;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowAccessor.java
220,6 → 220,10
return (foreignElement == null) ? null : foreignElement.getModelObject(this);
}
 
public final BigDecimal getOrder() {
return (BigDecimal) this.getObject(this.getTable().getOrderField().getName());
}
 
public final Calendar getCreationDate() {
final SQLField f = getTable().getCreationDateField();
return f == null ? null : this.getDate(f.getName());
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/DatabaseGraph.java
24,6 → 24,7
import org.openconcerto.sql.model.DBItemFileCache;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.FieldRef;
import org.openconcerto.sql.model.LoadingListener.GraphLoadingEvent;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLDataSource;
900,49 → 901,106
return this.getWhereClause(t1, t2, null);
}
 
public Where getWhereClause(final Step step) {
return this.getWhereClause(step.getFrom(), step.getTo(), step);
}
 
/**
* Renvoie la clause WHERE pour faire la jointure en t1 et t2.
*
* @param t1 la premiere table.
* @param t2 la deuxieme table.
* @param fields les champs à utiliser, <code>null</code> pour tous.
* @param fields les liens à utiliser, <code>null</code> pour tous.
* @return le OR des champs passés.
*/
public Where getWhereClause(TableRef t1, TableRef t2, Set<SQLField> fields) {
// OR car OBSsERVATION.ID_ARTICLE_1,2,3
public Where getWhereClause(TableRef t1, TableRef t2, Step fields) {
// OR car OBSERVATION.ID_ARTICLE_1,2,3
return Where.or(this.getStraightWhereClause(t1, t2, fields));
}
 
// MAYBE allow to pass self reference links in both directions with a SetMap<Direction, Link>,
// e.g. find both next and previous mission
 
/**
* Renvoie les clauses WHERE pour faire la jointure en t1 et t2.
*
* @param t1 la premiere table.
* @param t2 la deuxieme table.
* @param fields les champs à utiliser, <code>null</code> pour tous.
* @param step les liens à utiliser, <code>null</code> pour tous.
* @return les WHERE demandés.
*/
public Set<Where> getStraightWhereClause(TableRef t1, TableRef t2, Set<SQLField> fields) {
final Set<Where> res = new HashSet<Where>();
 
for (final Link l : this.getLinks(t1.getTable(), t2.getTable())) {
final SQLField f = l.getLabel();
if (fields == null || fields != null && fields.contains(f)) {
final SQLTable target = l.getTarget();
assert target == t1.getTable() || target == t2.getTable();
final Where w;
if (target == t1.getTable()) {
w = new Where(t2.getField(f.getName()), "=", t1.getKey());
public Set<Where> getStraightWhereClause(TableRef t1, TableRef t2, Step step) {
if (step == null) {
step = Step.create(t1.getTable(), t2.getTable());
} else {
w = new Where(t1.getField(f.getName()), "=", t2.getKey());
if (t1 == null)
t1 = step.getFrom();
else if (step.getFrom() != t1.getTable())
throw new IllegalArgumentException("step isn't from t1 " + step);
if (t2 == null)
t2 = step.getTo();
else if (step.getTo() != t2.getTable())
throw new IllegalArgumentException("step isn'ts to t2 " + step);
}
res.add(w);
 
final Set<Where> res = new HashSet<Where>();
for (final Link l : step.getLinks()) {
final Direction dir = step.getDirection(l);
res.add(getWhereClause(t1, t2, l, dir));
}
}
 
return res;
}
 
public Where getWhereClause(final Link l) {
return getWhereClause(l.getSource(), l.getTarget(), l, Direction.FOREIGN);
}
 
/**
* The where clause to join t1 to t2.
*
* @param t1 the first table, can be <code>null</code>, e.g. <code>null</code>.
* @param t2 the second table, can be <code>null</code>, e.g. "LOCAL loc".
* @param l a link between <code>t1</code> and <code>t2</code>, e.g. LOCAL.ID_BATIMENT.
* @param dir how to go through <code>l</code>, either {@link Direction#FOREIGN} or
* {@link Direction#REFERENT}, e.g. <code>REFERENT</code>.
* @return the WHERE, e.g. loc.ID_BATIMENT = BATIMENT.ID.
*/
public Where getWhereClause(final TableRef t1, final TableRef t2, final Link l, final Direction dir) {
if (l == null)
throw new NullPointerException("Null link");
TableRef src, dest;
if (dir == Direction.FOREIGN) {
src = t1;
dest = t2;
} else if (dir == Direction.REFERENT) {
src = t2;
dest = t1;
} else {
throw new IllegalArgumentException("Invalid direction : " + dir);
}
if (src == null)
src = l.getSource();
else if (src.getTable() != l.getSource())
throw new IllegalArgumentException("Wrong source table " + src.getTable() + " != " + l.getSource());
if (dest == null)
dest = l.getTarget();
else if (dest.getTable() != l.getTarget())
throw new IllegalArgumentException("Wrong target table " + dest.getTable() + " != " + l.getTarget());
final Iterator<SQLField> primaryKeys = dest.getTable().getPrimaryKeys().iterator();
Where w = null;
for (final SQLField f : l.getFields()) {
assert f.getTable() == src.getTable();
final FieldRef f1 = src.getField(f.getName());
final FieldRef f2 = dest.getField(primaryKeys.next().getName());
w = new Where(f1, "=", f2).and(w);
}
assert w != null : "Empty fields for " + l;
assert !primaryKeys.hasNext() : "Mismatch";
return w;
}
 
/**
* Renvoie la clause where pour faire la jointure suivant ce champ.
*
* @param f un champ, eg RAPPORT_GENERE.ID_MISSION.
956,11 → 1014,8
 
public Where getJointure(Path p) {
Where res = null;
for (int i = 1; i <= p.length(); i++) {
SQLTable previous = p.getTable(i - 1);
SQLTable table = p.getTable(i);
Where wc = this.getWhereClause(previous, table, p.getStepFields(i - 1));
res = wc.and(res);
for (int i = 0; i < p.length(); i++) {
res = this.getWhereClause(p.getStep(i)).and(res);
}
return res;
}
971,7 → 1026,7
} else if (p.length() == 1) {
final SQLTable previous = p.getTable(0);
final SQLTable table = p.getTable(1);
return this.getStraightWhereClause(previous, table, p.getStepFields(0));
return this.getStraightWhereClause(previous, table, p.getStep(0));
} else {
final Set<Where> res = new HashSet<Where>();
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/Step.java
17,6 → 17,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Tuple2.List2;
 
import java.util.AbstractMap;
import java.util.Collection;
27,6 → 28,7
import java.util.Map.Entry;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
/**
53,68 → 55,114
final Link l = fField.getDBSystemRoot().getGraph().getForeignLink(fField);
if (l == null)
throw new IllegalArgumentException(fField + " is not a foreign field.");
return create(start, l, direction);
}
 
public static final Step create(final SQLTable start, final Link l, final Direction direction) throws IllegalArgumentException {
if (l == null)
throw new NullPointerException("null link");
// throws an exception if l is not connected to start
final SQLTable end = l.oppositeVertex(start);
final SQLTable fieldStart = fField.getTable();
 
final Direction computedDirection;
if (start == end)
computedDirection = Direction.ANY;
else
computedDirection = Direction.fromForeign(fieldStart == start);
computedDirection = Direction.fromForeign(l.getSource() == start);
 
if (computedDirection == Direction.ANY && direction == Direction.ANY)
throw new IllegalArgumentException("the field references its table: " + fField + ", you must specify the direction");
throw new IllegalArgumentException("self reference : " + l + ", you must specify the direction");
if (direction != Direction.ANY && computedDirection != Direction.ANY && direction != computedDirection)
throw new IllegalArgumentException("wrong direction: " + direction + ", real is : " + computedDirection);
final Direction nonNullDir = direction == Direction.ANY ? computedDirection : direction;
assert nonNullDir != Direction.ANY;
 
return new Step(start, fField, nonNullDir, end);
return new Step(start, l, nonNullDir, end);
}
 
public static final Step create(final SQLTable start, final SQLTable end) {
final Set<SQLField> set = start.getDBSystemRoot().getGraph().getFields(start, end);
if (set.isEmpty())
throw new IllegalArgumentException("path is broken between " + start + " and " + end);
return create(start, set, end);
return create(start, end, Direction.ANY);
}
 
// no-check : fields must be between start and end
private static final Step create(final SQLTable start, final Set<SQLField> jFields, final SQLTable end) {
if (start == end)
throw new IllegalArgumentException("start and end are the same: " + start + " the direction can't be inferred");
final Map<SQLField, Direction> fields = new HashMap<SQLField, Direction>(jFields.size());
for (final SQLField f : jFields)
fields.put(f, Direction.fromForeign(start == f.getTable()));
return new Step(start, fields, CollectionUtils.getSole(fields.keySet()), end);
public static final Step create(final SQLTable start, final SQLTable end, final Direction dir) {
final Set<Link> set;
if (dir == Direction.ANY) {
set = start.getDBSystemRoot().getGraph().getLinks(start, end);
} else if (dir == Direction.FOREIGN) {
set = start.getDBSystemRoot().getGraph().getForeignLinks(start, end);
} else if (dir == Direction.REFERENT) {
set = start.getDBSystemRoot().getGraph().getForeignLinks(end, start);
} else {
throw new IllegalStateException("Unknown direction " + dir);
}
if (set.isEmpty())
throw new IllegalArgumentException("path is broken between " + start + " and " + end + " in direction " + dir);
if (dir == Direction.ANY) {
return create(start, set);
} else {
final Map<Link, Direction> links = new HashMap<Link, Direction>(set.size());
for (final Link l : set)
links.put(l, dir);
return new Step(start, links, end);
}
}
 
public static final Step create(final SQLTable start, final Collection<Link> links) {
if (links.size() == 0)
throw new IllegalArgumentException("empty fields");
final SQLTable end = links.iterator().next().oppositeVertex(start);
final Set<SQLField> set = new HashSet<SQLField>();
if (start == end)
throw new IllegalArgumentException("start and end are the same: " + links + " the direction can't be inferred");
final Map<Link, Direction> directions = new HashMap<Link, Direction>();
for (final Link l : links) {
if (end != l.oppositeVertex(start))
throw new IllegalArgumentException("fields do not point to the same table: " + links);
set.add(l.getLabel());
directions.put(l, Direction.fromForeign(start == l.getSource()));
}
 
return create(start, set, end);
return new Step(start, directions, end);
}
 
private static final List2<SQLTable> getStartEnd(final Link l, final Direction dir) {
if (dir == Direction.ANY)
throw new IllegalArgumentException("Unspecified direction");
if (dir == Direction.FOREIGN)
return new List2<SQLTable>(l.getSource(), l.getTarget());
else
return new List2<SQLTable>(l.getTarget(), l.getSource());
}
 
public static final Step create(final Map<Link, Direction> links) {
List2<SQLTable> startEnd = null;
for (final Entry<Link, Direction> e : links.entrySet()) {
final List2<SQLTable> currentStartEnd = getStartEnd(e.getKey(), e.getValue());
if (startEnd == null) {
startEnd = currentStartEnd;
} else if (!startEnd.equals(currentStartEnd)) {
throw new IllegalArgumentException("Start and end tables differ " + startEnd + " != " + currentStartEnd);
}
}
if (startEnd == null)
throw new IllegalArgumentException("empty links");
return new Step(startEnd.get0(), links, startEnd.get1());
}
 
private final SQLTable from;
private final SQLTable to;
private final Map<SQLField, Direction> fields;
private final Map<Link, Direction> fields;
// after profiling: doing getStep().iterator().next() costs a lot
// if there's only one link and it has a single field
private final SQLField singleField;
// if all links have single fields
@GuardedBy("this")
private Set<SQLField> singleFields;
@GuardedBy("this")
private boolean singleFieldsComputed;
 
// all constructors are private since they don't fully check the coherence of their parameters
private Step(final SQLTable start, final Map<SQLField, Direction> fields, final SQLField singleField, final SQLTable end) {
private Step(final SQLTable start, final Map<Link, Direction> fields, final SQLField singleField, final SQLTable end) {
assert start != null && end != null;
assert fields.size() > 0;
assert CollectionUtils.getSole(fields.keySet()) == singleField;
assert !new HashSet<Direction>(fields.values()).contains(Direction.ANY) : "some directions are unknown : " + fields;
// thread-safe since only mutable attributes are volatile
assert fields instanceof AbstractMap : "Fields might not be thread-safe";
122,14 → 170,21
this.to = end;
this.fields = Collections.unmodifiableMap(fields);
this.singleField = singleField;
if (singleField == null) {
this.singleFields = null;
this.singleFieldsComputed = false;
} else {
this.singleFields = Collections.singleton(singleField);
this.singleFieldsComputed = true;
}
}
 
private Step(final SQLTable start, final Map<SQLField, Direction> fields, final SQLTable end) {
this(start, new HashMap<SQLField, Direction>(fields), CollectionUtils.getSole(fields.keySet()), end);
private Step(final SQLTable start, final Map<Link, Direction> fields, final SQLTable end) {
this(start, new HashMap<Link, Direction>(fields), fields.size() == 1 ? CollectionUtils.getSole(fields.keySet()).getSingleField() : null, end);
}
 
private Step(final SQLTable start, SQLField field, final Direction foreign, final SQLTable end) {
this(start, Collections.singletonMap(field, foreign), field, end);
private Step(final SQLTable start, Link field, final Direction foreign, final SQLTable end) {
this(start, Collections.singletonMap(field, foreign), field.getSingleField(), end);
}
 
public Step(Step p) {
137,12 → 192,18
}
 
public final Step reverse() {
final Map<SQLField, Direction> reverseFields = new HashMap<SQLField, Direction>(this.fields.size());
for (final Entry<SQLField, Direction> e : this.fields.entrySet()) {
final Map<Link, Direction> reverseFields = new HashMap<Link, Direction>(this.fields.size());
for (final Entry<Link, Direction> e : this.fields.entrySet()) {
reverseFields.put(e.getKey(), e.getValue().reverse());
}
return new Step(this.to, reverseFields, this.from);
final Step res = new Step(this.to, reverseFields, this.singleField, this.from);
// no need to synchronize on res, it isn't yet published
synchronized (this) {
res.singleFields = this.singleFields;
res.singleFieldsComputed = this.singleFieldsComputed;
}
return res;
}
 
public final SQLTable getFrom() {
return this.from;
152,10 → 213,18
return this.to;
}
 
public final Set<SQLField> getFields() {
public final Set<Link> getLinks() {
return this.fields.keySet();
}
 
public synchronized final Set<SQLField> getFields() {
if (!this.singleFieldsComputed) {
this.singleFields = Link.getSingleFields(this.fields.keySet());
this.singleFieldsComputed = true;
}
return this.singleFields;
}
 
public final SQLField getSingleField() {
return this.singleField;
}
164,7 → 233,7
if (this.singleField != null)
return Collections.singleton(this);
final Set<Step> res = new HashSet<Step>(this.fields.size());
for (final Entry<SQLField, Direction> e : this.fields.entrySet()) {
for (final Entry<Link, Direction> e : this.fields.entrySet()) {
res.add(new Step(this.getFrom(), e.getKey(), e.getValue(), this.getTo()));
}
return res;
171,32 → 240,32
}
 
/**
* Whether this step goes through the field <code>f</code> forwards or backwards.
* Whether this step goes through the link <code>f</code> forwards or backwards.
*
* @param f the field.
* @param f the link.
* @return <code>true</code> if f is crossed forwards (e.g. going from SITE to CONTACT with
* ID_CONTACT_CHEF and ID_CONTACT_BUREAU).
*/
public final boolean isForeign(final SQLField f) {
public final boolean isForeign(final Link f) {
return this.getDirection(f) == Direction.FOREIGN;
}
 
/**
* Whether this step goes through the field <code>f</code> forwards or backwards.
* Whether this step goes through the link <code>f</code> forwards or backwards.
*
* @param f the field.
* @param f the link.
* @return <code>FOREIGN</code> if f is crossed forwards (e.g. going from SITE to CONTACT with
* ID_CONTACT_CHEF and ID_CONTACT_BUREAU), <code>REFERENT</code> otherwise.
*/
public final Direction getDirection(final SQLField f) {
public final Direction getDirection(final Link f) {
return this.fields.get(f);
}
 
/**
* Whether this step goes through all of its fields forwards or backwards.
* Whether this step goes through all of its links forwards or backwards.
*
* @return <code>true</code> if all fields are forwards, <code>null</code> if mixed.
* @see #isForeign(SQLField)
* @return <code>true</code> if all links are forwards, <code>null</code> if mixed.
* @see #getDirection()
*/
public final Boolean isForeign() {
final Direction soleDir = getDirection();
204,11 → 273,11
}
 
/**
* Whether this step goes through all of its fields forwards or backwards.
* Whether this step goes through all of its links forwards or backwards.
*
* @return <code>FOREIGN</code> or <code>REFERENT</code> if all fields go the same way,
* @return <code>FOREIGN</code> or <code>REFERENT</code> if all links go the same way,
* <code>ANY</code> if mixed.
* @see #isForeign(SQLField)
* @see #getDirection(Link)
*/
public final Direction getDirection() {
final Direction soleDir = CollectionUtils.getSole(new HashSet<Direction>(this.fields.values()));
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/Path.java
153,9 → 153,13
}
 
public Path minusLast() {
return this.subPath(0, this.length() - 1);
return this.minusLast(1);
}
 
public Path minusLast(final int count) {
return this.subPath(0, this.length() - count);
}
 
public Path subPath(int fromIndex) {
return this.subPath(fromIndex, this.length());
}
243,9 → 247,13
* itself and the current end of this path.
*/
public final Path add(final SQLTable destTable) {
return this.add(Step.create(getLast(), destTable));
return this.add(destTable, Direction.ANY);
}
 
public final Path add(final SQLTable destTable, final Direction dir) {
return this.add(Step.create(getLast(), destTable, dir));
}
 
/**
* Add a table at the end of the path. NOTE: the step will be composed of all the foreign fields
* between {@link #getLast()} and <code>tableName</code>.
411,7 → 419,7
}
 
public Path add(Link l, Direction direction) {
return this.add(l.getLabel(), direction);
return this.add(Step.create(getLast(), l, direction));
}
 
public final List<Step> getSteps() {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/Link.java
30,7 → 30,9
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
import net.jcip.annotations.ThreadSafe;
 
142,6 → 144,18
}
}
 
static final Set<SQLField> getSingleFields(final Set<Link> links) {
final Set<SQLField> res = new HashSet<SQLField>(links.size());
for (final Link l : links) {
final SQLField singleField = l.getSingleField();
if (singleField != null)
res.add(singleField);
else
return null;
}
return res;
}
 
// ArrayList is thread-safe if not modified
private final List<SQLField> cols;
private final List<String> colsNames;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxPG.java
169,7 → 169,7
}
 
protected String setNullable(SQLField f, boolean b) {
return SQLSelect.quote("ALTER COLUMN %n " + (b ? "DROP" : "SET") + " NOT NULL", f);
return "ALTER COLUMN " + f.getQuotedName() + " " + (b ? "DROP" : "SET") + " NOT NULL";
}
 
@Override
180,7 → 180,7
final String newType;
if (toAlter.contains(Properties.TYPE)) {
newType = type;
res.add(SQLSelect.quote("ALTER COLUMN %n TYPE " + newType, f));
res.add("ALTER COLUMN " + f.getQuotedName() + " TYPE " + newType);
} else
newType = getType(f);
if (toAlter.contains(Properties.DEFAULT))
190,12 → 190,12
 
@Override
public String getDropRoot(String name) {
return SQLSelect.quote("DROP SCHEMA IF EXISTS %i CASCADE ;", name);
return "DROP SCHEMA IF EXISTS " + SQLBase.quoteIdentifier(name) + " CASCADE ;";
}
 
@Override
public String getCreateRoot(String name) {
return SQLSelect.quote("CREATE SCHEMA %i ;", name);
return "CREATE SCHEMA " + SQLBase.quoteIdentifier(name) + " ;";
}
 
@Override
484,7 → 484,7
 
@Override
public String getDropTrigger(Trigger t) {
return SQLBase.quoteStd("DROP TRIGGER %i on %f", t.getName(), t.getTable());
return "DROP TRIGGER " + SQLBase.quoteIdentifier(t.getName()) + " on " + t.getTable().getSQLName().quote();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBSystemRoot.java
19,6 → 19,7
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.SQLCreateMoveableTable;
import org.openconcerto.sql.utils.SQLCreateRoot;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.IClosure;
 
340,7 → 341,7
// don't fire GraphLoadingEvent here since we might be in an atomicRefresh
this.getGraph().refresh(tablesRefreshed, readCache);
} catch (SQLException e) {
throw new IllegalStateException("Couldn't refresh the graph");
throw new IllegalStateException("Couldn't refresh the graph", e);
}
// the dataSource must always have all tables, to listen to them for its cache
if (tableListChange)
470,6 → 471,18
this.addRoots(roots, true, true);
}
 
public final DBRoot addRoot(final String root) throws SQLException {
return this.addRoot(root, true);
}
 
/**
* Add the passed root to the roots to map then refresh.
*
* @param root the roots name to add (must exist).
* @param readCache <code>false</code> to use JDBC.
* @return the newly added root, not <code>null</code>.
* @throws SQLException if problem while reloading.
*/
public final DBRoot addRoot(final String root, final boolean readCache) throws SQLException {
this.addRoots(Collections.singletonList(root), readCache);
return this.getRoot(root);
487,6 → 500,20
}
 
/**
* Create a root, add it to roots to map and refresh.
*
* @param rootName the root to create (mustn't exist).
* @return the new root.
* @throws SQLException if problem while reloading.
* @see #addRoot(String)
*/
public final DBRoot createRoot(final String rootName) throws SQLException {
for (final String s : new SQLCreateRoot(SQLSyntax.get(this), rootName).asList(rootName, false, true))
getDataSource().execute(s);
return this.addRoot(rootName);
}
 
/**
* Whether this root's structure is cached.
*
* @param rootName the name of the root.
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntax.java
227,7 → 227,7
final String onUpdate = updateRule == null ? "" : " ON UPDATE " + getRuleSQL(updateRule);
final String onDelete = deleteRule == null ? "" : " ON DELETE " + getRuleSQL(deleteRule);
// a prefix for the constraint name, since in psql constraints are db wide not table wide
return SQLSelect.quote("CONSTRAINT %i FOREIGN KEY ( " + quoteIdentifiers(fk) + " ) REFERENCES %i ", constraintPrefix + join(fk, "__") + "_fkey", refTable) + "( "
return "CONSTRAINT " + SQLBase.quoteIdentifier(constraintPrefix + join(fk, "__") + "_fkey") + " FOREIGN KEY ( " + quoteIdentifiers(fk) + " ) REFERENCES " + refTable.quote() + " ( "
// don't put ON DELETE CASCADE since it's dangerous, plus MS SQL only supports 1 fk with
// cascade : http://support.microsoft.com/kb/321843/en-us
+ quoteIdentifiers(referencedFields) + " )" + onUpdate + onDelete;
625,9 → 625,9
*/
protected final String setDefault(SQLField field, String defaut) {
if (defaut == null)
return SQLSelect.quote("ALTER %n DROP DEFAULT", field);
return "ALTER " + field.getQuotedName() + " DROP DEFAULT";
else
return SQLSelect.quote("ALTER COLUMN %n SET DEFAULT " + defaut, field);
return "ALTER COLUMN " + field.getQuotedName() + " SET DEFAULT " + defaut;
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTable.java
27,6 → 27,7
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Tuple3;
import org.openconcerto.utils.cc.CopyOnWriteMap;
49,6 → 50,7
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
110,7 → 112,10
return res;
}
});
undefT.addTableModifiedListener(new SQLTableModifiedListener() {
// be called early, since it's more likely that some transaction will create table,
// set its undefined ID, then use it in requests, than some other transaction
// needing the undefined ID. TODO The real fix is one tree per transaction.
undefT.addTableModifiedListener(new ListenerAndConfig(new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
synchronized (UNDEFINED_IDs) {
118,7 → 123,7
undefT.removeTableModifiedListener(this);
}
}
});
}, false));
} else {
r = Collections.emptyMap();
}
222,6 → 227,55
}
}
 
static private boolean AFTER_TX_DEFAULT = true;
 
static public void setDefaultAfterTransaction(final boolean val) {
AFTER_TX_DEFAULT = val;
}
 
static public final class ListenerAndConfig {
 
private final SQLTableModifiedListener l;
private final boolean afterTx;
 
public ListenerAndConfig(SQLTableModifiedListener l, boolean afterTx) {
super();
if (l == null)
throw new NullPointerException("Null listener");
this.l = l;
this.afterTx = afterTx;
}
 
public final SQLTableModifiedListener getListener() {
return this.l;
}
 
public final boolean callOnlyAfterTx() {
return this.afterTx;
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (this.afterTx ? 1231 : 1237);
result = prime * result + this.l.hashCode();
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ListenerAndConfig other = (ListenerAndConfig) obj;
return this.afterTx == other.afterTx && this.l.equals(other.l);
}
}
 
@GuardedBy("this")
private String version;
private final CopyOnWriteMap<String, SQLField> fields;
243,7 → 297,10
// always immutable so that fire can iterate safely ; to modify it, simply copy it before
// (adding listeners is a lot less common than firing events)
@GuardedBy("listenersMutex")
private List<SQLTableModifiedListener> tableModifiedListeners;
private List<ListenerAndConfig> tableModifiedListeners;
@GuardedBy("listenersMutex")
private final ListMap<TransactionPoint, FireState> transactions;
private final TransactionListener txListener;
private final Object listenersMutex = new String("tableModifiedListeners mutex");
// the id that foreign keys pointing to this, can use instead of NULL
// a null value meaning not yet known
259,6 → 316,13
SQLTable(SQLSchema schema, String name) {
super(schema, name);
this.tableModifiedListeners = Collections.emptyList();
this.transactions = new ListMap<TransactionPoint, FireState>();
this.txListener = new TransactionListener() {
@Override
public void transactionEnded(TransactionPoint point) {
fireFromTransaction(point, point.getCommitted());
}
};
// needed for getOrderedFields()
this.fields = new CopyOnWriteMap<String, SQLField>() {
@Override
633,6 → 697,10
return this.getSchema().getBase();
}
 
synchronized final String getVersion() {
return this.version;
}
 
/**
* Return the primary key of this table.
*
1082,16 → 1150,20
*/
 
public void addTableModifiedListener(SQLTableModifiedListener l) {
this.addTableModifiedListener(new ListenerAndConfig(l, AFTER_TX_DEFAULT));
}
 
public void addTableModifiedListener(ListenerAndConfig l) {
this.addTableModifiedListener(l, false);
}
 
public void addPremierTableModifiedListener(SQLTableModifiedListener l) {
public void addPremierTableModifiedListener(ListenerAndConfig l) {
this.addTableModifiedListener(l, true);
}
 
private void addTableModifiedListener(SQLTableModifiedListener l, final boolean before) {
private void addTableModifiedListener(ListenerAndConfig l, final boolean before) {
synchronized (this.listenersMutex) {
final List<SQLTableModifiedListener> newListeners = new ArrayList<SQLTableModifiedListener>(this.tableModifiedListeners.size() + 1);
final List<ListenerAndConfig> newListeners = new ArrayList<ListenerAndConfig>(this.tableModifiedListeners.size() + 1);
if (before)
newListeners.add(l);
newListeners.addAll(this.tableModifiedListeners);
1102,8 → 1174,12
}
 
public void removeTableModifiedListener(SQLTableModifiedListener l) {
this.removeTableModifiedListener(new ListenerAndConfig(l, AFTER_TX_DEFAULT));
}
 
public void removeTableModifiedListener(ListenerAndConfig l) {
synchronized (this.listenersMutex) {
final List<SQLTableModifiedListener> newListeners = new ArrayList<SQLTableModifiedListener>(this.tableModifiedListeners);
final List<ListenerAndConfig> newListeners = new ArrayList<ListenerAndConfig>(this.tableModifiedListeners);
if (newListeners.remove(l))
this.tableModifiedListeners = Collections.unmodifiableList(newListeners);
}
1184,20 → 1260,42
this.fireTableModified(evt);
}
 
static private final ThreadLocal<LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>> events = new ThreadLocal<LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>>() {
// the listeners and the event that was notified to them
static private class FireState extends Tuple2<List<ListenerAndConfig>, SQLTableEvent> {
public FireState(final List<ListenerAndConfig> listeners, final SQLTableEvent evt) {
super(listeners, evt);
}
 
private DispatchingState createDispatchingState(final Boolean callbackAfterTxListeners, final boolean oppositeEvt) {
final List<SQLTableModifiedListener> listeners = new LinkedList<SQLTableModifiedListener>();
for (final ListenerAndConfig l : get0()) {
if (callbackAfterTxListeners == null || callbackAfterTxListeners == l.callOnlyAfterTx())
listeners.add(l.getListener());
}
return new DispatchingState(listeners, oppositeEvt ? get1().opposite() : get1());
}
}
 
static private class DispatchingState extends Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent> {
public DispatchingState(final List<SQLTableModifiedListener> listeners, final SQLTableEvent evt) {
super(listeners.iterator(), evt);
}
}
 
static private final ThreadLocal<LinkedList<DispatchingState>> events = new ThreadLocal<LinkedList<DispatchingState>>() {
@Override
protected LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>> initialValue() {
return new LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>();
protected LinkedList<DispatchingState> initialValue() {
return new LinkedList<DispatchingState>();
}
};
 
// allow to maintain the dispatching of events in order when a listener itself fires an event
static private void fireTableModified(Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent> newTuple) {
final LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>> linkedList = events.get();
static private void fireTableModified(DispatchingState newTuple) {
final LinkedList<DispatchingState> linkedList = events.get();
// add new event
linkedList.addLast(newTuple);
// process all pending events
Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent> currentTuple;
DispatchingState currentTuple;
while ((currentTuple = linkedList.peekFirst()) != null) {
final Iterator<SQLTableModifiedListener> iter = currentTuple.get0();
final SQLTableEvent currentEvt = currentTuple.get1();
1211,13 → 1309,38
}
 
private void fireTableModified(final SQLTableEvent evt) {
final FireState fireState;
final TransactionPoint point = this.getDBSystemRoot().getDataSource().getTransactionPoint();
final Boolean callbackAfterTxListeners;
synchronized (this.listenersMutex) {
// no need to copy since this.tableModifiedListeners is immutable
final List<SQLTableModifiedListener> dispatchingListeners;
fireState = new FireState(this.tableModifiedListeners, evt);
if (point == null) {
// call back every listener
callbackAfterTxListeners = null;
} else {
if (!this.transactions.containsKey(point))
point.addListener(this.txListener);
this.transactions.add(point, fireState);
callbackAfterTxListeners = false;
}
}
fireTableModified(fireState.createDispatchingState(callbackAfterTxListeners, false));
}
 
// a transaction was committed or aborted, we must either notify listeners that wanted the
// transaction to commit, or re-notify the listeners that didn't want to wait
protected void fireFromTransaction(final TransactionPoint point, final boolean committed) {
final List<FireState> states;
synchronized (this.listenersMutex) {
dispatchingListeners = this.tableModifiedListeners;
states = this.transactions.remove(point);
}
fireTableModified(Tuple2.create(dispatchingListeners.iterator(), evt));
final ListIterator<FireState> iter = CollectionUtils.getListIterator(states, !committed);
while (iter.hasNext()) {
final FireState state = iter.next();
fireTableModified(state.createDispatchingState(committed, !committed));
}
}
 
public synchronized String toXML() {
final StringBuilder sb = new StringBuilder(16000);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLDataSource.java
27,6 → 27,7
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collections;
45,12 → 46,15
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
 
import javax.sql.DataSource;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.apache.commons.dbcp.AbandonedConfig;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingConnection;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.dbcp.SQLNestedException;
import org.apache.commons.dbutils.BasicRowProcessor;
61,6 → 65,9
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
171,6 → 178,9
private String initialShema;
// which Connection have the right default schema
@GuardedBy("this")
private final Map<Connection, Object> schemaUptodate;
// which Connection aren't invalidated
@GuardedBy("this")
private final Map<Connection, Object> uptodate;
 
private volatile int retryWait;
179,6 → 189,13
@GuardedBy("this")
private long softMinEvictableIdleTimeMillis;
 
@GuardedBy("this")
private int txIsolation;
@GuardedBy("this")
private Integer dbTxIsolation;
@GuardedBy("this")
private boolean checkOnceDBTxIsolation;
 
private final ReentrantLock testLock = new ReentrantLock();
 
public SQLDataSource(SQLServer server, String base, String login, String pass) {
201,6 → 218,10
 
if (this.server.getSQLSystem() == SQLSystem.MYSQL) {
this.addConnectionProperty("transformedBitIsBoolean", "true");
// by default allowMultiQueries, since it's more convenient (i.e. pass String around
// instead of List<String>) and faster (less trips to the server, allow
// SQLUtils.executeMultiple())
this.addConnectionProperty("allowMultiQueries", "true");
} else if (this.server.getSQLSystem() == SQLSystem.H2) {
this.addConnectionProperty("CACHE_SIZE", "32000");
}
246,10 → 267,21
private synchronized void updateCache() {
if (this.cache != null)
this.cache.clear();
if (this.cacheEnabled && this.tables.size() > 0)
this.cache = new SQLCache<List<?>, Object>(30, 30, "results of " + this.getClass().getSimpleName());
this.cache = createCache(this);
for (final HandlersStack s : this.handlers.values()) {
s.updateCache();
}
}
 
synchronized SQLCache<List<?>, Object> createCache(final Object o) {
final SQLCache<List<?>, Object> res;
if (this.isCacheEnabled() && this.tables.size() > 0)
// the general cache should wait for transactions to end, but the cache of transactions
// must not.
res = new SQLCache<List<?>, Object>(30, 30, "results of " + o.getClass().getSimpleName(), o == this);
else
this.cache = null;
res = null;
return res;
}
 
/**
266,6 → 298,10
}
}
 
public final synchronized boolean isCacheEnabled() {
return this.cacheEnabled;
}
 
/* pour le clonage */
private SQLDataSource(SQLServer server) {
this.server = server;
273,6 → 309,7
this.handlers = new Hashtable<Thread, HandlersStack>();
// weak, since this is only a hint to avoid initializing the connection
// on each borrowal
this.schemaUptodate = new WeakHashMap<Connection, Object>();
this.uptodate = new WeakHashMap<Connection, Object>();
this.initialShemaSet = false;
this.initialShema = null;
297,6 → 334,14
// immediately afterwards to ensure minIdle connections)
this.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(30));
 
// the default of many systems
this.txIsolation = Connection.TRANSACTION_READ_COMMITTED;
// by definition unknown without a connection
this.dbTxIsolation = null;
// it's rare that DB configuration changes, and it's expensive to add a trip to the server
// for each new connection
this.checkOnceDBTxIsolation = true;
 
// see #createDataSource() for properties not supported by this class
this.tables = Collections.emptySet();
this.cache = null;
431,6 → 476,11
final IResultSetHandler irsh = rsh instanceof IResultSetHandler ? (IResultSetHandler) rsh : null;
final SQLCache<List<?>, Object> cache;
synchronized (this) {
// transactions are isolated from one another, so their caches should be too
final HandlersStack handlersStack = getHandlersStack();
if (handlersStack != null && handlersStack.getCache() != null)
cache = handlersStack.getCache();
else
cache = this.cache;
}
final List<Object> key = cache != null && query.startsWith("SELECT") ? Arrays.asList(new Object[] { query, rsh }) : null;
482,8 → 532,6
if (key != null) {
synchronized (this) {
putInCache(cache, irsh, key, result, true);
if (this.cache != cache)
putInCache(this.cache, irsh, key, result, false);
}
}
info.releaseConnection();
690,7 → 738,7
public final <T, X extends Exception> T useConnection(ConnectionHandler<T, X> handler) throws SQLException, X {
final HandlersStack h;
if (!this.handlingConnection()) {
h = new HandlersStack(this.getNewConnection(), handler);
h = new HandlersStack(this, this.getNewConnection(), handler);
this.handlers.put(Thread.currentThread(), h);
} else if (handler.canRestoreState()) {
h = this.getHandlersStack().push(handler);
962,11 → 1010,12
* All connections obtained with {@link #getConnection()} will be closed immediately, but
* threads in {@link #useConnection(ConnectionHandler)} will get to keep them. After the last
* thread returns from {@link #useConnection(ConnectionHandler)} there won't be any connection
* left open. This instance won't be permanently closed, it can be reused later.
* left open. This instance will be permanently closed, it cannot be reused later.
*
* @throws SQLException if a database error occurs
*/
public synchronized void close() throws SQLException {
@SuppressWarnings("rawtypes")
final GenericObjectPool pool = this.connectionPool;
super.close();
// super close and unset our pool, but we need to keep it
993,10 → 1042,6
this.cache.clear();
}
 
public final synchronized boolean isClosed() {
return this.dataSource == null;
}
 
/**
* Retourne la connection à cette source de donnée.
*
1006,12 → 1051,19
* @see #handlingConnection()
*/
public final Connection getConnection() {
final HandlersStack res = this.handlers.get(Thread.currentThread());
final HandlersStack res = this.getHandlersStack();
if (res == null)
throw new IllegalStateException("useConnection() wasn't called");
return res.getConnection();
}
 
public final TransactionPoint getTransactionPoint() {
final HandlersStack handlersStack = this.getHandlersStack();
if (handlersStack == null)
return null;
return handlersStack.getLastTxPoint();
}
 
/**
* Retourne une connection à cette source de donnée (generally
* {@link #useConnection(ConnectionHandler)} should be used). Si la connexion échoue cette
1069,7 → 1121,7
boolean setSchema = false;
String schemaToSet = null;
synchronized (this) {
if (!this.uptodate.containsKey(res)) {
if (!this.schemaUptodate.containsKey(res)) {
if (this.initialShemaSet) {
setSchema = true;
schemaToSet = this.initialShema;
1076,9 → 1128,13
}
// safe to put before setSchema() since res cannot be passed to
// release/closeConnection()
this.schemaUptodate.put(res, null);
}
// a connection from the pool is up to date since we close all idle connections in
// invalidateAllConnections() and borrowed ones are closed before they return to the
// pool
this.uptodate.put(res, null);
}
}
// warmup the connection (executing a bogus simple query, like "SELECT 1") could help but in
// general doesn't since we often do getDS().execute() and thus our warm up thread will run
// after the execute(), making it useless.
1143,19 → 1199,193
}
}
 
/**
* Whether the database defaut transaction isolation is check only once for this instance. If
* <code>false</code>, every new connection will have its
* {@link Connection#setTransactionIsolation(int) isolation set}. If <code>true</code> the
* isolation will only be set if the {@link #setInitialTransactionIsolation(int) requested one}
* differs from the DB one. In other words, if you want to optimize DB access, the DB
* configuration must match the datasource configuration.
*
* @param checkOnce <code>true</code> to check only once the DB transaction isolation.
*/
public synchronized final void setTransactionIsolationCheckedOnce(final boolean checkOnce) {
this.checkOnceDBTxIsolation = checkOnce;
this.dbTxIsolation = null;
}
 
public synchronized final boolean isTransactionIsolationCheckedOnce() {
return this.checkOnceDBTxIsolation;
}
 
// don't use setDefaultTransactionIsolation() in super since it makes extra requests each time a
// connection is borrowed
public final void setInitialTransactionIsolation(int level) {
if (level != Connection.TRANSACTION_READ_UNCOMMITTED && level != Connection.TRANSACTION_READ_COMMITTED && level != Connection.TRANSACTION_REPEATABLE_READ
&& level != Connection.TRANSACTION_SERIALIZABLE)
throw new IllegalArgumentException("Invalid value :" + level);
synchronized (this) {
if (this.txIsolation != level) {
this.txIsolation = level;
// perhaps do like setInitialSchema() : i.e. call setTransactionIsolation() on
// existing connections
this.invalidateAllConnections(false);
}
}
}
 
public synchronized final int getInitialTransactionIsolation() {
return this.txIsolation;
}
 
public synchronized final Integer getDBTransactionIsolation() {
return this.dbTxIsolation;
}
 
final synchronized void setTransactionIsolation(Connection conn) throws SQLException {
if (this.dbTxIsolation == null) {
this.dbTxIsolation = conn.getTransactionIsolation();
assert this.dbTxIsolation != null;
}
// no need to try to change the level if the DB doesn't support transactions
if (this.dbTxIsolation != Connection.TRANSACTION_NONE && (!this.checkOnceDBTxIsolation || this.dbTxIsolation != this.txIsolation)) {
// if not check once, it's the desired action, so don't log
if (this.checkOnceDBTxIsolation)
Log.get().config("Setting transaction isolation to " + this.txIsolation);
conn.setTransactionIsolation(this.txIsolation);
}
}
 
// allow to know transaction states
private final class TransactionPoolableConnection extends PoolableConnection {
// perhaps call getAutoCommit() once to have the initial value
@GuardedBy("this")
private boolean autoCommit = true;
 
private TransactionPoolableConnection(Connection conn, @SuppressWarnings("rawtypes") ObjectPool pool, AbandonedConfig config) {
super(conn, pool, config);
}
 
private HandlersStack getNonNullHandlersStack() throws SQLException {
final HandlersStack res = getHandlersStack();
if (res == null)
throw new SQLException("Unsafe transaction, call useConnection() or SQLUtils.executeAtomic()");
return res;
}
 
@Override
protected synchronized DataSource createDataSource() throws SQLException {
if (isClosed()) {
// initialize lotta things
super.createDataSource();
// methods not defined in superclass and thus not called in super.createDataSource()
public synchronized void setAutoCommit(boolean autoCommit) throws SQLException {
if (this.autoCommit != autoCommit) {
// don't call setAutoCommit() if no stack
final HandlersStack handlersStack = getNonNullHandlersStack();
super.setAutoCommit(autoCommit);
this.autoCommit = autoCommit;
if (this.autoCommit)
// some delegates of the super implementation might have already called our
// commit(), but in this case, the following commit will be a no-op
handlersStack.commit();
else
handlersStack.addTxPoint(new TransactionPoint(this));
}
}
 
@Override
public synchronized void commit() throws SQLException {
super.commit();
final HandlersStack handlersStack = getNonNullHandlersStack();
handlersStack.commit();
handlersStack.addTxPoint(new TransactionPoint(this));
}
 
@Override
public synchronized void rollback() throws SQLException {
super.rollback();
final HandlersStack handlersStack = getNonNullHandlersStack();
handlersStack.rollback();
assert !this.autoCommit;
handlersStack.addTxPoint(new TransactionPoint(this));
}
 
@Override
public synchronized Savepoint setSavepoint() throws SQLException {
// don't call setSavepoint() if no stack
final HandlersStack handlersStack = getNonNullHandlersStack();
final Savepoint res = super.setSavepoint();
handlersStack.addTxPoint(new TransactionPoint(this, res, false));
return res;
}
 
@Override
public synchronized Savepoint setSavepoint(String name) throws SQLException {
// don't call setSavepoint() if no stack
final HandlersStack handlersStack = getNonNullHandlersStack();
final Savepoint res = super.setSavepoint(name);
handlersStack.addTxPoint(new TransactionPoint(this, res, true));
return res;
}
 
@Override
public synchronized void rollback(Savepoint savepoint) throws SQLException {
super.rollback(savepoint);
getNonNullHandlersStack().rollback(savepoint);
}
 
@Override
public synchronized void releaseSavepoint(Savepoint savepoint) throws SQLException {
// don't bother merging TransactionPoint
super.releaseSavepoint(savepoint);
}
}
 
@Override
protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory, @SuppressWarnings("rawtypes") KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration)
throws SQLException {
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, this.connectionPool, statementPoolFactory, this.validationQuery, this.validationQueryTimeout,
this.connectionInitSqls, this.defaultReadOnly, this.defaultAutoCommit, this.defaultTransactionIsolation, this.defaultCatalog, configuration) {
@Override
public Object makeObject() throws Exception {
Connection conn = this._connFactory.createConnection();
if (conn == null) {
throw new IllegalStateException("Connection factory returned null from createConnection");
}
initializeConnection(conn);
setTransactionIsolation(conn);
if (null != this._stmtPoolFactory) {
@SuppressWarnings("rawtypes")
KeyedObjectPool stmtpool = this._stmtPoolFactory.createPool();
conn = new PoolingConnection(conn, stmtpool);
stmtpool.setFactory((PoolingConnection) conn);
}
return new TransactionPoolableConnection(conn, this._pool, this._config);
}
};
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLException("Cannot create PoolableConnectionFactory", e);
}
}
 
@Override
protected void createConnectionPool() {
super.createConnectionPool();
// methods not defined in superclass and thus not called in super
this.connectionPool.setLifo(true);
this.setBlockWhenExhausted(this.blockWhenExhausted);
this.connectionPool.setSoftMinEvictableIdleTimeMillis(this.softMinEvictableIdleTimeMillis);
}
 
@Override
protected void createDataSourceInstance() throws SQLException {
// PoolingDataSource returns a PoolGuardConnectionWrapper that complicates a lot of
// things for nothing, so overload to simply return an object of the pool
this.dataSource = new PoolingDataSource(this.connectionPool) {
 
// we'll migrate to plain SQLException when our superclass does
@SuppressWarnings("deprecation")
@Override
public Connection getConnection() throws SQLException {
try {
1177,8 → 1407,6
}
};
}
return this.dataSource;
}
 
/**
* To return a connection to the pool.
1190,7 → 1418,7
// if !this.initialShemaSet the out of date cannot be brought up to date
final boolean unrecoverableOutOfDate;
synchronized (this) {
unrecoverableOutOfDate = !this.initialShemaSet && !this.uptodate.containsKey(con);
unrecoverableOutOfDate = !this.uptodate.containsKey(con) || !this.initialShemaSet && !this.schemaUptodate.containsKey(con);
}
if (isClosed() || unrecoverableOutOfDate)
// if closed : don't fill the pool (which will have thrown an exception anyway)
1223,6 → 1451,7
if (con != null) {
synchronized (this) {
this.uptodate.remove(con);
this.schemaUptodate.remove(con);
}
try {
// ATTN this always does _numActive--, so we can't call it multiple times
1240,6 → 1469,32
}
 
/**
* Invalidates all open connections. This immediately closes idle connections. Borrowed ones are
* marked as invalid, so that they are closed on return. In other words, after this method
* returns, no existing connection will be provided.
*/
public final void invalidateAllConnections() {
this.invalidateAllConnections(false);
}
 
public final void invalidateAllConnections(final boolean preventIdleConnections) {
// usefull since Evictor of GenericObjectPool might call ensureMinIdle()
if (preventIdleConnections) {
this.setMinIdle(0);
this.setMaxIdle(0);
}
synchronized (this) {
// otherwise nothing to invalidate
if (this.connectionPool != null) {
// closes all idle connections
this.connectionPool.clear();
// borrowed connections will be closed on return
this.uptodate.clear();
}
}
}
 
/**
* From now on, every new connection will have its default schema set to schemaName.
*
* @param schemaName the name of the initial default schema, <code>null</code> to remove any
1287,7 → 1542,7
synchronized (this) {
this.initialShemaSet = set;
this.initialShema = schemaName;
this.uptodate.clear();
this.schemaUptodate.clear();
if (!set)
// by definition we don't want to modify the connection,
// so empty the pool, that way new connections will be created
1294,7 → 1549,7
// the borrowed ones will be closed when returned
this.connectionPool.clear();
else
this.uptodate.put(newConn, null);
this.schemaUptodate.put(newConn, null);
}
this.returnConnection(newConn);
}
1326,32 → 1581,29
q = null;
} else
q = "USE " + schemaName;
} else if (this.getSystem() == SQLSystem.DERBY)
q = "SET SCHEMA \"" + schemaName + '"';
else if (this.getSystem() == SQLSystem.H2) {
} else if (this.getSystem() == SQLSystem.DERBY) {
q = "SET SCHEMA " + SQLBase.quoteIdentifier(schemaName);
} else if (this.getSystem() == SQLSystem.H2) {
q = "SET SCHEMA " + SQLBase.quoteIdentifier(schemaName);
// TODO use the line below, but for now it is only used after schema()
// plus there's no function to read it back
// q = "set SCHEMA_SEARCH_PATH " + SQLBase.quoteIdentifier(schemaName == null ? "" :
// schemaName);
} else if (this.getSystem() == SQLSystem.POSTGRESQL) {
final String schemasString;
if (schemaName == null) {
schemasString = "''";
// SET cannot empty the path
q = "select set_config('search_path', '', false)";
} else {
// ATTN does NOT work if a schema has " in its name
// current_schemas() instead of current_setting() since the former removes dups and
// returns an array
schemasString = SQLSelect.quote(" '\"' || array_to_string( cast (%s as name) || current_schemas(false) , '\", \"')|| '\"'", schemaName);
q = "set session search_path to " + SQLBase.quoteIdentifier(schemaName);
}
q = "select set_config('search_path', " + schemasString + " , false)";
} else if (this.getSystem() == SQLSystem.MSSQL) {
if (schemaName == null)
throw new IllegalArgumentException("cannot unset default schema in " + this.getSystem());
else
q = "alter user " + getUsername() + " with default_schema = " + SQLBase.quoteIdentifier(schemaName);
} else
} else {
throw new UnsupportedOperationException();
}
 
if (q != null)
this.execute(q, null, true, c);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSchema.java
135,6 → 135,12
return (SQLBase) this.getParent();
}
 
/**
* The version when this instance was last fully refreshed. In other words, if we refresh tables
* by names (even if we name them all) this version isn't updated.
*
* @return the version.
*/
synchronized final String getFullyRefreshedVersion() {
return this.version;
}
423,6 → 429,11
}
}
 
/**
* The current version in the database.
*
* @return current version in the database.
*/
public final String getVersion() {
return this.getFwkMetadata(VERSION_MDKEY);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/TM.java
New file
0,0 → 1,57
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql;
 
import org.openconcerto.sql.preferences.UserProps;
 
import java.util.Locale;
 
/**
* Translation manager, listen to locale of {@link UserProps} and apply it to lower translation
* managers.
*
* @author Sylvain
*/
public class TM extends UserPropsTM {
 
static private TM INSTANCE;
 
// to be called as soon as UserProps has the correct Locale to initialize other translation
// managers
static public TM getInstance() {
if (INSTANCE == null)
INSTANCE = new TM();
return INSTANCE;
}
 
// useful for static import
static public TM getTM() {
return getInstance();
}
 
static public final String tr(final String key, final Object... args) {
return getInstance().translate(key, args);
}
 
private TM() {
}
 
@Override
protected void updateLocale(final UserProps userProps) {
final Locale locale = userProps.getLocale();
this.setLocale(locale);
org.openconcerto.utils.i18n.TM.getInstance().setLocale(locale);
org.openconcerto.ui.TM.getInstance().setLocale(locale);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/State.java
13,45 → 13,18
package org.openconcerto.sql;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ExceptionUtils;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
 
/**
* Une classe qui permet de connaître l'état du Framework en temps réel. Le serveur est lancé sur le
* premier port disponible à partir de PORT.
* Une classe qui permet de connaître l'état du Framework en temps réel.
*
* @author Sylvain CUAZ
*/
public final class State extends Thread {
public final class State {
 
public static final String DEAF = "org.openconcerto.sql.deafState";
 
public static final int PORT = 1394;
 
public static final boolean DEBUG = true;
 
public static final org.openconcerto.sql.State INSTANCE = new org.openconcerto.sql.State();
81,21 → 54,12
 
private int framesVisible;
 
private PrintWriter out;
 
private Thread thread;
 
private int cacheHit;
 
private final ScriptEngine engine;
private final Bindings ognlCtxt;
 
/**
* Once created, this thread starts immediately.
*/
private State() {
super("State thread");
this.setDaemon(true);
this.requests = new ArrayList<String>();
this.requestsTotalCount = 0;
this.failed = new ArrayList<String>();
106,209 → 70,9
this.framesVisible = 0;
this.cacheHit = 0;
this.upDate = System.currentTimeMillis();
this.engine = new ScriptEngineManager().getEngineByName("javascript");
this.ognlCtxt = new SimpleBindings();
 
if (!Boolean.getBoolean(DEAF))
this.start();
}
 
private Bindings getContext() {
this.ognlCtxt.put("base", this.getBase());
this.ognlCtxt.put("dir", Configuration.getInstance().getDirectory());
return this.ognlCtxt;
}
 
/**
* Crée la socket du serveur.
*
* @return la socket ou <code>null</code> si pb.
*/
private ServerSocket createSocket() {
ServerSocket serverSocket = null;
int port = PORT;
while (serverSocket == null) {
try {
serverSocket = new ServerSocket(port);
Log.get().info("listening on " + port);
} catch (BindException e) {
Log.get().config("port " + port + " already in use");
// on essaye le suivant
port++;
} catch (IOException e) {
Log.get().warning("cannot create a socket");
e.printStackTrace();
return null;
}
}
return serverSocket;
}
 
public void run() {
final ServerSocket serverSocket = this.createSocket();
if (serverSocket == null)
return;
 
while (true) {
try {
Socket clientSocket = serverSocket.accept();
Log.get().info("accepted");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
this.out = new PrintWriter(clientSocket.getOutputStream(), true);
this.out.println("Console d'administration: " + clientSocket.getLocalSocketAddress() + " <-> " + clientSocket.getRemoteSocketAddress());
this.out.println("help (ou h) pour l'aide");
this.out.println("quit (ou q) pour quitter");
this.out.flush();
String inputLine;
while ((inputLine = in.readLine()) != null) {
if (inputLine.equals("q") || inputLine.startsWith("quit"))
break;
if (inputLine.equals("h") || inputLine.startsWith("help"))
printHelp();
 
try {
this.out.println(getAnswer(inputLine));
} catch (Exception exn) {
exn.printStackTrace();
exn.printStackTrace(this.out);
}
this.out.print(">");
this.out.flush();
}
 
in.close();
if (this.thread != null)
this.thread.interrupt();
this.out.close();
clientSocket.close();
Log.get().info("closed");
} catch (IOException e) {
e.printStackTrace();
}
}
// TODO detect when app quit to clean up
// serverSocket.close();
}
 
private void printHelp() {
this.out.println("Commandes possibles:");
this.out.println("req : affiche la liste des requêtes en cours");
this.out.println("all : affiche les stats du logiciels");
this.out.println("gc : lance le garbage collector");
this.out.println("log LEVEL [loggerName] : définit un niveau de log");
this.out.println("unarchive TABLE ID : désarchive un élément d'une table (et propage le desarchivage)");
this.out.println("\t ex: unarchive SITE 125 va désarchiver BATIMENTs etc...");
this.out.println("set : définit une variable OGNL ou affiche la liste");
this.out.println("\t ex: set siteT base.getTable('SITE')");
this.out.println("eval : evalue une expression OGNL");
this.out.println("\t ex: #siteT.getRow(123).getReferentRows()");
this.out.println();
this.out.flush();
}
 
private String getAnswer(String question) throws SQLException {
question = question.trim();
if (question.equals("req"))
return this.requests.toString();
else if (question.equals("all")) {
return getFull();
} else if (question.equals("gc")) {
System.gc();
return "System.gc() called.";
} else if (question.startsWith("log")) {
// log LEVEL [loggerName]
final String[] args = question.split(" ");
final String level = args[1];
final Level l;
try {
final Field f = Level.class.getField(level);
l = (Level) f.get(null);
} catch (Exception e) {
return "cannot get field '" + level + "' of Level";
}
final String name = args.length > 2 ? args[2] : "";
Logger.getLogger(name).setLevel(l);
return "logger '" + name + "' level set to " + l.getName();
} else if (question.equals("allc")) {
this.thread = new Thread() {
public void run() {
int i = 0;
while (true) {
org.openconcerto.sql.State.this.out.println("--------------");
org.openconcerto.sql.State.this.out.println("Iteration: " + i++);
org.openconcerto.sql.State.this.out.println("--------------");
org.openconcerto.sql.State.this.out.println(getFull());
org.openconcerto.sql.State.this.out.println("\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// s'arrêter
break;
}
}
}
};
this.thread.start();
return "beginning";
} else if (question.startsWith("unarchive")) {
return archive(question, false);
} else if (question.startsWith("archive")) {
return archive(question, true);
} else if (question.startsWith("eval")) {
try {
return ognlResult(eval(question.substring("eval".length())));
} catch (ScriptException e) {
return ExceptionUtils.getStackTrace(e);
}
} else if (question.startsWith("set")) {
final String[] args = question.split(" ", 3);
if (args.length == 1) {
return CollectionUtils.join(this.ognlCtxt.entrySet(), "\n");
} else {
try {
final Object res = eval(args[2]);
this.ognlCtxt.put(args[1], res);
return ognlResult(res);
} catch (ScriptException e) {
return ExceptionUtils.getStackTrace(e);
}
}
} else
return "commande non connue.";
}
 
private String archive(String question, boolean arch) throws SQLException {
final String[] args = question.split(" ");
final String tableName = args[1];
final int id = Integer.parseInt(args[2]);
final String s;
if (arch) {
this.getElement(tableName).archive(id);
s = "archived";
} else {
this.getElement(tableName).unarchive(id);
s = "unarchived";
}
return s + " " + this.getBase().getTable(tableName).getRow(id);
}
 
public final Object eval(String s) throws ScriptException {
return this.engine.eval(s, this.getContext());
}
 
private String ognlResult(Object res) {
return res == null ? "<no result>" : res.toString();
}
 
private final SQLBase getBase() {
return Configuration.getInstance().getBase();
}
 
private final SQLElement getElement(String tableName) {
return Configuration.getInstance().getDirectory().getElement(tableName);
}
 
private String getFull() {
final String getFull() {
String res = "failed requests: " + this.failed.size();
res += "\nfailed statements: " + this.failedStmts;
res += "\nrequests: " + this.requests.size();
341,16 → 105,6
return b / 1024 / 1024 + "MB";
}
 
/**
* Put an object in the context, it can then be accessed by "#<code>s</code>".
*
* @param s the name of the value, eg "count".
* @param val the value, eg 3.
*/
public void put(String s, Object val) {
this.ognlCtxt.put(s, val);
}
 
public synchronized void beginRequest(String req) {
this.requests.add(req);
this.requestsTotalCount++;
360,6 → 114,10
this.requests.remove(req);
}
 
public final synchronized List<String> getRequests() {
return new ArrayList<String>(this.requests);
}
 
public synchronized void addFailedRequest(String query) {
this.failed.add(query);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/BackupPanel.java
13,7 → 13,10
package org.openconcerto.sql.utils;
 
import static org.openconcerto.sql.TM.getTM;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JLabelBold;
46,25 → 49,22
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
 
import org.jdesktop.swingx.VerticalLayout;
 
public class BackupPanel extends JPanel implements ActionListener {
 
static final private DateFormat format = new SimpleDateFormat("EEEE");
private final DateFormat format = new SimpleDateFormat("EEEE", getTM().getTranslationsLocale());
 
private JProgressBar barDB = new JProgressBar();
private JTextField textDest = new JTextField();
private JButton buttonBackup = new JButton("Sauvegarder");
private JButton buttonBackup = new JButton(getTM().translate("backup"));
private JButton buttonBrowse = new JButton("...");
 
DateFormat dateFormat = new SimpleDateFormat("dd MM yyyy");
 
private JLabel labelErrors = new JLabel(" ");
private JLabel labelLastBackup = new JLabel(" ");
private JLabel labelLastDir = new JLabel(" ");
private JTextComponent labelLastBackup = new JTextArea();
private JLabel labelState = new JLabelBold(" ");
JButton buttonClose;
private List<String> listDb;
73,8 → 73,6
private boolean autoClose = false;
private ReloadPanel reloadPanel = new ReloadPanel();
 
private static String textErrors = "Des erreurs sont survenues lors de la dernière sauvegarde. Veuillez contacter le service technique.";
 
BackupProps props;
 
public BackupPanel(List<String> listDB, List<File> dirs2Save, boolean startNow, BackupProps props) {
95,15 → 93,12
 
topPanel.setBackground(Color.white);
 
topPanel.add(new JLabelBold("Sauvegarde"));
topPanel.add(new JLabelBold(getTM().translate("backup")));
topPanel.add(this.labelLastBackup);
topPanel.add(this.labelLastDir);
 
String lastBackup = props.getLastBackup();
if (lastBackup != null) {
this.labelLastBackup.setText("Derniére sauvegarde effectuée le " + lastBackup);
this.labelLastDir.setText("sur " + props.getProperty("LastBackupDestination"));
}
this.labelLastBackup.setEditable(false);
this.labelLastBackup.setFont(this.labelErrors.getFont());
updateLastBackup();
 
c.gridy++;
c.gridx = 0;
115,7 → 110,7
c.gridy++;
this.add(new JSeparator(), c);
 
JLabelBold sep1 = new JLabelBold("Emplacement");
JLabelBold sep1 = new JLabelBold(getTM().translate("location"));
c.gridy++;
c.gridx = 0;
c.fill = GridBagConstraints.HORIZONTAL;
146,10 → 141,10
c.gridy++;
c.gridx = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
this.add(new JLabel("Pensez à effectuer vos sauvegardes sur différents disques!"), c);
this.add(new JLabel(getTM().translate("backupPanel.differentDisks")), c);
 
// Progression
JLabelBold sep = new JLabelBold("Progression de la sauvegarde");
final JLabelBold sep = new JLabelBold(getTM().translate("backupPanel.progress"));
c.gridy++;
c.gridx = 0;
c.fill = GridBagConstraints.HORIZONTAL;
180,7 → 175,7
this.add(this.labelErrors, c);
int errors = props.getErrors();
if (errors > 0) {
this.labelErrors.setText(textErrors);
this.labelErrors.setText(getTM().translate("backupPanel.errorsOnLastBackup"));
}
 
// Etat de la sauvegarde
194,7 → 189,7
c.weighty = 1;
 
JPanel panelButton = new JPanel();
this.buttonClose = new JButton("Fermer");
this.buttonClose = new JButton(getTM().translate("close"));
if (!startNow) {
panelButton.add(this.buttonBackup);
}
222,6 → 217,15
}
}
 
protected void updateLastBackup() {
final Date lastBackup = this.props.getLastBackup();
if (lastBackup != null) {
this.labelLastBackup.setText(getTM().trM("backupPanel.lastBackup", "date", lastBackup, "destination", this.props.getProperty("LastBackupDestination")));
} else {
this.labelLastBackup.setText("");
}
}
 
public void doOnClose() {
// Default do nothing
}
231,7 → 235,7
if (e.getSource() == this.buttonBrowse) {
JFileChooser choose = new JFileChooser();
choose.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (choose.showDialog(this, "Sélectionner") == JFileChooser.APPROVE_OPTION) {
if (choose.showDialog(this, getTM().translate("choose")) == JFileChooser.APPROVE_OPTION) {
final File selectedFile = choose.getSelectedFile();
final String absolutePath = selectedFile.getAbsolutePath();
this.textDest.setText(absolutePath);
251,13 → 255,13
 
this.barDB.setStringPainted(false);
this.buttonBackup.setEnabled(false);
this.labelState.setText("Sauvegarde en cours!");
this.labelState.setText(getTM().translate("backupPanel.inProgress"));
this.reloadPanel.setMode(ReloadPanel.MODE_ROTATE);
this.barDB.setValue(1);
 
final String dest = this.textDest.getText();
final File fTmp = new File(dest, Configuration.getInstance().getAppName());
final File fDest = new File(fTmp, format.format(new Date()));
final File fDest = new File(fTmp, this.format.format(new Date()));
 
final Thread thread = new Thread(new Runnable() {
public void run() {
266,7 → 270,7
if (!fDest.exists() && !fDest.mkdirs()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(BackupPanel.this, "Impossible de créer le dossier de destination. Sauvegarde annulée!");
JOptionPane.showMessageDialog(BackupPanel.this, getTM().translate("backupPanel.createFolderError"));
}
});
} else {
276,7 → 280,7
if (!testFileCreate.createNewFile()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(BackupPanel.this, "Droits insuffisants sur le dossier de destination. Sauvegarde annulée!");
JOptionPane.showMessageDialog(BackupPanel.this, getTM().translate("backupPanel.folderRightsError"));
 
BackupPanel.this.barDB.setValue(0);
}
288,7 → 292,7
e1.printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(BackupPanel.this, "Droits insuffisants sur le dossier de destination. Sauvegarde annulée!");
JOptionPane.showMessageDialog(BackupPanel.this, getTM().translate("backupPanel.folderRightsError"));
BackupPanel.this.barDB.setValue(0);
}
});
300,28 → 304,21
// Sauvegarde de la base
if (BackupPanel.this.listDb != null) {
 
final SQLSystem system = Configuration.getInstance().getBase().getServer().getSQLSystem();
final DBRoot root = Configuration.getInstance().getRoot();
final DBSystemRoot sysRoot = root.getDBSystemRoot();
final SQLSystem system = sysRoot.getServer().getSQLSystem();
 
// Sauvegarde pour H2
if (system == SQLSystem.H2) {
 
// FIXME Close Connection
 
try {
Configuration.getInstance().getBase().getDataSource().close();
 
} catch (SQLException e) {
e.printStackTrace();
}
// Backup backup = new Backup(fDest);
// errors += backup.applyTo(new File( ));
// backup.close();
sysRoot.getDataSource().execute("backup to " + root.getBase().quoteString(new File(fDest, "Base.zip").getAbsolutePath()));
// TODO don't backup H2 files below ('backup to' is the only safe
// way);
} else {
// Sauvegarde autres bases
File fBase = new File(fDest, "Base");
Copy copy;
try {
copy = new Copy(true, fBase, Configuration.getInstance().getBase().getDBSystemRoot(), false, false);
copy = new Copy(true, fBase, sysRoot, false, false);
for (String db : BackupPanel.this.listDb) {
copy.applyTo(db, null);
}
378,18 → 375,17
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (backupErrors > 0) {
BackupPanel.this.labelErrors.setText(textErrors);
BackupPanel.this.labelState.setText("Sauvegarde terminée avec erreurs!");
BackupPanel.this.labelErrors.setText(getTM().translate("backupPanel.errorsOnLastBackup"));
BackupPanel.this.labelState.setText(getTM().translate("backupPanel.endFail"));
BackupPanel.this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
} else {
BackupPanel.this.labelErrors.setText("");
BackupPanel.this.labelState.setText("Sauvegarde terminée avec succés!");
BackupPanel.this.labelState.setText(getTM().translate("backupPanel.endSuccess"));
BackupPanel.this.reloadPanel.setMode(ReloadPanel.MODE_EMPTY);
closeAfter5Secondes();
}
 
BackupPanel.this.labelLastBackup.setText("Dernière sauvegarde effectuée le " + props.getLastBackup());
BackupPanel.this.labelLastDir.setText("sur " + props.getProperty("LastBackupDestination"));
updateLastBackup();
BackupPanel.this.buttonBackup.setEnabled(true);
}
 
397,7 → 393,7
 
}
} catch (Exception e) {
ExceptionHandler.handle("Echec de la sauvegarde", e);
ExceptionHandler.handle(BackupPanel.this, getTM().translate("backupPanel.failed"), e);
}
}
});
419,7 → 415,7
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
BackupPanel.this.buttonClose.setText("Fermeture dans " + rest + "s");
BackupPanel.this.buttonClose.setText(getTM().translate("backupPanel.closingIn", rest));
}
});
BackupPanel.this.seconde--;
433,7 → 429,7
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
BackupPanel.this.buttonClose.setText("Fermer");
BackupPanel.this.buttonClose.setText(getTM().translate("close"));
}
});
if (!BackupPanel.this.closed) {
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ReOrderPostgreSQL.java
20,6 → 20,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.UpdateBuilder;
 
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
31,7 → 32,8
super(t, spec);
}
 
public List<String> getSQL(Connection conn) throws SQLException {
@Override
public List<String> getSQL(Connection conn, BigDecimal inc) throws SQLException {
// see http://www.depesz.com/index.php/2007/08/17/rownum-anyone-cumulative-sum-in-one-query/
// http://explainextended.com/2009/05/05/postgresql-row-numbers/
// see http://www.postgresql.org/docs/8.4/interactive/functions-window.html
46,7 → 48,7
// FROM "test"."test"."OBSERVATION" "T"
// WHERE "T"."ORDRE" between 12 and 25
// ORDER BY "T"."ORDRE" ASC NULLS LAST, "T"."ID" ASC
final SQLSelect idsToReorder = new SQLSelect(this.t.getBase(), true);
final SQLSelect idsToReorder = new SQLSelect(true);
idsToReorder.addFrom(this.t, alias);
idsToReorder.addSelect(tID, null, "ID");
idsToReorder.setWhere(this.getWhere(tOrder));
59,7 → 61,7
res.add("CREATE LOCAL TEMPORARY TABLE REORDER as select M.\"ID\", nextval('\"reorderSeq\"') as index from (\n" + idsToReorder.asString() + ") M;");
res.add("DROP SEQUENCE \"reorderSeq\";");
 
res.add("create local temp table inc(val) as select " + getInc() + ";");
res.add("create local temp table inc(val) as select " + inc.toPlainString() + ";");
// remove if using deferrable uniqueness constraints
res.add(this.t.getBase().quote("UPDATE %f SET %n = -%n " + this.getWhere(), this.t, oF, oF) + ";");
 
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLCreateRoot.java
91,6 → 91,26
}
 
/**
* The SQL to update this root as a single list. I.e. this is just
* {@link #asLists(String, boolean, boolean, EnumSet)} flattened (without the boundaries
* parameter since all lists will be concatenated).
*
* @param r the name of the updated root, <code>null</code> meaning {@link #getName()}.
* @param drop whether to first drop the root.
* @param create whether to create the root, e.g. <code>false</code> if adding some tables to an
* existing root.
* @return the SQL needed.
*/
public final List<String> asList(final String r, final boolean drop, final boolean create) {
final List<List<String>> lists = this.asLists(r, drop, create, EnumSet.noneOf(ConcatStep.class));
assert lists.size() == 2;
final List<String> res = new ArrayList<String>(lists.get(0).size() + lists.get(1).size());
res.addAll(lists.get(0));
res.addAll(lists.get(1));
return res;
}
 
/**
* The SQL to update this root. The first item of the result is the drop/creation of the root
* and the {@link #addClause(String) clauses}, then <code>boundaries</code> size + 1 items for
* creating the tables.
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ReOrderMySQL.java
16,6 → 16,7
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLTable;
 
import java.math.BigDecimal;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
32,15 → 33,16
// SET @o := 3 - @inc ;
// UPDATE ADRESSE SET ORDRE = ( @o := @o +@inc ) where ORDRE < 0 ORDER BY ORDRE DESC;
 
public List<String> getSQL(Connection conn) {
@Override
public List<String> getSQL(Connection conn, BigDecimal inc) {
final SQLField oF = this.t.getOrderField();
 
final List<String> res = new ArrayList<String>();
res.add("SELECT " + getInc() + " into @inc");
res.add("SELECT " + inc.toPlainString() + " into @inc");
res.add(this.t.getBase().quote("UPDATE %f SET %n = -%n " + this.getWhere(), this.t, oF, oF));
// on commence à 0
res.add("SET @o := " + this.getFirstOrderValue() + "- @inc");
res.add(getLoop(oF, "<= -" + this.getFirstToReorder(), oF, "DESC"));
res.add(getLoop(oF, (this.isFirstToReorderInclusive() ? "<=" : "<") + this.getFirstToReorder().negate().toPlainString(), oF, "DESC"));
if (this.isAll()) {
res.add(getLoop(oF, "is null", this.t.getKey(), "ASC"));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ReOrderH2.java
21,6 → 21,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
 
import java.math.BigDecimal;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
31,11 → 32,12
super(t, spec);
}
 
public List<String> getSQL(Connection conn) {
@Override
public List<String> getSQL(Connection conn, BigDecimal inc) {
final SQLField oF = this.t.getOrderField();
 
final List<String> res = new ArrayList<String>();
res.add("SET @inc to SELECT " + getInc() + ";");
res.add("SET @inc to SELECT " + inc.toPlainString() + ";");
res.add(this.t.getBase().quote("UPDATE %f SET %n = -%n " + this.getWhere(), this.t, oF, oF));
 
final String alias = "T";
49,7 → 51,7
idsToReorder.addFrom(this.t, alias);
idsToReorder.addSelect(tID);
final Where updateNulls = this.isAll() ? new Where(tOrder, "is", (Object) null) : null;
final Where w = new Where(tOrder, "<=", this.getFirstToReorder().negate()).or(updateNulls);
final Where w = new Where(tOrder, this.isFirstToReorderInclusive() ? "<=" : "<", this.getFirstToReorder().negate()).or(updateNulls);
idsToReorder.setWhere(w);
idsToReorder.addFieldOrder(tOrder, Order.desc(), Order.nullsLast());
idsToReorder.addFieldOrder(tID, Order.asc());
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/RowURIFormat.java
169,7 → 169,7
try {
linkActivated(this.parser.parse(new URI(e.getDescription())), src);
} catch (Exception exn) {
ExceptionHandler.handle(src, "Impossible d'ouvrir " + e.getDescription(), exn);
ExceptionHandler.handle(src, org.openconcerto.ui.TM.tr("linkOpenError", e.getDescription()), exn);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ReOrder.java
24,6 → 24,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.UpdateBuilder;
import org.openconcerto.utils.convertor.NumberConvertor;
 
import java.math.BigDecimal;
import java.sql.Connection;
49,9 → 50,28
}
 
static public ReOrder create(final SQLTable t, final int first, final int count) {
return create(t, new Some(t, first, count));
return create(t, BigDecimal.valueOf(first), true, count, null);
}
 
/**
* Create a {@link ReOrder} for some rows of the passed table.
*
* @param t which table to reorder.
* @param first the first order to change.
* @param inclusive <code>true</code> if the row with the order <code>first</code> must be
* changed.
* @param count the number of orders (not rows) to change.
* @param newFirst the order the row with the order <code>first</code> will have after the
* change.
* @return a new instance.
* @throws IllegalArgumentException if <code>count</code> is negative or if
* <code>newFirst</code> isn't between <code>first</code> and <code>first + count</code>
* .
*/
static public ReOrder create(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
return create(t, new Some(t, first, inclusive, count, newFirst == null ? first : newFirst));
}
 
static private ReOrder create(final SQLTable t, final Spec spec) {
final SQLSystem system = t.getBase().getServer().getSQLSystem();
if (system == SQLSystem.MYSQL) {
82,6 → 102,10
return this.spec.getFirstToReorder();
}
 
protected final boolean isFirstToReorderInclusive() {
return this.spec.isFirstToReorderInclusive();
}
 
protected final BigDecimal getFirstOrderValue() {
return this.spec.getFirst();
}
95,17 → 119,13
return this.spec.getWhere(f);
}
 
protected final String getInc() {
return this.spec.getInc();
}
public abstract List<String> getSQL(Connection conn, BigDecimal inc) throws SQLException;
 
public abstract List<String> getSQL(Connection conn) throws SQLException;
 
// MAYBE return affected IDs
public final void exec() throws SQLException {
public final boolean exec() throws SQLException {
final UpdateBuilder updateUndef = new UpdateBuilder(this.t).set(this.t.getOrderField().getName(), MIN_ORDER.toPlainString());
updateUndef.setWhere(new Where(this.t.getKey(), "=", this.t.getUndefinedID()));
SQLUtils.executeAtomic(this.t.getBase().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
return (Boolean) SQLUtils.executeAtomic(this.t.getBase().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
@Override
public Object handle(SQLDataSource ds) throws SQLException, SQLException {
final Connection conn = ds.getConnection();
114,12 → 134,18
// reorder all, undef must be at 0
stmt.execute(updateUndef.asString());
}
for (final String s : getSQL(conn)) {
stmt.execute("SELECT " + ReOrder.this.spec.getInc());
final BigDecimal inc = NumberConvertor.toBigDecimal((Number) SQLDataSource.SCALAR_HANDLER.handle(stmt.getResultSet()));
// needed since the cast in getInc() rounds so if the real increment is 0.006 it
// might get rounded to 0.01 and thus the last rows will overlap non moved rows
if (inc.compareTo(ReOrder.this.t.getOrderULP().scaleByPowerOfTen(1)) < 0)
return false;
for (final String s : getSQL(conn, inc)) {
stmt.execute(s);
}
// MAYBE fire only changed IDs
ReOrder.this.t.fireTableModified(-1, Collections.singletonList(ReOrder.this.t.getOrderField().getName()));
return null;
return true;
}
});
}
130,28 → 156,35
 
private final SQLTable t;
private final BigDecimal firstToReorder;
private final boolean firstToReorderInclusive;
private final BigDecimal first;
private final BigDecimal lastToReorder;
 
public Some(final SQLTable t, final int first, final int count) {
public Some(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
this.t = t;
if (count <= 0)
throw new IllegalArgumentException("Negative Count : " + count);
if (first.compareTo(newFirst) > 0)
throw new IllegalArgumentException("New first before first : " + first + " > " + newFirst);
final BigDecimal originalLastToReorder = first.add(BigDecimal.valueOf(count));
if (newFirst.compareTo(originalLastToReorder) >= 0)
throw new IllegalArgumentException("New first after last to reorder : " + newFirst + " >= " + originalLastToReorder);
// the row with MIN_ORDER cannot be displayed since no row can be moved before it
// so don't change it
if (BigDecimal.valueOf(first).compareTo(MIN_ORDER) <= 0) {
this.firstToReorder = MIN_ORDER.add(t.getOrderULP());
if (first.compareTo(MIN_ORDER) <= 0) {
this.firstToReorder = MIN_ORDER;
this.firstToReorderInclusive = false;
// make some room before the first non MIN_ORDER row so that another on can came
// before it
this.first = MIN_ORDER.add(DISTANCE);
this.first = MIN_ORDER.add(DISTANCE).max(newFirst);
// try to keep asked value
this.lastToReorder = originalLastToReorder.compareTo(this.first) > 0 ? originalLastToReorder : this.first.add(BigDecimal.valueOf(count));
} else {
this.firstToReorder = BigDecimal.valueOf(first);
this.first = this.firstToReorder;
this.firstToReorder = first;
this.firstToReorderInclusive = inclusive;
this.first = newFirst;
this.lastToReorder = originalLastToReorder;
}
final BigDecimal simpleLastToReorder = this.firstToReorder.add(BigDecimal.valueOf(count));
// needed since first can be different than firstToReorder
this.lastToReorder = simpleLastToReorder.compareTo(this.first) > 0 ? simpleLastToReorder : this.first.add(DISTANCE.movePointRight(1));
// OK since DISTANCE is a lot greater than the ULP of ORDRE
assert this.getFirstToReorder().compareTo(this.getFirst()) <= 0 && this.getFirst().compareTo(this.getLast()) < 0 && this.getLast().compareTo(this.getLastToReorder()) <= 0;
}
 
161,7 → 194,7
final SQLSyntax syntax = SQLSyntax.get(this.t.getServer().getSQLSystem());
 
// last order of the whole table
final SQLSelect selTableLast = new SQLSelect(this.t.getBase(), true);
final SQLSelect selTableLast = new SQLSelect(true);
selTableLast.addSelect(oF, "MAX");
 
// cast inc to order type to avoid truncation error
180,7 → 213,7
order = this.t.getOrderField();
else if (order.getField() != this.t.getOrderField())
throw new IllegalArgumentException();
return new Where(order, this.getFirstToReorder(), this.getLastToReorder());
return new Where(order, this.getFirstToReorder(), this.firstToReorderInclusive, this.getLastToReorder(), true);
}
 
@Override
188,6 → 221,11
return this.firstToReorder;
}
 
@Override
public boolean isFirstToReorderInclusive() {
return this.firstToReorderInclusive;
}
 
private final BigDecimal getLastToReorder() {
return this.lastToReorder;
}
219,6 → 257,11
}
 
@Override
public boolean isFirstToReorderInclusive() {
return true;
}
 
@Override
public BigDecimal getFirst() {
return getFirstToReorder();
}
232,6 → 275,8
// before reorder
BigDecimal getFirstToReorder();
 
boolean isFirstToReorderInclusive();
 
// the first order value after reorder
BigDecimal getFirst();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ClassGenerator.java
18,6 → 18,8
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.RowItemDesc;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.io.NewLineWriter;
58,8 → 60,8
public static void generateMappingXML(SQLTable t, List<SQLField> f, NewLineWriter out) throws IOException {
out.println("<TABLE name=\"" + t.getName() + "\">");
for (final SQLField element : f) {
final String fieldName = StringUtils.firstUpThenLow(element.getName()).replace('_', ' ');
out.println(" <FIELD name=\"" + element.getName() + "\" label=\"" + fieldName + "\" titleLabel=\"" + fieldName + "\" />");
final RowItemDesc desc = SQLFieldTranslator.getDefaultDesc(element);
out.println(" <FIELD name=\"" + element.getName() + "\" label=\"" + desc.getLabel() + "\" titleLabel=\"" + desc.getTitleLabel() + "\" />");
 
}
out.println("</TABLE>");
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLUtils.java
29,8 → 29,11
import java.util.List;
import java.util.regex.Pattern;
 
import org.apache.commons.dbcp.DelegatingConnection;
import org.apache.commons.dbutils.ResultSetHandler;
 
import com.mysql.jdbc.ConnectionProperties;
 
public class SQLUtils {
 
/**
285,7 → 288,8
throw new IllegalArgumentException("Size mismatch " + queries + " / " + handlers);
final List<Object> results = new ArrayList<Object>(size);
 
if (sysRoot.getServer().getSQLSystem().isMultipleResultSetsSupported()) {
final SQLSystem system = sysRoot.getServer().getSQLSystem();
if (system.isMultipleResultSetsSupported()) {
final long timeMs = System.currentTimeMillis();
final long time = System.nanoTime();
final long afterCache = time;
302,6 → 306,14
@Override
public Object handle(SQLDataSource ds) throws SQLException {
final Connection conn = ds.getConnection();
 
if (system == SQLSystem.MYSQL) {
final ConnectionProperties connectionProperties = (ConnectionProperties) ((DelegatingConnection) conn).getInnermostDelegate();
if (!connectionProperties.getAllowMultiQueries()) {
throw new IllegalStateException("Multi queries not allowed and the setting can only be set before connecting");
}
}
 
final long afterQueryInfo = System.nanoTime();
final long afterExecute, afterHandle;
final Statement stmt = conn.createStatement();
/trunk/OpenConcerto/src/org/openconcerto/sql/preferences/SQLPreferences.java
13,15 → 13,19
package org.openconcerto.sql.preferences;
 
import org.openconcerto.sql.model.AliasedTable;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.IResultSetHandler;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSelectJoin;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.TablesMap;
30,10 → 34,12
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.Value;
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
43,6 → 49,7
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.prefs.AbstractPreferences;
49,6 → 56,9
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
56,8 → 66,15
*
* @author Sylvain CUAZ
*/
@ThreadSafe
public class SQLPreferences extends AbstractPreferences {
 
private static final boolean GET_NODE_JAVA_RECURSION = false;
 
static private final String getJoinName(final int i) {
return "j" + i;
}
 
private static final String PREF_NODE_TABLENAME = "PREF_NODE";
private static final String PREF_VALUE_TABLENAME = "PREF_VALUE";
 
126,15 → 143,24
return res;
}
 
// is called by methods holding this.lock, so it cannot try to acquire this.lock
private final Object nodeLock = new Object();
 
private final SQLTable prefRT, prefWT;
private final SQLTable nodeRT, nodeWT;
private final MemoryRep rep;
// immutable
private final List<SQLPreferences> ancestors;
// values from the DB
@GuardedBy("lock")
private Map<String, String> values;
// values changed in-memory (not yet committed)
@GuardedBy("lock")
private final Map<String, String> changedValues;
// values removed in-memory (not yet committed)
@GuardedBy("lock")
private final Set<String> removedKeys;
@GuardedBy("nodeLock")
private SQLRow node;
 
// root node
167,10 → 193,12
this.nodeWT = nodeWT;
this.rep = rep;
 
this.ancestors = Collections.unmodifiableList(this.findAncestors());
 
this.values = null;
this.changedValues = new HashMap<String, String>();
this.removedKeys = new HashSet<String>();
this.node = null;
this.resetNode();
}
 
private final SQLTable getNodeRT() {
219,7 → 247,14
}
}
 
private final LinkedList<SQLPreferences> getAncestors() {
private final boolean isRoot() {
return this.absolutePath().equals("/");
}
 
private final LinkedList<SQLPreferences> findAncestors() {
// parent() and node() take lock on root, so we have no way of finding our ancestor while we
// hold this.lock
assert !Thread.holdsLock(this.lock) : "Deadlock possible since we access parent()";
final LinkedList<SQLPreferences> res = new LinkedList<SQLPreferences>();
res.add(this);
SQLPreferences current = this;
231,19 → 266,146
return res;
}
 
// ATTN per the superclass documentation, if the current thread holds this.lock it cannot ask it
// for an ancestor.
private final List<SQLPreferences> getAncestors() {
if (this.isRemoved())
throw new IllegalStateException("Node has been removed.");
// else our ancestors cannot be removed
return this.ancestors;
}
 
// makes sure the next time getNode() is called, it will fetch up to date data.
private final void resetNode() {
synchronized (this.nodeLock) {
this.node = null;
}
}
 
private final void setNode(final SQLRow r) {
synchronized (this.nodeLock) {
this.node = r;
}
}
 
public final SQLRow getNode() {
synchronized (this.nodeLock) {
if (this.node != null)
return this.node;
if (!GET_NODE_JAVA_RECURSION) {
try {
final Value<SQLRow> res = this.getNodeFromRoot();
if (res.hasValue()) {
this.setNode(res.getValue());
return res.getValue();
}
} catch (SQLException e) {
// we'll try the other way
e.printStackTrace();
}
}
}
// in contrast to getNodeFromRoot() : fill in ancestor's nodes at the expense of one request
// per ancestor (note that we could change getNodeFromRoot() to return all rows from the
// root in one request)
final List<SQLPreferences> ancestors = getAncestors();
SQLRow currentNode = null;
for (final SQLPreferences ancestor : ancestors) {
currentNode = ancestor.getNode(currentNode);
}
return currentNode;
}
 
// go through the path in SQL rather than in Java objects.
// perhaps return all rows from the root to this
private final Value<SQLRow> getNodeFromRoot() throws SQLException {
final StringTokenizer tokenizer = new StringTokenizer(this.absolutePath(), "/");
final List<String> path = new ArrayList<String>();
while (tokenizer.hasMoreTokens()) {
path.add(tokenizer.nextToken());
}
 
final SQLBase base = getNodeRT().getSchema().getBase();
// don't bother with recursive CTE for trivial requests (further it doesn't support empty
// paths)
if (path.size() > 2 && base.getServer().getSQLSystem() == SQLSystem.POSTGRESQL) {
final int[] vers = base.getVersion();
if (vers[0] >= 9 || vers[0] == 8 && vers[1] >= 4) {
return Value.getSome(getNodeCTE(path, base));
}
}
 
return Value.getSome(getNodeJoins(path));
}
 
// perhaps do more than one query if path is long.
private SQLRow getNodeJoins(final List<String> path) {
final int size = path.size();
final SQLTable nodeT = getNodeRT();
 
final SQLSelect sel = new SQLSelect();
final AliasedTable rootT = new AliasedTable(nodeT, "root");
sel.addFrom(rootT);
String lastAlias = rootT.getAlias();
for (int i = 0; i < size; i++) {
final SQLSelectJoin join = sel.addBackwardJoin("INNER", getJoinName(i), nodeT.getField("ID_PARENT"), lastAlias);
join.setWhere(new Where(join.getJoinedTable().getField("NAME"), "=", path.get(i)));
lastAlias = join.getAlias();
}
sel.setWhere(Where.isNull(rootT.getField("ID_PARENT")));
 
sel.addSelectStar(size == 0 ? rootT : new AliasedTable(nodeT, getJoinName(size - 1)));
 
@SuppressWarnings("unchecked")
final List<Map<String, Object>> res = (List<Map<String, Object>>) execute(sel.asString(), SQLDataSource.MAP_LIST_HANDLER);
assert res.size() <= 1 : "Unique constraint not enforced";
if (res.size() == 0)
return null;
return new SQLRow(nodeT, res.get(0));
}
 
private final SQLRow getNodeCTE(final List<String> path, final SQLBase base) {
if (path.size() == 0)
throw new IllegalArgumentException("Empty path : use getNodeJoins()");
final List<List<String>> values = new ArrayList<List<String>>(path.size());
for (final String token : path) {
values.add(Arrays.asList(String.valueOf(values.size()), base.quoteString(token)));
}
 
final SQLTable nodeT = getNodeRT();
final StringBuilder sb = new StringBuilder(1024);
sb.append("with recursive path(idx, name) as (").append(SQLSyntax.get(base).getValues(values, 2)).append("),");
sb.append("\nt as (");
 
final SQLSelect selectRoot = new SQLSelect(true).addSelectStar(nodeT).addRawSelect("0", "depth").setWhere(Where.isNull(nodeT.getField("ID_PARENT")));
sb.append(selectRoot.asString()).append("\nUNION ALL\n");
sb.append(new SQLSelect(true).addSelectStar(nodeT).addRawSelect("\"depth\" + 1", "depth").asString());
sb.append("\nINNER JOIN t on t." + nodeT.getKey().getQuotedName() + " = " + nodeT.getField("ID_PARENT").getFieldRef());
sb.append("\nINNER JOIN path on path.idx = t.\"depth\" and path.name = ").append(nodeT.getField("NAME").getFieldRef());
sb.append("\n)");
sb.append("\nselect * from t where t.\"depth\" = (select max(idx)+1 from path)");
 
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) execute(sb.toString(), SQLDataSource.MAP_HANDLER);
if (map == null)
return null;
 
map.keySet().retainAll(nodeT.getFieldsName());
return new SQLRow(nodeT, map);
}
 
private final SQLRow getNode(final SQLRow parentNode) {
synchronized (this.nodeLock) {
if (this.node == null) {
final SQLPreferences parent = (SQLPreferences) parent();
final Where parentW;
if (parent == null) {
if (isRoot()) {
parentW = Where.isNull(this.getNodeRT().getField("ID_PARENT"));
} else {
final SQLRow parentNode = parent.getNode();
parentW = parentNode == null ? null : new Where(this.getNodeRT().getField("ID_PARENT"), "=", parentNode.getID());
}
if (parentW == null) {
// our parent is not in the DB, we can't
this.node = null;
this.setNode(null);
} else {
final SQLSelect sel = new SQLSelect().addSelectStar(this.getNodeRT());
sel.setWhere(parentW.and(new Where(this.getNodeRT().getField("NAME"), "=", name())));
250,48 → 412,51
 
@SuppressWarnings("unchecked")
final Map<String, ?> m = (Map<String, ?>) execute(sel.asString(), SQLDataSource.MAP_HANDLER);
this.node = m == null ? null : new SQLRow(this.getNodeRT(), m);
this.setNode(m == null ? null : new SQLRow(this.getNodeRT(), m));
}
}
return this.node;
}
}
 
private final SQLRow createThisNode() throws SQLException {
final SQLPreferences parent = (SQLPreferences) parent();
private final boolean createThisNode(final SQLRow parentNode) throws SQLException {
assert isRoot() == (parentNode == null) : "Either the root has a parent or a node has null parent (which shouldn't happen since we're inserting rows)";
 
final SQLRowValues insVals = new SQLRowValues(this.getNodeWT());
insVals.put("ID_PARENT", parent == null ? SQLRowValues.SQL_EMPTY_LINK : parent.node.getID());
insVals.put("ID_PARENT", parentNode == null ? SQLRowValues.SQL_EMPTY_LINK : parentNode.getID());
insVals.put("NAME", name());
this.node = insVals.insert();
return this.node;
synchronized (this.nodeLock) {
// need to be up to date to avoid the insert failing
this.resetNode();
final boolean res = this.getNode(parentNode) == null;
if (res)
this.setNode(insVals.insert());
assert this.node != null;
return res;
}
}
 
private final boolean createNode() throws SQLException {
if (this.node == null) {
// to avoid deadlocks, do all the SELECT...
final Iterator<SQLPreferences> iter = getAncestors().iterator();
boolean rowExists = true;
SQLPreferences ancestor = null;
while (iter.hasNext() && rowExists) {
ancestor = iter.next();
rowExists = ancestor.getNode() != null;
}
// ... then all the INSERT
// we used to insert the root node, which submitted a replicate, then select one of its
// children, thus waiting on the replicate. But it itself was waiting on our transaction
// since we inserted the root node before.
if (!rowExists) {
ancestor.createThisNode();
boolean created = false;
SQLRow parentNode = null;
while (iter.hasNext()) {
ancestor = iter.next();
ancestor.createThisNode();
final SQLPreferences ancestor = iter.next();
final boolean ancestorCreated;
synchronized (ancestor.nodeLock) {
ancestorCreated = ancestor.createThisNode(parentNode);
parentNode = ancestor.node;
}
return true;
// since we take and release the lock at each iteration, if more than one thread
// executes this method then each ancestor node might get created by a different thread.
// I.e. the results of createThisNode might be [false, true, false, false, true].
created |= ancestorCreated;
}
return created;
}
return false;
}
 
public final Map<String, String> getValues() {
synchronized (this.lock) {
if (this.values == null) {
this.values = new HashMap<String, String>();
final SQLRow node = getNode();
308,21 → 473,27
}
return this.values;
}
}
 
@Override
protected void putSpi(String key, String value) {
synchronized (this.lock) {
this.changedValues.put(key, value);
this.removedKeys.remove(key);
}
}
 
@Override
protected void removeSpi(String key) {
synchronized (this.lock) {
this.removedKeys.add(key);
this.changedValues.remove(key);
}
}
 
@Override
protected String getSpi(String key) {
synchronized (this.lock) {
if (this.removedKeys.contains(key))
return null;
else if (this.changedValues.containsKey(key))
330,6 → 501,7
else
return this.getValues().get(key);
}
}
 
// null means delete all
private void deleteValues(final Set<String> keys) {
348,6 → 520,7
 
@Override
protected void removeNodeSpi() throws BackingStoreException {
synchronized (this.lock) {
try {
final SQLRow node = this.getNode();
if (node != null) {
360,20 → 533,21
}
});
replicate();
this.node = null;
this.resetNode();
}
} catch (Exception e) {
throw new BackingStoreException(e);
}
assert this.node == null;
this.values = null;
this.removedKeys.clear();
this.changedValues.clear();
}
}
 
@Override
protected String[] keysSpi() throws BackingStoreException {
try {
synchronized (this.lock) {
final Set<String> committedKeys = this.getValues().keySet();
final Set<String> res;
if (this.removedKeys.isEmpty() && this.changedValues.isEmpty()) {
384,6 → 558,7
res.addAll(this.changedValues.keySet());
}
return res.toArray(new String[res.size()]);
}
} catch (Exception e) {
throw new BackingStoreException(e);
}
392,6 → 567,7
@Override
protected String[] childrenNamesSpi() throws BackingStoreException {
try {
synchronized (this.lock) {
final SQLRow node = this.getNode();
if (node == null) {
// OK since "This method need not return the names of any nodes already cached"
406,6 → 582,7
@SuppressWarnings("unchecked")
final List<String> names = (List<String>) execute(sel.asString(), SQLDataSource.COLUMN_LIST_HANDLER);
return names.toArray(new String[names.size()]);
}
} catch (Exception e) {
throw new BackingStoreException(e);
}
422,9 → 599,21
super.sync();
}
 
@Override
protected void syncSpi() throws BackingStoreException {
this.flushSpi();
// sync without flushing
public void reset() throws BackingStoreException {
this.replicate();
this.resetRec();
}
 
private final void resetRec() throws BackingStoreException {
synchronized (this.lock) {
for (final AbstractPreferences kid : this.cachedChildren()) {
((SQLPreferences) kid).resetRec();
}
this.resetThis();
}
}
 
// per our superclass documentation we must reflect the change of the persistent store :
// - if some keys were changed, the next getValues() will fetch them ;
// - if our node was deleted, we have to call removeNode() otherwise our parent will still
431,14 → 620,28
// have us in kidCache and sync() will be called our cached kids.
// Don't call getValues() or childrenNamesSpi() here so that when asked they'll be the most
// up to date
private final void resetThis() throws BackingStoreException {
synchronized (this.lock) {
this.values = null;
this.node = null;
this.removedKeys.clear();
this.changedValues.clear();
this.resetNode();
if (this.getNode() == null)
this.removeNode();
}
}
 
@Override
protected void syncSpi() throws BackingStoreException {
synchronized (this.lock) {
this.flushSpi();
this.resetThis();
}
}
 
@Override
protected void flushSpi() throws BackingStoreException {
synchronized (this.lock) {
if (!this.nodeExists(""))
// values and node already removed in removeNodeSpi()
return;
454,13 → 657,18
throw new BackingStoreException(e);
}
}
}
 
protected final void flushTxn() throws SQLException {
assert Thread.holdsLock(this.lock);
// create node even if there's no values nor any children
boolean masterChanged = createNode();
if (this.removedKeys.size() > 0 || this.changedValues.size() > 0) {
// also delete changed, so we can insert afterwards
this.deleteValues(CollectionUtils.union(this.removedKeys, this.changedValues.keySet()));
// MAYBE remove and clear after transaction commit
if (this.values != null)
this.values.keySet().removeAll(this.removedKeys);
this.removedKeys.clear();
 
if (this.changedValues.size() > 0) {
468,6 → 676,9
final List<String> insValues = new ArrayList<String>(this.changedValues.size());
for (final Entry<String, String> e : this.changedValues.entrySet()) {
insValues.add("(" + nodeID + ", " + this.getPrefWT().getBase().quoteString(e.getKey()) + ", " + this.getPrefWT().getBase().quoteString(e.getValue()) + ")");
// MAYBE put after transaction commit
if (this.values != null)
this.values.put(e.getKey(), e.getValue());
}
 
SQLRowValues.insertCount(this.getPrefWT(), "(\"ID_NODE\", \"NAME\", \"VALUE\") VALUES" + CollectionUtils.join(insValues, ", "));
/trunk/OpenConcerto/src/org/openconcerto/sql/preferences/UserProps.java
25,7 → 25,7
private static final String PASSWORD = "Password";
private static final String LAST_LOGIN = "LastLogin";
private static final String LAST_SOCIETE = "LastSociete";
private static final String LANGUAGE = "Language";
public static final String LOCALE = "Language";
private static UserProps instance;
 
public void setLastLoginName(String login) {
68,7 → 68,7
}
 
public Locale getLocale() {
final String p = getProperty(UserProps.LANGUAGE);
final String p = getProperty(UserProps.LOCALE);
if (p == null) {
return Locale.getDefault();
}
76,6 → 76,6
}
 
public void setLocale(Locale locale) {
setProperty(UserProps.LANGUAGE, locale.toString());
setProperty(UserProps.LOCALE, locale.toString());
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserTableCellRenderer.java
37,19 → 37,18
AlternateTableCellRenderer.setBGColorMap(label, Collections.singletonMap(c, cDark));
}
 
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
 
Integer v = (Integer) value;
TableCellRendererUtils.setBackgroundColor(label, table, isSelected);
if (v.intValue() > 1) {
User u = UserManager.getInstance().getUser(v);
 
final Integer v = (Integer) value;
try {
final User u = UserManager.getInstance().getUser(v);
if (!isSelected && u.equals(UserManager.getInstance().getCurrentUser())) {
label.setBackground(c);
}
label.setText(u.getFullName());
} else {
System.err.println("User incorrect à la ligne " + row + " column " + column + " Value " + value);
} catch (Exception e) {
label.setText(e.getLocalizedMessage());
}
 
return label;
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsManagerPanel.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsManagerModel.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsManager.java
13,7 → 13,6
package org.openconcerto.sql.users.rights;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.SQLRow;
36,6 → 35,7
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
42,16 → 42,87
import java.util.Map;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
 
public class UserRightsManager {
 
public static final String USER_RIGHT_TABLE = UserRightSQLElement.TABLE_NAME;
public static final String SUPERUSER_FIELD = "SUPERUSER";
/**
* Only administrators can see user rights.
*/
public static final String ADMIN_FIELD = "ADMIN";
private static UserRightsManager instance;
private static final CollectionMap<String, Tuple2<String, Boolean>> SUPERUSER_RIGHTS = CollectionMap.singleton(null, Tuple2.create((String) null, true));
private static final CollectionMap<String, Tuple2<String, Boolean>> NO_RIGHTS = CollectionMap.singleton(null, Tuple2.create((String) null, false));
public static final List<MacroRight> DEFAULT_MACRO_RIGHTS = Collections.synchronizedList(new ArrayList<MacroRight>());
static {
DEFAULT_MACRO_RIGHTS.add(new LockAdminUserRight());
DEFAULT_MACRO_RIGHTS.add(new TableAllRights(true));
DEFAULT_MACRO_RIGHTS.add(new TableAllRights(false));
}
 
/**
* Call setInstance() if none exists.
*
* @param conf where to find the table.
* @return the instance created, <code>null</code> if an instance already existed or if no table
* could be found.
* @see #clearInstanceIfSame(UserRightsManager)
*/
public synchronized static UserRightsManager setInstanceIfNone(final DBRoot root) {
if (instance != null) {
return null;
} else {
return setInstanceFromRoot(root);
}
}
 
/**
* Call setInstance(null) if the instance is the same as the parameter.
*
* @param urMngr a manager.
* @return <code>true</code> if the instance was cleared.
*/
public synchronized static boolean clearInstanceIfSame(final UserRightsManager urMngr) {
if (instance == urMngr) {
setInstance(null);
return true;
} else {
return false;
}
}
 
/**
* Set the instance using the table in the passed root.
*
* @param root the root where the rights should be.
* @return the new instance, <code>null</code> if <code>root</code> does not contain the
* {@value #USER_RIGHT_TABLE} table.
*/
public static UserRightsManager setInstanceFromRoot(final DBRoot root) {
// do not require rights, one can check the result to see if it's not null
return setInstance(root.findTable(USER_RIGHT_TABLE, false));
}
 
/**
* Set the instance.
*
* @param t the table, <code>null</code> to remove.
* @return the new instance, <code>null</code> if t was.
*/
public synchronized static UserRightsManager setInstance(final SQLTable t) {
final SQLTable currentTable = instance == null ? null : instance.getTable();
if (currentTable != t) {
if (instance != null) {
instance.destroy();
}
instance = t == null ? null : new UserRightsManager(t);
}
return getInstance();
}
 
public synchronized static UserRightsManager getInstance() {
if (instance == null) {
instance = new UserRightsManager();
}
return instance;
}
 
58,13 → 129,8
public static final UserRights getCurrentUserRights() {
final UserManager mngr = UserManager.getInstance();
// if right table doesn't exist, give access to everything
if (!getInstance().isValid())
return new UserRights(SQLRow.NONEXISTANT_ID) {
@Override
public boolean haveRight(final String code, final String object, final Equalizer<? super String> objectMatcher) {
return true;
}
};
if (getInstance() == null)
return UserRights.ALLOW_ALL;
// else if there are rights (and thus users) but no user is defined, use the default rights
else if (mngr.getCurrentUser() == null)
return new UserRights(mngr.getTable().getUndefinedID());
76,15 → 142,25
private final Map<String, MacroRight> macroRights;
// {user -> {code -> [<object, bool>]}}
private final Map<Integer, CollectionMap<String, Tuple2<String, Boolean>>> rights;
private SQLTable table;
private final SQLTable table;
@GuardedBy("this")
private SQLTableModifiedListener tableL;
private final CollectionMap<Integer, RightTuple> javaRights;
 
private UserRightsManager() {
private UserRightsManager(final SQLTable t) {
if (t == null)
throw new NullPointerException("Missing table");
this.macroRights = new HashMap<String, MacroRight>();
this.rights = new HashMap<Integer, CollectionMap<String, Tuple2<String, Boolean>>>();
this.javaRights = new CollectionMap<Integer, RightTuple>();
// lazy init, so as to not require a conf
this.table = null;
this.table = t;
this.tableL = new SQLTableModifiedListener() {
@Override
public void tableModified(final SQLTableEvent evt) {
rightsInvalid();
}
};
this.table.addTableModifiedListener(this.tableL);
defaultRegister();
}
 
92,10 → 168,12
* enregistre les instances gérants les droits
*/
private void defaultRegister() {
register(new LockAdminUserRight());
register(new TableAllRights(true));
register(new TableAllRights(false));
synchronized (DEFAULT_MACRO_RIGHTS) {
for (final MacroRight macroRight : DEFAULT_MACRO_RIGHTS) {
register(macroRight);
}
}
}
 
/**
* Ajoute une instance pour la gestion d'un droit
117,22 → 195,19
this.rightsInvalid();
}
 
public final boolean isValid() {
return this.getTable() != null;
public synchronized final boolean isValid() {
return this.tableL != null;
}
 
public final SQLTable getTable() {
if (this.table == null) {
this.table = Configuration.getInstance().getRoot().findTable("USER_RIGHT");
if (this.table != null) {
this.table.addTableModifiedListener(new SQLTableModifiedListener() {
@Override
public void tableModified(final SQLTableEvent evt) {
rightsInvalid();
public synchronized final void destroy() {
if (this.isValid()) {
this.getTable().removeTableModifiedListener(this.tableL);
this.tableL = null;
}
});
assert !this.isValid();
}
}
 
public final SQLTable getTable() {
return this.table;
}
 
264,7 → 339,7
private final CollectionMap<String, Tuple2<String, Boolean>> loadRightsForUser(final int userID) {
try {
final SQLRow userRow = this.getTable().getForeignTable("ID_USER_COMMON").getRow(userID);
if (userRow != null && userRow.getBoolean("SUPERUSER"))
if (userRow != null && userRow.getBoolean(SUPERUSER_FIELD))
return SUPERUSER_RIGHTS;
 
final CollectionMap<String, Tuple2<String, Boolean>> res = new CollectionMap<String, Tuple2<String, Boolean>>(ArrayList.class);
272,7 → 347,7
// only superuser can modify RIGHTs
expand(res, unicity, TableAllRights.createRight(TableAllRights.CODE_MODIF, this.getTable().getForeignTable("ID_RIGHT"), false));
// only admin can modify or see USER_RIGHTs
expand(res, unicity, TableAllRights.createRight(TableAllRights.CODE, this.getTable(), userRow != null && userRow.getBoolean("ADMIN")));
expand(res, unicity, TableAllRights.createRight(TableAllRights.CODE, this.getTable(), userRow != null && userRow.getBoolean(ADMIN_FIELD)));
 
// java rights have priority over SQL rights
for (final RightTuple t : this.javaRights.getNonNull(userID)) {
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/JListSQLTablePanel.java
13,8 → 13,8
package org.openconcerto.sql.users.rights;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.ComboSQLRequest;
27,8 → 27,6
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;
 
import javax.swing.JLabel;
import javax.swing.JList;
76,9 → 74,8
}
};
 
public static ComboSQLRequest createComboRequest(final SQLTable table, boolean withUndef) {
 
ComboSQLRequest request = Configuration.getInstance().getDirectory().getElement(table).getComboRequest(true);
public static ComboSQLRequest createComboRequest(final SQLElement element, boolean withUndef) {
final ComboSQLRequest request = element.getComboRequest(true);
request.setFieldSeparator(" ");
if (withUndef) {
// add undefined
88,7 → 85,7
public SQLSelect transformChecked(SQLSelect input) {
if (trans != null)
input = trans.transformChecked(input);
input.setExcludeUndefined(false, table);
input.setExcludeUndefined(false, request.getPrimaryTable());
return input;
}
});
132,7 → 129,7
c.weighty = 0;
c.gridy++;
c.fill = GridBagConstraints.HORIZONTAL;
this.add(new JLabel("Rechercher"), c);
this.add(new JLabel(TM.tr("search")), c);
 
// Filtre
c.gridx++;
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRights.java
17,6 → 17,7
import static org.openconcerto.sql.users.rights.TableAllRights.DELETE_ROW_TABLE;
import static org.openconcerto.sql.users.rights.TableAllRights.MODIFY_ROW_TABLE;
import static org.openconcerto.sql.users.rights.TableAllRights.VIEW_ROW_TABLE;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.CompareUtils.Equalizer;
31,6 → 32,14
* @see #canModify(SQLTable)
*/
public class UserRights {
 
static public final UserRights ALLOW_ALL = new UserRights(SQLRow.NONEXISTANT_ID) {
@Override
public boolean haveRight(final String code, final String object, final Equalizer<? super String> objectMatcher) {
return true;
}
};
 
private final int userID;
 
public UserRights(final int userID) {
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsPanel.java
13,6 → 13,10
package org.openconcerto.sql.users.rights;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ListSQLRequest;
25,7 → 29,6
 
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Arrays;
 
import javax.swing.JLabel;
import javax.swing.JPanel;
41,16 → 44,21
private final ListeModifyPanel modifPanel;
 
public UserRightsPanel() {
this(Configuration.getInstance().getDirectory());
}
 
public UserRightsPanel(final SQLElementDirectory dir) {
super(new GridBagLayout());
 
// init the list before adding it, otherwise we see the first refresh from all lines to just
// these of undef
// instantiate our own element to be safe
this.modifPanel = new ListeModifyPanel(new UserRightSQLElement());
this.modifPanel = new ListeModifyPanel(dir.getElement(UserRightSQLElement.class));
this.modifPanel.setSearchFullMode(false);
final SQLTable table = this.getTable().getForeignTable("ID_USER_COMMON");
 
this.list = new JListSQLTablePanel(JListSQLTablePanel.createComboRequest(table, true), "Droits par défaut");
final SQLElement usersElem = dir.getElement(table);
this.list = new JListSQLTablePanel(JListSQLTablePanel.createComboRequest(usersElem, true), TM.tr("rightsPanel.defaultRights"));
// only superusers can see superusers (that's how we prevent the setting of superuser
// rights)
if (!UserRightsManager.getCurrentUserRights().isSuperUser())
75,7 → 83,7
GridBagConstraints c = new DefaultGridBagConstraints();
c.weightx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
listePanel.add(new JLabel("Liste des utilisateurs"), c);
listePanel.add(new JLabel(TM.getInstance().trM("element.list", "element", usersElem.getName())), c);
 
c.weightx = 1;
c.weighty = 1;
87,7 → 95,7
JPanel panelDroits = new JPanel(new GridBagLayout());
GridBagConstraints c2 = new DefaultGridBagConstraints();
c2.gridwidth = GridBagConstraints.REMAINDER;
panelDroits.add(new JLabel("Droits"), c2);
panelDroits.add(new JLabel(TM.tr("rights")), c2);
c2.gridy++;
c2.weightx = 1;
c2.weighty = 0.7;
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightSQLElement.java
14,24 → 14,57
package org.openconcerto.sql.users.rights;
 
import static java.util.Arrays.asList;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.ConfSQLElement;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.UISQLComponent;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.sqlobject.ElementComboBox;
import org.openconcerto.sql.sqlobject.SQLRequestComboBox;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.i18n.I18nUtils;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
 
public class UserRightSQLElement extends ConfSQLElement {
public static final String TABLE_NAME = "USER_RIGHT";
 
static public List<SQLCreateTable> getCreateTables(final SQLTable userT) {
final DBRoot root = userT.getDBRoot();
final SQLTable t = root.findTable(TABLE_NAME, false);
if (t != null) {
return Collections.emptyList();
}
 
final List<SQLCreateTable> res = new ArrayList<SQLCreateTable>();
final SQLCreateTable create = new SQLCreateTable(root, TABLE_NAME);
create.addForeignColumn(userT.getName());
if (root.contains(RightSQLElement.TABLE_NAME)) {
create.addForeignColumn(RightSQLElement.TABLE_NAME);
} else {
final SQLCreateTable createRight = RightSQLElement.getCreateTable(root);
res.add(createRight);
create.addForeignColumn(createRight);
}
// NULL meaning any
create.addColumn("OBJECT", "varchar(150) NULL DEFAULT NULL");
create.addColumn("HAVE_RIGHT", "boolean NOT NULL");
res.add(create);
 
return res;
}
 
public UserRightSQLElement() {
super("USER_RIGHT", "un droit utilisateur", "droits utilisateurs");
super(TABLE_NAME);
this.setL18nPackageName(I18nUtils.getPackageName(TM.class));
}
 
protected List<String> getListFields() {
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/RightSQLElement.java
13,11 → 13,15
package org.openconcerto.sql.users.rights;
 
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.ConfSQLElement;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.UISQLComponent;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.ui.component.ITextArea;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.i18n.I18nUtils;
 
import java.util.Arrays;
import java.util.List;
24,8 → 28,19
 
public class RightSQLElement extends ConfSQLElement {
 
public static final String TABLE_NAME = "RIGHT";
 
static public SQLCreateTable getCreateTable(final DBRoot root) {
final SQLCreateTable res = new SQLCreateTable(root, TABLE_NAME);
res.addVarCharColumn("CODE", 128);
res.addVarCharColumn("NOM", 256);
res.addVarCharColumn("DESCRIPTION", 500);
return res;
}
 
public RightSQLElement() {
super("RIGHT", "un droit", "droits");
super(TABLE_NAME);
this.setL18nPackageName(I18nUtils.getPackageName(TM.class));
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserCommonSQLElement.java
14,6 → 14,7
package org.openconcerto.sql.users;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.BaseSQLComponent;
import org.openconcerto.sql.element.ConfSQLElement;
import org.openconcerto.sql.element.SQLComponent;
29,6 → 30,7
import org.openconcerto.ui.warning.JLabelWarning;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.checks.ValidState;
import org.openconcerto.utils.i18n.I18nUtils;
import org.openconcerto.utils.text.SimpleDocumentListener;
 
import java.awt.GridBagConstraints;
55,7 → 57,8
public static final String LEGACY_PASSWORDS = "org.openconcerto.sql.legacyPasswords";
 
public UserCommonSQLElement() {
super("USER_COMMON", "un utilisateur", "utilisateurs");
super("USER_COMMON");
this.setL18nPackageName(I18nUtils.getPackageName(TM.class));
}
 
@Override
126,7 → 129,7
final JLabelWarning labelWarning = new JLabelWarning();
// labelWarning.setBorder(null);
this.panelWarning.add(labelWarning, c);
final JLabel labelTextWarning = new JLabel("Confirmation incorrecte");
final JLabel labelTextWarning = new JLabel(TM.tr("user.passwordsDontMatch.short"));
// labelTextWarning.setBorder(null);
c.gridx++;
this.panelWarning.add(labelTextWarning, c);
172,7 → 175,7
// Confirmation password
c.gridx++;
c.weightx = 0;
final JLabel labelConfirmationPass = new JLabel("Confirmation");
final JLabel labelConfirmationPass = new JLabel(getLabelFor("PASSWORD_CONFIRM"));
labelConfirmationPass.setHorizontalAlignment(SwingConstants.RIGHT);
this.add(labelConfirmationPass, c);
this.passFieldConfirm = new JPasswordField(15);
346,7 → 349,7
 
@Override
public synchronized ValidState getValidState() {
return super.getValidState().and(ValidState.createCached(checkValidityPassword(), "Les mots de passe ne correspondent pas"));
return super.getValidState().and(ValidState.createCached(checkValidityPassword(), TM.tr("user.passwordsDontMatch")));
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserManager.java
39,11 → 39,16
return instance;
}
 
public static final int getUserID() {
public static final User getUser() {
final UserManager mngr = getInstance();
return mngr == null || mngr.getCurrentUser() == null ? SQLRow.NONEXISTANT_ID : mngr.getCurrentUser().getId();
return mngr == null ? null : mngr.getCurrentUser();
}
 
public static final int getUserID() {
final User user = getUser();
return user == null ? SQLRow.NONEXISTANT_ID : user.getId();
}
 
private final SQLTable t;
private final Map<Integer, User> byID;
private boolean dirty;
/trunk/OpenConcerto/src/org/openconcerto/sql/RemoteShell.java
New file
0,0 → 1,297
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ExceptionUtils;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
 
/**
* Server allowing to evaluate JavaScript commands from a remote client.
*
* @author Sylvain CUAZ
*/
public final class RemoteShell extends Thread {
 
public static final String START_DEFAULT_SERVER = "org.openconcerto.sql.RemoteShell";
public static final int PORT = 1394;
private static RemoteShell INSTANCE = null;
 
static synchronized public final boolean startDefaultInstance() {
if (INSTANCE == null && Boolean.getBoolean(START_DEFAULT_SERVER)) {
setDefaultInstance(new RemoteShell());
return true;
} else {
return false;
}
}
 
static public final void setDefaultInstance(RemoteShell i) {
if (i.getState() == State.TERMINATED)
throw new IllegalArgumentException("Dead shell : " + i);
synchronized (RemoteShell.class) {
INSTANCE = i;
if (i.getState() == State.NEW)
i.start();
}
}
 
static synchronized public final RemoteShell getDefaultInstance() {
return INSTANCE;
}
 
private PrintWriter out;
 
private Thread thread;
 
private final ScriptEngine engine;
private final Bindings ognlCtxt;
 
/**
* Create a new server which will listen on the first available port from {@value #PORT} when
* {@link #start() started}.
*/
public RemoteShell() {
super(RemoteShell.class.getSimpleName() + " (not started)");
this.setDaemon(true);
 
this.engine = new ScriptEngineManager().getEngineByName("javascript");
this.ognlCtxt = new SimpleBindings();
}
 
private Bindings getContext() {
this.ognlCtxt.put("base", this.getBase());
this.ognlCtxt.put("dir", Configuration.getInstance().getDirectory());
return this.ognlCtxt;
}
 
/**
* Crée la socket du serveur.
*
* @return la socket ou <code>null</code> si pb.
*/
private ServerSocket createSocket() {
ServerSocket serverSocket = null;
int port = PORT;
while (serverSocket == null) {
try {
serverSocket = new ServerSocket(port);
Log.get().info("listening on " + port);
this.setName(RemoteShell.class.getSimpleName() + " on port " + port);
} catch (BindException e) {
Log.get().config("port " + port + " already in use");
// on essaye le suivant
port++;
} catch (IOException e) {
Log.get().warning("cannot create a socket");
e.printStackTrace();
return null;
}
}
return serverSocket;
}
 
public void run() {
final ServerSocket serverSocket = this.createSocket();
if (serverSocket == null)
return;
 
while (true) {
try {
Socket clientSocket = serverSocket.accept();
Log.get().info("accepted");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
this.out = new PrintWriter(clientSocket.getOutputStream(), true);
this.out.println("Console d'administration: " + clientSocket.getLocalSocketAddress() + " <-> " + clientSocket.getRemoteSocketAddress());
this.out.println("help (ou h) pour l'aide");
this.out.println("quit (ou q) pour quitter");
this.out.flush();
String inputLine;
while ((inputLine = in.readLine()) != null) {
if (inputLine.equals("q") || inputLine.startsWith("quit"))
break;
if (inputLine.equals("h") || inputLine.startsWith("help"))
printHelp();
 
try {
this.out.println(getAnswer(inputLine));
} catch (Exception exn) {
exn.printStackTrace();
exn.printStackTrace(this.out);
}
this.out.print(">");
this.out.flush();
}
 
in.close();
if (this.thread != null)
this.thread.interrupt();
this.out.close();
clientSocket.close();
Log.get().info("closed");
} catch (IOException e) {
e.printStackTrace();
}
}
// TODO detect when app quit to clean up
// serverSocket.close();
}
 
private void printHelp() {
this.out.println("Commandes possibles:");
this.out.println("req : affiche la liste des requêtes en cours");
this.out.println("all : affiche les stats du logiciels");
this.out.println("gc : lance le garbage collector");
this.out.println("log LEVEL [loggerName] : définit un niveau de log");
this.out.println("unarchive TABLE ID : désarchive un élément d'une table (et propage le desarchivage)");
this.out.println("\t ex: unarchive SITE 125 va désarchiver BATIMENTs etc...");
this.out.println("set : définit une variable OGNL ou affiche la liste");
this.out.println("\t ex: set siteT base.getTable('SITE')");
this.out.println("eval : evalue une expression OGNL");
this.out.println("\t ex: #siteT.getRow(123).getReferentRows()");
this.out.println();
this.out.flush();
}
 
private String getAnswer(String question) throws SQLException {
question = question.trim();
if (question.equals("req"))
return org.openconcerto.sql.State.INSTANCE.getRequests().toString();
else if (question.equals("all")) {
return org.openconcerto.sql.State.INSTANCE.getFull();
} else if (question.equals("gc")) {
System.gc();
return "System.gc() called.";
} else if (question.startsWith("log")) {
// log LEVEL [loggerName]
final String[] args = question.split(" ");
final String level = args[1];
final Level l;
try {
final Field f = Level.class.getField(level);
l = (Level) f.get(null);
} catch (Exception e) {
return "cannot get field '" + level + "' of Level";
}
final String name = args.length > 2 ? args[2] : "";
Logger.getLogger(name).setLevel(l);
return "logger '" + name + "' level set to " + l.getName();
} else if (question.equals("allc")) {
this.thread = new Thread() {
public void run() {
int i = 0;
while (true) {
org.openconcerto.sql.RemoteShell.this.out.println("--------------");
org.openconcerto.sql.RemoteShell.this.out.println("Iteration: " + i++);
org.openconcerto.sql.RemoteShell.this.out.println("--------------");
org.openconcerto.sql.RemoteShell.this.out.println(org.openconcerto.sql.State.INSTANCE.getFull());
org.openconcerto.sql.RemoteShell.this.out.println("\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// s'arrêter
break;
}
}
}
};
this.thread.start();
return "beginning";
} else if (question.startsWith("unarchive")) {
return archive(question, false);
} else if (question.startsWith("archive")) {
return archive(question, true);
} else if (question.startsWith("eval")) {
try {
return ognlResult(eval(question.substring("eval".length())));
} catch (ScriptException e) {
return ExceptionUtils.getStackTrace(e);
}
} else if (question.startsWith("set")) {
final String[] args = question.split(" ", 3);
if (args.length == 1) {
return CollectionUtils.join(this.ognlCtxt.entrySet(), "\n");
} else {
try {
final Object res = eval(args[2]);
this.ognlCtxt.put(args[1], res);
return ognlResult(res);
} catch (ScriptException e) {
return ExceptionUtils.getStackTrace(e);
}
}
} else
return "commande non connue.";
}
 
private String archive(String question, boolean arch) throws SQLException {
final String[] args = question.split(" ");
final String tableName = args[1];
final int id = Integer.parseInt(args[2]);
final String s;
if (arch) {
this.getElement(tableName).archive(id);
s = "archived";
} else {
this.getElement(tableName).unarchive(id);
s = "unarchived";
}
return s + " " + this.getBase().getTable(tableName).getRow(id);
}
 
public final Object eval(String s) throws ScriptException {
return this.engine.eval(s, this.getContext());
}
 
private String ognlResult(Object res) {
return res == null ? "<no result>" : res.toString();
}
 
private final SQLBase getBase() {
return Configuration.getInstance().getBase();
}
 
private final SQLElement getElement(String tableName) {
return Configuration.getInstance().getDirectory().getElement(tableName);
}
 
/**
* Put an object in the context, it can then be accessed by "#<code>s</code>".
*
* @param s the name of the value, eg "count".
* @param val the value, eg 3.
*/
public void put(String s, Object val) {
this.ognlCtxt.put(s, val);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/AddUserRight.java
New file
0,0 → 1,68
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.changer.convert;
 
import org.openconcerto.sql.changer.Changer;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRightSQLElement;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.sql.utils.AlterTable;
import org.openconcerto.sql.utils.SQLCreateTable;
 
import java.sql.SQLException;
import java.util.List;
 
/**
* Add rights tables.
*
* @author Sylvain
* @see {@link UserRightsManager}
*/
public class AddUserRight extends Changer<DBRoot> {
 
public AddUserRight(DBSystemRoot b) {
super(b);
}
 
@Override
protected void changeImpl(DBRoot r) throws SQLException {
this.getStream().println(r + "... ");
final UserManager userMngr = UserManager.getInstance();
if (userMngr == null)
throw new IllegalStateException("No user manager");
final SQLTable userT = userMngr.getTable();
 
final List<SQLCreateTable> createTables = UserRightSQLElement.getCreateTables(userT);
if (createTables.size() == 0) {
getStream().println("Tables already created");
} else {
r.createTables(createTables);
getStream().println("Tables created");
}
 
final AlterTable alterTable = new AlterTable(userT);
for (final String f : new String[] { UserRightsManager.SUPERUSER_FIELD, UserRightsManager.ADMIN_FIELD }) {
if (!userT.contains(f))
alterTable.addColumn(f, "boolean not null default false");
}
if (!alterTable.isEmpty()) {
getDS().execute(alterTable.toString());
r.getSchema().updateVersion();
getStream().println("Fields created in " + userT);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/correct/FixSharedPrivate.java
16,6 → 16,7
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.changer.Changer;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
40,16 → 41,34
*/
public class FixSharedPrivate extends Changer<SQLTable> {
 
private final SQLElementDirectory dir;
 
public FixSharedPrivate(DBSystemRoot b) {
this(b, null);
}
 
public FixSharedPrivate(DBSystemRoot b, final SQLElementDirectory dir) {
super(b);
if (dir == null) {
if (Configuration.getInstance() == null)
throw new IllegalStateException("no conf");
this.dir = Configuration.getInstance().getDirectory();
if (this.dir == null)
throw new IllegalStateException("no directory in conf");
} else {
this.dir = dir;
}
assert this.dir != null;
}
 
public final SQLElementDirectory getDir() {
return this.dir;
}
@Override
protected void changeImpl(final SQLTable t) throws SQLException {
getStream().print(t);
if (Configuration.getInstance() == null || Configuration.getInstance().getDirectory() == null)
throw new IllegalStateException("no directory");
final SQLElement elem = Configuration.getInstance().getDirectory().getElement(t);
final SQLElement elem = this.getDir().getElement(t);
if (elem == null) {
getStream().println(" : no element");
return;
66,7 → 85,7
// where q.ID != 1
// GROUP BY q.ID
// HAVING count(q.ID) > 1;
final SQLSelect sel = new SQLSelect(t.getBase());
final SQLSelect sel = new SQLSelect();
sel.setArchivedPolicy(ArchiveMode.BOTH);
sel.addSelect(privateTable.getKey());
sel.addBackwardJoin("INNER", "m", t.getField(pff), null);
85,7 → 104,7
public Object create() throws SQLException {
// for each private pointed by more than one parent
for (final Number privateID : privateIDs) {
final SQLSelect fixSel = new SQLSelect(t.getBase());
final SQLSelect fixSel = new SQLSelect();
fixSel.setArchivedPolicy(ArchiveMode.BOTH);
fixSel.addSelect(t.getKey());
if (archF != null)
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchSpecUtils.java
32,4 → 32,16
return result;
}
 
static public final <T> T filterOne(final T obj, final SearchSpec search) {
if (obj == null)
return null;
 
final T result;
if (search == null || search.isEmpty() || search.match(obj))
result = obj;
else {
result = null;
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchItemComponent.java
13,6 → 13,7
package org.openconcerto.sql.view.search;
 
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.BaseSQLComponent;
import org.openconcerto.sql.view.search.TextSearchSpec.Mode;
import org.openconcerto.utils.CollectionMap;
27,6 → 28,8
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
50,10 → 53,13
import javax.swing.event.TableModelListener;
import javax.swing.text.BadLocationException;
 
import net.jcip.annotations.Immutable;
 
public class SearchItemComponent extends JPanel {
private static final String TOUT = "Tout";
private static final Column TOUT = new Column(TM.tr("all"), null, -1);
private static final Mode[] MODES = { Mode.CONTAINS, Mode.CONTAINS_STRICT, Mode.LESS_THAN, Mode.EQUALS, Mode.EQUALS_STRICT, Mode.GREATER_THAN };
 
@Immutable
protected static final class Column {
private final String label, id;
private final int index;
85,7 → 91,7
private JTextField textFieldRecherche = new JTextField(10);
private final JComboBox comboColonnePourRecherche;
private final JComboBox searchMode;
private JCheckBox invertSearch = new JCheckBox("inverser");
private JCheckBox invertSearch = new JCheckBox(TM.tr("toReverse"));
private JButton buttonAdd = new JButton("+");
private JButton buttonRemove = new JButton();
final SearchListComponent list;
96,9 → 102,12
this.list = list;
this.setOpaque(false);
// Initialisation de l'interface graphique
this.searchMode = new JComboBox(new String[] { "Contient", "Contient exactement", "Est inférieur à", "Est égal à", "Est exactement égal à", "Est supérieur à", "Est vide" });
final ListComboBoxModel comboModel = new ListComboBoxModel();
this.searchMode = new JComboBox(new String[] { TM.tr("contains"), TM.tr("contains.exactly"), TM.tr("isLessThan"), TM.tr("isEqualTo"), TM.tr("isExactlyEqualTo"), TM.tr("isGreaterThan"),
TM.tr("isEmpty") });
final ListComboBoxModel comboModel = new ListComboBoxModel(Arrays.asList(TOUT));
comboModel.setSelectOnAdd(false);
// allow getColIndex() and thus getSearchItem() to work from now on
assert comboModel.getSelectedItem() != null;
this.comboColonnePourRecherche = new JComboBox(comboModel);
uiInit();
}
201,11 → 210,9
updateSearchList();
}
};
fillColumnCombo(listener);
selectAllColumnsItem();
this.searchMode.addItemListener(listener);
 
this.list.getTableModel().addTableModelListener(new TableModelListener() {
final TableModelListener tableModelL = new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getColumn() == TableModelEvent.ALL_COLUMNS && e.getFirstRow() == TableModelEvent.HEADER_ROW) {
212,7 → 219,22
columnsChanged(listener);
}
}
};
// allow the TableModel to die
this.addHierarchyListener(new HierarchyListener() {
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
if (e.getChanged().isDisplayable()) {
columnsChanged(listener);
SearchItemComponent.this.list.getTableModel().addTableModelListener(tableModelL);
} else {
SearchItemComponent.this.list.getTableModel().removeTableModelListener(tableModelL);
}
}
}
});
// that way the TableModelListener will get added automatically
assert !this.isDisplayable();
}
 
private void selectAllColumnsItem() {
231,7 → 253,7
// use column index as columns names are not unique
final SortedMap<String, Integer> map = solve(names, indexes);
final List<Column> cols = new ArrayList<Column>(columnCount);
cols.add(new Column(TOUT, null, -1));
cols.add(TOUT);
for (final Entry<String, Integer> e : map.entrySet()) {
final int colIndex = e.getValue().intValue();
final String[] colNames = names[colIndex];
/trunk/OpenConcerto/src/org/openconcerto/sql/view/listview/ListSQLView.java
21,9 → 21,8
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.utils.checks.EmptyChangeSupport;
import org.openconcerto.utils.checks.EmptyListener;
import org.openconcerto.utils.checks.EmptyObject;
import org.openconcerto.utils.checks.EmptyObjectHelper;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidObject;
import org.openconcerto.utils.checks.ValidState;
45,7 → 44,6
 
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
 
/**
* A SQLRowItemView that edits a list.
52,7 → 50,7
*
* @author Sylvain CUAZ
*/
public class ListSQLView extends JPanel implements SQLRowItemView, EmptyObject {
public class ListSQLView extends JPanel implements SQLRowItemView {
 
private final SQLComponent parent;
private final String name;
60,7 → 58,7
private final List<ListItemSQLView> items;
 
private final PropertyChangeSupport supp;
private final EmptyObjectHelper helper;
private final EmptyChangeSupport helper;
 
private final JButton addBtn;
private final JPanel itemsPanel;
89,11 → 87,7
}
});
 
this.helper = new EmptyObjectHelper(this, new Predicate() {
public boolean evaluate(Object object) {
return ((List) object).isEmpty();
}
});
this.helper = new EmptyChangeSupport(this);
 
this.addBtn = new JButton("Ajout");
// for when an item is removed the following ones go up
152,6 → 146,7
this.itemsPanel.add(newItem, this.itemsConstraints);
this.itemsConstraints.gridy++;
this.supp.firePropertyChange("value", null, null);
this.helper.fireEmptyChange(this.isEmpty());
}
 
protected final void removeItem(ListItemSQLView viewToRemove) {
162,6 → 157,7
this.revalidate();
this.getPool().removeItem(viewToRemove.getRowItemView());
this.supp.firePropertyChange("value", null, null);
this.helper.fireEmptyChange(this.isEmpty());
}
 
public final SQLComponent getSQLParent() {
235,22 → 231,21
return this;
}
 
@Override
public boolean isEmpty() {
return this.helper.isEmpty();
return this.getPool().getItems().isEmpty();
}
 
public Object getValue() throws IllegalStateException {
return this.helper.getValue();
@Override
public void addEmptyListener(EmptyListener l) {
this.helper.addEmptyListener(l);
}
 
public Object getUncheckedValue() {
return this.getPool().getItems();
@Override
public void removeEmptyListener(EmptyListener l) {
this.helper.removeEmptyListener(l);
}
 
public void addEmptyListener(EmptyListener l) {
this.helper.addListener(l);
}
 
public void addValueListener(PropertyChangeListener l) {
this.supp.addPropertyChangeListener(l);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelColumnPath.java
20,6 → 20,8
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.RowItemDesc;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.utils.CollectionUtils;
 
import java.sql.SQLException;
35,8 → 37,16
*/
public class SQLTableModelColumnPath extends SQLTableModelColumn {
 
private static final RowItemDesc getDescFor(SQLField field) {
final Configuration conf = Configuration.getInstance();
if (conf == null)
return SQLFieldTranslator.getDefaultDesc(field);
else
return conf.getTranslator().getDescFor(field.getTable(), field.getName());
}
 
private static final String getLabelFor(SQLField field) {
return Configuration.getInstance().getTranslator().getLabelFor(field);
return getDescFor(field).getLabel();
}
 
private final FieldPath p;
51,7 → 61,7
}
 
public SQLTableModelColumnPath(FieldPath fp) {
this(fp, Configuration.getInstance().getTranslator().getTitleFor(fp.getField()));
this(fp, getDescFor(fp.getField()).getTitleLabel());
}
 
public SQLTableModelColumnPath(FieldPath fp, final String name) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/search/SearchQueue.java
15,9 → 15,12
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.Step;
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.sql.view.list.LineListener;
import org.openconcerto.sql.view.list.ListAccess;
26,6 → 29,7
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.IFutureTask;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SleepingQueue;
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.cc.ITransformer;
50,6 → 54,19
return (f instanceof IFutureTask) && ((IFutureTask<?>) f).getRunnable() instanceof SearchRunnable;
}
 
/**
* The last referent step of the passed path.
*
* @param p a path.
* @return the name of the field of the last referent step, <code>null</code> if it doesn't
* exist.
*/
public static String getLastReferentField(final Path p) {
final Step lastStep = p.length() == 0 ? null : p.getStep(-1);
final boolean lastIsForeign = lastStep == null || lastStep.getDirection() == Direction.FOREIGN;
return lastIsForeign ? null : lastStep.getSingleField().getName();
}
 
private final ITableModel model;
SearchSpec search;
private final List<ListSQLLine> fullList;
81,23 → 98,22
/**
* The lines and their path affected by a change of the passed row.
*
* @param t the table that has changed.
* @param id the id that has changed.
* @param r the row that has changed.
* @return the refreshed lines and their changed paths.
*/
public CollectionMap<ListSQLLine, Path> getAffectedLines(final SQLTable t, final int id) {
return this.execGetAffected(t, id, new CollectionMap<ListSQLLine, Path>(), true);
public CollectionMap<ListSQLLine, Path> getAffectedLines(final SQLRow r) {
return this.execGetAffected(r, new CollectionMap<ListSQLLine, Path>(), true);
}
 
public CollectionMap<Path, ListSQLLine> getAffectedPaths(final SQLTable t, final int id) {
return this.execGetAffected(t, id, new CollectionMap<Path, ListSQLLine>(), false);
public CollectionMap<Path, ListSQLLine> getAffectedPaths(final SQLRow r) {
return this.execGetAffected(r, new CollectionMap<Path, ListSQLLine>(), false);
}
 
private <K, V> CollectionMap<K, V> execGetAffected(final SQLTable t, final int id, final CollectionMap<K, V> res, final boolean byLine) {
private <K, V> CollectionMap<K, V> execGetAffected(final SQLRow r, final CollectionMap<K, V> res, final boolean byLine) {
return this.execute(new Callable<CollectionMap<K, V>>() {
@Override
public CollectionMap<K, V> call() throws Exception {
return getAffected(t, id, res, byLine);
return getAffected(r, res, byLine);
}
});
}
120,13 → 136,15
}
 
// must be called from within this queue, as this method use fullList
private <K, V> CollectionMap<K, V> getAffected(final SQLTable t, final int id, CollectionMap<K, V> res, boolean byLine) {
private <K, V> CollectionMap<K, V> getAffected(final SQLRow r, CollectionMap<K, V> res, boolean byLine) {
final SQLTable t = r.getTable();
final int id = r.getID();
if (id < SQLRow.MIN_VALID_ID)
throw new IllegalArgumentException("invalid ID: " + id);
if (!this.fullList.isEmpty()) {
final SQLRowValues proto = this.getModel().getLinesSource().getParent().getMaxGraph();
final List<Path> pathsToT = new ArrayList<Path>();
proto.walkGraph(pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
@Override
public List<Path> transformChecked(State<List<Path>> input) {
if (input.getCurrent().getTable() == t) {
134,12 → 152,28
}
return input.getAcc();
}
});
}, RecursionType.BREADTH_FIRST, null);
for (final Path p : pathsToT) {
final String lastReferentField = getLastReferentField(p);
for (final ListSQLLine line : this.fullList) {
final SQLRowValues current = line.getRow().followPath(p);
boolean put = false;
for (final SQLRowValues current : line.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
// works for rowValues w/o any ID
if (current != null && current.getID() == id) {
put = true;
}
}
// if the modified row isn't in the existing line, it might still affect it if
// it's a referent row insertion
if (!put && lastReferentField != null && r.exists()) {
final int foreignID = r.getInt(lastReferentField);
for (final SQLRowValues current : line.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false)) {
if (current.getID() == foreignID) {
put = true;
}
}
}
if (put) {
// add to the list of paths that have been refreshed
if (byLine)
res.put(line, p);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/KeyTableCellRenderer.java
16,6 → 16,7
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable.ListenerAndConfig;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.SQLTableModifiedListener;
97,7 → 98,7
m.put(comboSelectionItem.getId(), comboSelectionItem);
}
cacheMap.put(KeyTableCellRenderer.this.el, m);
KeyTableCellRenderer.this.el.getTable().addPremierTableModifiedListener(new SQLTableModifiedListener() {
KeyTableCellRenderer.this.el.getTable().addPremierTableModifiedListener(new ListenerAndConfig(new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
final int id = evt.getId();
106,7 → 107,7
else
m.put(id, KeyTableCellRenderer.this.el.getComboRequest().getComboItem(id));
}
});
}, true));
 
KeyTableCellRenderer.this.isLoading = false;
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListe.java
17,6 → 17,7
import org.openconcerto.openoffice.spreadsheet.SpreadSheet;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLField;
85,7 → 86,6
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
157,7 → 157,6
}
 
private static boolean FORCE_ALT_CELL_RENDERER = false;
private static final DateFormat MODIF_DATE_FORMAT = new SimpleDateFormat("'le' dd MMMM yyyy 'à' HH:mm:ss");
static final String SEP = " ► ";
public static final TableCellRenderer DATE_RENDERER = new FormatRenderer(DateFormat.getDateInstance(DateFormat.MEDIUM));
public static final TableCellRenderer TIME_RENDERER = new FormatRenderer(DateFormat.getTimeInstance(DateFormat.SHORT));
286,10 → 285,10
 
final SQLRowValues row = ITableModel.getLine(this.getModel(), rowIndex).getRow();
 
final String create = getLine("Créée", row, getSource().getPrimaryTable().getCreationUserField(), getSource().getPrimaryTable().getCreationDateField());
final String create = getLine(true, row, getSource().getPrimaryTable().getCreationUserField(), getSource().getPrimaryTable().getCreationDateField());
if (create != null)
infoL.add(create);
final String modif = getLine("Modifiée", row, getSource().getPrimaryTable().getModifUserField(), getSource().getPrimaryTable().getModifDateField());
final String modif = getLine(false, row, getSource().getPrimaryTable().getModifUserField(), getSource().getPrimaryTable().getModifDateField());
if (modif != null)
infoL.add(modif);
 
302,20 → 301,25
return info;
}
 
public String getLine(final String verb, final SQLRowValues row, final SQLField userF, final SQLField dateF) {
public String getLine(final boolean created, final SQLRowValues row, final SQLField userF, final SQLField dateF) {
final Calendar date = dateF == null ? null : row.getDate(dateF.getName());
final SQLRowAccessor user = userF == null || row.isForeignEmpty(userF.getName()) ? null : row.getForeign(userF.getName());
if (user == null && date == null)
return null;
 
String res = verb;
if (user != null)
res += " par " + user.getString("PRENOM") + " " + user.getString("NOM");
final int userParam;
final String firstName, lastName;
if (user != null) {
userParam = 1;
firstName = user.getString("PRENOM");
lastName = user.getString("NOM");
} else {
userParam = 0;
firstName = null;
lastName = null;
}
 
if (date != null)
res += " " + MODIF_DATE_FORMAT.format(date.getTime());
 
return res;
return TM.tr("ilist.metadata", created ? 1 : 0, userParam, firstName, lastName, date == null ? 0 : 1, date == null ? null : date.getTime());
}
 
@Override
603,7 → 607,7
private final JPopupMenu popupMenu;
{
this.popupMenu = new JPopupMenu();
this.popupMenu.add(new JCheckBoxMenuItem(new AbstractAction("Ajuster la largeur des colonnes") {
this.popupMenu.add(new JCheckBoxMenuItem(new AbstractAction(TM.tr("ilist.setColumnsWidth")) {
@Override
public void actionPerformed(ActionEvent e) {
toggleAutoAdjust();
880,7 → 884,7
}
 
public void deplacerDe(final int inc) {
this.getModel().moveBy(this.getSelectedId(), inc);
this.getModel().moveBy(this.getSelectedRows(), inc);
}
 
/**
927,6 → 931,14
}
 
public final List<SQLRowAccessor> getSelectedRows() {
return iterateSelectedRows(SQLRowAccessor.class);
}
 
public final List<SQLRowValues> copySelectedRows() {
return iterateSelectedRows(SQLRowValues.class);
}
 
private final <R extends SQLRowAccessor> List<R> iterateSelectedRows(final Class<R> clazz) {
final ListSelectionModel selectionModel = this.getJTable().getSelectionModel();
if (selectionModel.isSelectionEmpty())
return Collections.emptyList();
933,11 → 945,13
 
final int start = selectionModel.getMinSelectionIndex();
final int stop = selectionModel.getMaxSelectionIndex();
final List<SQLRowAccessor> res = new ArrayList<SQLRowAccessor>();
final List<R> res = new ArrayList<R>();
for (int i = start; i <= stop; i++) {
if (selectionModel.isSelectedIndex(i))
res.add(new SQLImmutableRowValues(this.getLine(i).getRow()));
if (selectionModel.isSelectedIndex(i)) {
final SQLRowValues internalRow = this.getLine(i).getRow();
res.add(clazz.cast(clazz == SQLRowValues.class ? internalRow.deepCopy() : new SQLImmutableRowValues(internalRow)));
}
}
return res;
}
 
1165,7 → 1179,10
}
 
private void loadTableState() {
if (this.getConfigFile() != null)
// - if configFile changes setConfigFile() calls us
// - if the model changes, fireTableStructureChanged() is called and thus
// JTable.createDefaultColumnsFromModel() which calls us
if (this.getConfigFile() != null && this.getModel() != null)
this.tableStateManager.loadState();
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ChangeFKRunnable.java
13,6 → 13,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.CollectionMap;
 
24,15 → 25,15
private final Path p;
 
/**
* Change the foreign key at the end of the passed path to point to <code>id</code>.
* Furthermore it fetch the new id from the db and updates all other lines that are affected.
* Change the foreign key at the end of the passed path to point to <code>id</code>. Furthermore
* it fetch the new id from the DB and updates all other lines that are affected.
*
* @param l the line having a foreign key changed.
* @param p the path to the foreign key, eg RECEPTEUR.ID_OBSERVATION.ID_TENSION.
* @param p the path to the foreign key, e.g. RECEPTEUR.ID_OBSERVATION.ID_TENSION.
* @param id the new id.
*/
public ChangeFKRunnable(ListSQLLine l, Path p, int id) {
super(l.getSrc().getModel(), p.getLast(), id);
super(l.getSrc().getModel(), new SQLRow(p.getLast(), id));
this.l = l;
this.p = p;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ListSQLLine.java
14,9 → 14,12
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.utils.CollectionUtils;
 
import java.util.ArrayList;
142,15 → 145,50
/**
* Load the passed values into this row at the passed path.
*
* @param id ID of vals, needed when vals is <code>null</code>.
* @param vals values to load, eg CONTACT.NOM = "Dupont".
* @param p where to load the values, eg "SITE.ID_CONTACT_CHEF".
*/
void loadAt(SQLRowValues vals, Path p) {
final SQLRowValues current = this.getRow().assurePath(p);
void loadAt(int id, SQLRowValues vals, Path p) {
final String lastReferentField = SearchQueue.getLastReferentField(p);
// load() empties vals, so getFields() before
final Set<Integer> indexes = this.pathToIndex(p, vals.getFields());
final Set<Integer> indexes = lastReferentField == null ? this.pathToIndex(p, vals.getFields()) : null;
// replace our values with the new ones
current.load(vals, null);
if (lastReferentField == null) {
for (final SQLRowValues v : this.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
v.load(vals.deepCopy(), null);
}
} else {
// e.g. if p is SITE <- BATIMENT <- LOCAL, lastField is LOCAL.ID_BATIMENT
// if p is SITE -> CLIENT <- SITE (i.e. siblings of a site), lastField is SITE.ID_CLIENT
final SQLField lastField = p.getStep(-1).getSingleField();
final Collection<SQLRowValues> previous;
if (p.length() > 1 && p.getStep(-2).reverse().equals(p.getStep(-1)))
previous = this.getRow().followPath(p.minusLast(2), CreateMode.CREATE_NONE, false);
else
previous = null;
// the rows that vals should point to, e.g. BATIMENT or CLIENT
final Collection<SQLRowValues> targets = this.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false);
for (final SQLRowValues target : targets) {
// remove existing referent with the updated ID
SQLRowValues toRemove = null;
for (final SQLRowValues toUpdate : target.getReferentRows(lastField)) {
// don't back track (in the example a given SITE will be at the primary location
// and a second time along its siblings)
if ((previous == null || !previous.contains(toUpdate)) && toUpdate.getID() == id) {
if (toRemove != null)
throw new IllegalStateException("Duplicate IDs " + id + " : " + System.identityHashCode(toRemove) + " and " + System.identityHashCode(toUpdate) + "\n"
+ this.getRow().printGraph());
toRemove = toUpdate;
}
}
if (toRemove != null)
toRemove.remove(lastField.getName());
// attach updated values
if (vals != null && vals.getLong(lastField.getName()) == target.getIDNumber().longValue())
vals.deepCopy().put(lastField.getName(), target);
}
}
// update our cache
if (indexes == null)
this.clearCache();
166,11 → 204,11
* @return the index of columns using "CPI.ID_LOCAL.DESIGNATION", or null for every columns.
*/
private Set<Integer> pathToIndex(final Path p, final Collection<String> modifiedFields) {
if (containsFK(p.getLast(), modifiedFields))
// eg CPI.ID_LOCAL, easier to just refresh the whole line, than to search for each
// column affected (that would mean expanding the fk)
if (containsFK(p.getLast(), modifiedFields)) {
// e.g. CPI.ID_LOCAL, easier to just refresh the whole line, than to search for each
// column affected (that would mean expanding the FK)
return null;
else {
} else {
final Set<Integer> res = new HashSet<Integer>();
final Set<FieldPath> modifiedPaths = FieldPath.create(p, modifiedFields);
final List<? extends SQLTableModelColumn> cols = this.src.getParent().getAllColumns();
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ChangeAllRunnable.java
13,6 → 13,8
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.SQLRow;
 
import java.util.List;
 
/**
23,7 → 25,7
abstract class ChangeAllRunnable extends UpdateRunnable {
 
public ChangeAllRunnable(ITableModel model) {
super(model, model.getTable(), -1);
super(model, new SQLRow(model.getTable(), SQLRow.NONEXISTANT_ID));
}
 
public final void run() {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOffline.java
128,7 → 128,7
f.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
if (ListSQLRequest.lockSelect)
if (ListSQLRequest.getDefaultLockSelect())
input.addWaitPreviousWriteTXTable(getParent().getPrimaryTable().getName());
final Where w = new Where(getParent().getPrimaryTable().getKey(), "=", id);
return getParent().getFetcher().getSelTransf().transformChecked(input).andWhere(w);
170,7 → 170,7
@Override
public void commit(ListSQLLine l, Path path, SQLRowValues vals) {
checkCanModif(l, path);
l.loadAt(vals, path);
l.loadAt(vals.getID(), vals, path);
}
 
private synchronized void checkCanModif(ListSQLLine l, Path path) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateRunnable.java
13,6 → 13,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.view.list.search.SearchQueue;
55,15 → 56,14
}
 
private final ITableModel model;
private final SQLTable table;
private final int id;
private final SQLRow row;
 
protected UpdateRunnable(ITableModel model, SQLTable t, int id) {
protected UpdateRunnable(final ITableModel model, final SQLRow r) {
this.model = model;
this.table = t;
this.id = id;
this.row = r;
}
 
@Override
public final String toString() {
return this.getClass().getSimpleName() + "@" + this.hashCode() + " " + this.getTable() + "[" + this.getID() + "] on " + this.model;
}
80,11 → 80,15
return this.getModel().getSearchQueue();
}
 
protected final SQLRow getRow() {
return this.row;
}
 
protected final SQLTable getTable() {
return this.table;
return this.getRow().getTable();
}
 
protected final int getID() {
return this.id;
return this.getRow().getID();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSource.java
273,7 → 273,7
}
 
/**
* All the displayed tables, ie tables of {@link #getLineFields()}.
* All the displayed tables, i.e. tables of {@link #getLineFields()}.
*
* @return the displayed tables.
*/
282,15 → 282,20
}
 
/**
* All fields that affects a line of this source. Ie not just the displayed fields, but also the
* foreign keys (eg if this displays [LOCAL.DES, CPI.DES] CPI.ID_LOCAL matters).
* All fields that affects a line of this source. I.e. not just the displayed fields, but also
* the foreign keys, including intermediate ones (e.g. if this displays [BATIMENT.DES, CPI.DES]
* LOCAL.ID_BATIMENT matters).
*
* @return the fields affecting this.
*/
public final Set<SQLField> getLineFields() {
final Set<SQLField> res = new HashSet<SQLField>();
for (final SQLTableModelColumn col : this.getAllColumns())
res.addAll(col.getFields());
for (final SQLRowValues v : getMaxGraph().getGraph().getItems()) {
for (final String f : v.getFields())
res.add(v.getTable().getField(f));
if (v.getTable().isArchivable())
res.add(v.getTable().getArchiveField());
}
return res;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/MoveQueue.java
13,39 → 13,67
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.OrderComparator;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.UpdateBuilder;
import org.openconcerto.sql.utils.ReOrder;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.utils.DecimalUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.SleepingQueue;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
final class MoveQueue extends SleepingQueue {
public final class MoveQueue extends SleepingQueue {
 
private final ITableModel tableModel;
 
public MoveQueue(ITableModel model) {
MoveQueue(ITableModel model) {
super(MoveQueue.class.getSimpleName() + " on " + model);
this.tableModel = model;
}
 
public void move(final int id, final int inc) {
public void move(final List<? extends SQLRowAccessor> rows, final int inc) {
if (inc == 0 || rows.size() == 0)
return;
 
final boolean after = inc > 0;
final List<? extends SQLRowAccessor> l = new ArrayList<SQLRowAccessor>(rows);
Collections.sort(l, after ? Collections.reverseOrder(OrderComparator.INSTANCE) : OrderComparator.INSTANCE);
final int id = l.get(0).getID();
this.put(new Runnable() {
public void run() {
final FutureTask<Integer> destID = new FutureTask<Integer>(new Callable<Integer>() {
final FutureTask<ListSQLLine> destID = new FutureTask<ListSQLLine>(new Callable<ListSQLLine>() {
@Override
public Integer call() {
return MoveQueue.this.tableModel.getDestID(id, inc);
public ListSQLLine call() {
return MoveQueue.this.tableModel.getDestLine(id, inc);
}
});
MoveQueue.this.tableModel.invokeLater(destID);
try {
if (destID.get() != null) {
moveQuick(id, destID.get());
SQLUtils.executeAtomic(MoveQueue.this.tableModel.getTable().getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, Exception>() {
@Override
public Object handle(SQLDataSource ds) throws Exception {
moveQuick(l, after, destID.get().getRow().asRow());
return null;
}
});
}
} catch (Exception e) {
throw ExceptionUtils.createExn(IllegalStateException.class, "move failed", e);
}
53,18 → 81,105
});
}
 
final void moveQuick(final int srcId, final int destId) throws SQLException {
final SQLRow srcRow = this.getTable().getRow(srcId);
final SQLRow destRow = this.getTable().getRow(destId);
 
final boolean after = srcRow.getOrder().compareTo(destRow.getOrder()) < 0;
 
final SQLRowValues vals = srcRow.createEmptyUpdateRow();
vals.setOrder(destRow, after).update();
final void moveQuick(final List<? extends SQLRowAccessor> srcRows, final boolean after, final SQLRow destRow) throws SQLException {
final int rowCount = srcRows.size();
// if only some rows are moved, update one by one (avoids refreshing the whole list)
if (rowCount < 5 && rowCount < (this.tableModel.getRowCount() / 3)) {
final SQLRowValues vals = new SQLRowValues(getTable());
for (final SQLRowAccessor srcRow : srcRows) {
assert srcRow.getTable() == vals.getTable();
vals.setOrder(destRow, after).update(srcRow.getID());
}
} else {
// update all rows at once and refresh the whole list
moveAtOnce(getTable(), srcRows, rowCount, after, destRow);
}
}
 
private SQLTable getTable() {
return this.tableModel.getTable();
}
 
static public void moveAtOnce(final List<? extends SQLRowAccessor> srcRows, final boolean after, final SQLRow destRow) throws SQLException {
final int rowCount = srcRows.size();
if (rowCount == 0)
return;
 
final SQLTable t = srcRows.get(0).getTable();
moveAtOnce(t, new ArrayList<SQLRowAccessor>(srcRows), rowCount, after, destRow);
}
 
// srcRows will be modified
static private void moveAtOnce(final SQLTable t, final List<? extends SQLRowAccessor> srcRows, final int rowCount, final boolean after, final SQLRow destRow) throws SQLException {
// ULP * 10 to give a little breathing room
final BigDecimal minDistance = t.getOrderULP().scaleByPowerOfTen(1);
final BigDecimal places = BigDecimal.valueOf(rowCount + 1);
// the minimum room so that we can move all rows
final BigDecimal room = minDistance.multiply(places);
 
final BigDecimal destOrder = destRow.getOrder();
final SQLRow nextRow = destRow.getRow(true);
final BigDecimal inc;
final boolean destRowReordered;
if (nextRow == null) {
// if destRow is the last row, we can choose whatever increment we want
inc = ReOrder.DISTANCE;
// but we need to move destRow if we want to add before it
destRowReordered = false;
} else {
final BigDecimal nextOrder = nextRow.getOrder();
assert nextOrder.compareTo(destOrder) > 0;
final BigDecimal diff = nextOrder.subtract(destOrder);
if (diff.compareTo(room) < 0) {
// if there's not enough room, reorder to squeeze rows upwards
// since we keep increasing count, we will eventually reorder all rows afterwards
int count = 100;
final int tableRowCount = t.getRowCount();
boolean reordered = false;
while (!reordered) {
// only push destRow upwards if we want to add before
reordered = ReOrder.create(t, destOrder, !after, count, destOrder.add(room)).exec();
if (!reordered && count > tableRowCount)
throw new IllegalStateException("Unable to reorder " + count + " rows in " + t);
count *= 10;
}
inc = minDistance;
destRowReordered = true;
} else {
// truncate
inc = DecimalUtils.round(diff.divide(places, DecimalUtils.HIGH_PRECISION), t.getOrderDecimalDigits(), RoundingMode.DOWN);
destRowReordered = false;
}
}
assert inc.compareTo(minDistance) >= 0;
 
BigDecimal newOrder = destOrder;
// by definition if we want to add after, destOrder should remain unchanged
if (after) {
newOrder = newOrder.add(inc);
}
final List<List<String>> newOrdersAndIDs = new ArrayList<List<String>>(rowCount);
// we go from newOrder and up, we need to have the source rows in ascending order
Collections.sort(srcRows, OrderComparator.INSTANCE);
for (final SQLRowAccessor srcRow : srcRows) {
newOrdersAndIDs.add(Arrays.asList(srcRow.getIDNumber().toString(), newOrder.toPlainString()));
newOrder = newOrder.add(inc);
}
// move out before general request as most DB systems haven't got DEFERRABLE constraints
if (!after && !destRowReordered) {
final UpdateBuilder updateDestRow = new UpdateBuilder(t);
updateDestRow.set(t.getOrderField().getName(), newOrder.toPlainString());
updateDestRow.setWhere(destRow.getWhere());
t.getDBSystemRoot().getDataSource().execute(updateDestRow.asString());
}
 
final SQLSyntax syntax = SQLSyntax.get(t);
final UpdateBuilder update = new UpdateBuilder(t);
final String constantTableAlias = "newOrdersAndIDs";
update.addVirtualJoin(syntax.getConstantTable(newOrdersAndIDs, constantTableAlias, Arrays.asList("ID", "newOrder")), constantTableAlias, true, "ID", t.getKey().getName());
update.setFromVirtualJoinField(t.getOrderField().getName(), constantTableAlias, "newOrder");
t.getDBSystemRoot().getDataSource().execute(update.asString());
 
t.fireTableModified(SQLRow.NONEXISTANT_ID, Collections.singletonList(t.getOrderField().getName()));
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateOneRunnable.java
23,26 → 23,26
private final SQLTableEvent evt;
 
public UpdateOneRunnable(ITableModel model, SQLTableEvent evt) {
super(model, evt.getTable(), evt.getId());
super(model, evt.getRow());
this.evt = evt;
}
 
@Override
public void run() {
if (this.getTable() == this.getReq().getParent().getPrimaryTable()) {
final ListSQLLine line = this.getReq().get(this.getID());
// handle deleted rows (ie line == null) by using this.getID()
// handle deleted rows (i.e. line == null) by using this.getID(), must be done before
// finding lines
if (line == null)
this.getReq().fireLineChanged(this.getID(), line, null);
else {
final CollectionMap<Path, ListSQLLine> affectedPaths = this.getAffectedPaths();
// the line should be in the list (since SQLTableModelLinesSource.get()
// returned it), so if not yet part of the list add it.
if (affectedPaths.getNonNull(new Path(getTable())).isEmpty())
if (line != null && affectedPaths.getNonNull(new Path(getTable())).isEmpty())
line.clearCache();
// then, update affectedPaths (it's not because the changed table is the primary
// table, that it's not also referenced, eg CIRCUIT.ORIGINE)
// table, that it's not also referenced, e.g. CIRCUIT.ORIGINE)
updateLines(affectedPaths);
}
} else {
// eg CONTACT[3] has changed
updateLines(this.getAffectedPaths());
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateQueue.java
13,6 → 13,9
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
19,12 → 22,16
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.view.list.UpdateRunnable.RmAllRunnable;
import org.openconcerto.utils.IFutureTask;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SleepingQueue;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.FutureTask;
 
import org.apache.commons.collections.CollectionUtils;
53,10 → 60,8
putUpdateAll();
else if (evt.getMode() == Mode.ROW_UPDATED) {
rowModified(evt);
} else if (evt.getMode() == Mode.ROW_ADDED) {
rowAdded(evt.getTable(), evt.getId());
} else if (evt.getMode() == Mode.ROW_DELETED) {
rowDeleted(evt.getTable(), evt.getId());
} else {
rowAddedOrDeleted(evt);
}
}
 
69,8 → 74,8
 
private final ITableModel tableModel;
private final TableListener tableListener;
// TODO rm : needed for now since our optimizations are false if the graph contains referent
// rows, see http://192.168.1.10:3000/issues/show/22
// TODO rm : needed for now since our optimizations are false if there's a where not on the
// primary table, see http://192.168.1.10:3000/issues/show/22
private boolean alwaysUpdateAll = false;
 
public UpdateQueue(ITableModel model) {
111,7 → 116,7
 
void rowModified(final SQLTableEvent evt) {
final int id = evt.getId();
if (id < 0) {
if (id < SQLRow.MIN_VALID_ID) {
this.putUpdateAll();
} else if (CollectionUtils.containsAny(this.tableModel.getReq().getLineFields(), evt.getFields())) {
this.put(evt);
119,35 → 124,32
// si on n'affiche pas le champ ignorer
}
 
void rowAdded(SQLTable table, int id) {
if (!table.equals(this.tableModel.getReq().getPrimaryTable())) {
// on ignore
} else {
this.update(id);
// takes 1-2ms, perhaps cache
final Set<SQLTable> getNotForeignTables() {
final Set<SQLTable> res = new HashSet<SQLTable>();
final SQLRowValues maxGraph = this.tableModel.getReq().getMaxGraph();
maxGraph.getGraph().walk(maxGraph, res, new ITransformer<State<Set<SQLTable>>, Set<SQLTable>>() {
@Override
public Set<SQLTable> transformChecked(State<Set<SQLTable>> input) {
if (input.getPath().length() == 0 || input.isBackwards())
input.getAcc().add(input.getCurrent().getTable());
return input.getAcc();
}
}, RecursionType.BREADTH_FIRST, null);
return res;
}
 
final void rowDeleted(SQLTable table, int id) {
if (!table.equals(this.tableModel.getReq().getPrimaryTable())) {
// on ignore
} else {
if (id < 0)
// MAYBE faire tout effacer
throw new IllegalArgumentException("remove id:" + id + " < 0");
 
this.update(id);
void rowAddedOrDeleted(final SQLTableEvent evt) {
if (evt.getId() < SQLRow.MIN_VALID_ID)
this.putUpdateAll();
// if a row of a table that we point to is added, we will care when the referent table will
// point to it
else if (this.getNotForeignTables().contains(evt.getTable()))
this.put(evt);
}
}
 
// *** puts
 
private void update(final int id) {
if (id < 0)
this.putUpdateAll();
else
this.put(new SQLTableEvent(this.tableModel.getTable(), id, Mode.ROW_UPDATED));
}
 
private void put(SQLTableEvent evt) {
this.put(UpdateRunnable.create(this.tableModel, evt));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AbstractUpdateOneRunnable.java
16,14 → 16,16
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.Collection;
32,14 → 34,14
 
abstract class AbstractUpdateOneRunnable extends UpdateRunnable {
 
public AbstractUpdateOneRunnable(ITableModel model, SQLTable table, int id) {
super(model, table, id);
public AbstractUpdateOneRunnable(ITableModel model, final SQLRow r) {
super(model, r);
if (this.getID() < SQLRow.MIN_VALID_ID)
throw new IllegalArgumentException("id is not valid : " + this.getID());
}
 
protected final CollectionMap<Path, ListSQLLine> getAffectedPaths() {
return this.getSearchQ().getAffectedPaths(this.getTable(), this.getID());
return this.getSearchQ().getAffectedPaths(this.getRow());
}
 
protected final void updateLines(CollectionMap<Path, ListSQLLine> paths) {
51,19 → 53,27
if (!lines.isEmpty()) {
// deepCopy() instead of new SQLRowValues() otherwise the used line's graph will be
// modified (eg the new instance would be linked to it)
final SQLRowValues proto = getModel().getLinesSource().getParent().getMaxGraph().followPath(p).deepCopy();
final SQLRowValues proto = getModel().getLinesSource().getParent().getMaxGraph().followPathToOne(p, CreateMode.CREATE_NONE, false).deepCopy();
final String lastReferentField = SearchQueue.getLastReferentField(p);
// there's only one path from the graph start to proto, and we will graft the newly
// fetched values at the end of p, so remove other values
if (lastReferentField != null) {
proto.put(lastReferentField, null);
} else {
proto.clearReferents();
// keep only what has changed, eg CONTACT.NOM
proto.retainAll(getModifedFields());
}
// fetch the changed rowValues
// ATTN this doesn't use the original fetcher that was used in the updateAll
// MAYBE add a slower but accurate mode using the updateAll fetcher (and thus
// reloading rows from the primary table and not just the changed rows)
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(proto);
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(proto);
BaseFillSQLRequest.setupForeign(fetcher);
final ITransformer<SQLSelect, SQLSelect> transf = new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
if (ListSQLRequest.lockSelect)
if (ListSQLRequest.getDefaultLockSelect())
input.addWaitPreviousWriteTXTable(getTable().getName());
return input.setWhere(new Where(getTable().getKey(), "=", getID()));
}
73,14 → 83,15
if (fetched.size() > 1)
throw new IllegalStateException("more than one row fetched for " + this + " with " + fetcher.getReq() + " :\n" + fetched);
 
if (fetched.size() == 0) {
// OK if lastReferentField != null : a referent row has been deleted
if (fetched.size() == 0 && lastReferentField == null) {
Log.get().fine("no row fetched for " + this + ", lines have been changed without the TableModel knowing : " + lines + " req :\n" + fetcher.getReq());
getModel().updateAll();
} else {
final SQLRowValues soleFetched = fetched.get(0);
final SQLRowValues soleFetched = CollectionUtils.getSole(fetched);
// copy it to each affected lines
for (final ListSQLLine line : lines) {
line.loadAt(soleFetched.deepCopy(), p);
line.loadAt(getID(), soleFetched, p);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowValuesTable.java
260,8 → 260,14
this.repaint();
}
 
public void insertFrom(SQLRowAccessor rowVals) {
this.model.insertFrom(rowVals);
this.revalidate();
this.repaint();
}
 
public void insertFrom(String field, SQLRowValues rowVals) {
this.model.insertFrom(field, rowVals);
this.model.insertFrom(rowVals);
this.revalidate();
this.repaint();
}
394,6 → 400,11
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
 
}
 
@Override
public boolean isEmpty() {
return false;
}
414,7 → 425,12
return ValidState.getTrueInstance();
} else {
final SQLFieldTranslator trans = Configuration.getInstance().getTranslator();
final String text = "au moins " + this.model.getSQLElement().getSingularName() + " n'a pas le champ requis \"" + trans.getLabelFor(this.model.getRequiredField()) + "\" rempli";
String fields = "(";
for (SQLField field : this.model.getRequiredsField()) {
fields += trans.getLabelFor(field) + ",";
}
fields += ")";
final String text = "au moins " + this.model.getSQLElement().getSingularName() + " n'a pas le(s) champ(s) requis \"" + fields + "\" rempli(s)";
return new ValidState(false, text);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTextComboTableCellEditor.java
14,6 → 14,8
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.sqlobject.SQLRequestComboBox;
33,7 → 35,6
import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
42,6 → 43,8
public class SQLTextComboTableCellEditor extends AbstractCellEditor implements TableCellEditor {
 
private SQLRequestComboBox comboBox;
 
private Where w;
// Stock Value of Combo to fix problem with undefined
int val = 1;
 
69,7 → 72,7
// Mimic JTable.GenericEditor behavior
this.comboBox.getTextComp().setBorder(new EmptyBorder(0, 0, 0, 18));
this.comboBox.setBorder(new LineBorder(Color.black));
((JComponent) this.comboBox.getPulseComponent()).setBorder(null);
this.comboBox.getPulseComponents().iterator().next().setBorder(null);
 
ComboSQLRequest c = elt.getComboRequest(true);
this.comboBox.uiInit(c);
132,7 → 135,28
}
}
});
// Filtre sur une valeur specifique
if (this.fieldWhere != null && table instanceof RowValuesTable) {
RowValuesTable rowVals = (RowValuesTable) table;
SQLRowValues rowValues = rowVals.getRowValuesTableModel().getRowValuesAt(row);
if (rowValues.isForeignEmpty(this.fieldWhere.getName())) {
 
if (this.w != null) {
this.comboBox.getRequest().setWhere(this.w);
} else {
this.comboBox.getRequest().setWhere(null);
}
} else {
final Where w2 = new Where(this.fieldWhere, "=", rowValues.getForeign(this.fieldWhere.getName()).getID());
if (this.w != null) {
this.comboBox.getRequest().setWhere(this.w.and(w2));
} else {
this.comboBox.getRequest().setWhere(w2);
}
}
}
return this.comboBox;
 
}
 
public int getComboSelectedId() {
139,7 → 163,14
return SQLTextComboTableCellEditor.this.comboBox.getSelectedId();
}
 
private SQLField fieldWhere;
 
public void setDynamicWhere(SQLField field) {
this.fieldWhere = field;
}
 
public void setWhere(Where w) {
this.w = w;
this.comboBox.getRequest().setWhere(w);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOnline.java
15,6 → 15,7
 
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.change.ListChangeIndex;
 
48,7 → 49,9
// add needed fields for each new column
for (final SQLTableModelColumn col : change.getItemsAdded()) {
for (final FieldPath p : col.getPaths()) {
final SQLRowValues assurePath = this.getReq().getGraphToFetch().assurePath(p.getPath());
// don't back track : e.g. if path is SITE -> CLIENT <- SITE we want the siblings of
// SITE, if we want fields of the primary SITE we pass the path SITE
final SQLRowValues assurePath = this.getReq().getGraphToFetch().followPathToOne(p.getPath(), CreateMode.CREATE_ONE, false);
if (!assurePath.getFields().contains(p.getFieldName()))
assurePath.put(p.getFieldName(), null);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowValuesTableControlPanel.java
17,6 → 17,7
import org.openconcerto.sql.view.IListFrame;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JComponentUtils;
import org.openconcerto.sql.TM;
 
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
85,7 → 86,7
this.add(this.buttonBas, c);
this.buttonBas.setEnabled(false);
 
this.buttonAjouter = new JButton("Ajouter une ligne");
this.buttonAjouter = new JButton(TM.tr("addNewLine"));
this.buttonAjouter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
RowValuesTableControlPanel.this.model.addNewRowAt(table.getRowCount());
95,7 → 96,7
JComponentUtils.setMinimumWidth(this.buttonAjouter, 88);
this.add(this.buttonAjouter, c);
 
this.buttonInserer = new JButton("Insérer une ligne");
this.buttonInserer = new JButton(TM.tr("insertNewLine"));
this.buttonInserer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
RowValuesTableControlPanel.this.model.addNewRowAt(table.getSelectedRow());
106,7 → 107,7
JComponentUtils.setMinimumWidth(this.buttonInserer, 85);
this.add(this.buttonInserer, c);
 
this.buttonClone = new JButton("Dupliquer une ligne");
this.buttonClone = new JButton(TM.tr("duplicateLine"));
this.buttonClone.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
cloneLine(table.getSelectedRow());
117,7 → 118,7
JComponentUtils.setMinimumWidth(this.buttonClone, 95);
this.add(this.buttonClone, c);
 
this.buttonSuppr = new JButton("Supprimer la sélection");
this.buttonSuppr = new JButton(TM.tr("deleteLine"));
this.buttonSuppr.addActionListener(new ActionListener() {
 
public void actionPerformed(ActionEvent event) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowValuesTableModel.java
16,6 → 16,7
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
58,6 → 59,8
private List<SQLTableElement> list; // Liste de SQLTableElement
private Map<String, Integer> mapColumnField = new HashMap<String, Integer>();
 
private List<SQLField> requiredFields;
 
private SQLField requiredField, validationField;
 
private SQLRowValues defautRow;
103,6 → 106,9
protected void init(final SQLElement e, final List<SQLTableElement> list, SQLField validField, boolean addDefault, SQLRowValues rowParDefaut, SQLField validationField) {
this.element = e;
this.requiredField = validField;
this.requiredFields = new ArrayList<SQLField>();
 
this.requiredFields.add(validField);
this.validationField = validationField;
this.list = list;
this.nbColumn = list.size();
119,6 → 125,10
}
}
 
public void addRequiredField(SQLField f) {
this.requiredFields.add(f);
}
 
public SQLRowValues getDefaultRowValues() {
return this.defautRow;
}
185,8 → 195,14
public void putValue(Object value, int rowIndex, String fieldName) {
checkEDT();
final SQLRowValues rowVal = this.rowValues.get(rowIndex);
Object oldValue = rowVal.getObject(fieldName);
if (oldValue == value) {
return;
}
if (oldValue != null && oldValue.equals(value)) {
return;
}
rowVal.put(fieldName, value);
 
for (SQLTableElement sqlTableElem : this.list) {
sqlTableElem.fireModification(rowVal);
}
200,6 → 216,15
if (this.list.size() <= columnIndex) {
return;
}
 
Object oldValue = getValueAt(rowIndex, columnIndex);
if (aValue == oldValue) {
return;
}
if (oldValue != null && oldValue.equals(aValue)) {
return;
}
 
SQLTableElement sqlTableElem = this.list.get(columnIndex);
 
SQLRowValues rowVal = this.rowValues.get(rowIndex);
236,26 → 261,20
checkEDT();
final List<SQLRowValues> rowsToCommmit = new ArrayList<SQLRowValues>();
rowsToCommmit.addAll(this.rowValues);
runnableQueue.execute(new Runnable() {
 
@Override
public void run() {
try {
final int size = rowsToCommmit.size();
for (int i = 0; i < size; i++) {
final SQLRowValues r = rowsToCommmit.get(i);
SQLRow row = r.commit();
SQLRow row;
 
row = r.commit();
r.setID(row.getIDNumber());
}
} catch (SQLException e) {
ExceptionHandler.handle("Commit fail", e);
ExceptionHandler.handle("Unable to commit rows", e);
}
 
}
});
 
}
 
public void addTableModelListener(TableModelListener l) {
this.tableModelListeners.add(l);
}
296,11 → 315,6
throw new IllegalArgumentException(index + " <0");
}
 
runnableQueue.submit(new Runnable() {
 
@Override
public void run() {
 
final SQLRowValues newRowParDefaut = new SQLRowValues(RowValuesTableModel.this.defautRow);
final BigDecimal maxOrder = newRowParDefaut.getTable().getMaxOrder();
SwingUtilities.invokeLater(new Runnable() {
365,8 → 379,6
});
 
}
});
}
 
/**
* Suppression d'une ligne de la table
423,13 → 435,20
 
SQLRowValues row = this.rowValues.get(index);
 
if (this.requiredField.isKey()) {
return !row.isForeignEmpty(this.requiredField.getName());
boolean valid = true;
for (SQLField f : this.requiredFields) {
if (f.isKey()) {
valid &= !row.isForeignEmpty(f.getName());
} else {
final Object object = row.getObject(this.requiredField.getName());
return object != null && object.toString().trim().length() > 0;
final Object object = row.getObject(f.getName());
valid &= object != null && object.toString().trim().length() > 0;
}
if (!valid) {
break;
}
}
return valid;
}
 
public boolean isValidated() {
boolean b = true;
558,7 → 577,7
* @param field
* @param rowVals
*/
public void insertFrom(final String field, final SQLRowValues rowVals) {
public void insertFrom(final SQLRowAccessor rowVals) {
if (!SwingUtilities.isEventDispatchThread()) {
Thread.dumpStack();
}
580,10 → 599,10
}
 
} else {
Collection<SQLRowValues> colRows = rowVals.getReferentRows();
for (SQLRowValues rowValues : colRows) {
Collection<? extends SQLRowAccessor> colRows = rowVals.getReferentRows();
for (SQLRowAccessor rowValues : colRows) {
if (rowValues.getTable().getName().equalsIgnoreCase(RowValuesTableModel.this.element.getTable().getName())) {
newRows.add(rowValues);
newRows.add(rowValues.asRowValues());
}
}
}
833,4 → 852,8
public SQLField getRequiredField() {
return this.requiredField;
}
 
public List<SQLField> getRequiredsField() {
return this.requiredFields;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ITableModel.java
18,6 → 18,8
import static org.openconcerto.sql.view.list.ITableModel.SleepState.SLEEPING;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLFieldsSet;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.users.rights.TableAllRights;
71,7 → 73,8
}
 
private static Timer autoHibernateTimer = null;
private static boolean defaultEditable = true;
// not editable since default editors are potentially not safe (no validation)
private static boolean defaultEditable = false;
 
public static Timer getAutoHibernateTimer() {
if (autoHibernateTimer == null)
239,6 → 242,13
this.updateQ.putUpdateAll();
}
 
/**
* If there's a where not on the primary table, the list doesn't know which lines to refresh and
* it must reload all lines.
*
* @param b <code>true</code> if the list shouldn't search for lines to refresh, but just reload
* all of them.
*/
public final void setAlwaysUpdateAll(final boolean b) {
this.getUpdateQ().setAlwaysUpdateAll(b);
}
384,8 → 394,6
}
 
private boolean hasRight(final SQLTableModelColumn col) {
if (!UserRightsManager.getInstance().isValid())
return true;
final UserRights u = UserRightsManager.getCurrentUserRights();
for (final SQLTable t : new SQLFieldsSet(col.getFields()).getTables()) {
if (!TableAllRights.hasRight(u, TableAllRights.MODIFY_ROW_TABLE, t))
441,7 → 449,7
* @return all affected lines in the fullList (un-searched).
*/
public CollectionMap<ListSQLLine, Path> getAffectedLines(final SQLTable t, final int id) {
return this.getSearchQueue().getAffectedLines(t, id);
return this.getSearchQueue().getAffectedLines(new SQLRow(t, id));
}
 
// *** search
468,19 → 476,19
 
// *** move
 
public void moveBy(final int rowID, final int inc) {
this.moveQ.move(rowID, inc);
public void moveBy(final List<? extends SQLRowAccessor> rows, final int inc) {
this.moveQ.move(rows, inc);
}
 
/**
* Compute the id of the row which is <code>inc</code> lines from rowID.
* Search the row which is <code>inc</code> lines from rowID.
*
* @param rowID an ID of a row of this table.
* @param inc the offset of visible lines.
* @return the destination ID or <code>null</code> if it's the same as <code>rowID</code> or
* @return the destination line or <code>null</code> if it's the same as <code>rowID</code> or
* <code>rowID</code> is inexistant.
*/
Integer getDestID(int rowID, int inc) {
ListSQLLine getDestLine(int rowID, int inc) {
final int rowIndex = this.indexFromID(rowID);
if (rowIndex < 0)
return null;
492,7 → 500,7
else if (destIndex > max)
destIndex = max;
if (destIndex != rowIndex) {
return this.idFromIndex(destIndex);
return this.getRow(destIndex);
} else
return null;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/IListFrame.java