OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 141 → Rev 142

/trunk/OpenConcerto/lib/DS_Desktop_Notify.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/DS_Desktop_Notify.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenDocument-1.4rc2.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mime_util.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mime_util.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/json-smart-2.2.1.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/json-smart-2.2.1.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.40-bin.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.40-bin.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenCalendar.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/accessors-smart-1.1.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/accessors-smart-1.1.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/icudata_56.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/icudata_56.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/src/org/jopendocument/link/OOConnexion.java
117,15 → 117,33
}
 
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("Usage : " + OOConnexion.class.getName() + " officeFile");
if (args.length == 0 || args.length > 2) {
System.out.println("Usage : " + OOConnexion.class.getName() + " officeFile | --type param");
System.out.println("Open officeFile in the default installation of LibreOffice");
System.out.println("--type is either file or url");
System.exit(1);
}
final OOConnexion conn = OOConnexion.create();
if (conn == null)
throw new IllegalStateException("No Office found");
conn.loadDocument(new File(args[0]), false);
final boolean file;
final String arg;
if (args.length == 1) {
file = true;
arg = args[0];
} else if (args[0].equals("--file")) {
file = true;
arg = args[1];
} else if (args[0].equals("--url")) {
file = false;
arg = args[1];
} else {
throw new IllegalArgumentException("Type not valid : " + args[0]);
}
if (file)
conn.loadDocument(new File(arg), false);
else
conn.loadDocumentFromURLAsync(arg, false);
conn.closeConnexion();
}
 
/trunk/OpenConcerto/src/org/jopenchart/sample/devguide/PieChartSample.java
63,14 → 63,13
 
private static void chart3() {
PieChartWithSeparatedLabels c = new PieChartWithSeparatedLabels();
c.setInnerDimension(50, 50);
c.addLabel(new Label("AAAAAA"), Color.red);
c.addLabel(new Label("BBBB"));
c.addLabel(new Label("CCCCCCCCCCCCCCCCCCCCCCCCCCCC"));
c.addLabel(new Label("D"));
c.addLabel(new Label("EEE"));
c.addLabel(new Label("FFF"));
c.addLabel(new Label("GG"));
c.addLabel(new Label("HHH"));
 
c.setDimension(new Dimension(400, 200));
 
ArrayList<Number> l = new ArrayList<Number>();
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/AddMDFields.java
17,6 → 17,8
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.utils.AlterTable;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.utils.SQLCreateTableBase;
 
import java.sql.SQLException;
import java.util.ArrayList;
42,6 → 44,21
return fields.subList(2, 4);
}
 
static public final void addFields(final SQLCreateTable createTable) {
addFields(createTable, createTable.getRoot().findTable("USER_COMMON"));
}
 
static public final void addFields(final SQLCreateTableBase<?> createTable, final SQLTable userT) {
if (userT == null)
throw new IllegalArgumentException("Missing user table");
for (final String fk : getFKFields()) {
createTable.addForeignColumn(fk, userT);
}
for (final String fk : getDateFields()) {
createTable.addDateAndTimeColumn(fk);
}
}
 
public AddMDFields(DBSystemRoot b) {
super(b);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/MergeTable.java
161,7 → 161,7
}
}
 
final SQLSyntax syntax = t.getServer().getSQLSystem().getSyntax();
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/correct/SetFFRules.java
84,7 → 84,7
SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
@Override
public Object create() throws SQLException {
for (final List<String> l : ChangeTable.cat(Collections.singleton(alterTable), t.getDBRoot().getName(), EnumSet.of(ConcatStep.ADD_FOREIGN))) {
for (final List<String> l : ChangeTable.cat(Collections.singleton(alterTable), t.getDBRoot().getName(), EnumSet.of(ConcatStep.ADD_CONSTRAINT))) {
for (final String sql : l)
getDS().execute(sql);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/Changer.java
170,6 → 170,6
}
 
protected final SQLSyntax getSyntax() {
return SQLSyntax.get(this.getSystemRoot().getServer().getSQLSystem());
return this.getSystemRoot().getSyntax();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/replication/MemoryRep.java
132,7 → 132,7
}
}).getSystemRoot("");
// slave is a copy so it needn't have checks (and it simplify replicate())
this.slave.getDataSource().execute(this.slave.getServer().getSQLSystem().getSyntax().disableFKChecks(null));
this.slave.getDataSource().execute(this.slave.getSyntax().disableFKChecks(null));
this.count = new AtomicInteger(0);
this.canceledCount = 0;
}
305,7 → 305,7
}
 
protected final void replicateStruct() throws SQLException, IOException {
final SQLSystem slaveSystem = this.slave.getServer().getSQLSystem();
final SQLSyntax slaveSyntax = this.slave.getSyntax();
final SQLDataSource slaveDS = this.slave.getDataSource();
final List<SQLCreateTableBase<?>> createTables = new ArrayList<SQLCreateTableBase<?>>();
// undefined IDs by table by root
313,7 → 313,7
for (final Entry<String, Set<String>> e : this.tables.entrySet()) {
final String rootName = e.getKey();
final Set<String> tableNames = e.getValue();
slaveDS.execute(new SQLCreateRoot(slaveSystem.getSyntax(), rootName).asString());
slaveDS.execute(new SQLCreateRoot(this.slave.getSyntax(), rootName).asString());
final DBRoot root = this.master.getRoot(rootName);
 
final Map<String, Number> rootUndefIDs = new HashMap<String, Number>(tableNames.size());
321,7 → 321,7
 
for (final String tableName : tableNames) {
final SQLTable masterTable = root.getTable(tableName);
final SQLCreateMoveableTable ct = masterTable.getCreateTable(slaveSystem);
final SQLCreateMoveableTable ct = masterTable.getCreateTable(slaveSyntax);
// remove constraints towards non-copied tables
for (final FCSpec fc : new ArrayList<FCSpec>(ct.getForeignConstraints())) {
final SQLName refTable = new SQLName(rootName, tableName).resolve(fc.getRefTable());
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ChangeFKRunnable.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceStateOnline.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceStateOffline.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ListSQLLine.java
18,6 → 18,7
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.utils.CollectionUtils;
68,7 → 69,7
@GuardedBy("this")
private SQLRowValues row;
// Immutable
private final SQLTableModelColumns columns;
private final SQLTableModelSourceState state;
private final int id;
// allow to order by something not in the row
@GuardedBy("this")
79,12 → 80,12
@GuardedBy("this")
private List<Object> list;
 
ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id, final SQLTableModelColumns columns) {
ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id, final SQLTableModelSourceState state) {
super();
this.src = src;
this.setRow(row);
this.id = id;
this.columns = columns;
this.state = state;
this.clearCache();
}
 
95,6 → 96,14
updateList(columnCount, Collections.<Integer> emptySet());
}
 
public final SQLTableModelSourceState getState() {
return this.state;
}
 
public final SQLTableModelColumns getColumns() {
return this.getState().getAllColumns();
}
 
public final SQLTableModelLinesSource getSrc() {
return this.src;
}
140,7 → 149,7
}
 
public final void setValueAt(Object obj, int colIndex) {
this.columns.getColumns().get(colIndex).put(this, obj);
this.getColumns().getColumns().get(colIndex).put(this, obj);
}
 
// should update this.list at the passed indexes, and then recursively for dependent columns
175,7 → 184,7
for (int i = 0; i < newSize; i++) {
final Object o;
if (i >= alreadyLoaded || colsToUpdate.contains(i))
o = this.columns.getAllColumns().get(i).show(this.getRow());
o = this.getColumns().getAllColumns().get(i).show(this.getRow());
else
o = this.list.get(i);
newList.add(o);
200,7 → 209,11
* @return the columns that were affected, <code>null</code> meaning all.
*/
synchronized Set<Integer> loadAt(int id, SQLRowValues vals, Path p) {
assert vals == null || vals.getID() == id;
final String lastReferentField = SearchQueue.getLastReferentField(p);
// null vals means id was deleted, the only way we care is if it was pointing to us
// (otherwise the foreign key pointing to it would have changed first)
assert vals != null || lastReferentField != null;
// load() empties vals, so getFields() before
final Set<Integer> indexes = lastReferentField == null ? this.pathToIndex(p, vals.getFields()) : null;
// replace our values with the new ones
207,6 → 220,9
final SQLRowValues copy = this.getRow().deepCopy();
if (lastReferentField == null) {
for (final SQLRowValues v : copy.followPath(p, CreateMode.CREATE_NONE, false)) {
// check id, e.g. if p is BATIMENT <- LOCAL -> FAMILLE_LOCAL, then there's multiple
// familles
if (v.getID() == id)
v.load(vals.deepCopy(), null);
}
} else {
267,7 → 283,7
} else {
final Set<Integer> res = new HashSet<Integer>();
final Set<FieldPath> modifiedPaths = FieldPath.create(p, modifiedFields);
final List<? extends SQLTableModelColumn> cols = this.columns.getAllColumns();
final List<? extends SQLTableModelColumn> cols = this.getColumns().getAllColumns();
for (int i = 0; i < cols.size(); i++) {
final SQLTableModelColumn col = cols.get(i);
if (CollectionUtils.containsAny(col.getPaths(), modifiedPaths))
278,8 → 294,9
}
 
private static boolean containsFK(final SQLTable t, Collection<String> fields) {
final Set<SQLField> ffs = t.getFields(VirtualFields.FOREIGN_KEYS);
for (final String f : fields) {
if (t.getForeignKeys().contains(t.getField(f)))
if (ffs.contains(t.getField(f)))
return true;
}
return false;
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSource.java
16,6 → 16,7
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.IPredicate;
 
51,6 → 52,10
 
public abstract SQLTableModelSource getParent();
 
public final ListSQLRequest getUpdateQueueReq() {
return this.getModel().getUpdateQ().getState().getReq();
}
 
public abstract List<ListSQLLine> getAll();
 
/**
126,7 → 131,7
} else {
throw new IllegalArgumentException("No ID for " + v);
}
final ListSQLLine res = new ListSQLLine(this, v, id, this.getModel().getUpdateQ().getState().getAllColumns());
final ListSQLLine res = new ListSQLLine(this, v, id, this.getModel().getUpdateQ().getState());
this.lineCreated(res);
return res;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOffline.java
16,21 → 16,18
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.OrderComparator;
import org.openconcerto.sql.model.SQLDataSource;
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.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLRowValuesCluster.WalkOptions;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IClosure;
 
import java.sql.SQLException;
import java.util.ArrayList;
42,6 → 39,7
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
62,16 → 60,16
// row container with equals() using reference equality
@NotThreadSafe
static private final class Row {
private final Number id;
private final Integer id;
private SQLRowValues vals;
 
private Row(Number id, SQLRowValues vals) {
private Row(Integer id, SQLRowValues vals) {
super();
this.id = id;
this.setRow(vals);
}
 
public final Number getID() {
public final Integer getID() {
return this.id;
}
 
109,7 → 107,7
private final List<Row> lines;
// since new lines have no database ID, give them a virtual one
private int freeID;
private final Map<Number, Row> id2line;
private final Map<Integer, Row> id2line;
// values that can be modified, read-only
private final SQLRowValues modifiableVals;
// original value for modified lines
123,7 → 121,7
this.lines = new LinkedList<Row>();
// the firsts are used in other part of the fwk
this.freeID = SQLRow.MIN_VALID_ID - 10;
this.id2line = new HashMap<Number, Row>();
this.id2line = new HashMap<Integer, Row>();
this.dbOrder = true;
}
 
131,19 → 129,6
super(model);
this.parent = parent;
this.modifiableVals = this.getParent().getElem().getPrivateGraph().toImmutable();
if (this.modifiableVals.getGraphSize() > 1) {
// because of updateRow() and commit() (precisely SQLElement.update())
if (this.modifiableVals.hasReferents())
throw new IllegalArgumentException("Referents are not supported");
this.modifiableVals.getGraph().walk(this.modifiableVals, null, new ITransformer<State<Object>, Object>() {
@Override
public Object transformChecked(State<Object> input) {
if (input.isBackwards())
throw new IllegalArgumentException("Referents are not supported");
return null;
}
}, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
}
this.dbVals = new HashMap<Row, SQLRowValues>();
this.deleted = new HashSet<Number>();
}
157,15 → 142,15
return this.getModel().getUpdateQ().currentlyInQueue();
}
 
private final Row getRow(Number id) {
private final Row getRow(Integer id) {
return this.getRow(id, false);
}
 
private final Row getRow(Number id, final boolean required) {
private final Row getRow(Integer id, final boolean required) {
return this.getRow(id, required, null);
}
 
private final Row getRow(Number id, final boolean required, final Object idSource) {
private final Row getRow(Integer id, final boolean required, final Object idSource) {
final Row res = this.id2line.get(id);
if (required && res == null)
throw new IllegalArgumentException("Not in the list : " + (idSource == null ? id : idSource));
253,14 → 238,10
}
}
 
protected final SQLRowValuesListFetcher getFetcher() {
return ((SQLTableModelSourceStateOffline) this.getModel().getUpdateQ().getState()).getFetcher();
protected final List<SQLRowValues> fetch() {
return this.getUpdateQueueReq().getValues();
}
 
protected final List<SQLRowValues> fetch(final Where w) {
return this.getFetcher().fetch(w, true);
}
 
/**
* Fetch all rows and update our lines. Must be called by the {@link UpdateQueue}. Deleted rows
* will be removed, inserted rows added, virtual rows unchanged, and updated rows will only be
271,7 → 252,7
@Override
public List<ListSQLLine> getAll() {
assert checkUpdateThread();
final List<SQLRowValues> dbRows = this.fetch(null);
final List<SQLRowValues> dbRows = this.fetch();
 
if (this.lines.isEmpty()) {
// optimization of the else block
280,13 → 261,13
}
} else {
// delete
final Set<Number> dbIDs = new HashSet<Number>();
final Set<Integer> dbIDs = new HashSet<Integer>();
for (final SQLRowValues dbRow : dbRows) {
dbIDs.add(dbRow.getIDNumber(true));
dbIDs.add(dbRow.getIDNumber(true).intValue());
}
final Set<Number> deletedIDs = new HashSet<Number>(this.id2line.keySet());
final Set<Integer> deletedIDs = new HashSet<Integer>(this.id2line.keySet());
deletedIDs.removeAll(dbIDs);
for (final Number id : deletedIDs) {
for (final Integer id : deletedIDs) {
// don't delete virtual rows
if (id.intValue() >= SQLRow.MIN_VALID_ID) {
final Value<ListSQLLine> val = this.updateRow(id.intValue(), null);
332,10 → 313,7
@Override
public Value<ListSQLLine> get(final int id) {
assert checkUpdateThread();
final Where w = new Where(getParent().getPrimaryTable().getKey(), "=", id);
// since we use "=" pk, either 1 or 0
final SQLRowValues row = CollectionUtils.getSole(this.fetch(w));
return updateRow(id, row);
return updateRow(id, this.getUpdateQueueReq().getValues(id));
}
 
// *** Modify virtual rows ***
356,13 → 334,13
assert checkUpdateThread();
// make sure every needed path is there
if (grow)
vals.grow(getFetcher().getGraph(), false);
vals.grow(getUpdateQueueReq().getGraphToFetch(), false);
// ATTN only works because vals was just fetched or just copied
vals.getGraph().freeze();
final boolean fromDB = vals.hasID();
final List<Number> order;
final List<Integer> order;
final Row r;
r = new Row(fromDB ? vals.getIDNumber() : this.freeID--, vals);
r = new Row(fromDB ? vals.getID() : this.freeID--, vals);
this.id2line.put(r.getID(), r);
this.lines.add(r);
 
391,7 → 369,7
return this.getModel().getUpdateQ().execute(new FutureTask<SQLRowValues>(new OfflineCallable<SQLRowValues>() {
@Override
public SQLRowValues call() throws Exception {
final Row r = getRow(id);
final Row r = getRow(id.intValue());
return r == null ? null : rm(r).vals;
}
}));
429,63 → 407,99
@Override
public void run() {
getModel().getUpdateQ().updateLine(l, path, copy.get().getID(), copy.get());
recordOriginal(getRow(l.getID()), l);
recordOriginal(l);
}
});
}
 
public Future<?> updateRow(final Number id, final Path path, final SQLRowValues vals) {
checkCanModif(path);
// since SQLRowValues isn't thread-safe, use AtomicReference to safely pass it to another
// thread
final AtomicReference<SQLRowValues> copy = new AtomicReference<SQLRowValues>(vals.toImmutable());
public Future<?> replaceRow(final Number id, final SQLRowValues vals) {
checkCanModif(Path.get(vals.getTable()));
final SQLRowValues copy = vals.deepCopy();
return this.updateRow(id, new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues newVals) {
final Set<String> contentFields = newVals.getTable().getFieldsNames(VirtualFields.CONTENT);
newVals.clearReferents().removeAll(contentFields);
newVals.load(copy, contentFields);
if (copy.hasReferents()) {
for (final Entry<SQLField, Set<SQLRowValues>> e : new SetMap<SQLField, SQLRowValues>(copy.getReferentsMap()).entrySet()) {
for (final SQLRowValues ref : e.getValue()) {
ref.put(e.getKey().getName(), newVals);
}
}
}
assert copy.getGraphSize() == 1;
}
}, false);
}
 
public Future<?> updateRow(final Number id, final SQLRowValues vals) {
checkCanModif(Path.get(vals.getTable()));
if (vals.getGraphSize() > 1)
throw new IllegalArgumentException("This method doesn't merge graphs");
final SQLRowValues copy = vals.deepCopy();
return this.updateRow(id, new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues newVals) {
final Set<String> contentFields = newVals.getTable().getFieldsNames(VirtualFields.CONTENT);
newVals.load(copy, contentFields);
}
}, false);
}
 
public Future<?> updateRow(final Number id, final IClosure<SQLRowValues> valsClosure) {
return this.updateRow(id, valsClosure, true);
}
 
private Future<?> updateRow(final Number id, final IClosure<SQLRowValues> valsClosure, final boolean mdCanChange) {
return this.getModel().getUpdateQ().put(new OfflineRunnable() {
@Override
public void run() {
final ListSQLLine l = getModel().getUpdateQ().getLine(id);
// ATTN only works if path direction is only FOREIGN (e.g. for new rows referents
// won't have an ID)
getModel().getUpdateQ().updateLine(l, path, copy.get().getID(), copy.get());
recordOriginal(getRow(id), l);
_updateRow(id, valsClosure, mdCanChange);
}
});
}
 
protected Row _updateRow(final Number id, final IClosure<SQLRowValues> valsClosure, final boolean mdCanChange) {
assert checkUpdateThread();
final Row r = getRow(id.intValue(), true);
final SQLRowValues newVals = r.getRow().deepCopy();
valsClosure.executeChecked(newVals);
// make sure PK and metadata don't change
if (mdCanChange) {
final Set<String> notContent = newVals.getTable().getFieldsNames(VirtualFields.CONTENT.complement());
newVals.removeAll(notContent);
newVals.putAll(r.getRow().getValues(notContent, false));
}
assert CompareUtils.equals(r.getRow().getIDNumber(), newVals.getIDNumber());
// make sure every needed path is there
newVals.grow(getUpdateQueueReq().getGraphToFetch(), false);
newVals.getGraph().freeze();
setRow(r, newVals);
 
// call createLine() to apply filter
this.getModel().getUpdateQ().replaceLine(r.getID().intValue(), createLine(r));
return r;
}
 
private void checkCanModif(Path path) {
if (this.modifiableVals.followPath(path) == null)
throw new IllegalArgumentException("can only modify " + this.modifiableVals);
}
 
private void recordOriginal(Row r, ListSQLLine l) {
assert r.getID().intValue() == l.getID();
// if l isn't in the db, no need to update, the new values will be inserted
private void recordOriginal(ListSQLLine l) {
setRow(getRow(l.getID(), true), l.getRow());
}
 
private void setRow(Row r, SQLRowValues newVals) {
// if the existing row isn't in the DB, no need to update, the new values will be inserted
if (r.getRow().hasID() && !this.dbVals.containsKey(r)) {
// copy the initial state
this.dbVals.put(r, r.getRow());
}
r.setRow(l.getRow());
r.setRow(newVals);
}
 
// change foreign key at the outer edge of our private graph
// e.g. if this is a CPI can change CPI.ID_LOCAL, but not LOCAL.ID_BATIMENT, and not
// CPI.ID_OBSERVATION
public void changeFK(final Number lineID, final Path p, final int id) {
checkCanModif(p.minusLast());
// Disallow modification of private
if (this.modifiableVals.followPath(p) != null)
throw new IllegalArgumentException("can only modify a foreign key of " + this.modifiableVals);
this.getModel().getUpdateQ().put(new OfflineRunnable() {
@Override
public void run() {
final ListSQLLine line = getModel().getUpdateQ().getLine(lineID);
// TODO extract updateLines() from AbstractUpdateOneRunnable and delete
// ChangeFKRunnable
new ChangeFKRunnable(line, p, id).run();
recordOriginal(getRow(lineID, true), line);
}
});
}
 
// *** Order ***
 
@Override
509,12 → 523,12
final int count = this.lines.size();
final boolean after = inc > 0;
 
final List<Number> order;
final List<Integer> order;
// same algorithm as MoveQueue
int outerIndex = -1;
final List<Row> ourLines = new ArrayList<Row>(list.size());
for (final SQLRowAccessor r : list) {
final Row ourLine = this.getRow(r.getIDNumber(), true, r);
final Row ourLine = this.getRow(r.getID(), true, r);
final int index = this.indexOf(ourLine);
ourLines.add(ourLine);
if (outerIndex < 0 || after && index > outerIndex || !after && index < outerIndex) {
533,8 → 547,8
this.getModel().getUpdateQ().reorder(order);
}
 
private List<Number> getIDsOrder() {
final List<Number> ids = new ArrayList<Number>();
private List<Integer> getIDsOrder() {
final List<Integer> ids = new ArrayList<Integer>();
for (final Row r : this.lines)
ids.add(r.getID());
return ids;
554,10 → 568,10
assert checkUpdateThread();
final int count = this.lines.size();
 
final List<Number> order;
final List<Integer> order;
final List<Row> list = new ArrayList<Row>(ids.size());
for (final Object o : ids) {
final Number id = o instanceof SQLRowAccessor ? ((SQLRowAccessor) o).getIDNumber() : (Number) o;
final Integer id = o instanceof SQLRowAccessor ? ((SQLRowAccessor) o).getID() : ((Number) o).intValue();
list.add(this.getRow(id, true, o));
}
if (index <= 0) {
616,7 → 630,7
this.deleted.clear();
this.setDBOrder(true);
 
for (final SQLRowValues r : this.fetch(null))
for (final SQLRowValues r : this.fetch())
this._add(r, false, false);
this.getModel().getUpdateQ().setFullList(getLines(), null);
}
657,6 → 671,12
}
 
protected void coreCommit() throws SQLException {
// delete. Must be done first (e.g. there's a unique constraint and a deleted row conflicts
// with a new row)
getParent().getElem().archiveIDs(this.deleted);
this.deleted.clear();
 
// ordered rows
final Map<Row, SQLRow> newRows = new LinkedHashMap<Row, SQLRow>();
// insert, copy since we will remove some of the lines
for (final Row l : this.lines) {
668,7 → 688,7
newRow = l.getRow().asRow();
}
// if the line is to be updated, this will get replaced below but it won't
// changed the ordering of the map
// change the ordering of the map
newRows.put(l, newRow);
}
 
686,10 → 706,5
if (!wantedOrder.equals(dbOrder)) {
MoveQueue.moveAtOnce(wantedOrder.subList(1, wantedOrder.size()), true, wantedOrder.get(0));
}
 
// delete
for (final Number id : this.deleted)
getParent().getElem().archive(id.intValue());
this.deleted.clear();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSource.java
13,7 → 13,9
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldsSet;
import org.openconcerto.sql.model.SQLRow;
22,6 → 24,8
import org.openconcerto.sql.model.SQLRowValues.ForeignCopyMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.change.ListChangeIndex;
import org.openconcerto.utils.change.ListChangeRecorder;
34,6 → 38,7
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
 
import javax.swing.SwingUtilities;
 
75,7 → 80,8
}
}
 
private final SQLTable table;
private final ListSQLRequest req;
private final SQLElement elem;
// only from EDT
private SQLRowValues inited;
// this.cols + debugCols, unmodifiable
95,14 → 101,23
this.lines = new ArrayList<WeakReference<SQLTableModelLinesSource>>();
}
 
public SQLTableModelSource(SQLRowValues graph) {
this.table = graph.getTable();
public SQLTableModelSource(final ListSQLRequest req, final SQLElement elem) {
if (elem == null)
throw new IllegalArgumentException("Missing element");
this.req = req;
this.elem = elem;
if (!this.getPrimaryTable().equals(this.elem.getTable()))
throw new IllegalArgumentException("not the same table: " + this.getPrimaryTable() + " != " + this.elem);
this.setAllCols(SQLTableModelColumns.empty());
this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>());
this.debugCols = new ArrayList<SQLTableModelColumn>();
this.inited = graph;
this.inited = req.getGraph();
}
 
public SQLElement getElem() {
return this.elem;
}
 
// lazy initialization since this method calls colsChanged() which subclasses overload and
// they need their own attribute that aren't set yet since super() must be the first statement.
public void init() {
117,7 → 132,7
public void executeChecked(final FieldPath input) {
final SQLField f = input.getField();
if (f.getTable().getLocalContentFields().contains(f)) {
final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input);
final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input, null, getElem().getDirectory());
SQLTableModelSource.this.cols.add(col);
} else
SQLTableModelSource.this.debugCols.add(new SQLTableModelColumnPath(input.getPath(), f.getName(), f.toString()) {
145,7 → 160,8
}
 
public SQLTableModelSource(SQLTableModelSource src) {
this.table = src.table;
this.req = src.req;
this.elem = src.elem;
this.setAllCols(src.getAllColumns());
this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>(src.cols));
this.debugCols = new ArrayList<SQLTableModelColumn>(src.debugCols);
178,13 → 194,46
fireColsChanged(beforeState, afterState);
}
 
protected abstract SQLTableModelSourceState createState();
protected final SQLTableModelSourceState createState() {
return new SQLTableModelSourceState(this.getAllColumns(), this.getReq());
}
 
protected void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
private final void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
final AtomicBoolean biggerGraph = new AtomicBoolean(false);
// add needed fields for each new column
this.getReq().changeGraphToFetch(new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues g) {
for (final SQLTableModelColumn col : change.getItemsAdded()) {
// DebugRow should uses all *fetched* fields, but since it cannot know them
// getPaths() return all fields in the table. So don't fetch all fields just for
// this debug column
if (!(col instanceof DebugRow)) {
for (final IFieldPath p : col.getPaths()) {
if (BaseFillSQLRequest.addToFetch(g, p.getPath(), Collections.singleton(p.getFieldName())))
biggerGraph.set(true);
}
}
}
}
});
if (biggerGraph.get() && !allowBiggerGraph())
throw new IllegalStateException("Bigger graph not allowed");
}
 
protected abstract boolean allowBiggerGraph();
 
private void fireColsChanged(final SQLTableModelSourceState beforeState, final SQLTableModelSourceState afterState) {
// let know each of our LinesSource that the columns have changed
for (final SQLTableModelLinesSource line : getLines()) {
line.colsChanged(beforeState, afterState);
}
// before notifying our regular listeners
this.supp.firePropertyChange("cols", null, this.cols);
}
 
private final List<SQLTableModelLinesSource> getLines() {
final List<SQLTableModelLinesSource> res = new ArrayList<SQLTableModelLinesSource>();
int i = 0;
while (i < this.lines.size()) {
final WeakReference<SQLTableModelLinesSource> l = this.lines.get(i);
192,14 → 241,17
if (line == null)
this.lines.remove(i);
else {
line.colsChanged(beforeState, afterState);
res.add(line);
i++;
}
}
// before notifying our regular listeners
this.supp.firePropertyChange("cols", null, this.cols);
return res;
}
 
protected final int getLinesCount() {
return this.getLines().size();
}
 
public final SQLTableModelLinesSource createLinesSource(ITableModel model) {
this.init();
final SQLTableModelLinesSource res = this._createLinesSource(model);
214,7 → 266,9
*
* @return the maximum graph of our lines.
*/
public abstract SQLRowValues getMaxGraph();
public final SQLRowValues getMaxGraph() {
return this.getReq().getGraphToFetch();
}
 
// * columns
 
293,8 → 347,12
 
// * SQLIdentifier
 
public final ListSQLRequest getReq() {
return this.req;
}
 
public final SQLTable getPrimaryTable() {
return this.table;
return this.getReq().getPrimaryTable();
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/TableAction.java
18,6 → 18,7
 
public class TableAction {
private String id = null;
private boolean showList = false;
private List<RowAction> actions = new ArrayList<RowAction>();
public TableAction(final RowAction action) {
48,7 → 49,15
return null;
}
public void setShowList(final boolean showList) {
this.showList = showList;
}
public boolean isShowList() {
return this.showList;
}
public int getActionsCount() {
return this.actions.size();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateQueue.java
28,6 → 28,7
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.sql.view.list.search.SearchQueue.SetStateRunnable;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SleepingQueue;
50,8 → 51,6
 
import net.jcip.annotations.GuardedBy;
 
import org.apache.commons.collections.CollectionUtils;
 
public final class UpdateQueue extends SleepingQueue {
 
/**
132,7 → 131,7
@Override
protected void started() {
// savoir quand les tables qu'on affiche changent
addListeners();
addSourceListener();
stateChanged(null, this.getModel().getReq().createState());
// Only starts once there's something to search, that way the runnable passed to
// ITableModel.search() will be meaningful
222,7 → 221,7
if (id < SQLRow.MIN_VALID_ID)
throw new IllegalArgumentException("invalid ID: " + id);
if (!fullList.isEmpty()) {
final SQLRowValues proto = this.getModel().getLinesSource().getParent().getMaxGraph();
final SQLRowValues proto = this.getState().getReq().getGraphToFetch();
final List<Path> pathsToT = new ArrayList<Path>();
proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
@Override
286,7 → 285,7
this.tableModel.getSearchQueue().fullListChanged();
}
 
final void reorder(final List<Number> idsOrder) {
final void reorder(final List<Integer> idsOrder) {
final List<ListSQLLine> fullList = this.getFullList();
synchronized (fullList) {
for (final ListSQLLine l : fullList) {
383,7 → 382,7
put(new SetStateRunnable() {
@Override
public void run() {
UpdateQueue.this.state = afterState;
setState(afterState);
}
});
// TODO if request didn't change and the new graph is smaller, copy and prune the
393,6 → 392,14
});
}
 
protected final void setState(final SQLTableModelSourceState newState) {
if (this.state != null)
this.rmTableListener();
this.state = newState;
if (this.state != null)
this.addTableListener();
}
 
protected final SQLTableModelSourceState getState() {
assert this.currentlyInQueue();
if (this.state == null)
402,29 → 409,24
 
@Override
protected void willDie() {
this.removeListeners();
this.rmTableListener();
this.removeSourceListener();
super.willDie();
}
 
protected final void addTableListener() {
for (final SQLTable t : this.tableModel.getReq().getTables()) {
t.addTableModifiedListener(this.tableListener);
this.getState().getReq().addTableListener(this.tableListener);
}
}
 
private void addListeners() {
this.addTableListener();
private void addSourceListener() {
this.tableModel.getLinesSource().addListener(this.tableListener);
}
 
protected final void rmTableListener() {
for (final SQLTable t : this.tableModel.getReq().getTables()) {
t.removeTableModifiedListener(this.tableListener);
this.getState().getReq().removeTableListener(this.tableListener);
}
}
 
private void removeListeners() {
this.rmTableListener();
private void removeSourceListener() {
this.tableModel.getLinesSource().rmListener(this.tableListener);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AutoCompletionManager.java
232,6 → 232,15
} else {
rowV = AutoCompletionManager.this.fillFrom.getTable().getRow(id);
}
// Test pour éviter de perdre la sélection d'un article ayant la même désignation
// mais un code différent (Problème remonté par Afhymat avec la tabulation)
if (fillFrom != null && fillFrom.getTable().getName().equals("ARTICLE") && fillFrom.getName().equals("NOM") && rowDest != null && !rowDest.isUndefined()) {
SQLRowAccessor rowArt = rowDest.getForeign("ID_ARTICLE");
if (rowArt != null && !rowArt.isUndefined() && rowArt.getString("NOM") != null && rowArt.getString("NOM").trim().equalsIgnoreCase(rowV.getString("NOM").trim())) {
return;
}
}
 
final Set<String> keys = AutoCompletionManager.this.fillBy.keySet();
// Fill the table model rowvalue with the selected item using the fields defined
// with 'fill'
250,7 → 259,8
|| !AutoCompletionManager.this.table.getRowValuesTableModel().getValueAt(rowE, column).equals(fromV)) {
AutoCompletionManager.this.table.getRowValuesTableModel().setValueAt(fromV, rowE, column);
// Test Only if not foreign --> Bug avec le
// sqltextcombocelleditor, si test edit cellAt -> fire idSelected
// sqltextcombocelleditor, si test edit cellAt -> fire
// idSelected
// -1 sur la combo ce qui entraine une déselection (Bug Remonté
// par SA Poulignier)
if (!AutoCompletionManager.this.foreign && AutoCompletionManager.this.table.getEditingColumn() == column
278,10 → 288,12
}).start();
}
 
public void fillRowValues(SQLRowAccessor from, SQLRowValues to) {
final Set<String> keys = AutoCompletionManager.this.fillBy.keySet();
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
String fromField = iter.next();
public Set<String> getFieldsFrom() {
return this.fillBy.keySet();
}
 
public void fillRowValues(SQLRowAccessor from, Set<String> fields, SQLRowValues to) {
for (String fromField : fields) {
String toField = AutoCompletionManager.this.fillBy.get(fromField);
to.put(toField, getValueFrom(from.asRow(), fromField, to));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AbstractUpdateOneRunnable.java
19,7 → 19,6
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSelect;
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;
78,9 → 77,10
final List<ListSQLLine> lines = (List<ListSQLLine>) e.getValue();
// primary table already handled above
if (p.length() > 0 && !lines.isEmpty()) {
final ListSQLRequest updateQueueReq = getModel().getLinesSource().getUpdateQueueReq();
// 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().followPathToOne(p, CreateMode.CREATE_NONE, false).deepCopy();
final SQLRowValues proto = updateQueueReq.getGraphToFetch().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
102,25 → 102,22
// reloading rows from the primary table and not just the changed rows)
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(proto);
BaseFillSQLRequest.setupForeign(fetcher);
final ITransformer<SQLSelect, SQLSelect> transf = new ITransformer<SQLSelect, SQLSelect>() {
if (updateQueueReq.isLockSelect()) {
fetcher.appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
if (ListSQLRequest.getDefaultLockSelect())
input.addLockedTable(getTable().getName());
return input.setWhere(getRow().getWhere());
return input;
}
};
fetcher.setSelTransf(transf);
final List<SQLRowValues> fetched = fetcher.fetch();
if (fetched.size() > 1)
throw new IllegalStateException("more than one row fetched for " + this + " with " + fetcher.getReq() + " :\n" + fetched);
});
}
final SQLRowValues soleFetched = fetcher.fetchOne(getRow().getIDNumber());
 
// OK if lastReferentField != null : a referent row has been deleted
if (fetched.size() == 0 && lastReferentField == null) {
if (soleFetched == null && 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 = CollectionUtils.getSole(fetched);
// copy it to each affected lines
for (final ListSQLLine line : lines) {
// don't update a part of the line, if the whole has been be passed to
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTextComboTableCellEditor.java
129,13 → 129,11
}
this.comboBox.grabFocus();
 
this.comboBox.addValueListener(new PropertyChangeListener() {
this.comboBox.addModelListener("wantedID", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!SQLTextComboTableCellEditor.this.comboBox.isUpdating()) {
SQLTextComboTableCellEditor.this.val = SQLTextComboTableCellEditor.this.comboBox.getSelectedId();
public void propertyChange(final PropertyChangeEvent evt) {
SQLTextComboTableCellEditor.this.val = SQLTextComboTableCellEditor.this.comboBox.getWantedID();
}
}
});
// Filtre sur une valeur specifique
if (this.fieldWhere != null && table instanceof RowValuesTable) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOnline.java
53,7 → 53,7
fireChanged(evt);
}
};
this.getReq().addWhereListener(this.listener);
this.getParent().getReq().addWhereListener(this.listener);
this.moveQ = null;
}
 
68,7 → 68,7
 
@Override
protected void die() {
this.getReq().rmWhereListener(this.listener);
this.getParent().getReq().rmWhereListener(this.listener);
 
if (this.moveQ != null) {
final RunningState threadState = this.moveQ.getRunningState();
88,14 → 88,6
return this.parent;
}
 
public final ListSQLRequest getReq() {
return this.getParent().getReq();
}
 
public final ListSQLRequest getUpdateQueueReq() {
return ((SQLTableModelSourceStateOnline) this.getModel().getUpdateQ().getState()).getReq();
}
 
@Override
public List<ListSQLLine> getAll() {
final List<SQLRowValues> values = this.getUpdateQueueReq().getValues();
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceState.java
13,17 → 13,25
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.request.ListSQLRequest;
 
import net.jcip.annotations.Immutable;
 
@Immutable
abstract class SQLTableModelSourceState {
public final class SQLTableModelSourceState {
 
private final ListSQLRequest req;
private final SQLTableModelColumns allCols;
 
protected SQLTableModelSourceState(final SQLTableModelColumns allCols) {
protected SQLTableModelSourceState(final SQLTableModelColumns allCols, final ListSQLRequest req) {
this.req = req.toUnmodifiable();
this.allCols = allCols;
}
 
public final ListSQLRequest getReq() {
return this.req;
}
 
public final SQLTableModelColumns getAllColumns() {
return this.allCols;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOffline.java
14,8 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.request.ListSQLRequest;
 
/**
* A SQLTableModelSource *not* directly tied to the database.
24,28 → 23,14
*/
public class SQLTableModelSourceOffline extends SQLTableModelSource {
 
private final SQLRowValuesListFetcher fetcher;
private final SQLElement elem;
 
public SQLTableModelSourceOffline(final SQLRowValuesListFetcher fetcher, final SQLElement elem) {
super(fetcher.getGraph());
this.fetcher = fetcher.toUnmodifiable();
this.elem = elem;
if (!this.getPrimaryTable().equals(this.elem.getTable()))
throw new IllegalArgumentException("not the same table: " + this.getPrimaryTable() + " != " + this.elem);
public SQLTableModelSourceOffline(final ListSQLRequest req, final SQLElement elem) {
super(req, elem);
}
 
public final SQLRowValuesListFetcher getFetcher() {
return this.fetcher;
}
 
public SQLElement getElem() {
return this.elem;
}
 
@Override
protected SQLTableModelSourceState createState() {
return new SQLTableModelSourceStateOffline(this.getAllColumns(), this.getFetcher());
protected boolean allowBiggerGraph() {
// MAYBE allow if all lines source have only committed rows (refresh them)
return this.getLinesCount() == 0;
}
 
@Override
52,9 → 37,4
protected SQLTableModelLinesSourceOffline _createLinesSource(final ITableModel model) {
return new SQLTableModelLinesSourceOffline(this, model);
}
 
@Override
public SQLRowValues getMaxGraph() {
return this.getFetcher().getGraph();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOnline.java
13,15 → 13,9
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.change.ListChangeIndex;
 
import java.util.Collections;
 
/**
* A SQLTableModelSource directly tied to the database. Any changes to its lines are propagated to
* the database without any delay.
30,55 → 24,21
*/
public class SQLTableModelSourceOnline extends SQLTableModelSource {
 
private final ListSQLRequest req;
 
public SQLTableModelSourceOnline(ListSQLRequest req) {
super(req.getGraph());
this.req = req;
public SQLTableModelSourceOnline(final ListSQLRequest req, final SQLElement elem) {
super(req, elem);
}
 
public SQLTableModelSourceOnline(SQLTableModelSourceOnline src) {
super(src);
this.req = src.req;
}
 
public final ListSQLRequest getReq() {
return this.req;
}
 
@Override
protected SQLTableModelSourceState createState() {
return new SQLTableModelSourceStateOnline(this.getAllColumns(), this.getReq());
protected boolean allowBiggerGraph() {
return true;
}
 
@Override
protected void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
super.colsChanged(change);
// add needed fields for each new column
this.getReq().changeGraphToFetch(new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues g) {
for (final SQLTableModelColumn col : change.getItemsAdded()) {
// DebugRow should uses all *fetched* fields, but since it cannot know them
// getPaths() return all fields in the table. So don't fetch all fields just for
// this debug column
if (!(col instanceof DebugRow)) {
for (final IFieldPath p : col.getPaths()) {
BaseFillSQLRequest.addToFetch(g, p.getPath(), Collections.singleton(p.getFieldName()));
}
}
}
}
});
}
 
@Override
protected SQLTableModelLinesSourceOnline _createLinesSource(final ITableModel model) {
return new SQLTableModelLinesSourceOnline(this, model);
}
 
@Override
public SQLRowValues getMaxGraph() {
return this.getReq().getGraphToFetch();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ITableModel.java
16,6 → 16,7
import static org.openconcerto.sql.view.list.ITableModel.SleepState.AWAKE;
import static org.openconcerto.sql.view.list.ITableModel.SleepState.HIBERNATING;
import static org.openconcerto.sql.view.list.ITableModel.SleepState.SLEEPING;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.model.SQLRowAccessor;
936,5 → 937,4
}
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelColumnPath.java
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
36,14 → 37,10
*/
public class SQLTableModelColumnPath extends SQLTableModelColumn {
 
// try to find a RowItemDesc, fall back to SQLFieldTranslator.getDefaultDesc()
private static final RowItemDesc getDescFor(SQLField field) {
// FIXME : remove Configuration.getInstance() need
return getDescFor(field, Configuration.getInstance());
}
 
public static final RowItemDesc getDescFor(SQLField field, Configuration conf) {
final RowItemDesc res = conf == null ? SQLFieldTranslator.NULL_DESC : conf.getTranslator().getDescFor(field.getTable(), field.getName());
public static final RowItemDesc getDescFor(final SQLField field, SQLElementDirectory dir) {
if (dir == null && Configuration.getInstance() != null)
dir = Configuration.getInstance().getDirectory();
final RowItemDesc res = dir == null ? SQLFieldTranslator.NULL_DESC : dir.getTranslator().getDescFor(field.getTable(), field.getName());
if (res.equals(SQLFieldTranslator.NULL_DESC))
return SQLFieldTranslator.getDefaultDesc(field);
else
50,15 → 47,12
return res;
}
 
private static final String getLabelFor(SQLField field) {
return getDescFor(field).getLabel();
}
 
private final FieldPath p;
private final SQLElementDirectory dir;
private boolean editable;
 
public SQLTableModelColumnPath(Path p, String fieldName, final String name) {
this(new FieldPath(p, fieldName), name);
this(new FieldPath(p, fieldName), name, null);
}
 
public SQLTableModelColumnPath(SQLField f) {
66,12 → 60,13
}
 
public SQLTableModelColumnPath(FieldPath fp) {
this(fp, getDescFor(fp.getField()).getTitleLabel());
this(fp, null, null);
}
 
public SQLTableModelColumnPath(FieldPath fp, final String name) {
super(name);
public SQLTableModelColumnPath(FieldPath fp, final String name, final SQLElementDirectory dir) {
super(name == null ? getDescFor(fp.getField(), dir).getTitleLabel() : name);
this.p = fp;
this.dir = dir;
this.editable = true;
}
 
84,9 → 79,9
public String getToolTip() {
final List<String> humanPath = new ArrayList<String>(this.p.getPath().length());
for (final SQLField f : this.p.getPath().getSingleFields()) {
humanPath.add(getLabelFor(f));
humanPath.add(getDescFor(f, this.dir).getLabel());
}
humanPath.add(getLabelFor(this.p.getField()));
humanPath.add(getDescFor(this.p.getField(), this.dir).getLabel());
return CollectionUtils.join(humanPath, IListe.SEP);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListe.java
15,7 → 15,6
 
import org.openconcerto.openoffice.XMLFormatVersion;
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.SQLComponent;
136,6 → 135,8
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
 
import net.jcip.annotations.GuardedBy;
 
/**
* Une liste de lignes correspondant à une ListSQLRequest. Diagramme pour la sélection :
* <img src="doc-files/listSelection.png"/><br/>
272,11 → 273,14
private final JTable jTable;
private final JTextField filter;
private boolean debugFilter;
@GuardedBy("this")
private FilterWorker filterWorker;
// optional popup on the table
private final JPopupMenu popup;
private final TableSorter sorter;
// record the source when non-displayable (ie getModel() == null)
@GuardedBy("this")
// record the source when non-displayable (ie getModel() == null), also allow to be read outside
// of the EDT
private SQLTableModelSource src;
private boolean adjustVisible;
private ColumnSizeAdjustor tcsa;
306,23 → 310,11
 
private int retainCount = 0;
 
public IListe(final ListSQLRequest req) {
this(req, null);
}
 
public IListe(final ListSQLRequest req, File configFile) {
this((Object) req, configFile);
}
 
public IListe(final SQLTableModelSource req) {
this(req, null);
}
 
public IListe(final SQLTableModelSource req, File configFile) {
this((Object) req, configFile);
}
 
private IListe(final Object req, File configFile) {
public IListe(final SQLTableModelSource req, final File configFile) {
if (req == null)
throw new NullPointerException("Création d'une IListe avec une requete null");
 
441,7 → 433,6
this.filter = new JTextField();
this.filter.setEditable(false);
this.debugFilter = false;
this.filterWorker = null;
 
// do not handle F2, let our application use it :
// remove F2 keybinding, use space
529,10 → 520,7
// MAYBE only set this.src and let the model be null so that the mere creation of an IListe
// does not spawn several threads and access the db. But a lot of code assumes there's
// immediately a model.
if (req instanceof SQLTableModelSource)
this.setSource((SQLTableModelSource) req);
else
this.setRequest((ListSQLRequest) req);
this.setSource(req);
this.state = ListSelectionState.manage(this.jTable.getSelectionModel(), new TableListStateModel(this.sorter));
this.state.addPropertyChangeListener("selectedIndex", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
736,6 → 724,9
}
});
FontUtils.setFontFor(this.filter, SEP);
// initially hide to limit modifications for instances which don't need the filter, see
// setFilter() comment
this.setFilter(null);
this.updateFilter();
 
// * JTable
981,10 → 972,15
* @param text the text to display, <code>null</code> to hide the label.
*/
private void setFilter(String text) {
final boolean currentVisible = this.filter.isVisible();
final boolean newVisible = text != null;
// limit modifications due to a bug in Swing that can bring the frame to the back.
if (newVisible || currentVisible) {
this.filter.setText(text == null ? "" : text);
this.filter.setVisible(text != null);
this.filter.setVisible(newVisible);
this.revalidate();
}
}
 
public void selectID(final int id) {
this.selectIDs(Collections.singleton(id));
1146,7 → 1142,7
return clazz.cast(toCast);
}
 
public SQLRow fetchRow(int id) {
private SQLRow fetchRow(int id) {
if (id < SQLRow.MIN_VALID_ID) {
return null;
} else
1419,32 → 1415,25
}
 
public final ListSQLRequest getRequest() {
// TODO a superclass of ListSQLRequest for use in SQLTableModelSource
// our clients always use either setWhere() or setSelTransf()
// also add the ability to Offline to respect the filter
return ((SQLTableModelSourceOnline) this.getSource()).getReq();
return this.getSource().getReq();
}
 
public final void setRequest(ListSQLRequest listReq) {
// a ListSQLRequest can be changed with setWhere()/setFilterEnable(), so copy it
this.setSource(new SQLTableModelSourceOnline(listReq));
}
 
public final void setSource(SQLTableModelSource src) {
if (src == null)
throw new NullPointerException();
// necessary to limit table model changes, since it recreates columns (and thus forget about
// customizations, eg renderers)
synchronized (this) {
// necessary to limit table model changes, since it recreates columns (and thus forget
// about customizations, eg renderers)
if (this.src == src)
return;
 
this.src = src;
}
this.setTableModel(new ITableModel(src));
}
 
public final SQLTableModelSource getSource() {
final ITableModel m = this.getModel();
return m == null ? null : m.getReq();
public synchronized final SQLTableModelSource getSource() {
return this.src;
}
 
public final File getConfigFile() {
1485,7 → 1474,7
if (!requiredToLive && !this.isDead()) {
this.setTableModel(null);
} else if (requiredToLive && this.isDead()) {
this.setTableModel(new ITableModel(this.src));
this.setTableModel(new ITableModel(this.getSource()));
}
}
 
1577,7 → 1566,7
Thread.sleep(60);
 
final List<String> ancestors = new ArrayList<String>();
final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
final SQLElementDirectory dir = getSource().getElem().getDirectory();
// always put the description of getRows(), but only put their ancestor if they all have
// the same parent
Tuple2<SQLRow, String> parentAndDesc = getParent(this.getRows(), dir);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/IListFrame.java
14,6 → 14,7
package org.openconcerto.sql.view;
 
import static org.openconcerto.utils.FileUtils.FILENAME_ESCAPER;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.State;
import org.openconcerto.sql.element.SQLComponent;
21,8 → 22,10
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.ui.FrameUtil;
import org.openconcerto.ui.state.WindowStateManager;
import org.openconcerto.utils.StringUtils;
 
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.PropertyChangeEvent;
42,27 → 45,38
public class IListFrame extends JFrame {
 
public static final String SHORT_TITLE = "org.openconcerto.listframe.shortTitle";
private static final String FILE_STRUCT_VERSION = "20160923";
 
// state-EditFrame/cpi-window.xml
// windowState-20160923/IListFrame/ARTICLE.xml
static public final File getConfigFile(final SQLElement elem, final Class<? extends JFrame> c) {
return getConfigFile(elem, null, c);
}
 
// state-EditFrame/cpi-FullComp-window.xml
// windowState-20160923/IListFrame/ARTICLE-componentCode.xml
static public final File getConfigFile(final SQLComponent comp, final Class<? extends JFrame> c) {
return getConfigFile(comp.getElement(), comp, c);
}
 
static private final File getConfigFile(final SQLElement elem, final SQLComponent comp, final Class<? extends JFrame> c) {
static private final File getConfigFile(final SQLElement elem, final SQLComponent comp, final Class<? extends Window> c) {
final String compName = comp == null ? "" : "-" + comp.getCode();
return getConfigFile(c, elem.getCode() + compName);
}
 
// windowState-20160923/WindowClass/code.xml
static public final File getConfigFile(final Class<? extends Window> c, final String code) {
final Configuration conf = Configuration.getInstance();
if (conf == null)
return null;
// no getSimpleName() since for inner classes this is empty
final String compName = comp == null ? "" : "-" + comp.getClass().getName();
final String filename = FILENAME_ESCAPER.escape(elem.getPluralName() + compName) + "-window.xml";
return new File(conf.getConfDir(), "state-" + c.getSimpleName() + File.separator + filename);
final File structFile = new File(conf.getConfDir(), "windowState-" + FILE_STRUCT_VERSION);
return new File(structFile, c.getSimpleName() + File.separator + getConfigFileName(code));
}
 
static final String getConfigFileName(String code) {
if (StringUtils.isEmpty(code, true))
code = "default";
return StringUtils.Shortener.MD5.getBoundedLengthString(FILENAME_ESCAPER.escape(code), 70) + ".xml";
}
 
private final IListPanel panel;
private String title;
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/IListPanel.java
15,6 → 15,7
 
import static javax.swing.JOptionPane.DEFAULT_OPTION;
import static javax.swing.JOptionPane.QUESTION_MESSAGE;
 
import org.openconcerto.openoffice.ContentType;
import org.openconcerto.openoffice.OOUtils;
import org.openconcerto.openoffice.XMLFormatVersion;
24,7 → 25,6
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.sqlobject.SQLRequestComboBox;
import org.openconcerto.sql.users.rights.TableAllRights;
import org.openconcerto.sql.users.rights.UserRights;
38,6 → 38,7
import org.openconcerto.ui.SwingThreadUtils;
import org.openconcerto.ui.component.JRadioButtons.JStringRadioButtons;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Tuple2.List2;
import org.openconcerto.utils.cc.IClosure;
98,16 → 99,24
 
protected static final String FALLBACK_KEY = "FALLBACK_ACTION";
 
private static final String FILE_STRUCT_VERSION = "20161018";
 
static public final File getConfigFile(final SQLElement elem, final Class<? extends Container> c) {
return getConfigFile(elem, c, null);
}
 
static public final File getConfigFile(final SQLElement elem, final Class<? extends Container> c, final String variant) {
final String suffix = StringUtils.isEmpty(variant, true) ? "" : "-" + variant;
return getConfigFile(c, elem.getCode() + suffix);
}
 
static public final File getConfigFile(final Class<? extends Container> c, String code) {
final Configuration conf = Configuration.getInstance();
if (conf == null)
return null;
final String suffix = variant == null || variant.length() == 0 ? "" : "-" + variant;
return new File(conf.getConfDir(), "state-" + c.getSimpleName() + "-list" + File.separator + elem.getPluralName() + suffix + ".xml");
 
final File structFile = new File(conf.getConfDir(), "jtableState-" + FILE_STRUCT_VERSION);
return new File(structFile, c.getSimpleName() + File.separator + IListFrame.getConfigFileName(code));
}
 
private final IListe liste;
834,12 → 843,6
this.searchComponent.setSearchFullMode(b);
}
 
public void setRequest(ListSQLRequest req) {
if (!req.getPrimaryTable().equals(this.getElement().getTable()))
throw new IllegalArgumentException("table diff: " + req + " / " + this.getElement());
this.getListe().setRequest(req);
}
 
/**
* Récupérer les valeurs de la row sélectionnée lors de l'ajout
*
/trunk/OpenConcerto/src/org/openconcerto/sql/view/EditPanel.java
26,7 → 26,6
import org.openconcerto.sql.users.rights.UserRights;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.sql.view.list.IListe;
import org.openconcerto.ui.component.InteractionMode;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidObject;
171,7 → 170,7
this.component = e;
try {
this.component.setMode(mode.getCompMode());
this.component.setNonExistantEditable(this.mode == CREATION);
this.component.setNonExistantEditable(this.getMode() == CREATION);
if (this.component instanceof BaseSQLComponent) {
for (int i = 0; i < hiddenFields.size(); i++) {
final SQLField hiddenField = hiddenFields.get(i);
179,7 → 178,7
}
}
 
if (this.mode != READONLY) {
if (this.getMode() != READONLY) {
// on écoute les changements de validation,
// avant component.uiInit() car il fait un fireValidChange()
this.component.addValidListener(new ValidListener() {
213,6 → 212,10
}
}
 
public final EditMode getMode() {
return this.mode;
}
 
private void updateBtns() {
updateBtn(this.jButtonAjouter, true, false, "noRightToAdd", TableAllRights.ADD_ROW_TABLE);
updateBtn(this.jButtonModifier, true, true, "noRightToModify", TableAllRights.MODIFY_ROW_TABLE);
300,7 → 303,7
c.gridheight = 1;
c.fill = GridBagConstraints.NONE;
this.keepOpen.setOpaque(false);
if (this.mode == CREATION) {
if (this.getMode() == CREATION) {
 
c.gridx = 1;
c.fill = GridBagConstraints.HORIZONTAL;
331,7 → 334,7
}
}
});
} else if (this.mode == MODIFICATION) {
} else if (this.getMode() == MODIFICATION) {
c.gridx = 1;
c.anchor = GridBagConstraints.EAST;
this.jButtonModifier = new JButton(TM.tr("saveModifications"));
347,7 → 350,7
c.weightx = 0;
c.gridx = 3;
c.anchor = GridBagConstraints.EAST;
if (this.mode == READONLY)
if (this.getMode() == READONLY)
this.jButtonAnnuler = new JButton(TM.tr("close"));
else
this.jButtonAnnuler = new JButton(TM.tr("cancel"));
390,7 → 393,7
// laisser passer les valides qui écrasent tout autant.
if (id < SQLRow.MIN_VALID_ID)
this.component.select(null);
else if (this.mode == CREATION) {
else if (this.getMode() == CREATION) {
this.component.select(this.element.createCopy(id));
} else {
this.component.select(id);
399,11 → 402,11
 
protected final void apply() {
final JButton b;
if (this.mode == CREATION)
if (this.getMode() == CREATION)
b = this.jButtonAjouter;
else if (this.mode == MODIFICATION)
else if (this.getMode() == MODIFICATION)
b = this.jButtonModifier;
else if (this.mode == READONLY)
else if (this.getMode() == READONLY)
b = this.jButtonAnnuler;
else
b = null;
610,7 → 613,7
}
 
public String getDocId() {
return this.mode + "_" + this.element.getTable().getName();
return this.getMode() + "_" + this.element.getTable().getName();
}
 
public String getGenericDoc() {
/trunk/OpenConcerto/src/org/openconcerto/sql/PropsConfiguration.java
27,8 → 27,8
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.LogUtils;
import org.openconcerto.utils.MultipleOutputStream;
43,7 → 43,6
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
122,6 → 121,10
* redirect {@link System#err} and {@link System#out}.
*/
public static final String REDIRECT_TO_FILE = "redirectToFile";
/**
* Must be one of {@link StandardStreamsDest}.
*/
public static final String STD_STREAMS_DESTINATION = "stdStreamsDest";
 
// properties cannot contain null, so to be able to override a default, a non-null value
// meaning empty must be chosen (as setProperty(name, null) is the same as remove(name) i.e.
186,12 → 189,15
private ProductInfo productInfo;
@GuardedBy("restLock")
private SQLFilter filter;
private final Addable<SQLFieldTranslator> translator;
private final Addable<SQLElementDirectory> directory;
@GuardedBy("restLock")
private File wd;
@GuardedBy("restLock")
private BaseDirs baseDirs;
@GuardedBy("restLock")
private File logDir;
private final boolean inIDE;
 
// split sql tree and the rest since creating the tree is costly
// and nodes are inter-dependant, while the rest is mostly fast
// different instances, otherwise lock every Conf instances
234,7 → 240,9
this.directory = new Addable<SQLElementDirectory>() {
@Override
protected SQLElementDirectory create() {
return createDirectory();
final SQLElementDirectory res = createDirectory();
res.setTranslator(createTranslator(res));
return res;
}
 
@Override
242,18 → 250,7
obj.putAll(conf.getDirectory());
}
};
// SQLFieldTranslator is thread-safe
this.translator = new Addable<SQLFieldTranslator>() {
@Override
protected SQLFieldTranslator create() {
return createTranslator();
}
 
@Override
protected void add(SQLFieldTranslator obj, Configuration conf) {
obj.putAll(conf.getTranslator());
}
};
this.inIDE = Boolean.getBoolean("inIDE");
this.setUp();
}
 
271,7 → 268,6
UserRightsManager.clearInstanceIfSame(this.urMngr);
}
 
this.translator.destroy();
this.directory.destroy();
super.destroy();
}
291,6 → 287,10
throw new IllegalStateException("Destroyed");
}
 
public final boolean isInIDE() {
return this.inIDE;
}
 
public final String getProperty(final String name) {
return this.props.getProperty(name);
}
438,9 → 438,15
if (!hasWANProperties(wanAddr, wanPort))
return doCreateServer();
 
final String serverPropVal = getProperty("server.ip");
// TODO add and use server.port
final List<String> serverAndPort = Arrays.asList(serverPropVal.split(":"));
if (serverAndPort.size() != 2)
throw new IllegalStateException("Not in 'host:port' format : " + serverPropVal);
 
// if wanAddr is specified, always include it in ID, that way if we connect through the LAN
// or through the WAN we have the same ID
final String serverID = "tunnel to " + wanAddr + ":" + wanPort + " then " + getProperty("server.ip");
final String serverID = "tunnel to " + wanAddr + ":" + wanPort + " then " + serverPropVal;
final Logger log = Log.get();
Exception origExn = null;
final SQLServer defaultServer;
465,10 → 471,8
log.info("ssl connection to " + this.conn.getHost() + ":" + this.conn.getPort());
final int localPort = NetUtils.findFreePort(5436);
try {
// TODO add and use server.port
final String[] serverAndPort = getProperty("server.ip").split(":");
log.info("ssl tunnel from local port " + localPort + " to remote " + serverAndPort);
this.conn.setPortForwardingL(localPort, serverAndPort[0], Integer.valueOf(serverAndPort[1]));
this.conn.setPortForwardingL(localPort, serverAndPort.get(0), Integer.valueOf(serverAndPort.get(1)));
} catch (final Exception e1) {
throw new IllegalStateException("Impossible de créer la liaison sécurisée. Vérifier que le logiciel n'est pas déjà lancé.", e1);
}
686,14 → 690,28
this.setupLogging("logs");
}
 
public void setupLogging(final String dirName) {
this.setupLogging(dirName, Boolean.getBoolean(REDIRECT_TO_FILE));
public final void setupLogging(final String dirName) {
this.setupLogging(dirName, getStandardStreamsDestination());
}
 
protected boolean keepStandardStreamsWhenRedirectingToFile() {
return true;
protected final StandardStreamsDest getStandardStreamsDestination() {
String propVal = System.getProperty(STD_STREAMS_DESTINATION);
if (propVal == null)
propVal = this.getProperty(STD_STREAMS_DESTINATION);
 
final StandardStreamsDest res;
if (propVal != null)
res = StandardStreamsDest.valueOf(propVal);
else
res = getStandardStreamsDestinationDefault();
return res;
}
 
// used if neither system property nor configuration property are set
protected StandardStreamsDest getStandardStreamsDestinationDefault() {
return Boolean.getBoolean(REDIRECT_TO_FILE) ? StandardStreamsDest.ALSO_TO_FILE : StandardStreamsDest.DEFAULT;
}
 
protected DateFormat getLogDateFormat() {
return DATE_FORMAT;
}
722,7 → 740,20
return logDir;
}
 
public void setupLogging(final String dirName, final boolean redirectToFile) {
static public enum StandardStreamsDest {
DEFAULT(true), ONLY_TO_FILE(false), ALSO_TO_FILE(true);
private final boolean hasDefaultStreams;
 
private StandardStreamsDest(boolean hasDefaultStreams) {
this.hasDefaultStreams = hasDefaultStreams;
}
 
public final boolean hasDefaultStreams() {
return this.hasDefaultStreams;
}
}
 
public final void setupLogging(final String dirName, final StandardStreamsDest stdRedirect) {
final File logDir;
synchronized (this.restLock) {
if (this.logDir != null)
733,26 → 764,22
final String logNameBase = this.getAppName() + "_" + getLogDateFormat().format(new Date());
 
// must be done before setUpConsoleHandler(), otherwise log output not redirected
if (redirectToFile) {
if (stdRedirect != StandardStreamsDest.DEFAULT) {
final File logFile = new File(logDir, (logNameBase + ".txt"));
try {
FileUtils.mkdir_p(logFile.getParentFile());
System.out.println("Log file: " + logFile.getAbsolutePath());
System.out.println("Standard output and error file: " + logFile.getAbsolutePath());
final OutputStream fileOut = new FileOutputStream(logFile, true);
final OutputStream out, err;
System.out.println("Java System console:" + System.console());
boolean launchedFromEclipse = new File(".classpath").exists();
if (launchedFromEclipse) {
System.out.println("Launched from eclipse");
}
if ((System.console() != null || launchedFromEclipse) && this.keepStandardStreamsWhenRedirectingToFile()) {
if (this.isInIDE() || stdRedirect != StandardStreamsDest.ONLY_TO_FILE) {
System.out.println("Redirecting standard output to file and console");
out = new MultipleOutputStream(fileOut, new FileOutputStream(FileDescriptor.out));
out = new MultipleOutputStream(fileOut, System.out);
System.out.println("Redirecting error output to file and console");
err = new MultipleOutputStream(fileOut, new FileOutputStream(FileDescriptor.err));
 
err = new MultipleOutputStream(fileOut, System.err);
} else {
System.out.println("Redirecting standard output to file");
out = fileOut;
System.out.println("Redirecting error output to file");
err = fileOut;
}
System.setErr(new PrintStream(new BufferedOutputStream(err, 128), true));
824,12 → 851,12
return Arrays.asList("mapping", "mapping-" + this.getProperty("customer"));
}
 
protected SQLFieldTranslator createTranslator() {
protected SQLFieldTranslator createTranslator(final SQLElementDirectory dir) {
final List<String> mappings = getMappings();
if (mappings.size() == 0)
throw new IllegalStateException("empty mappings");
 
final SQLFieldTranslator trns = new SQLFieldTranslator(this.getRoot(), null, this.getDirectory());
final SQLFieldTranslator trns = new SQLFieldTranslator(this.getRoot(), null, dir);
// perhaps listen to UserProps (as in TM)
return loadTranslations(trns, this.getRoot(), mappings);
}
868,6 → 895,10
return new File(this.getProperty("wd"));
}
 
protected BaseDirs createBaseDirs() {
return BaseDirs.create(getProductInfo(), getAppVariant());
}
 
// *** add
 
/**
879,7 → 910,6
*/
@Override
public final Configuration add(final Configuration conf) {
this.translator.add(conf);
this.directory.add(conf);
return this;
}
1066,7 → 1096,7
 
@Override
public final SQLFieldTranslator getTranslator() {
return this.translator.get();
return this.getDirectory().getTranslator();
}
 
@Override
1098,6 → 1128,15
}
}
 
@Override
public BaseDirs getBaseDirs() {
synchronized (this.restLock) {
if (this.baseDirs == null)
this.baseDirs = this.createBaseDirs();
return this.baseDirs;
}
}
 
// *** setters
 
// MAYBE add synchronized (not necessary since they're private, and only called with the lock)
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/RadioButtons.java
48,7 → 48,8
public RadioButtons(String colName) {
super();
this.colName = colName;
this.tm = Transformer.nopTransformer();
// ATTN javac 1.6 cannot infer the generic type
this.tm = Transformer.<String> nopTransformer();
}
 
public final RadioButtons initLocalization(final TM tm) {
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/ConnexionPanel.java
149,6 → 149,9
 
public ConnexionPanel(final Runnable r, final JImage imageLogo, final boolean societeSelector, final boolean allowStoredPass) {
super();
if (r == null) {
throw new IllegalArgumentException("null runnable");
}
this.login = new Login(Configuration.getInstance().getRoot());
 
this.societeSelector = societeSelector;
372,14 → 375,10
 
private boolean areFieldsValidated() {
if (this.societeSelector) {
final SQLRow selectedRow = this.comboSociete.getSelectedRow();
// don't use isData() since it calls isArchived() and since ComboRequest doesn't include
// ARCHIVE field this triggers a request
if (selectedRow == null || selectedRow.isUndefined()) {
if (this.comboSociete.isEmpty()) {
return false;
}
}
 
if (this.textLogin == null || this.textLogin.isEmpty() || this.textPassWord == null) {
return false;
} else {
486,6 → 485,7
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
ConnexionPanel.this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
JOptionPane.showMessageDialog(ConnexionPanel.this, TM.getTM().translate("loginPanel." + error, userName));
// Guillaume wants this for the Nego
492,7 → 492,10
if (Login.UNKNOWN_USER.equals(error))
ConnexionPanel.this.textLogin.setValue(ConnexionPanel.this.adminLogin);
setConnecting(false);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/SoftwareInfoPanel.java
22,6 → 22,7
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.ui.FormLayouter;
import org.openconcerto.ui.component.HTMLTextField;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.SystemInfo;
import org.openconcerto.utils.cc.IFactory;
87,9 → 88,16
if (propsConf != null && propsConf.isUsingSSH()) {
res.put(Info.SECURE_LINK, propsConf.getWanHostAndPort());
}
if (conf != null)
res.put(Info.DB_URL, conf.getSystemRoot().getDataSource().getUrl());
if (conf != null) {
final String logs = propsConf == null ? "" : " ; " + SystemInfo.getLink(TM.tr("infoPanel.logs"), propsConf.getLogDir().toURI(), html);
res.put(Info.DIRS, SystemInfo.getLink(TM.tr("infoPanel.docs"), conf.getWD().toURI(), html) + logs);
final BaseDirs baseDirs = conf.getBaseDirs();
String dirs = " ; " + SystemInfo.getLink(TM.tr("infoPanel.dataDir"), baseDirs.getAppDataFolder().toURI(), html);
dirs = dirs + " ; " + SystemInfo.getLink(TM.tr("infoPanel.prefsDir"), baseDirs.getPreferencesFolder().toURI(), html);
dirs = dirs + " ; " + SystemInfo.getLink(TM.tr("infoPanel.cacheDir"), baseDirs.getCacheFolder().toURI(), html);
res.put(Info.DIRS, SystemInfo.getLink(TM.tr("infoPanel.docs"), conf.getWD().toURI(), html) + logs + dirs);
}
 
return res;
}
116,7 → 124,11
if (secureLink != null) {
this.l.add(TM.tr("infoPanel.secureLink"), new JLabel(secureLink));
}
this.l.add(TM.tr("infoPanel.dbURL"), new JLabel(infos.get(Info.DB_URL)));
this.l.add(TM.tr("infoPanel.dirs"), new HTMLTextField(infos.get(Info.DIRS)));
final JLabel dbURL = new JLabel(infos.get(Info.DB_URL));
if (dbURL != null)
this.l.add(TM.tr("infoPanel.dbURL"), dbURL);
final String dirs = infos.get(Info.DIRS);
if (dirs != null)
this.l.add(TM.tr("infoPanel.dirs"), new HTMLTextField(dirs));
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/SearchInfo.java
13,6 → 13,7
package org.openconcerto.sql.ui.light;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.view.search.SearchList;
import org.openconcerto.sql.view.search.TextSearchSpec;
import org.openconcerto.sql.view.search.TextSearchSpec.Mode;
23,6 → 24,7
import java.util.List;
 
public class SearchInfo {
// TODO: add notion of operator
private final SearchList list = new SearchList();
private final List<String> texts = new ArrayList<String>();
 
32,7 → 34,7
final SearchContent param = params.getContent().get(i);
final String col = param.getColumn();
final String type = param.getType();
final String text = param.getText();
final String[] tTexts = param.getText().split(" ");
 
Mode mode = Mode.CONTAINS;
if (type.equals("contains")) {
47,12 → 49,13
throw new IllegalArgumentException("mode " + type + " not supported");
}
 
System.err.println("SearchInfo.SearchInfo() column:" + col + "type:" + type + " text:" + text);
System.err.println("SearchInfo.SearchInfo() ignoring column " + col);
for (final String text : tTexts) {
this.list.addSearchItem(new TextSearchSpec(text, mode));
this.texts.add(text);
Log.get().info("searching column:" + col + "type:" + type + " text:" + text);
}
}
}
 
public List<String> getTexts() {
return this.texts;
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightAutoCompleteComboBox.java
New file
0,0 → 1,104
/*
* 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.model.Where;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
import org.openconcerto.ui.light.LightUIComboBox;
import org.openconcerto.ui.light.LightUIComboBoxElement;
import org.openconcerto.utils.io.JSONConverter;
 
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
 
import net.minidev.json.JSONObject;
 
public class LightAutoCompleteComboBox extends LightUIComboBox {
private static final String FILTER = "filter";
 
private final static Pattern QUERY_SPLIT_PATTERN = Pattern.compile("\\s+");
 
private String filter;
 
private ComboSQLRequest request;
 
public LightAutoCompleteComboBox(final JSONObject json) {
super(json);
}
 
public LightAutoCompleteComboBox(final String id) {
super(id);
this.setType(TYPE_AUTOCOMPLETE_COMBOBOX);
}
 
public void setComboRequest(final ComboSQLRequest request) {
this.request = request;
this.setFilter("");
this.setAlreadyFilled(true);
}
 
public ComboSQLRequest getComboRequest() {
return this.request;
}
 
public String getFilter() {
return this.filter;
}
 
public void setFilter(final String filter) {
this.filter = filter;
this.applyFilter();
}
 
private void applyFilter() {
if (this.request != null) {
Integer selectedId = null;
if (this.hasSelectedValue()) {
selectedId = this.getSelectedValue().getId();
}
 
this.clearValues();
if (this.hasNotSpecifedLine()) {
this.addValue(LightUIComboBox.getDefaultValue());
}
 
final Where where = this.hasSelectedValue() ? new Where(this.request.getPrimaryTable().getKey(), "=", this.getSelectedValue().getId()) : null;
final List<IComboSelectionItem> items = this.request.getComboItems(true, Arrays.asList(QUERY_SPLIT_PATTERN.split(this.filter)), Locale.getDefault(), where);
 
System.err.println("LightAutoCompleteComboBox.applyFilter() - items count: " + items.size());
for (final IComboSelectionItem item : items) {
this.addValue(new LightUIComboBoxElement(item.getId(), item.getLabel()));
}
 
this.setSelectedId(selectedId);
}
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put(FILTER, this.filter);
return json;
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
 
this.filter = JSONConverter.getParameterFromJSON(json, FILTER, String.class);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightUIPanelFiller.java
15,25 → 15,23
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.Constraint;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.sqlobject.ElementComboBoxUtils;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
import org.openconcerto.ui.light.LightUIComboBox;
import org.openconcerto.ui.light.LightUIComboBoxElement;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUILine;
import org.openconcerto.ui.light.LightUIPanel;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.io.JSONConverter;
 
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;
 
/**
* Fill value from default or database
64,11 → 62,11
this.fillFromRow(this.panel, configuration, row);
}
 
private void fillFromRow(final LightUIPanel panel, final PropsConfiguration configuration, SQLRowAccessor row) {
private void fillFromRow(final LightUIPanel panel, final PropsConfiguration configuration, SQLRowAccessor sqlRow) {
final int panelChildCount = panel.getChildrenCount();
// Convert as sqlrow if possible to get all values from db
if (row.hasID()) {
row = row.asRow();
if (sqlRow.hasID()) {
sqlRow = sqlRow.asRow();
}
for (int i = 0; i < panelChildCount; i++) {
final LightUILine panelChild = panel.getChild(i, LightUILine.class);
75,55 → 73,86
final int lineChildCount = panelChild.getChildrenCount();
for (int j = 0; j < lineChildCount; j++) {
final LightUIElement element = panelChild.getChild(j);
final SQLField field = configuration.getFieldMapper().getSQLFieldForItem(element.getId());
final SQLField sqlField = configuration.getFieldMapper().getSQLFieldForItem(element.getId());
 
SQLRowAccessor sqlRowTmp = this.getSQLRowForField(sqlRow, sqlField);
if (sqlRowTmp == null) {
throw new IllegalArgumentException("Impossible to reach the field: " + sqlField.getName() + " from table " + sqlRow.getTable().getName());
}
 
int type = element.getType();
if (type == LightUIElement.TYPE_TEXT_FIELD || type == LightUIElement.TYPE_TEXT_AREA) {
 
if (field == null) {
if (sqlField == 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.setValue(sqlRowTmp.getString(sqlField.getName()));
} else if (sqlField != null && sqlField.isKey() && (type == LightUIElement.TYPE_COMBOBOX || type == LightUIElement.TYPE_AUTOCOMPLETE_COMBOBOX)) {
// send: id,value
final LightUIComboBox combo = (LightUIComboBox) element;
if (!combo.isFillFromConvertor()) {
SQLTable foreignTable = field.getForeignTable();
final List<SQLField> fieldsToFetch = configuration.getDirectory().getElement(foreignTable).getComboRequest().getFields();
 
if (row.getObject(field.getName()) != null) {
final Where where = new Where(foreignTable.getKey(), "=", row.getForeignID(field.getName()));
final SQLRowValues graph = ElementComboBoxUtils.getGraphToFetch(configuration, foreignTable, fieldsToFetch);
final List<Tuple2<Path, List<FieldPath>>> expanded = ElementComboBoxUtils.expandGroupBy(graph, configuration.getDirectory());
List<SQLRowValues> fetchedRows = ElementComboBoxUtils.fetchRows(graph, where);
if (fetchedRows.size() > 1) {
throw new IllegalStateException("multiple rows fetched, id: " + ((row.hasID()) ? row.getID() : "undefined") + " table: " + row.getTable().getName());
LightUIComboBoxElement value = null;
final Number foreignID = sqlRowTmp.getForeignIDNumber(sqlField.getName());
if (foreignID != null) {
final SQLTable foreignTable = sqlField.getForeignTable();
final ComboSQLRequest req = configuration.getDirectory().getElement(foreignTable).getComboRequest();
final IComboSelectionItem comboItem = req.getComboItem(foreignID.intValue());
if (comboItem != null) {
value = new LightUIComboBoxElement(comboItem.getId());
value.setValue1(comboItem.getLabel());
}
 
for (final SQLRowValues vals : fetchedRows) {
LightUIComboBoxElement value = ElementComboBoxUtils.createLightUIItem(expanded, vals);
}
combo.setSelectedValue(value);
}
} else {
element.setValue(null);
}
}
} else if (type == LightUIElement.TYPE_CHECKBOX) {
if (row.getBoolean(field.getName())) {
if (sqlRowTmp.getObject(sqlField.getName()) != null && sqlRowTmp.getBoolean(sqlField.getName())) {
element.setValue("true");
} else {
element.setValue("false");
}
} else if (type == LightUIElement.TYPE_DATE) {
Calendar date = row.getDate(field.getName());
Calendar date = sqlRowTmp.getDate(sqlField.getName());
if (date != null) {
element.setValue(JSONConverter.getJSON(date).toString());
}
} else if (type == LightUIElement.TYPE_PANEL) {
this.fillFromRow((LightUIPanel) element, configuration, row);
this.fillFromRow((LightUIPanel) element, configuration, sqlRowTmp);
} else if (type == LightUIElement.TYPE_SLIDER) {
final Integer value = sqlRowTmp.getInt(sqlField.getName());
if (value != null) {
element.setValue(value.toString());
}
}
}
}
}
 
public SQLRowAccessor getSQLRowForField(final SQLRowAccessor sqlRow, final SQLField sqlField) {
SQLRowAccessor sqlRowResult = sqlRow;
if (sqlField != null && !sqlField.getTable().getName().equals(sqlRow.getTable().getName())) {
sqlRowResult = this.findSQLRow(sqlRow, sqlField);
}
return sqlRowResult;
}
 
public SQLRowAccessor findSQLRow(final SQLRowAccessor sqlRow, final SQLField sqlField) {
final Set<Constraint> constraints = sqlRow.getTable().getAllConstraints();
for (final Constraint constraint : constraints) {
if (constraint.getType().equals(ConstraintType.FOREIGN_KEY)) {
// FIXME: this doesn't work when foreign key is composed of more than one field
final String firstFkCols = constraint.getCols().get(0);
final SQLRowAccessor fkRow = sqlRow.getForeign(firstFkCols);
if (fkRow != null) {
if (fkRow.getTable().getName().equals(sqlField.getTable().getName())) {
return fkRow;
} else {
final SQLRowAccessor sqlRowResult = this.findSQLRow(fkRow, sqlField);
if (sqlRowResult != null) {
return sqlRowResult;
}
}
}
}
}
return null;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightForeignRowValuesTableOffline.java
New file
0,0 → 1,91
/*
* 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.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.sql.view.list.SQLTableModelLinesSourceOffline;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.SearchSpec;
import org.openconcerto.ui.light.TableContent;
 
import java.util.concurrent.Future;
 
import net.minidev.json.JSONObject;
 
public class LightForeignRowValuesTableOffline extends LightRowValuesTable {
 
private SQLField foreignField;
private Number parentRowId;
 
public LightForeignRowValuesTableOffline(final Configuration configuration, final Number userId, final String id, final ITableModel model, final SQLField foreignField, final Number parentRowId) {
super(configuration, userId, id, model);
 
this.foreignField = foreignField;
this.parentRowId = parentRowId;
this.init();
}
 
public LightForeignRowValuesTableOffline(final LightForeignRowValuesTableOffline table) {
super(table);
 
this.foreignField = table.foreignField;
this.parentRowId = table.parentRowId;
this.init();
}
 
private final void init() {
if (this.getTableSpec().getContent() == null) {
this.getTableSpec().setContent(new TableContent(this.getId()));
}
}
 
public final SQLField getForeignField() {
return this.foreignField;
}
 
public final Number getParentRowId() {
return this.parentRowId;
}
 
public Future<?> commitRows() {
return ((SQLTableModelLinesSourceOffline) this.getModel().getLinesSource()).commit();
}
 
public void addNewRow(final SQLRowValues sqlRow) {
((SQLTableModelLinesSourceOffline) this.getModel().getLinesSource()).add(sqlRow);
}
 
@Override
public void doSearch(final Configuration configuration, final SearchSpec searchSpec, final int offset) {
// TODO: Implement search in offline table
this.getModel().fireTableRowsInserted(0, Integer.MAX_VALUE);
}
 
@Override
public LightUIElement clone() {
return new LightForeignRowValuesTableOffline(this);
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
 
if (this.getTableSpec().getContent() != null) {
this.getTableSpec().getContent().clearRows();
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightForeignRowValuesTableOnline.java
New file
0,0 → 1,51
/*
* 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.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.ui.light.LightUIElement;
 
public class LightForeignRowValuesTableOnline extends LightRowValuesTableOnline {
private SQLField foreignField;
private Number parentRowId;
 
public LightForeignRowValuesTableOnline(final Configuration configuration, final Number userId, final String id, final ITableModel model, final SQLField foreignField, final Number parentRowId) {
super(configuration, userId, id, model);
 
this.foreignField = foreignField;
this.parentRowId = parentRowId;
}
 
public LightForeignRowValuesTableOnline(final LightForeignRowValuesTableOnline table) {
super(table);
 
this.foreignField = table.foreignField;
this.parentRowId = table.parentRowId;
}
 
public final SQLField getForeignField() {
return this.foreignField;
}
 
public final Number getParentRowId() {
return this.parentRowId;
}
 
@Override
public LightUIElement clone() {
return new LightForeignRowValuesTableOnline(this);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/GroupToLightUIConvertor.java
18,7 → 18,7
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.FieldMapper;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.request.RowItemDesc;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.ui.group.Group;
26,7 → 26,6
import org.openconcerto.ui.group.LayoutHints;
import org.openconcerto.ui.light.CustomEditorProvider;
import org.openconcerto.ui.light.LightUICheckBox;
import org.openconcerto.ui.light.LightUIComboBox;
import org.openconcerto.ui.light.LightUIDate;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUIFrame;
38,6 → 37,8
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.awt.Color;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
48,7 → 49,6
private PropsConfiguration configuration;
private FieldMapper mapper;
private Map<String, CustomEditorProvider> customEditorProviders = new HashMap<String, CustomEditorProvider>();
private Map<String, ConvertorModifer> modifers = new HashMap<String, ConvertorModifer>();
 
public GroupToLightUIConvertor(PropsConfiguration conf) {
this(conf, 4);
63,7 → 63,7
}
}
 
public LightEditFrame convert(final Group group, final SQLRowValues defaultRow, final LightUIFrame parentFrame, final EditMode editMode) {
public LightEditFrame convert(final Group group, final SQLRowAccessor defaultRow, final LightUIFrame parentFrame, final EditMode editMode) {
if (group == null) {
throw new IllegalArgumentException("Null Group");
}
76,66 → 76,69
throw new IllegalArgumentException("This group isn't attached to this SQLElement, group ID: " + group.getId() + " element code: " + sqlElement.getCode());
}
 
final LightEditFrame editFrame = new LightEditFrame(this.configuration, group, defaultRow, parentFrame, editMode);
final LightEditFrame editFrame = new LightEditFrame(this.configuration, group, defaultRow.asRowValues(), parentFrame, editMode);
final LightUIPanel framePanel = editFrame.getFirstChild(LightUIPanel.class);
append(framePanel, group);
append(sqlElement, framePanel, group);
 
String frameTitle = TranslationManager.getInstance().getTranslationForItem(group.getId());
if (frameTitle == null) {
frameTitle = group.getId();
}
editFrame.setTitle(frameTitle);
 
editFrame.createTitlePanel(frameTitle);
 
Log.get().warning("No translation for " + group.getId());
return editFrame;
}
 
private void append(final LightUIPanel panel, final Item item) {
private void append(final SQLElement sqlElement, final LightUIPanel panel, final Item item) {
if (item instanceof Group) {
final Group gr = (Group) item;
int size = gr.getSize();
 
final String groupTitle = TranslationManager.getInstance().getTranslationForItem(gr.getId());
final LightUIPanel childPanel = new LightUIPanel(gr.getId());
childPanel.setFillWidth(true);
childPanel.setGridWidth(4);
 
if (gr.getLocalHint().isFoldable()) {
final LightUIPanel childPanel = new LightUIPanel(gr.getId());
childPanel.setTitle(groupTitle);
childPanel.setFoldable(true);
childPanel.setGridWidth(4);
childPanel.setFillWidth(true);
for (int i = 0; i < size; i++) {
this.append(childPanel, gr.getItem(i));
}
if (this.modifers.containsKey(gr.getId())) {
this.modifers.get(gr.getId()).process(childPanel);
}
final LightUILine line = new LightUILine();
line.addChild(childPanel);
panel.addChild(line);
} else {
if (groupTitle != null) {
final LightUILine titleLine = new LightUILine();
final LightUILabel titleLabel = new LightUILabel(gr.getId() + ".title.label");
titleLabel.setGridWidth(4);
titleLabel.setFontBold(true);
titleLabel.setLabel(groupTitle);
titleLabel.setFillWidth(true);
titleLine.addChild(titleLabel);
panel.addChild(titleLine);
childPanel.addChild(titleLine);
final LightUILine line = new LightUILine();
panel.addChild(line);
childPanel.addChild(line);
}
}
 
for (int i = 0; i < size; i++) {
final Item it = gr.getItem(i);
this.append(panel, it);
this.append(sqlElement, childPanel, gr.getItem(i));
}
}
 
final LightUILine line = new LightUILine();
line.addChild(childPanel);
panel.addChild(line);
} else {
final LayoutHints localHint = item.getLocalHint();
LightUILine currentLine = panel.getLastLine();
 
if (currentLine.getTotalGridWidth() >= 4) {
currentLine = new LightUILine();
panel.addChild(currentLine);
}
 
currentLine.setMarginTop(1);
currentLine.setMarginBottom(1);
if (localHint.isSeparated()) {
if (currentLine.getWidth() > 0) {
if (currentLine.getChildrenCount() > 0) {
currentLine = new LightUILine();
panel.addChild(currentLine);
}
148,7 → 151,7
currentLine.setWeightY(1);
}
 
if (currentLine.getWidth() >= this.maxColumnCount) {
if (currentLine.getChildrenCount() >= this.maxColumnCount) {
currentLine = new LightUILine();
panel.addChild(currentLine);
}
155,28 → 158,21
 
final SQLField field = this.mapper.getSQLFieldForItem(item.getId());
LightUILabel elementLabel = null;
 
String label = this.getLabelForItem(field, item);
if (label == null) {
label = item.getId();
Log.get().warning("No translation for " + item.getId());
}
 
if (localHint.showLabel()) {
currentLine.setElementPadding(5);
 
elementLabel = new LightUILabel(item.getId() + ".label");
elementLabel.setHorizontalAlignement(LightUIElement.HALIGN_RIGHT);
String label = TranslationManager.getInstance().getTranslationForItem(item.getId());
 
if (label == null && field != null) {
final RowItemDesc desc = this.configuration.getTranslator().getDescFor(field.getTable(), field.getName());
if (desc != null) {
label = desc.getLabel();
}
}
 
if (label == null) {
label = item.getId();
elementLabel.setBackgroundColor(Color.ORANGE);
elementLabel.setToolTip("No translation for " + item.getId());
Log.get().warning("No translation for " + item.getId());
}
 
elementLabel.setLabel(label);
elementLabel.setWeightX(0);
if (localHint.isSplit()) {
elementLabel.setHorizontalAlignement(LightUIElement.HALIGN_LEFT);
if (currentLine.getChildrenCount() != 0) {
195,8 → 191,9
if (field != null) {
Class<?> javaType = field.getType().getJavaType();
if (field.isKey()) {
elementEditor = new LightUIComboBox(item.getId());
elementEditor = new LightAutoCompleteComboBox(item.getId());
elementEditor.setMinInputSize(20);
elementEditor.setValueType(LightUIElement.VALUE_TYPE_REF);
} else if (javaType.equals(String.class)) {
if (field.getType().getSize() > 1000) {
elementEditor = new LightUITextArea(item.getId());
207,15 → 204,24
elementEditor.setValue("");
elementEditor.setMinInputSize(10);
}
elementEditor.setValueType(LightUIElement.VALUE_TYPE_STRING);
} else if (javaType.equals(Boolean.class)) {
elementEditor = new LightUICheckBox(item.getId(), "");
elementEditor.setLabel(label);
elementEditor.setValueType(LightUIElement.VALUE_TYPE_BOOLEAN);
elementLabel.setLabel("");
} else if (javaType.equals(Date.class)) {
elementEditor = new LightUIDate(item.getId());
} else if (javaType.equals(Boolean.class)) {
elementEditor = new LightUICheckBox(item.getId(), "");
elementEditor.setValueType(LightUIElement.VALUE_TYPE_DATE);
} else if (javaType.equals(Timestamp.class)) {
elementEditor = new LightUIDate(item.getId());
} else if (javaType.equals(Integer.class)) {
elementEditor.setValueType(LightUIElement.VALUE_TYPE_DATE);
} else if (javaType.equals(Integer.class) || javaType.equals(Long.class) || javaType.equals(Short.class) || javaType.equals(BigInteger.class)) {
elementEditor = new LightUITextField(item.getId());
elementEditor.setValueType(LightUIElement.VALUE_TYPE_INTEGER);
} else if (javaType.equals(BigDecimal.class) || javaType.equals(Float.class) || javaType.equals(Double.class)) {
elementEditor = new LightUITextField(item.getId());
elementEditor.setValueType(LightUIElement.VALUE_TYPE_DECIMAL);
} else {
elementEditor = new LightUITextField(item.getId());
Log.get().warning("unsupported type " + javaType.getName());
225,6 → 231,7
elementEditor = new LightUITextField(item.getId());
elementEditor.setMinInputSize(10);
elementEditor.setToolTip("No field attached to " + item.getId());
elementEditor.setValueType(LightUIElement.VALUE_TYPE_STRING);
Log.get().warning("No field attached to " + item.getId());
if (elementLabel != null) {
elementLabel.setBackgroundColor(Color.ORANGE);
235,12 → 242,10
 
if (elementEditor != null) {
elementEditor.setWeightX(1);
if (this.modifers.containsKey(item.getId())) {
this.modifers.get(item.getId()).process(elementEditor);
}
}
 
if (localHint.isSplit()) {
if (currentLine.getWidth() > 0) {
if (currentLine.getTotalGridWidth() > 0) {
currentLine = new LightUILine();
panel.addChild(currentLine);
}
259,11 → 264,24
elementEditor.setGridWidth(1);
}
elementEditor.setFillWidth(localHint.fillWidth());
elementEditor.setMarginBottom(4);
currentLine.addChild(elementEditor);
 
}
}
 
private String getLabelForItem(final SQLField field, final Item item) {
String label = TranslationManager.getInstance().getTranslationForItem(item.getId());
 
if (label == null && field != null) {
final RowItemDesc desc = this.configuration.getTranslator().getDescFor(field.getTable(), field.getName());
if (desc != null) {
label = desc.getLabel();
}
}
return label;
}
 
private LightUIElement getCustomEditor(final String id) {
final CustomEditorProvider customEditorProvider = this.customEditorProviders.get(id);
if (customEditorProvider != null) {
283,12 → 301,4
public void putAllCustomEditorProvider(final Map<String, CustomEditorProvider> map) {
this.customEditorProviders.putAll(map);
}
 
public void addModifer(final String itemId, final ConvertorModifer modifer) {
this.modifers.put(itemId, modifer);
}
 
public void addAllModifer(final Map<String, ConvertorModifer> modifers) {
this.modifers.putAll(modifers);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightRowValuesTable.java
14,211 → 14,309
package org.openconcerto.sql.ui.light;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.sql.view.list.ListSQLLine;
import org.openconcerto.sql.view.list.SQLTableModelColumn;
import org.openconcerto.sql.view.list.SQLTableModelSourceOnline;
import org.openconcerto.ui.SwingThreadUtils;
import org.openconcerto.ui.light.ColumnSpec;
import org.openconcerto.ui.light.ColumnsSpec;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUITable;
import org.openconcerto.ui.light.Row;
import org.openconcerto.ui.light.TableContent;
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.ui.light.SearchSpec;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.cc.ITransformer;
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
 
import net.minidev.json.JSONArray;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelListener;
 
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.DOMBuilder;
 
import net.minidev.json.JSONObject;
 
public class LightRowValuesTable extends LightUITable {
List<SQLRowValues> listRowValues = new ArrayList<SQLRowValues>();
public abstract class LightRowValuesTable extends LightUITable {
 
String fieldRefName;
private SQLRowAccessor refRow;
private boolean autoCommit = false;
private List<Integer> deletedIds = new ArrayList<Integer>();
public static final int MAX_LINE_TO_SEND = 100;
 
// Init from json constructor
public LightRowValuesTable(final JSONObject json) {
super(json);
this.init();
private int totalRowCount = -1;
 
private int offset = 0;
 
private ITableModel model;
 
private final ITransformer<SQLSelect, SQLSelect> orginTransformer;
 
private List<TableModelListener> tableModelListeners = new ArrayList<TableModelListener>();
 
public LightRowValuesTable(final Configuration configuration, final Number userId, final String id, final ITableModel model) {
super(id);
 
this.model = model;
final ColumnsSpec columnsSpec = this.createColumnsSpecFromModelSource(configuration, userId);
 
this.getTableSpec().setColumns(columnsSpec);
 
if (model.getReq() instanceof SQLTableModelSourceOnline) {
final SQLTableModelSourceOnline source = (SQLTableModelSourceOnline) model.getReq();
this.orginTransformer = source.getReq().getSelectTransf();
} else {
this.orginTransformer = null;
}
 
this.setDynamicLoad(true);
}
 
// Clone constructor
public LightRowValuesTable(final LightRowValuesTable tableElement) {
super(tableElement);
this.listRowValues = tableElement.listRowValues;
this.fieldRefName = tableElement.fieldRefName;
this.deletedIds = tableElement.deletedIds;
this.init();
 
this.model = tableElement.model;
this.totalRowCount = tableElement.totalRowCount;
this.orginTransformer = tableElement.orginTransformer;
}
 
public LightRowValuesTable(final LightUITable table, final String fieldRefName) {
super(table);
this.fieldRefName = fieldRefName;
this.init();
// Json constructor
public LightRowValuesTable(final JSONObject json) {
super(json);
this.orginTransformer = null;
}
 
public String getFieldRefName() {
return this.fieldRefName;
public final int getTotalRowsCount() {
return this.totalRowCount;
}
 
public SQLRowValues getRowValues(final int index) {
return this.listRowValues.get(index);
public final int getOffset() {
return this.offset;
}
 
public int getRowValuesCount() {
return this.listRowValues.size();
public final void setOffset(final int offset) {
this.offset = offset;
}
 
public boolean isAutoCommit() {
return this.autoCommit;
public final void addTableModelListener(final TableModelListener tableModelListener) {
this.tableModelListeners.add(tableModelListener);
this.model.addTableModelListener(tableModelListener);
}
 
public void setAutoCommit(boolean autoCommit) {
if (autoCommit) {
if (this.refRow == null) {
throw new IllegalArgumentException("Set parent row before put this table in auto commit mode");
public final void removeTableModelListener(final TableModelListener tableModelListener) {
this.tableModelListeners.remove(tableModelListener);
this.model.removeTableModelListener(tableModelListener);
}
 
public ITableModel getModel() {
return this.model;
}
this.autoCommit = autoCommit;
}
 
/**
* Permet de charger les lignes et de lier les nouvelles lignes
*
*/
public void setParentSQLRow(final Configuration configuration, final SQLElement sqlElement, final SQLRowAccessor sqlRow) {
 
final TableContent content = new TableContent(getId());
content.setRows(new ArrayList<Row>());
getTableSpec().setContent(content);
this.refRow = sqlRow;
this.refetchTable(configuration, sqlElement);
public final LightListSqlRow getRowFromSqlID(final Number sqlID) {
if (this.hasRow()) {
final int size = this.getTableSpec().getContent().getRowsCount();
for (int i = 0; i < size; i++) {
final LightListSqlRow row = (LightListSqlRow) this.getRow(i);
if (NumberUtils.areNumericallyEqual(row.getSqlRow().getIDNumber(), sqlID)) {
return row;
}
}
}
return null;
}
 
public void refetchTable(final Configuration configuration, final SQLElement sqlElement) {
this.getTableSpec().getContent().getRows().clear();
this.listRowValues.clear();
if (this.refRow != null && !this.refRow.isUndefined()) {
final SQLTableModelSourceOnline tableSource = sqlElement.getTableSource(true);
public final LightListSqlRow createLightListRowFromListLine(final ListSQLLine listSqlLine, final int index) throws IllegalStateException {
final ColumnsSpec columnsSpec = this.getTableSpec().getColumns();
final List<SQLTableModelColumn> sqlColumns = this.getModelColumns();
final int colSize = sqlColumns.size();
 
final ListSQLRequest req = tableSource.getReq();
req.setWhere(new Where(sqlElement.getTable().getField(getFieldRefName()), "=", this.refRow.getID()));
List<SQLRowValues> listRowValues = req.getValues();
final LightListSqlRow row = new LightListSqlRow(listSqlLine.getRow(), listSqlLine.getID());
final List<Object> values = new ArrayList<Object>();
for (int i = 0; i < colSize; i++) {
final String columnId = columnsSpec.getColumn(i).getId();
final SQLTableModelColumn col = getColumnFromId(sqlColumns, columnId);
 
for (final SQLRowValues rowValues : listRowValues) {
this.addRowValues(configuration, rowValues);
if (col != null) {
Object value = col.show(row.getSqlRow());
if (col.getLightUIrenderer() != null) {
value = col.getLightUIrenderer().getLightUIElement(value, 0, i);
}
values.add(value);
} else {
throw new IllegalArgumentException("column " + columnId + " is in ColumnsSpec but it is not found in SQLTableModelColumn");
}
}
row.setValues(values);
 
public SQLRowAccessor getRefRow() {
return this.refRow;
return row;
}
 
public void clearRowValues(){
this.getTableSpec().getContent().getRows().clear();
this.listRowValues.clear();
public final SQLRowAccessor getFirstSelectedSqlRow() {
final List<Row> selectedRows = this.getSelectedRows();
if (selectedRows.isEmpty()) {
return null;
} else {
return ((LightListSqlRow) selectedRows.get(0)).getSqlRow();
}
public void removeRowValuesAt(int index) {
final TableContent content = this.getTableSpec().getContent();
content.getRows().remove(index);
this.listRowValues.remove(index);
}
 
public void addRowValues(final Configuration configuration, final SQLRowValues rowValues) {
final TableContent content = this.getTableSpec().getContent();
this.listRowValues.add(rowValues);
 
content.getRows().add(this.createRowFromRowValues(configuration, rowValues, this.listRowValues.size() - 1));
private final List<SQLTableModelColumn> getModelColumns() {
try {
// TODO: clean swing
return SwingThreadUtils.call(new Callable<List<SQLTableModelColumn>>() {
@Override
public List<SQLTableModelColumn> call() throws Exception {
return LightRowValuesTable.this.getModel().getReq().getColumns();
}
 
public void setRowValues(final Configuration configuration, final SQLRowValues rowValues, final int index) {
final TableContent content = this.getTableSpec().getContent();
this.listRowValues.set(index, rowValues);
content.getRows().set(index, this.createRowFromRowValues(configuration, rowValues, index));
});
} catch (final Exception ex) {
throw new IllegalStateException(ex);
}
}
 
public void archiveDeletedRows(final Configuration configuration) {
final SQLElement sqlElement = configuration.getDirectory().getElementForCode(this.getElementCode());
for (final Integer deletedId : this.deletedIds) {
try {
sqlElement.archive(deletedId);
} catch (final SQLException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
private final SQLTableModelColumn getColumnFromId(final List<SQLTableModelColumn> allCols, final String columnId) {
final int columnSize = allCols.size();
for (int i = 0; i < columnSize; i++) {
final SQLTableModelColumn tableModelColumn = allCols.get(i);
if (tableModelColumn.getIdentifier().equals(columnId)) {
return tableModelColumn;
}
}
return null;
}
 
public void addDeletedId(final int idToDelete) {
this.deletedIds.add(idToDelete);
}
/**
* Get columns user preferences for a specific table
*
* @param configuration - The user SQL configuration
* @param userId - Id of the user who want view the table
*
* @return the XML which contains user preferences
*
* @throws IllegalArgumentException
* @throws IllegalStateException
*
*/
// TODO: move in LightUITable, maybe move LightUITable in FrameWork_SQL
private final Document getColumnsSpecUserPerfs(final Configuration configuration, final Number userId) throws IllegalArgumentException, IllegalStateException {
Document columnsPrefs = null;
final DOMBuilder in = new DOMBuilder();
org.w3c.dom.Document w3cDoc = null;
 
protected Row createRowFromRowValues(final Configuration configuration, final SQLRowValues sqlRow, final int index) {
final SQLElement element = configuration.getDirectory().getElementForCode(this.getElementCode());
if (element == null) {
throw new IllegalArgumentException("Unable to find element for code: " + this.getElementCode());
w3cDoc = configuration.getXMLConf(userId, this.getId());
if (w3cDoc != null) {
columnsPrefs = in.build(w3cDoc);
}
return columnsPrefs;
}
 
final SQLTableModelSourceOnline tableSource = element.getTableSource(true);
final List<SQLTableModelColumn> allCols = tableSource.getColumns();
/**
* Create ColumnsSpec from list of SQLTableModelColumn and apply user preferences
*
* @param configuration - current SQL configuration of user
* @param userId - Id of user
*
* @return New ColumnsSpec with user preferences application
*
* @throws IllegalArgumentException
* @throws IllegalStateException
*/
private final ColumnsSpec createColumnsSpecFromModelSource(final Configuration configuration, final Number userId) throws IllegalArgumentException, IllegalStateException {
final List<String> possibleColumnIds = new ArrayList<String>();
final List<String> sortedIds = new ArrayList<String>();
final List<ColumnSpec> columnsSpec = new ArrayList<ColumnSpec>();
 
final Row row = element.createRowFromSQLRow(sqlRow, allCols, this.getTableSpec().getColumns());
row.setId(index);
return row;
final List<SQLTableModelColumn> columns = this.getModelColumns();
final int columnsCount = columns.size();
 
for (int i = 0; i < columnsCount; i++) {
final SQLTableModelColumn sqlColumn = columns.get(i);
// TODO : creer la notion d'ID un peu plus dans le l'esprit sales.invoice.amount
final String columnId = sqlColumn.getIdentifier();
 
possibleColumnIds.add(columnId);
Class<?> valueClass = sqlColumn.getValueClass();
if (sqlColumn.getLightUIrenderer() != null) {
valueClass = LightUIElement.class;
}
 
private void init() {
if (this.getTableSpec().getContent() == null) {
this.getTableSpec().setContent(new TableContent(this.getId()));
columnsSpec.add(new ColumnSpec(columnId, valueClass, sqlColumn.getName(), null, false, null));
}
 
// TODO : recuperer l'info sauvegardée sur le serveur par user (à coder)
sortedIds.add(columnsSpec.get(0).getId());
 
final ColumnsSpec cSpec = new ColumnsSpec(this.getId(), columnsSpec, possibleColumnIds, sortedIds);
cSpec.setAllowMove(true);
cSpec.setAllowResize(true);
 
final Document xmlColumnsPref = this.getColumnsSpecUserPerfs(configuration, userId);
if (!cSpec.setUserPrefs(xmlColumnsPref)) {
configuration.removeXMLConf(userId, this.getId());
}
 
@Override
public LightUIElement clone() {
return new LightRowValuesTable(this);
return cSpec;
}
 
// TODO: merge with OpenConcerto List search system
public abstract void doSearch(final Configuration configuration, final SearchSpec searchSpec, final int offset);
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
 
// TODO: implement row values JSONAble
if (this.listRowValues != null && !this.listRowValues.isEmpty()) {
json.put("list-row-values", null);
public Row getRowById(Number rowId) {
for (int i = 0; i < this.model.getRowCount(); i++) {
if (NumberUtils.areNumericallyEqual(this.model.getRow(i).getID(), rowId)) {
return this.createLightListRowFromListLine(this.model.getRow(i), i);
}
if (this.deletedIds != null && !this.deletedIds.isEmpty()) {
json.put("deleted-ids", JSONConverter.getJSON(this.deletedIds));
}
 
return json;
return super.getRowById(rowId);
}
 
/**
* Create default columns preferences from the SQLTableModelLinesSourceOnline
*
* @return XML document with columns preferences
*/
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
final JSONArray jsonListRowValues = JSONConverter.getParameterFromJSON(json, "list-row-values", JSONArray.class);
this.listRowValues = new ArrayList<SQLRowValues>();
// TODO: implement row values JSONAble
if (jsonListRowValues != null) {
public Document createDefaultXmlPreferences() {
final Element rootElement = new Element("list");
final List<SQLTableModelColumn> columns = this.getModelColumns();
 
final int sqlColumnsCount = columns.size();
for (int i = 0; i < sqlColumnsCount; i++) {
final SQLTableModelColumn sqlColumn = columns.get(i);
final String columnId = sqlColumn.getIdentifier();
final ColumnSpec columnSpec = new ColumnSpec(columnId, sqlColumn.getValueClass(), sqlColumn.getName(), null, false, null);
final Element columnElement = this.createXmlColumn(columnId, columnSpec.getMaxWidth(), columnSpec.getMinWidth(), columnSpec.getWidth());
rootElement.addContent(columnElement);
}
final Document xmlConf = new Document(rootElement);
 
if (this.getTableSpec().getContent() != null) {
this.getTableSpec().getContent().getRows().clear();
return xmlConf;
}
 
final JSONArray jsonDeletedIds = JSONConverter.getParameterFromJSON(json, "deleted-ids", JSONArray.class);
this.deletedIds = new ArrayList<Integer>();
if (jsonDeletedIds != null) {
for (final Object jsonDeletedId : jsonDeletedIds) {
this.deletedIds.add(JSONConverter.getObjectFromJSON(jsonDeletedId, Integer.class));
@Override
public void destroy() {
super.destroy();
 
// TODO: clean swing
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
final List<TableModelListener> tableModelListeners = LightRowValuesTable.this.tableModelListeners;
for (int i = tableModelListeners.size() - 1; i > -1; i--) {
LightRowValuesTable.this.removeTableModelListener(tableModelListeners.get(i));
}
}
});
} catch (final Exception ex) {
throw new IllegalStateException(ex);
}
 
this.clearRows();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightEditFrame.java
20,12 → 20,12
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.sql.view.list.ListSQLLine;
import org.openconcerto.sql.view.list.SQLTableModelLinesSourceOffline;
import org.openconcerto.ui.group.Group;
import org.openconcerto.ui.group.Item;
import org.openconcerto.ui.light.ComboValueConvertor;
import org.openconcerto.ui.light.CustomEditorProvider;
import org.openconcerto.ui.light.JSONToLightUIConvertor;
import org.openconcerto.ui.light.LightUICheckBox;
33,13 → 33,14
import org.openconcerto.ui.light.LightUIDate;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUIFrame;
import org.openconcerto.ui.light.StringValueConvertor;
import org.openconcerto.utils.io.JSONConverter;
 
import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.Future;
 
import net.minidev.json.JSONObject;
 
87,10 → 88,13
/**
* Commit the SQLRowValues attached to this frame
*
* @param configuration Current configuration
* @param configuration - Current configuration
*
* @return The inserted SQLRow
*
* @throws SQLException When an error occur in SQLRowValues.commit()
*/
public SQLRow commitSqlRow(final Configuration configuration) {
public SQLRow commitSqlRow(final Configuration configuration) throws SQLException {
if (this.editMode.equals(EditMode.READONLY)) {
throw new IllegalArgumentException("Impossible to commit values when the frame is read only");
}
98,7 → 102,7
try {
return this.sqlRow.prune(sqlElement.getPrivateGraph()).commit();
} catch (final SQLException ex) {
throw new IllegalArgumentException("Unable to commit SQLRowValues, edit frame ID: " + this.getId());
throw ex;
}
}
 
120,14 → 124,14
* @param conf
* @param userId
*/
public void updateRow(final Configuration configuration, final int userId) {
public void updateRow(final Configuration configuration, final String sessionSecurityToken, final int userId) {
if (this.editMode.equals(EditMode.READONLY)) {
throw new IllegalArgumentException("Impossible to update values when the frame is read only");
}
this.updateRow(configuration, this.group, userId);
this.updateRow(configuration, this.group, sessionSecurityToken, userId);
}
 
private void updateRow(final Configuration configuration, final Group group, final int userId) {
private void updateRow(final Configuration configuration, final Group group, final String sessionSecurityToken, final int userId) {
final FieldMapper fieldMapper = configuration.getFieldMapper();
if (fieldMapper == null) {
throw new IllegalStateException("null field mapper");
134,26 → 138,25
}
 
final SQLElement sqlElement = configuration.getDirectory().getElement(this.sqlRow.getTable());
final Map<String, ComboValueConvertor<?>> valueConvertors = sqlElement.getComboConvertors();
 
Map<String, CustomEditorProvider> customEditors = null;
final Map<String, CustomEditorProvider> customEditors;
if (this.editMode.equals(EditMode.CREATION)) {
customEditors = sqlElement.getCustomEditorProviderForCreation(configuration, userId);
customEditors = sqlElement.getCustomEditorProviderForCreation(configuration, sessionSecurityToken);
} else {
customEditors = sqlElement.getCustomEditorProviderForModification(configuration, this.sqlRow, userId);
customEditors = sqlElement.getCustomEditorProviderForModification(configuration, this.sqlRow, sessionSecurityToken);
}
 
this.createRowValues(configuration, fieldMapper, this.group, valueConvertors, customEditors);
this.createRowValues(configuration, sqlElement, fieldMapper, this.group, customEditors);
this.setMetaData(userId);
}
 
final protected void createRowValues(final Configuration conf, final FieldMapper fieldMapper, final Group group, final Map<String, ComboValueConvertor<?>> valueConvertors,
final protected void createRowValues(final Configuration configuration, final SQLElement sqlElement, final FieldMapper fieldMapper, final Group group,
final Map<String, CustomEditorProvider> customEditors) {
final int itemCount = group.getSize();
for (int i = 0; i < itemCount; i++) {
final Item item = group.getItem(i);
if (item instanceof Group) {
this.createRowValues(conf, fieldMapper, (Group) item, valueConvertors, customEditors);
this.createRowValues(configuration, sqlElement, fieldMapper, (Group) item, customEditors);
} else {
final SQLField field = fieldMapper.getSQLFieldForItem(item.getId());
if (field != null) {
163,28 → 166,10
throw new IllegalArgumentException("Impossible to find UI Element with id: " + item.getId());
}
 
if (!valueConvertors.containsKey(item.getId()) && !customEditors.containsKey(item.getId())) {
this.putValueFromUserControl(conf, field, uiElement);
} else if (valueConvertors.containsKey(item.getId())) {
if (!(uiElement instanceof LightUIComboBox)) {
throw new IllegalArgumentException("The UI Element with ID " + item.getId() + ", must be an instance of LightUIComboBox");
if (!uiElement.isNotSaved()) {
this.putValueFromUserControl(configuration, sqlElement, field, uiElement, customEditors);
}
final LightUIComboBox combo = (LightUIComboBox) uiElement;
if (combo.hasSelectedValue() && combo.getSelectedValue().getId() != 0) {
final ComboValueConvertor<?> valueConvertor = valueConvertors.get(item.getId());
if (valueConvertor instanceof StringValueConvertor) {
this.sqlRow.put(field.getFieldName(), ((StringValueConvertor) valueConvertor).getIdFromIndex(combo.getSelectedValue().getId()));
} else {
final int selectedId = combo.getSelectedValue().getId();
this.sqlRow.put(field.getFieldName(), selectedId);
}
} else {
this.sqlRow.put(field.getFieldName(), null);
}
} else if (customEditors.containsKey(item.getId())) {
Log.get().warning("Unable to save value of element: " + item.getId());
}
} else {
Log.get().warning("No field attached to " + item.getId());
}
}
191,59 → 176,79
}
}
 
final protected void putValueFromUserControl(final Configuration conf, final SQLField field, final LightUIElement uiElement) {
final Class<?> fieldType = field.getType().getJavaType();
if (field.isKey()) {
final protected void putValueFromUserControl(final Configuration configuration, final SQLElement sqlElement, final SQLField sqlField, final LightUIElement uiElement,
final Map<String, CustomEditorProvider> customEditors) {
if (!uiElement.isNotSaved()) {
final Class<?> fieldType = sqlField.getType().getJavaType();
if (customEditors.containsKey(uiElement.getId())) {
final CustomEditorProvider customEditor = customEditors.get(uiElement.getId());
if (customEditor instanceof SavableCustomEditorProvider) {
((SavableCustomEditorProvider) customEditor).save(this.sqlRow, sqlField, uiElement);
}
} else {
final String fieldName = sqlField.getFieldName();
if (sqlField.isKey()) {
if (!(uiElement instanceof LightUIComboBox)) {
throw new IllegalArgumentException("Invalid UI Element for field: " + field.getName() + ". When field is foreign key, UI Element must be a LightUIDate");
throw new IllegalArgumentException("Invalid UI Element for field: " + fieldName + ". When field is foreign key, UI Element must be a LightUIDate");
}
final LightUIComboBox combo = (LightUIComboBox) uiElement;
 
if (combo.hasSelectedValue()) {
final SQLRowValues tmp = new SQLRowValues(this.sqlRow.getTable()).put(field.getName(), null);
conf.getDirectory().getShowAs().expand(tmp);
final SQLRowValues toFetch = (SQLRowValues) tmp.getForeign(field.getName());
tmp.remove(field.getName());
 
final SQLRowValues fetched = SQLRowValuesListFetcher.create(toFetch).fetchOne(combo.getSelectedValue().getId());
if (fetched == null) {
throw new IllegalArgumentException("Impossible to find Row in database - table: " + field.getForeignTable().getName() + ", id: " + combo.getSelectedValue().getId());
this.sqlRow.put(fieldName, combo.getSelectedValue().getId());
} else {
this.sqlRow.put(fieldName, null);
}
 
this.sqlRow.put(field.getFieldName(), fetched);
} else {
this.sqlRow.put(field.getFieldName(), null);
}
} else if (fieldType.equals(String.class)) {
this.sqlRow.put(field.getFieldName(), uiElement.getValue());
final String value = uiElement.getValue();
if (value == null && !sqlField.isNullable()) {
Log.get().warning("ignoring null value for not nullable field " + fieldName + " from table " + sqlField.getTable().getName());
} else {
if (fieldType.equals(String.class)) {
// FIXME check string size against field size
this.sqlRow.put(fieldName, value);
} else if (fieldType.equals(Date.class)) {
if (!(uiElement instanceof LightUIDate)) {
throw new IllegalArgumentException("Invalid UI Element for field: " + field.getName() + ". When field is Date, UI Element must be a LightUIDate");
throw new IllegalArgumentException("Invalid UI Element for field: " + fieldName + ". When field is Date, UI Element must be a LightUIDate");
}
this.sqlRow.put(field.getFieldName(), ((LightUIDate) uiElement).getValueAsDate());
this.sqlRow.put(fieldName, ((LightUIDate) uiElement).getValueAsDate());
} else if (fieldType.equals(Boolean.class)) {
if (!(uiElement instanceof LightUICheckBox)) {
throw new IllegalArgumentException("Invalid UI Element for field: " + field.getName() + ". When field is Boolean, UI Element must be a LightUICheckBox");
throw new IllegalArgumentException("Invalid UI Element for field: " + fieldName + ". When field is Boolean, UI Element must be a LightUICheckBox");
}
this.sqlRow.put(field.getFieldName(), ((LightUICheckBox) uiElement).isChecked());
this.sqlRow.put(fieldName, ((LightUICheckBox) uiElement).isChecked());
} else if (fieldType.equals(Timestamp.class)) {
if (!(uiElement instanceof LightUIDate)) {
throw new IllegalArgumentException("Invalid UI Element for field: " + field.getName() + ". When field is Date, UI Element must be a LightUIDate");
throw new IllegalArgumentException("Invalid UI Element for field: " + fieldName + ". When field is Date, UI Element must be a LightUIDate");
}
this.sqlRow.put(field.getFieldName(), ((LightUIDate) uiElement).getValueAsDate());
this.sqlRow.put(fieldName, ((LightUIDate) uiElement).getValueAsDate());
} else if (fieldType.equals(Integer.class)) {
if (uiElement.getValue() != null && !uiElement.getValue().trim().isEmpty()) {
if (!uiElement.getValue().matches("^-?\\d+$")) {
throw new IllegalArgumentException("Invalid value for field: " + field.getName() + " value: " + uiElement.getValue());
if (value != null && !value.trim().isEmpty()) {
if (!value.matches("^-?\\d+$")) {
throw new IllegalArgumentException("Invalid value for field: " + fieldName + " value: " + value);
}
this.sqlRow.put(field.getFieldName(), Integer.parseInt(uiElement.getValue()));
this.sqlRow.put(fieldName, Integer.parseInt(value));
} else {
this.sqlRow.put(field.getFieldName(), null);
this.sqlRow.put(fieldName, null);
}
} else if (fieldType.equals(Double.class) || fieldType.equals(Float.class) || fieldType.equals(BigDecimal.class)) {
if (value != null && !value.trim().isEmpty()) {
try {
this.sqlRow.put(fieldName, new BigDecimal(value));
} catch (final Exception ex) {
throw new IllegalArgumentException("Invalid value for field: " + fieldName + " value: " + value);
}
 
} else {
Log.get().warning("unsupported type " + fieldType.getName());
this.sqlRow.put(fieldName, null);
}
} else {
Log.get().warning("unsupported type " + fieldName);
}
}
}
}
}
}
 
/**
* Save all referent rows store in LightRowValuesTable
253,45 → 258,40
* @param row Element saved row
* @param customEditors List of custom editors used in element edit frame
*/
final public void saveReferentRows(final Configuration configuration, final SQLRow parentSqlRow, final Map<String, CustomEditorProvider> customEditors) {
this.saveReferentRows(configuration, this.group, parentSqlRow, customEditors);
final public void saveReferentRows(final Configuration configuration, final SQLRow parentSqlRow, final Map<String, CustomEditorProvider> customEditors, final String sessionSecurityToken) {
this.saveReferentRows(configuration, this.group, parentSqlRow, customEditors, sessionSecurityToken);
}
 
final private void saveReferentRows(final Configuration configuration, final Group group, final SQLRow parentSqlRow, final Map<String, CustomEditorProvider> customEditors) {
final private void saveReferentRows(final Configuration configuration, final Group group, final SQLRow parentSqlRow, final Map<String, CustomEditorProvider> customEditors,
final String sessionSecurityToken) {
for (int i = 0; i < group.getSize(); i++) {
final Item item = group.getItem(i);
if (item instanceof Group) {
this.saveReferentRows(configuration, (Group) item, parentSqlRow, customEditors);
this.saveReferentRows(configuration, (Group) item, parentSqlRow, customEditors, sessionSecurityToken);
} else if (customEditors.containsKey(item.getId())) {
final LightUIElement element = this.findChild(item.getId(), false);
if (element instanceof LightRowValuesTable) {
final LightRowValuesTable rowValsTable = (LightRowValuesTable) element;
final int rowValuesCount = rowValsTable.getRowValuesCount();
for (int j = 0; j < rowValuesCount; j++) {
SQLRowValues rowValues = rowValsTable.getRowValues(j);
if (!rowValues.isFrozen()) {
rowValues.put(rowValsTable.getFieldRefName(), parentSqlRow.getID());
if (element instanceof LightForeignRowValuesTableOffline) {
final LightForeignRowValuesTableOffline foreignTable = (LightForeignRowValuesTableOffline) element;
for (int j = 0; j < foreignTable.getRowsCount(); j++) {
final ListSQLLine listLine = foreignTable.getModel().getRow(j);
final SQLRowValues rowVals = listLine.getRow().createEmptyUpdateRow();
rowVals.put(foreignTable.getForeignField().getName(), parentSqlRow.getID());
((SQLTableModelLinesSourceOffline) foreignTable.getModel().getLinesSource()).updateRow(listLine.getID(), rowVals);
}
final Future<?> fCommit = foreignTable.commitRows();
 
try {
final SQLElement el = configuration.getDirectory().getElement(rowValues.getTable());
boolean insertion = !rowValues.hasID();
SQLRow rowInserted = rowValues.prune(el.getPrivateGraph()).commit();
if (insertion) {
((SQLElement) el).doAfterLightInsert(rowInserted);
fCommit.get();
} catch (final Exception ex) {
throw new IllegalArgumentException(ex);
}
} catch (SQLException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
}
rowValsTable.archiveDeletedRows(configuration);
}
}
}
}
 
final protected void setMetaData(final int userId) {
final SQLTable sqlTable = this.sqlRow.getTable();
// FIXME: Creation user not specified.
if (this.sqlRow.getObject(sqlTable.getCreationUserField().getName()) == null || this.sqlRow.getObject(sqlTable.getCreationDateField().getName()) == null) {
this.sqlRow.put(sqlTable.getCreationUserField().getName(), userId);
this.sqlRow.put(sqlTable.getCreationDateField().getName(), new Date());
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/SavableCustomEditorProvider.java
New file
0,0 → 1,31
/*
* 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.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.ui.light.CustomEditorProvider;
import org.openconcerto.ui.light.LightUIElement;
 
public abstract class SavableCustomEditorProvider extends CustomEditorProvider {
 
public final void save(final SQLRowValues sqlRow, final SQLField sqlField, final LightUIElement uiElement) throws IllegalArgumentException, IllegalStateException {
if (sqlRow == null || sqlField == null) {
throw new IllegalStateException("Impossible to save this editor: " + uiElement.getId());
}
this._save(sqlRow, sqlField, uiElement);
}
 
protected abstract void _save(final SQLRowValues sqlRow, final SQLField sqlField, final LightUIElement uiElement) throws IllegalArgumentException, IllegalStateException;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightListSqlRow.java
New file
0,0 → 1,30
/*
* 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.model.SQLRowValues;
import org.openconcerto.ui.light.Row;
 
public class LightListSqlRow extends Row {
final private SQLRowValues sqlRow;
 
public LightListSqlRow(final SQLRowValues sqlRow, final Number id) {
super(id);
this.sqlRow = sqlRow;
}
 
public SQLRowValues getSqlRow() {
return this.sqlRow;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightRowValuesTableOnline.java
New file
0,0 → 1,147
/*
* 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.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFunctionField;
import org.openconcerto.sql.model.SQLFunctionField.SQLFunction;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.sql.view.list.SQLTableModelColumn;
import org.openconcerto.sql.view.list.SQLTableModelSource;
import org.openconcerto.sql.view.list.SQLTableModelSourceOnline;
import org.openconcerto.ui.light.SearchSpec;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
import javax.swing.SwingUtilities;
 
import net.minidev.json.JSONObject;
 
public class LightRowValuesTableOnline extends LightRowValuesTable {
private final ITransformer<SQLSelect, SQLSelect> orginTransformer;
 
public LightRowValuesTableOnline(final Configuration configuration, final Number userId, final String id, final ITableModel model) {
super(configuration, userId, id, model);
 
this.orginTransformer = ((SQLTableModelSourceOnline) model.getReq()).getReq().getSelectTransf();
}
 
// Clone constructor
public LightRowValuesTableOnline(final LightRowValuesTableOnline tableElement) {
super(tableElement);
 
this.orginTransformer = tableElement.orginTransformer;
}
 
// Json constructor
public LightRowValuesTableOnline(final JSONObject json) {
super(json);
this.orginTransformer = null;
}
 
@Override
public void doSearch(final Configuration configuration, final SearchSpec searchSpec, final int offset) {
this.getTableSpec().setSearch(searchSpec);
 
this.setOffset(offset);
 
final SQLTableModelSource tableSource = this.getModel().getReq();
final SearchInfo sInfo = (searchSpec != null) ? new SearchInfo(searchSpec) : null;
 
final FutureTask<ListSQLRequest> f = new FutureTask<ListSQLRequest>(new Callable<ListSQLRequest>() {
@Override
public ListSQLRequest call() throws Exception {
final ListSQLRequest req = tableSource.getReq();
final List<SQLTableModelColumn> columns = tableSource.getColumns();
req.setSelectTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(final SQLSelect sel) {
if (LightRowValuesTableOnline.this.orginTransformer != null) {
LightRowValuesTableOnline.this.orginTransformer.transformChecked(sel);
}
setWhere(sel, sInfo, columns);
return sel;
}
});
return req;
}
});
 
// TODO: clean swing
SwingUtilities.invokeLater(f);
 
try {
final ListSQLRequest req = f.get();
 
// get values
long t4 = System.currentTimeMillis();
final List<SQLRowValues> rowValues = req.getValues();
final int size = rowValues.size();
long t5 = System.currentTimeMillis();
 
System.err.println("DefaultTableContentHandler.handle() getValues() :" + size + " : " + (t5 - t4) + " ms");
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
 
/**
* Apply filter on ListSQLRequest
*
* @param sel - The ListSQLRequest select
* @param sInfo - Search parameters
*/
private final void setWhere(final SQLSelect sel, final SearchInfo sInfo, final List<SQLTableModelColumn> cols) {
if (sInfo != null) {
final Set<SQLField> fields = new HashSet<SQLField>();
for (final SQLTableModelColumn sqlTableModelColumn : cols) {
fields.addAll(sqlTableModelColumn.getFields());
}
final List<Where> wheres = new ArrayList<Where>();
final List<Where> wFields = new ArrayList<Where>();
 
final List<String> texts = sInfo.getTexts();
for (String string : texts) {
wFields.clear();
for (final SQLField sqlField : fields) {
if (sqlField.getType().getJavaType().equals(String.class)) {
final Where w = new Where(new SQLFunctionField(SQLFunction.LOWER, sel.getAlias(sqlField)), "LIKE", "%" + string.toLowerCase() + "%");
wFields.add(w);
}
}
wheres.add(Where.or(wFields));
}
 
final Where w;
if (sel.getWhere() != null) {
w = Where.and(sel.getWhere(), Where.and(wheres));
} else {
w = Where.and(wheres);
}
sel.setWhere(w);
System.err.println(sel.asString());
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/Login.java
138,7 → 138,7
return res;
}
 
private final List<SQLRow> findUser(final String login) {
public final List<SQLRow> findUser(final String login) {
final SQLSelect selUser = new SQLSelect();
selUser.addSelect(this.userT.getField("ID"));
selUser.addSelect(this.userT.getField("PASSWORD"));
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ElementComboBoxUtils.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/JUniqueTextField.java
69,6 → 69,9
*/
public class JUniqueTextField extends JPanel implements ValueWrapper<String>, Documented, TextComponent, RowItemViewComponent, SQLComponentItem, MouseListener {
 
public static int RETRY_COUNT = 5;
public static int SLEEP_WAIT_MS = 200;
 
public static final boolean exists(final SQLField f, final String t, final Number idToExclude, final boolean withCache) throws RTInterruptedException {
final SQLSelect selNum = new SQLSelect();
selNum.addSelectFunctionStar("COUNT");
182,7 → 185,16
while (JUniqueTextField.this.loop) {
 
try {
checkValidation(false);
if (!checkValidation(false)) {
final String number = getAutoRefreshNumber();
if (number != null && number.trim().length() > 0 && !number.equals(getText())) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JUniqueTextField.this.textField.setText(number);
}
});
}
}
} catch (RTInterruptedException e) {
// Arret normal si le texte a changé
}
200,6 → 212,15
this.validationThread.start();
}
 
/**
* Actualisation automatique du numéro si il est pris lors la vérification automatique
*
* @return le nouveau numéro. Par défaut null, pas d'actualisation automatique.
*/
public String getAutoRefreshNumber() {
return null;
}
 
public synchronized boolean checkValidation() throws RTInterruptedException {
return checkValidation(true);
}
228,7 → 249,7
return false;
}
 
if (System.currentTimeMillis() - this.lastCheck < 1000) {
if (withCache && System.currentTimeMillis() - this.lastCheck < 1000) {
// Ne pas checker 2 fois dans la meme seconde
// On laisse le watcher le faire
this.waitTime = 1000;
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/IComboModel.java
20,13 → 20,17
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.view.list.ITableModel.SleepState;
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.CompareUtils;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.SwingWorker2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.checks.EmptyChangeSupport;
41,11 → 45,15
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
 
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
66,8 → 74,8
 
private boolean filledOnce = false;
private ITransformer<List<IComboSelectionItem>, IComboSelectionItem> firstFillTransf = null;
private boolean isADirtyDrityGirl = true;
private boolean isOnScreen = false;
@GuardedBy("this")
private boolean isADirtyDrityGirl;
private boolean sleepAllowed = true;
@GuardedBy("this")
private int requestDelay = 50;
90,15 → 98,25
 
@GuardedBy("this")
private SearchSpec search;
private final boolean reqSearchable;
@GuardedBy("this")
private List<String> searchQuery;
@GuardedBy("this")
private Where searchForceInclude;
 
private PropertyChangeListener filterListener;
// whether this is listening in order to self-update
private boolean running;
@GuardedBy("this")
private SleepState sleepState;
 
private boolean debug = false;
private boolean addMissingItem;
 
public IComboModel(final ComboSQLRequest req) {
this(req, req.isSearchable());
}
 
public IComboModel(final ComboSQLRequest req, final boolean reqSearchable) {
if (req == null)
throw new NullPointerException("null request");
this.req = req;
108,6 → 126,10
this.idToSelect = SQLRow.NONEXISTANT_ID;
 
this.search = null;
// if req change and is no longer searchable, getComboItems() will fail
this.reqSearchable = reqSearchable;
this.searchQuery = Collections.emptyList();
this.searchForceInclude = null;
this.runnables = new ArrayList<Runnable>();
this.willUpdate = null;
this.updating = false;
114,7 → 136,8
this.itemsByID = new HashMap<Integer, IComboSelectionItem>();
this.addMissingItem = true;
 
this.running = false;
this.sleepState = SleepState.HIBERNATING;
this.isADirtyDrityGirl = true;
 
this.setSelectOnAdd(false);
// we always change the selection after changing the items so don't make an extra fire
201,21 → 224,27
super.fireContentsChanged(source, index0, index1);
}
 
void setRunning(final boolean b) {
synchronized void setSleepState(SleepState newState) {
assert SwingUtilities.isEventDispatchThread();
if (this.running != b) {
this.running = b;
if (this.running) {
if (newState == SleepState.SLEEPING && !this.isSleepAllowed())
newState = SleepState.AWAKE;
final SleepState prev = this.sleepState;
if (prev != newState) {
this.sleepState = newState;
if (prev == SleepState.HIBERNATING) {
this.req.addTableListener(this);
this.req.addWhereListener(this.filterListener);
// since we weren't listening, we must have missed lots of things
this.fillCombo();
} else {
this.isADirtyDrityGirl = true;
} else if (newState == SleepState.HIBERNATING) {
this.req.removeTableListener(this);
this.req.rmWhereListener(this.filterListener);
}
if (newState == SleepState.AWAKE && this.isADirtyDrityGirl) {
this.fillCombo();
}
}
}
 
public final ComboSQLRequest getRequest() {
return this.req;
230,19 → 259,6
Log.get().info(s);
}
 
synchronized void setOnScreen(boolean isOnScreen) {
if (this.isOnScreen != isOnScreen) {
this.isOnScreen = isOnScreen;
if (this.isOnScreen && this.isADirtyDrityGirl) {
this.fillCombo();
}
}
}
 
private synchronized boolean isOnScreen() {
return this.isOnScreen;
}
 
/**
* Whether this combo is allowed to delay {@link #fillCombo()} when it isn't visible.
*
256,6 → 272,14
return this.sleepAllowed;
}
 
private final synchronized boolean isActive() {
return this.sleepState == SleepState.AWAKE;
}
 
private final synchronized boolean areValuesObsolete() {
return this.isUpdating() || !this.isActive();
}
 
public synchronized final int getRequestDelay() {
return this.requestDelay;
}
273,7 → 297,7
/**
* Reload this combo. This method is thread-safe.
*/
public synchronized final void fillCombo() {
public final void fillCombo() {
this.fillCombo(null, true);
}
 
280,7 → 304,7
public synchronized final void fillCombo(final Runnable r, final boolean readCache) {
// wholly synch otherwise we might get onScreen after the if
// and thus completely ignore that fillCombo()
if (!this.isSleepAllowed() || this.isOnScreen() || r != null) {
if (this.isActive() || r != null) {
this.doUpdateAll(r, readCache);
} else {
this.isADirtyDrityGirl = true;
323,6 → 347,9
final int delay = this.getRequestDelay();
// copy the current search, if it changes fillCombo() will be called
final SearchSpec search = this.getSearch();
final List<String> searchQuery = this.getSearchQuery();
final Where searchForceInclude = this.getSearchForceInclude();
log("will call getComboItems(" + readCache + ", " + searchQuery + ", " + searchForceInclude + ")");
// commencer l'update après, sinon modeToSelect == 0
final SwingWorker2<List<IComboSelectionItem>, Object> worker = new SwingWorker2<List<IComboSelectionItem>, Object>() {
 
330,7 → 357,7
protected List<IComboSelectionItem> doInBackground() throws InterruptedException {
// attends 1 peu pour voir si on va pas être annulé
Thread.sleep(delay);
return SearchSpecUtils.filter(IComboModel.this.req.getComboItems(readCache), search);
return SearchSpecUtils.filter(IComboModel.this.req.getComboItems(readCache, searchQuery, Locale.getDefault(), searchForceInclude), search);
}
 
// Runs on the event-dispatching thread.
345,7 → 372,7
 
final boolean firstFill = !IComboModel.this.filledOnce;
// store before removing since it can trigger a selection change
final int idToSelect = IComboModel.this.idToSelect;
final int idToSelect = getWantedID();
List<IComboSelectionItem> items = null;
try {
items = this.get();
378,6 → 405,10
IComboModel.this.setSelectedItem(items.get(0));
else if (noSelection && firstFill && getFirstFillSelection() != null)
IComboModel.this.setSelectedItem(getFirstFillSelection().transformChecked(items));
// if the wanted ID has changed after we made the request, and we didn't
// get the it, retry
else if (!noSelection && getComboItem(idToSelect) == null && !CompareUtils.equals(getSearchForceInclude(), searchForceInclude))
fillCombo();
else
selectID(idToSelect);
 
485,16 → 516,20
*/
@Override
public final IComboSelectionItem getValue() {
if (!this.isUpdating())
return this.getSelectedValue();
else if (this.getWantedID() == SQLRow.NONEXISTANT_ID)
return null;
else if (this.getRequest().getKeepMode() == KeepMode.NONE)
return new IComboSelectionItem(getWantedID(), null);
else
final IComboSelectionItem res;
if (!areValuesObsolete()) {
res = this.getSelectedValue();
} else if (this.getWantedID() == SQLRow.NONEXISTANT_ID) {
res = null;
} else if (this.getRequest().getKeepMode() == KeepMode.NONE) {
res = new IComboSelectionItem(getWantedID(), null);
} else {
// no point in passing an SQLRowValues as the graph would be limited to just this row
return new IComboSelectionItem(new SQLRow(this.getForeignTable(), getWantedID()), null);
res = new IComboSelectionItem(new SQLRow(this.getForeignTable(), getWantedID()), null);
}
assert (this.getWantedID() != SQLRow.NONEXISTANT_ID && this.getWantedID() == res.getId()) || (this.getWantedID() == SQLRow.NONEXISTANT_ID && res == null);
return res;
}
 
/**
* The currently selected item. I.e. the value before the last {@link #setValue(int)}) (possibly
520,9 → 555,11
}
 
private final void setWantedID(int id) {
assert SwingUtilities.isEventDispatchThread();
if (this.idToSelect != id) {
final int old = this.idToSelect;
this.idToSelect = id;
this.setSearchForceInclude(id);
this.propSupp.firePropertyChange("wantedID", old, id);
this.propSupp.firePropertyChange("value", null, getValue());
this.emptySupp.fireEmptyChange(this.isEmpty());
574,23 → 611,31
log("entering selectID " + id);
assert SwingUtilities.isEventDispatchThread();
 
// no need to launch another updateAll() if one is already underway
if (this.neverBeenFilled() && !isUpdating())
// don't use fillCombo() which won't really update unless we're on screen
this.doUpdateAll(null, true);
 
if (this.isUpdating()) {
if (this.areValuesObsolete()) {
this.setWantedID(id);
log("isUpdating: this.idToSelect = " + id);
// if this is updating, the wanted ID will eventually get selected, otherwise signal
// that on a wake up we should check wantedID
if (!this.isUpdating()) {
synchronized (this) {
this.isADirtyDrityGirl = true;
}
}
log("values are obsolete: wantedID = " + id);
} else if (id == SQLRow.NONEXISTANT_ID) {
this.setSelectedItem(null);
log("NONEXISTANT_ID: setSelectedItem(null)");
} else {
final IComboSelectionItem item = this.getComboItem(id);
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 :
final boolean forceIncludeChanged = this.setSearchForceInclude(id);
log("valid id : " + id + " forceIncludeChanged: " + forceIncludeChanged + " item: " + item);
if (forceIncludeChanged && item == null) {
this.fillCombo();
assert this.isUpdating() : "If not isUpdating(), getValue() will return the current UI value instead of the wantedID";
this.setWantedID(id);
} else {
// * 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
597,8 → 642,8
// 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
// si l'ID voulu n'est pas là, essayer d'aller le chercher directement dans la
// base sans respecter le filtre
final ComboSQLRequest comboSQLRequest = this.req.clone();
comboSQLRequest.setFilterEnabled(false);
comboSQLRequest.setWhere(null);
637,6 → 682,7
}
}
}
}
 
/**
* Whether missing item are fetched from the database. If {@link #setValue(int)} is called with
787,21 → 833,66
 
@Override
public boolean isSearchable() {
return !this.getRequest().getSearchFields().isEmpty();
return this.reqSearchable;
}
 
private final static Pattern QUERY_SPLIT_PATTERN = Pattern.compile("\\s+");
 
/**
* Set the search query. The query will be used to match rows using
* {@link ComboSQLRequest#setSearchFields(java.util.Collection)}. I.e. if there's no field set,
* this method won't have any effect.
*
* @param s the search query.
* @param r {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public boolean setSearch(String s, Runnable r) {
if (this.getRequest().setSearch(s)) {
if (r != null) {
// no need to trim() since trailing empty strings are not returned
final List<String> split = StringUtils.isEmpty(s) ? Collections.<String> emptyList() : Arrays.asList(QUERY_SPLIT_PATTERN.split(s));
boolean res = false;
synchronized (this) {
this.runnables.add(r);
if (!split.equals(this.searchQuery)) {
this.searchQuery = split;
if (this.isSearchable()) {
res = true;
}
}
return true;
} else {
SwingUtilities.invokeLater(r);
log("setSearch() fillCombo: " + res + " query: " + this.searchQuery);
}
if (res) {
this.fillCombo(r, true);
} else if (r != null) {
SwingThreadUtils.invoke(r);
}
return res;
}
 
private synchronized List<String> getSearchQuery() {
assert this.searchQuery != null : "Null query means don't use the search which should only be governed by isSearchable()";
return this.isSearchable() ? this.searchQuery : null;
}
 
private boolean setSearchForceInclude(final int id) {
if (!this.isSearchable()) {
assert this.getSearchForceInclude() == null;
return false;
}
final Where newVal = id != SQLRow.NONEXISTANT_ID ? new Where(getRequest().getPrimaryTable().getKey(), "=", id) : null;
final boolean changed;
synchronized (this) {
changed = !CompareUtils.equals(this.searchForceInclude, newVal);
if (changed) {
this.searchForceInclude = newVal;
}
}
// don't fillCombo() (as when searchQuery is changed) since we want to avoid it if the ID is
// available
return changed;
}
 
private synchronized Where getSearchForceInclude() {
return this.searchForceInclude;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/IComboSelectionItem.java
20,6 → 20,8
import org.openconcerto.ui.component.combo.VarDesc;
import org.openconcerto.utils.CompareUtils;
 
import java.util.Comparator;
 
/**
* @author ILM Informatique
*/
35,6 → 37,19
private final String fLabel;
private int flag;
 
public static final Comparator<? super IComboSelectionItem> CASE_SENSITIVE_LABEL_COMPARATOR = new Comparator<IComboSelectionItem>() {
@Override
public int compare(IComboSelectionItem o1, IComboSelectionItem o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
};
public static final Comparator<? super IComboSelectionItem> CASE_INSENSITIVE_LABEL_COMPARATOR = new Comparator<IComboSelectionItem>() {
@Override
public int compare(IComboSelectionItem o1, IComboSelectionItem o2) {
return o1.getLabel().compareToIgnoreCase(o2.getLabel());
}
};
 
public IComboSelectionItem(final int id, final String label) {
this(null, id, label);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/SQLRequestComboBox.java
23,6 → 23,7
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.request.SQLForeignRowItemView;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.view.list.ITableModel.SleepState;
import org.openconcerto.sql.view.search.SearchSpec;
import org.openconcerto.ui.FontUtils;
import org.openconcerto.ui.component.ComboLockedMode;
228,7 → 229,7
// CloturePayeMensuellePanel)
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SQLRequestComboBox.this.req.setOnScreen(true);
updateListeners();
}
});
}
235,7 → 236,7
 
@Override
public void ancestorRemoved(AncestorEvent event) {
SQLRequestComboBox.this.req.setOnScreen(false);
updateListeners();
}
 
@Override
288,25 → 289,14
 
this.uiLayout();
 
// Initialise UI : mode was set in the constructor, but the UI wasn't updated (since it
// is
// either not created or depending on the request). Do it before setRunning() since it
// might
// trigger setEnabled() and the one below would be unnecessary.
// Initialise UI : mode attribute was set in the constructor, but the UI wasn't updated
// since it wasn't created (and lacking the request). If updateEnabled() doesn't set the
// mode, set it with the value set in the constructor.
if (!updateEnabled())
this.setInteractionMode(this.getInteractionMode());
 
// *without* : resetValue() => doUpdateAll() since it was never filled
// then this is made displayable => setRunning(true) => dirty = true since not on screen
// finally made visible => setOnScreen(true) => second doUpdateAll()
// *with* : setRunning(true) => update ignored since not on screen, dirty = true
// resetValue() => doUpdateAll() since it was never filled, dirty = false
// then this is made displayable => setRunning(true) => no change
// finally made visible => setOnScreen(true) => not dirty => no update
this.req.setRunning(true);
 
}
 
// return true if setEnabled() was called
private final boolean updateEnabled() {
boolean res = false;
if (isDisabledState()) {
344,7 → 334,14
 
protected void updateListeners() {
if (hasModel()) {
this.req.setRunning(this.isDisplayable());
final SleepState newState;
if (!this.isDisplayable())
newState = SleepState.HIBERNATING;
else if (!this.isShowing())
newState = SleepState.SLEEPING;
else
newState = SleepState.AWAKE;
this.req.setSleepState(newState);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/itemview/VWRowItemView.java
155,7 → 155,7
}
 
@Override
public void removeValueListener(PropertyChangeListener l) {
public final void removeValueListener(PropertyChangeListener l) {
assert SwingUtilities.isEventDispatchThread();
this.supp.removePropertyChangeListener(VALUE_PROPNAME, l);
if (!this.supp.hasListeners(VALUE_PROPNAME)) {
181,7 → 181,7
}
 
@Override
public void removeEmptyListener(EmptyListener l) {
public final void removeEmptyListener(EmptyListener l) {
this.helper.removeEmptyListener(l);
}
 
198,7 → 198,7
}
 
@Override
public void removeValidListener(ValidListener l) {
public final void removeValidListener(ValidListener l) {
this.getWrapper().removeValidListener(new ChainValidListener(this, l));
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ElementComboBox.java
22,6 → 22,7
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.sql.view.EditPanelListener;
import org.openconcerto.sql.view.IListButton;
import org.openconcerto.sql.view.IListFrame;
289,7 → 290,7
}
if (!displayed) {
if (this.viewFrame == null) {
this.viewFrame = new EditFrame(this.element, this.isModif ? EditPanel.MODIFICATION : EditPanel.READONLY);
this.viewFrame = createEditFrame(this.isModif ? EditPanel.MODIFICATION : EditPanel.READONLY);
// dispose since if we change canModif, the old frame will be orphaned
this.viewFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
318,7 → 319,7
 
private final EditFrame getAddFrame() {
if (this.addFrame == null) {
this.addFrame = new EditFrame(this.element, EditPanel.CREATION);
this.addFrame = createEditFrame(EditPanel.CREATION);
this.addFrame.addEditPanelListener(new EditPanelListener() {
@Override
public void cancelled() {
341,6 → 342,17
return this.addFrame;
}
 
protected final EditFrame createEditFrame(final EditMode mode) {
final SQLComponent res = this.createSQLComponent(mode);
if (res.getElement() != this.getElement())
throw new IllegalStateException("Wrong element");
return new EditFrame(res, mode);
}
 
protected SQLComponent createSQLComponent(final EditMode mode) {
return this.element.createDefaultComponent();
}
 
/**
* The sql component of the add frame.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/Configuration.java
16,22 → 16,6
*/
package org.openconcerto.sql;
 
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
 
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.DBFileCache;
48,7 → 32,26
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
 
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
 
import net.jcip.annotations.GuardedBy;
 
/**
75,8 → 78,24
 
public static final void setInstance(Configuration instance) {
Configuration.instance = instance;
try {
instance.migrateToNewDBConfDir();
} catch (IOException e) {
throw new IllegalStateException("Couldn't migrate");
}
}
 
static public final void migrateToNewDir(final File oldDir, final File newDir) throws IOException {
if (oldDir.exists() && !newDir.exists()) {
if (!oldDir.isDirectory())
throw new IOException("Old file isn't a directory : " + oldDir);
FileUtils.mkdir_p(newDir.getParentFile());
final String err = FileUtils.mv(oldDir, newDir);
if (err != null)
throw new IOException("Couldn't migrate from " + oldDir + " : " + err);
}
}
 
@GuardedBy("this")
private ExecutorService nonInteractiveSQLExecutor;
 
112,16 → 131,27
*/
public final String getAppID() {
final String appName = this.getAppName();
if (appName == null || appName.length() == 0)
if (StringUtils.isEmpty(appName))
return null;
return appName + getAppIDSuffix();
final String variant = this.getAppVariant();
if (StringUtils.isEmpty(variant, true))
return appName;
return appName + '-' + variant;
}
 
protected String getAppIDSuffix() {
return "";
public String getAppVariant() {
return null;
}
 
public File getConfDir() {
public abstract BaseDirs getBaseDirs();
 
public final File getConfDir() {
return getBaseDirs().getPreferencesFolder();
}
 
// for migration use
@Deprecated
protected File getOldConfDir() {
return new File(getDefaultConfDir(), this.getAppID());
}
 
134,30 → 164,29
return getConfDir(getRoot());
}
 
/**
* Move {@link #getConfDir()}/<code>name</code> to {@link #getConfDirForRoot()}/
* <code>name</code> if necessary.
*
* @param name the name of the file or directory to move.
* @return the new file in <code>getConfDirForRoot()</code>.
*/
public final File migrateToConfDirForRoot(final String name) {
final File oldFile = new File(this.getConfDir(), name);
final File newFile = new File(this.getConfDirForRoot(), name);
if (oldFile.exists() && !newFile.exists()) {
try {
FileUtils.mkdir_p(newFile.getParentFile());
oldFile.renameTo(newFile);
} catch (IOException e) {
e.printStackTrace();
FileUtils.rmR(oldFile);
public final void migrateToNewDBConfDir() throws IOException {
final File oldFile = getOldDBConfDir();
final File newFile = getDBConfDir();
migrateToNewDir(oldFile, newFile);
}
 
// for migration use
private File getOldDBConfDir() {
return new File(getOldConfDir(), "dataDepedent");
}
return newFile;
 
// for migration use
@Deprecated
protected final File getOldConfDir(DBStructureItem<?> db) {
return DBItemFileCache.getDescendant(getOldDBConfDir(), DBFileCache.getJDBCAncestorNames(db, true));
}
 
private File getDBConfDir() {
return new File(getConfDir(), "dataDependent");
}
 
public final File getConfDir(DBStructureItem<?> db) {
return DBItemFileCache.getDescendant(new File(getConfDir(), "dataDepedent"), DBFileCache.getJDBCAncestorNames(db, true));
return DBItemFileCache.getDescendant(getDBConfDir(), DBFileCache.getJDBCAncestorNames(db, true));
}
 
/**
188,11 → 217,13
/**
* Get xml value from table FWK_LIST_PREFS for an user and a table.
*
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param userId - Id of user
* @param idTable - Id of table
*
* @throws IllegalStateException
* @throws IllegalArgumentException
*/
public Document getXMLConf(final long userId, final String idTable) throws ParserConfigurationException, SAXException, IOException {
public Document getXMLConf(final Number userId, final String idTable) throws IllegalStateException, IllegalArgumentException {
final SQLElement element = this.getDirectory().getElement("FWK_LIST_PREFS");
final SQLTable columnPrefsTable = element.getTable();
final SQLSelect select = new SQLSelect();
201,13 → 232,41
final List<SQLRow> rqResult = SQLRowListRSH.execute(select);
if (rqResult != null && !rqResult.isEmpty()) {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
} catch (final ParserConfigurationException ex) {
throw new IllegalStateException("Impossible to create new XML document", ex);
}
 
try {
return docBuilder.parse(new InputSource(new StringReader(rqResult.get(0).getString("VALUE"))));
} catch (final SAXException ex) {
throw new IllegalArgumentException("Impossible to parse XML from database", ex);
} catch (final IOException ex) {
throw new IllegalStateException("Impossible to read content of database", ex);
}
}
return null;
}
 
/**
* Remove XML value from table FWK_LIST_PREFS for an user and a table.
*
* @param userId - Id of user
* @param idTable - Id of table
*
* @throws IllegalStateException
* @throws IllegalArgumentException
*/
public void removeXMLConf(final Number userId, final String idTable) throws IllegalStateException, IllegalArgumentException {
final SQLElement element = this.getDirectory().getElement("FWK_LIST_PREFS");
final SQLTable columnPrefsTable = element.getTable();
 
this.getRoot().getDBSystemRoot().getDataSource().execute("DELETE FROM " + columnPrefsTable.getSQLName().quote() + " WHERE \"ID_USER\" = " + userId + " AND \"ID_TABLE\" = '" + idTable + "'");
}
 
/**
* An executor that should be used for background SQL requests. It can be used to limit the
* concurrent number of database connections (as establishing a connection is expensive and the
* server might have restrictions).
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLRowView.java
16,7 → 16,6
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLComponent;
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.SQLRowValues;
37,7 → 36,6
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
410,19 → 408,6
this.select(null);
}
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.devis.request.BaseSQLRequest#getAllFields()
*/
public Collection<SQLField> getAllFields() {
final Set<SQLField> res = new HashSet<SQLField>();
for (final SQLRowItemView view : this.getViewsFast()) {
res.addAll(view.getFields());
}
return res;
}
 
private void setSelectedID(int selectedID) {
this.selectedID = selectedID;
if (!existsInDB())
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ComboSQLRequest.java
17,17 → 17,17
* @author ILM Informatique
*/
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.TransfFieldExpander;
import org.openconcerto.sql.FieldExpander;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSearchMode;
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.sqlobject.ElementComboBoxUtils;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
43,6 → 43,8
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
50,7 → 52,7
 
// final: use setSelectTransf()
@ThreadSafe
public final class ComboSQLRequest extends FilteredFillSQLRequest {
public final class ComboSQLRequest extends FilteredFillSQLRequest implements Cloneable {
 
static private final SQLCache<CacheKey, List<IComboSelectionItem>> cache = new SQLCache<CacheKey, List<IComboSelectionItem>>(60, -1, "items of " + ComboSQLRequest.class);
 
61,6 → 63,10
private final SQLRowValues graph;
private final SQLRowValuesListFetcher fetcher;
private final Where where;
private final ITransformer<SQLSelect, SQLSelect> selTransformer;
// compute request once and for all to speed up equals (OK since the fetcher and its
// parameter are immutable)
private final String select;
private final String fieldSeparator;
private final String undefLabel;
private final KeepMode keepRows;
68,8 → 74,9
private final IClosure<IComboSelectionItem> customizeItem;
private final Comparator<? super IComboSelectionItem> itemsOrder;
 
public CacheKey(SQLRowValues graph, SQLRowValuesListFetcher f, Where w, String fieldSeparator, String undefLabel, IClosure<IComboSelectionItem> c, KeepMode keepRows,
Comparator<? super IComboSelectionItem> itemsOrder) {
// ATTN selTransformer (as the fetcher and the where) should be immutable
public CacheKey(SQLRowValues graph, SQLRowValuesListFetcher f, Where w, ITransformer<SQLSelect, SQLSelect> selTransformer, String fieldSeparator, String undefLabel,
IClosure<IComboSelectionItem> c, KeepMode keepRows, Comparator<? super IComboSelectionItem> itemsOrder) {
super();
if (!graph.isFrozen())
throw new IllegalArgumentException("Not frozen : " + graph);
80,6 → 87,8
throw new IllegalArgumentException("Not frozen : " + f);
this.fetcher = f;
this.where = w;
this.selTransformer = selTransformer;
this.select = this.fetcher.getReq(this.where, this.selTransformer).asString();
 
this.fieldSeparator = fieldSeparator;
this.undefLabel = undefLabel;
108,12 → 117,12
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
if (obj == null || getClass() != obj.getClass())
return false;
final CacheKey other = (CacheKey) obj;
return this.keepRows == other.keepRows && this.fieldSeparator.equals(other.fieldSeparator) && CompareUtils.equals(this.undefLabel, other.undefLabel)
&& CompareUtils.equals(this.where, other.where) && this.graph.getGraphFirstDifference(other.graph, true) == null && CompareUtils.equals(this.fetcher, other.fetcher)
&& CompareUtils.equals(this.customizeItem, other.customizeItem) && CompareUtils.equals(this.itemsOrder, other.itemsOrder);
&& this.graph.getGraphFirstDifference(other.graph, true) == null && this.select.equals(other.select) && CompareUtils.equals(this.customizeItem, other.customizeItem)
&& CompareUtils.equals(this.itemsOrder, other.itemsOrder);
}
};
 
164,10 → 173,21
setDefaultItemsOrder(null);
}
 
private static final SQLElementDirectory getDirectory(final SQLElementDirectory dir) {
if (dir != null)
return dir;
 
final Configuration conf = Configuration.getInstance();
return conf == null ? null : conf.getDirectory();
}
 
private static final FieldExpander getExpander(SQLElementDirectory dir) {
dir = getDirectory(dir);
return dir != null ? ComboSQLRequestUtils.getShowAs(dir) : FieldExpander.getEmpty();
}
 
// immutable
@GuardedBy("this")
private List<SQLField> comboFields;
private final TransfFieldExpander exp;
private final SQLElementDirectory dir;
 
@GuardedBy("this")
private String fieldSeparator = getDefaultFieldSeparator();
179,8 → 199,6
private IClosure<IComboSelectionItem> customizeItem;
 
@GuardedBy("this")
private List<Path> order;
@GuardedBy("this")
private Comparator<? super IComboSelectionItem> itemsOrder;
 
public ComboSQLRequest(SQLTable table, List<String> l) {
188,23 → 206,27
}
 
public ComboSQLRequest(SQLTable table, List<String> l, Where where) {
super(table, where);
this(table, l, where, null);
}
 
public ComboSQLRequest(SQLTable table, List<String> l, Where where, final SQLElementDirectory dir) {
this(computeGraph(table, l, getExpander(dir)), where, dir);
}
 
public ComboSQLRequest(SQLRowValues graph, Where where, final SQLElementDirectory dir) {
super(graph, where);
this.dir = dir;
this.undefLabel = null;
// don't use memory
this.keepRows = KeepMode.NONE;
this.customizeItem = null;
this.order = null;
this.itemsOrder = getDefaultItemsOrder();
this.exp = ElementComboBoxUtils.getShowAs(Configuration.getInstance());
this.setFields(l);
}
 
protected ComboSQLRequest(ComboSQLRequest c, final boolean freeze) {
super(c, freeze);
this.exp = new TransfFieldExpander(c.exp);
this.dir = c.dir;
synchronized (c) {
this.comboFields = c.comboFields;
this.order = c.order == null ? null : new ArrayList<Path>(c.order);
this.itemsOrder = c.itemsOrder;
 
this.fieldSeparator = c.fieldSeparator;
214,6 → 236,10
}
}
 
private final SQLElementDirectory getDirectory() {
return getDirectory(this.dir);
}
 
@Override
public ComboSQLRequest toUnmodifiable() {
return this.toUnmodifiableP(this.getClass());
231,27 → 257,6
return new ComboSQLRequest(this, forFreeze);
}
 
public final void setFields(Collection<String> fields) {
final List<SQLField> l = new ArrayList<SQLField>();
for (final String fName : fields) {
l.add(this.getPrimaryTable().getField(fName));
}
setSQLFieldsUnsafe(l);
}
 
public final void setSQLFields(Collection<SQLField> fields) {
for (final SQLField f : fields)
if (f.getTable() != getPrimaryTable())
throw new IllegalArgumentException("Not in " + getPrimaryTable() + " : " + f);
setSQLFieldsUnsafe(new ArrayList<SQLField>(fields));
}
 
private synchronized void setSQLFieldsUnsafe(List<SQLField> fields) {
checkFrozen();
this.comboFields = Collections.unmodifiableList(fields);
this.clearGraph();
}
 
/**
* Set the label of the undefined row. If <code>null</code> (the default) then the undefined
* will not be fetched, otherwise it will and its label will be <code>undefLabel</code>.
281,7 → 286,7
*/
public final IComboSelectionItem getComboItem(int id) {
// historically this method didn't use the cache
final List<IComboSelectionItem> res = getComboItems(id, false, false);
final List<IComboSelectionItem> res = getComboItems(id, null, null, null, false, false);
return getSole(res, id);
}
 
290,17 → 295,20
}
 
public final List<IComboSelectionItem> getComboItems(final boolean readCache) {
return this.getComboItems(null, readCache, true);
return this.getComboItems(readCache, null, null, null);
}
 
private final List<IComboSelectionItem> getComboItems(final Number id, final boolean readCache, final boolean writeCache) {
if (this.getFields().isEmpty())
throw new IllegalStateException("Empty fields");
public final List<IComboSelectionItem> getComboItems(final boolean readCache, final List<String> searchQuery, final Locale locale, final Where searchForceInclude) {
return this.getComboItems(null, searchQuery, locale, searchForceInclude, readCache, true);
}
 
private final List<IComboSelectionItem> getComboItems(final Number id, final List<String> searchQuery, final Locale locale, final Where searchForceInclude, final boolean readCache,
final boolean writeCache) {
final Where w = id == null ? null : new Where(this.getPrimaryTable().getKey(), "=", id);
 
// this encapsulates a snapshot of our state, so this method doesn't access any of our
// fields and doesn't need to be synchronized
final CacheKey cacheKey = getCacheKey(w);
final CacheKey cacheKey = getCacheKey(w, searchQuery, locale, searchForceInclude);
final SQLRowValuesListFetcher comboSelect = cacheKey.fetcher;
final CacheResult<List<IComboSelectionItem>> l = cache.check(cacheKey, readCache, writeCache, comboSelect.getGraph().getGraph().getTables());
if (l.getState() == CacheResult.State.INTERRUPTED)
311,10 → 319,10
try {
// group fields by ancestor, need not be part of CacheKey assuming parent-child
// relations don't change
final List<Tuple2<Path, List<FieldPath>>> ancestors = ElementComboBoxUtils.expandGroupBy(cacheKey.graph, Configuration.getInstance().getDirectory());
final List<Tuple2<Path, List<FieldPath>>> ancestors = ComboSQLRequestUtils.expandGroupBy(cacheKey.graph, getDirectory());
final List<IComboSelectionItem> result = new ArrayList<IComboSelectionItem>();
// SQLRowValuesListFetcher doesn't cache
for (final SQLRowValues vals : comboSelect.fetch(w)) {
for (final SQLRowValues vals : comboSelect.fetch(w, cacheKey.selTransformer, null)) {
if (Thread.currentThread().isInterrupted())
throw new RTInterruptedException("interrupted in fill");
// each item should be created with the same state and since it will be put in
336,11 → 344,12
}
 
protected final CacheKey getCacheKey() {
return getCacheKey(null);
return getCacheKey(null, null, null, null);
}
 
private final synchronized CacheKey getCacheKey(final Where w) {
return new CacheKey(this.getGraph(), this.getFetcher(), w, this.fieldSeparator, this.undefLabel, this.customizeItem, this.keepRows, this.itemsOrder);
private final synchronized CacheKey getCacheKey(final Where w, final List<String> searchQuery, final Locale l, final Where searchForceInclude) {
return new CacheKey(this.getGraph(), this.getFetcher(), w, this.createSearchTransformer(searchQuery, l, searchForceInclude), this.fieldSeparator, this.undefLabel, this.customizeItem,
this.keepRows, this.itemsOrder);
}
 
@Override
350,12 → 359,26
}
 
@Override
protected synchronized final List<Path> getOrder() {
if (this.order != null)
return this.order;
protected Collection<SearchField> getDefaultSearchFields() {
final List<SearchField> res = new ArrayList<SearchField>();
final List<Tuple2<Path, List<FieldPath>>> expandGroupBy = ComboSQLRequestUtils.expandGroupBy(getGraph(), getDirectory());
int rank = 10;
final ListIterator<Tuple2<Path, List<FieldPath>>> iter = expandGroupBy.listIterator(expandGroupBy.size());
assert !iter.hasNext();
while (iter.hasPrevious()) {
final Tuple2<Path, List<FieldPath>> element = iter.previous();
for (final FieldPath fp : element.get1()) {
res.add(new SearchField(fp, SQLSearchMode.CONTAINS, rank));
}
rank *= 5;
}
return res;
}
 
@Override
protected List<Path> getDefaultOrder() {
// order the combo by ancestors
final List<Tuple2<Path, List<FieldPath>>> expandGroupBy = ElementComboBoxUtils.expandGroupBy(getGraph(), Configuration.getInstance().getDirectory());
final List<Tuple2<Path, List<FieldPath>>> expandGroupBy = ComboSQLRequestUtils.expandGroupBy(getGraph(), getDirectory());
final List<Path> res = new ArrayList<Path>(expandGroupBy.size());
for (final Tuple2<Path, List<FieldPath>> ancestor : expandGroupBy)
res.add(0, ancestor.get0());
362,17 → 385,6
return res;
}
 
/**
* Change the ordering of this combo. By default this is ordered by ancestors.
*
* @param l the list of tables, <code>null</code> to restore the default.
*/
public synchronized final void setOrder(List<Path> l) {
checkFrozen();
this.order = Collections.unmodifiableList(new ArrayList<Path>(l));
this.clearGraph();
}
 
public final void setNaturalItemsOrder(final boolean b) {
this.setItemsOrder(b ? CompareUtils.<IComboSelectionItem> naturalOrder() : null);
}
422,11 → 434,6
return res;
}
 
@Override
protected final TransfFieldExpander getShowAs() {
return this.exp;
}
 
/**
* Renvoie la valeur du champ sous forme de String. De plus est sensé faire quelques
* conversions, eg traduire les booléens en "oui" "non".
446,11 → 453,6
return result;
}
 
@Override
public synchronized final List<SQLField> getFields() {
return this.comboFields;
}
 
/**
* Set the string that is used to join the fields of a row.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/request/UpdateBuilder.java
15,6 → 15,7
 
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
 
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLName;
62,6 → 63,10
return this.t;
}
 
public final SQLSyntax getSyntax() {
return SQLSyntax.get(this.getTable());
}
 
private final void checkField(final String field) {
checkField(field, getTable());
}
242,7 → 247,7
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;
return "UPDATE " + this.getSyntax().getUpdate(this.getTable(), unmodifiableList(computedTables), unmodifiableMap(this.fields)) + w;
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/request/Inserter.java
13,10 → 13,13
package org.openconcerto.sql.request;
 
import org.openconcerto.sql.model.ColumnListHandlerGeneric;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.ListListHandlerGeneric;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
26,7 → 29,9
 
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
 
/**
* Allow to insert some rows into a table and get some feedback.
40,6 → 45,12
}
 
public static final class Insertion<T> {
 
// syntactic sugar to allow new Insertion<?>()
static public final <I> Insertion<I> create(List<I> res, int count) {
return new Insertion<I>(res, count);
}
 
private final List<T> list;
private final int count;
 
73,12 → 84,24
 
private static final String[] EMPTY_ARRAY = new String[0];
 
private static List<Class<?>> getPKTypes(SQLTable t) {
final Set<SQLField> pk = t.getPrimaryKeys();
final List<Class<?>> res = new ArrayList<Class<?>>(pk.size());
for (final SQLField f : pk) {
res.add(f.getType().getJavaType());
}
return res;
}
 
private final List<String> pk;
private final List<Class<?>> pkTypes;
private final DBSystemRoot sysRoot;
private final SQLName tableName;
 
public Inserter(final SQLTable t) {
this(t.getDBSystemRoot(), t.getSQLName(), t.getPKsNames());
// pass types since we can't always rely on ResultSet.getObject() for generated keys (e.g.
// MySQL always return BigInt no matter the actual type of the keys)
this(t.getDBSystemRoot(), t.getSQLName(), t.getPKsNames(), getPKTypes(t));
}
 
public Inserter(final SQLCreateTable t) {
90,6 → 113,10
}
 
public Inserter(final DBSystemRoot sysRoot, final SQLName tableName, final List<String> pk) {
this(sysRoot, tableName, pk, null);
}
 
public Inserter(final DBSystemRoot sysRoot, final SQLName tableName, final List<String> pk, final List<Class<?>> pkTypes) {
super();
if (sysRoot == null || tableName == null)
throw new NullPointerException();
96,6 → 123,9
this.sysRoot = sysRoot;
this.tableName = tableName;
this.pk = pk;
this.pkTypes = pkTypes == null ? null : new ArrayList<Class<?>>(pkTypes);
if (this.pkTypes != null && this.pkTypes.size() != this.pk.size())
throw new IllegalArgumentException("Size mismatch");
}
 
protected final SQLSystem getSystem() {
119,8 → 149,8
* @throws SQLException if an error occurs while inserting.
*/
@SuppressWarnings("unchecked")
public final Insertion<Object[]> insertReturnAllFields(final String sql) throws SQLException {
return (Insertion<Object[]>) insert(sql, ReturnMode.ALL_FIELDS, true);
public final Insertion<List<?>> insertReturnAllFields(final String sql) throws SQLException {
return (Insertion<List<?>>) insert(sql, ReturnMode.ALL_FIELDS, true);
}
 
/**
186,14 → 216,20
if (dontGetGK) {
list = null;
} else {
list = (List<?>) (mode == ReturnMode.FIRST_FIELD ? SQLDataSource.COLUMN_LIST_HANDLER : SQLDataSource.ARRAY_LIST_HANDLER).handle(stmt.getGeneratedKeys());
if (Inserter.this.pkTypes == null) {
list = (List<?>) (mode == ReturnMode.FIRST_FIELD ? SQLDataSource.COLUMN_LIST_HANDLER : SQLDataSource.LIST_LIST_HANDLER).handle(stmt.getGeneratedKeys());
} else {
if (mode == ReturnMode.FIRST_FIELD) {
list = ColumnListHandlerGeneric.create(1, Inserter.this.pkTypes.get(0)).handle(stmt.getGeneratedKeys());
} else {
list = ListListHandlerGeneric.create(Object.class, Inserter.this.pkTypes).handle(stmt.getGeneratedKeys());
}
}
assert list.size() <= count;
if (requireAllRows && list.size() != count)
throw new IllegalStateException("Missing keys");
}
@SuppressWarnings("unchecked")
final Insertion<?> res = new Insertion<Object>((List<Object>) list, count);
return res;
return Insertion.create(list, count);
} finally {
stmt.close();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/FilteredFillSQLRequest.java
89,8 → 89,8
};
}
 
public FilteredFillSQLRequest(final SQLTable primaryTable, Where w) {
super(primaryTable, w);
public FilteredFillSQLRequest(final SQLRowValues graph, Where w) {
super(graph, w);
this.filter = getDefaultFilter();
this.filterInfo = Tuple2.create(null, null);
this.setFilterEnabled(true);
/trunk/OpenConcerto/src/org/openconcerto/sql/request/BaseSQLRequest.java
13,14 → 13,6
package org.openconcerto.sql.request;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldsSet;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableModifiedListener;
 
import java.util.Collection;
import java.util.Set;
 
import net.jcip.annotations.ThreadSafe;
 
@ThreadSafe
29,27 → 21,4
public BaseSQLRequest() {
super();
}
 
public final Set<SQLTable> getTables() {
return new SQLFieldsSet(this.getAllFields()).getTables();
}
 
/**
* Tous les champs qui intéressent cette requête. Souvent les champs après expansion.
*
* @return les champs qui intéressent cette requête.
*/
protected abstract Collection<SQLField> getAllFields();
 
public final void addTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.addTableModifiedListener(l);
}
}
 
public final void removeTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.removeTableModifiedListener(l);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ListSQLRequest.java
21,8 → 21,6
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import net.jcip.annotations.ThreadSafe;
30,60 → 28,41
@ThreadSafe
public class ListSQLRequest extends FilteredFillSQLRequest {
 
// les champs à afficher (avant expansion)
// immutable
private final List<SQLField> listFields;
private static final FieldExpander getExpander(final FieldExpander showAs) {
final FieldExpander res;
if (showAs != null) {
res = showAs;
} else {
final Configuration conf = Configuration.getInstance();
if (conf == null) {
res = FieldExpander.getEmpty();
} else {
res = conf.getShowAs();
}
}
return res;
}
 
private final FieldExpander showAs;
 
public ListSQLRequest(SQLTable table, List fieldss) {
public ListSQLRequest(SQLTable table, List<String> fieldss) {
this(table, fieldss, null);
}
 
// fieldss : list de String (préferré) ou de SQLField
public ListSQLRequest(SQLTable table, List fieldss, Where where) {
public ListSQLRequest(SQLTable table, List<String> fieldss, Where where) {
this(table, fieldss, where, null);
}
 
public ListSQLRequest(SQLTable table, List fieldss, Where where, final FieldExpander showAs) {
super(table, where);
if (!this.getPrimaryTable().isOrdered())
throw new IllegalArgumentException(table + " is not ordered.");
 
final List<SQLField> tmpList = new ArrayList<SQLField>();
for (final Object field : fieldss) {
final SQLField f;
if (field instanceof String)
f = this.getPrimaryTable().getField((String) field);
else if (field instanceof SQLField) {
final SQLField fToCheck = (SQLField) field;
if (fToCheck.getTable().equals(this.getPrimaryTable()))
f = fToCheck;
else
throw new IllegalArgumentException("field " + fToCheck + " not part of the primary table : " + this.getPrimaryTable());
} else
throw new IllegalArgumentException("must be a fieldname or a SQLField but got : " + field);
 
tmpList.add(f);
public ListSQLRequest(SQLTable table, List<String> fieldss, Where where, final FieldExpander showAs) {
this(computeGraph(table, fieldss, getExpander(showAs)), where);
}
this.listFields = Collections.unmodifiableList(tmpList);
 
if (showAs != null) {
this.showAs = showAs;
} else {
final Configuration conf = Configuration.getInstance();
if (conf == null) {
this.showAs = FieldExpander.getEmpty();
} else {
this.showAs = conf.getShowAs();
public ListSQLRequest(final SQLRowValues graph, final Where where) {
super(graph, where);
if (!this.getPrimaryTable().isOrdered())
throw new IllegalArgumentException(this.getPrimaryTable() + " is not ordered.");
}
}
}
 
protected ListSQLRequest(ListSQLRequest req, final boolean freeze) {
super(req, freeze);
this.listFields = req.listFields;
this.showAs = req.showAs;
}
 
// wasFrozen() : our showAs might change but our fetcher won't, MAYBE remove final modifier and
94,12 → 73,9
return this.toUnmodifiableP(ListSQLRequest.class);
}
 
@Override
public ListSQLRequest clone() {
synchronized (this) {
return this.clone(false);
}
}
// can't implement Cloneable since the contract is to return an object of the same class. But
// this would either prevent the use of anonymous classes if we call clone(false), or require a
// call to super.clone() and less final fields.
 
@Override
protected ListSQLRequest clone(boolean forFreeze) {
106,23 → 82,9
return new ListSQLRequest(this, forFreeze);
}
 
// MAYBE use changeGraphToFetch()
@Override
protected FieldExpander getShowAs() {
return this.showAs;
}
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.devis.request.BaseSQLRequest#getFields()
*/
@Override
public final List<SQLField> getFields() {
return this.listFields;
}
 
@Override
protected void customizeToFetch(SQLRowValues graphToFetch) {
protected final void customizeToFetch(SQLRowValues graphToFetch) {
super.customizeToFetch(graphToFetch);
addField(graphToFetch, getPrimaryTable().getCreationDateField());
addField(graphToFetch, getPrimaryTable().getCreationUserField());
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ComboSQLRequestUtils.java
New file
0,0 → 1,122
/*
* 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.request;
 
import org.openconcerto.sql.TransfFieldExpander;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
final class ComboSQLRequestUtils {
 
// return the foreign row in field and its fields
private static Tuple2<SQLRowValues, List<FieldPath>> expandOnce(final SQLRowValues vals, final IFieldPath field) {
assert vals.getFields().contains(field.getFieldName());
 
final Object foreignObj = vals.getObject(field.getFieldName());
if (!(foreignObj instanceof SQLRowValues))
return Tuple2.create(null, Collections.<FieldPath> emptyList());
 
final SQLRowValues foreign = (SQLRowValues) foreignObj;
// foreign since we used getObject()
final Path newPath = field.getPath().add(field.getField(), Direction.FOREIGN);
final List<FieldPath> res = new ArrayList<FieldPath>();
for (final String f : foreign.getFields())
res.add(new FieldPath(newPath, f));
 
return Tuple2.create(foreign, res);
}
 
// recursively walk vals to collect all foreign fields
public static final List<FieldPath> expand(final SQLRowValues vals, final IFieldPath field) {
assert vals.getTable() == field.getTable();
final List<FieldPath> fields = new ArrayList<FieldPath>();
 
if (!field.getTable().getForeignKeys().contains(field.getField())) {
// si ce n'est pas une clef alors il n'y a pas à l'expandre
fields.add(field.getFieldPath());
} else {
final Tuple2<SQLRowValues, List<FieldPath>> tmp = expandOnce(vals, field);
for (final FieldPath f : tmp.get1()) {
fields.addAll(expand(tmp.get0(), f));
}
}
 
return fields;
}
 
/**
* Group fields of the passed row by the parent foreign key. For each item of the result, the
* path to the ancestor is also included, e.g. [ LOCAL, LOCAL.ID_BATIMENT,
* LOCAL.ID_BATIMENT.ID_SITE, LOCAL.ID_BATIMENT.ID_SITE.ID_ETABLISSEMENT ].
*
* @param vals the row to go through.
* @param dir how to find the parents.
* @return the complete expansion, e.g. [ [LOCAL.DESIGNATION], [BAT.DES], [SITE.DES,
* ADRESSE.CP], [ETABLISSEMENT.DES] ].
*/
public static final List<Tuple2<Path, List<FieldPath>>> expandGroupBy(final SQLRowValues vals, final SQLElementDirectory dir) {
return expandGroupBy(new Path(vals.getTable()), vals, dir);
}
 
private static final List<Tuple2<Path, List<FieldPath>>> expandGroupBy(final Path fieldsPath, final SQLRowValues vals, final SQLElementDirectory dir) {
assert fieldsPath.getLast() == vals.getTable();
if (vals.size() == 0)
return Collections.emptyList();
 
final SQLElement element = dir.getElement(fieldsPath.getLast());
// treat missing element as the root
final SQLField parentFF = element == null ? null : element.getParentForeignField();
 
final List<Tuple2<Path, List<FieldPath>>> res = new ArrayList<Tuple2<Path, List<FieldPath>>>();
final List<FieldPath> currentL = new ArrayList<FieldPath>();
res.add(Tuple2.create(fieldsPath, currentL));
SQLRowValues parent = null;
for (final String f : vals.getFields()) {
if (parentFF != null && f.equals(parentFF.getName())) {
final Object val = vals.getObject(f);
parent = val instanceof SQLRowValues ? (SQLRowValues) val : null;
} else {
currentL.addAll(expand(vals, new FieldPath(fieldsPath, f)));
}
}
if (parent != null)
res.addAll(expandGroupBy(fieldsPath.add(parentFF, Direction.FOREIGN), parent, dir));
 
return res;
}
 
public static final TransfFieldExpander getShowAs(final SQLElementDirectory dir) {
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 dir.getElement(foreignTable).getComboRequest().getFields();
}
});
return exp;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/BaseFillSQLRequest.java
14,7 → 14,9
package org.openconcerto.sql.request;
 
import org.openconcerto.sql.FieldExpander;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.FieldRef;
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
21,34 → 23,43
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSearchMode;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSyntax.CaseBuilder;
import org.openconcerto.sql.model.SQLSyntax.DateProp;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
@ThreadSafe
public abstract class BaseFillSQLRequest extends BaseSQLRequest implements Cloneable {
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
 
private final static Pattern QUERY_SPLIT_PATTERN = Pattern.compile("\\s+");
private static boolean DEFAULT_SELECT_LOCK = true;
 
/**
75,32 → 86,39
fetcher.setReferentsOrdered(true, true);
}
 
static public final void addToFetch(final SQLRowValues input, final Path p, final Collection<String> fields) {
static public final boolean addToFetch(final SQLRowValues input, final Path p, final Collection<String> fields) {
assert p == null || p.isSingleLink() : "Graph size not sufficient to know if graph was modified";
final int graphSize = input.getGraphSize();
// 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 r = p == null ? input : input.followPathToOne(p, CreateMode.CREATE_ONE, false);
boolean modified = input.getGraphSize() > graphSize;
for (final String f : fields) {
// don't overwrite foreign rows
if (!r.getFields().contains(f))
// don't overwrite foreign rows and update modified
if (!r.getFields().contains(f)) {
r.put(f, null);
modified = true;
}
}
return modified;
}
 
private final SQLTable primaryTable;
@GuardedBy("this")
private List<Path> order;
@GuardedBy("this")
private Where where;
@GuardedBy("this")
private Map<SQLField, SQLSearchMode> searchFields;
private Map<IFieldPath, SearchField> searchFields;
@GuardedBy("this")
private List<String> searchQuery;
private int searchLimit;
@GuardedBy("this")
private ITransformer<SQLSelect, SQLSelect> selTransf;
@GuardedBy("this")
private boolean lockSelect;
 
private final SQLRowValues graph;
@GuardedBy("this")
private SQLRowValues graph;
@GuardedBy("this")
private SQLRowValues graphToFetch;
 
@GuardedBy("this")
113,17 → 131,18
 
private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
 
public BaseFillSQLRequest(final SQLTable primaryTable, final Where w) {
public BaseFillSQLRequest(final SQLRowValues graph, final Where w) {
super();
if (primaryTable == null)
if (graph == null)
throw new NullPointerException();
this.primaryTable = primaryTable;
this.primaryTable = graph.getTable();
this.setOrder(null);
this.where = w;
this.searchFields = Collections.emptyMap();
this.searchQuery = Collections.emptyList();
this.searchLimit = 35;
this.selTransf = null;
this.lockSelect = getDefaultLockSelect();
this.graph = null;
this.graph = graph.toImmutable();
this.graphToFetch = null;
}
 
131,9 → 150,10
super();
this.primaryTable = req.getPrimaryTable();
synchronized (req) {
this.order = req.order;
this.where = req.where;
this.searchFields = req.searchFields;
this.searchQuery = req.searchQuery;
this.searchLimit = req.searchLimit;
this.selTransf = req.selTransf;
this.lockSelect = req.lockSelect;
// use methods since they're both lazy
199,53 → 219,24
return casted;
}
 
@Override
public BaseFillSQLRequest clone() {
synchronized (this) {
return this.clone(false);
}
}
 
// must be called with our lock
protected abstract BaseFillSQLRequest clone(boolean forFreeze);
 
private final SQLRowValues computeGraph() {
if (this.getFields() == null)
return null;
 
final SQLRowValues vals = new SQLRowValues(this.getPrimaryTable());
for (final SQLField f : this.getFields()) {
vals.put(f.getName(), null);
}
 
this.getShowAs().expand(vals);
static protected final SQLRowValues computeGraph(final SQLTable t, final Collection<String> fields, final FieldExpander exp) {
final SQLRowValues vals = new SQLRowValues(t).putNulls(fields);
exp.expand(vals);
return vals.toImmutable();
}
 
/**
* The graph computed by expanding {@link #getFields()} by {@link #getShowAs()}.
* The graph with fields to be automatically added to the UI.
*
* @return the expanded frozen graph.
*/
public final SQLRowValues getGraph() {
synchronized (this) {
if (this.graph == null) {
assert !this.isFrozen() : "no computation should take place after frozen()";
this.graph = this.computeGraph();
}
return this.graph;
}
}
 
// should be called if getFields(), getOrder() or getShowAs() change
protected final void clearGraph() {
synchronized (this) {
checkFrozen();
this.graph = null;
this.graphToFetch = null;
}
}
 
/**
* The graph to fetch, should be a superset of {@link #getGraph()}. To modify it, see
* {@link #addToGraphToFetch(Path, Set)} and {@link #changeGraphToFetch(IClosure)}.
258,16 → 249,8
assert !this.isFrozen() : "no computation should take place after frozen()";
final SQLRowValues tmp = this.getGraph().deepCopy();
this.customizeToFetch(tmp);
// fetch order fields, so that consumers can order an updated row in an existing
// list
for (final Path orderP : this.getOrder()) {
final SQLRowValues orderVals = tmp.followPath(orderP);
if (orderVals != null && orderVals.getTable().isOrdered()) {
orderVals.put(orderVals.getTable().getOrderField().getName(), null);
this.setGraphToFetch(tmp, true);
}
}
this.graphToFetch = tmp.toImmutable();
}
return this.graphToFetch;
}
}
296,18 → 279,30
public void executeChecked(SQLRowValues input) {
addToFetch(input, p, fields);
}
});
}, false);
}
 
public final void changeGraphToFetch(IClosure<SQLRowValues> cl) {
this.changeGraphToFetch(cl, true);
}
 
private final void changeGraphToFetch(IClosure<SQLRowValues> cl, final boolean checkNeeded) {
synchronized (this) {
checkFrozen();
final SQLRowValues tmp = this.getGraphToFetch().deepCopy();
cl.executeChecked(tmp);
this.graphToFetch = tmp.toImmutable();
this.setGraphToFetch(tmp, checkNeeded);
}
fireWhereChange();
}
 
private final void setGraphToFetch(final SQLRowValues tmp, final boolean checkNeeded) {
assert Thread.holdsLock(this) && !this.isFrozen();
if (checkNeeded && !tmp.graphContains(this.getGraph()))
throw new IllegalArgumentException("New graph too small");
this.graphToFetch = tmp.toImmutable();
}
 
protected void customizeToFetch(final SQLRowValues graphToFetch) {
}
 
314,9 → 309,18
protected synchronized final SQLRowValuesListFetcher getFetcher() {
if (this.isFrozen())
return this.frozen;
// fetch order fields, so that consumers can order an updated row in an existing list
final SQLRowValues tmp = getGraphToFetch().deepCopy();
for (final Path orderP : this.getOrder()) {
final SQLRowValues orderVals = tmp.followPath(orderP);
if (orderVals != null && orderVals.getTable().isOrdered()) {
orderVals.put(orderVals.getTable().getOrderField().getName(), null);
}
}
// graphToFetch can be modified freely so don't the use the simple constructor
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(getGraphToFetch(), false);
return setupFetcher(fetcher);
// order to have predictable result (this will both order the referent rows and main rows.
// The latter will be overwritten by our own getOrder())
return setupFetcher(SQLRowValuesListFetcher.create(tmp, true));
}
 
// allow to pass fetcher since they are mostly immutable (and for huge graphs they are slow to
342,10 → 346,27
return fetcher;
}
 
protected List<Path> getOrder() {
protected synchronized final List<Path> getOrder() {
if (this.order != null)
return this.order;
return this.getDefaultOrder();
}
 
protected List<Path> getDefaultOrder() {
return Collections.singletonList(Path.get(getPrimaryTable()));
}
 
/**
* Change the ordering of this request.
*
* @param l the list of tables, <code>null</code> to restore the {@link #getDefaultOrder()
* default} .
*/
public synchronized final void setOrder(List<Path> l) {
checkFrozen();
this.order = l == null ? null : Collections.unmodifiableList(new ArrayList<Path>(l));
}
 
public final void setWhere(final Where w) {
synchronized (this) {
checkFrozen();
365,9 → 386,14
* <code>false</code> to not be searchable.
*/
public final void setSearchable(final boolean b) {
this.setSearchFields(b ? this.getFields() : Collections.<SQLField> emptyList());
this.setSearchFields(b ? getDefaultSearchFields() : Collections.<SearchField> emptyList());
}
 
protected Collection<SearchField> getDefaultSearchFields() {
final Set<String> names = CollectionUtils.inter(this.getGraph().getFields(), this.getPrimaryTable().getFieldsNames(VirtualFields.LOCAL_CONTENT));
return mapOfModesToSearchFields(CollectionUtils.<String, SQLSearchMode> createMap(names));
}
 
/**
* Set the fields used to search.
*
374,10 → 400,18
* @param searchFields only rows with these fields containing the terms will match.
* @see #setSearch(String)
*/
public final void setSearchFields(final Collection<SQLField> searchFields) {
this.setSearchFields(CollectionUtils.<SQLField, SQLSearchMode> createMap(searchFields));
public final void setSearchFieldsNames(final Collection<String> searchFields) {
this.setSearchFieldsNames(CollectionUtils.<String, SQLSearchMode> createMap(searchFields));
}
 
protected final Collection<SearchField> mapOfModesToSearchFields(Map<String, SQLSearchMode> searchFields) {
final List<SearchField> list = new ArrayList<SearchField>();
for (final Entry<String, SQLSearchMode> e : searchFields.entrySet()) {
list.add(new SearchField(getPrimaryTable().getField(e.getKey()), e.getValue() == null ? SQLSearchMode.CONTAINS : e.getValue()));
}
return list;
}
 
/**
* Set the fields used to search.
*
384,57 → 418,42
* @param searchFields for each field to search, how to match.
* @see #setSearch(String)
*/
public final void setSearchFields(Map<SQLField, SQLSearchMode> searchFields) {
public final void setSearchFieldsNames(Map<String, SQLSearchMode> searchFields) {
this.setSearchFields(mapOfModesToSearchFields(searchFields));
}
 
public final void setSearchFields(final Collection<SearchField> searchFields) {
// can be outside the synchronized block, since it can't be reverted
checkFrozen();
searchFields = new HashMap<SQLField, SQLSearchMode>(searchFields);
final Iterator<Entry<SQLField, SQLSearchMode>> iter = searchFields.entrySet().iterator();
while (iter.hasNext()) {
final Entry<SQLField, SQLSearchMode> e = iter.next();
if (!String.class.isAssignableFrom(e.getKey().getType().getJavaType())) {
iter.remove();
} else if (e.getValue() == null) {
e.setValue(SQLSearchMode.CONTAINS);
final Map<IFieldPath, SearchField> copy = new HashMap<IFieldPath, SearchField>();
for (final SearchField f : searchFields) {
final SearchField prev = copy.put(f.getField(), f);
if (prev != null)
throw new IllegalArgumentException("Duplicate : " + f.getField());
}
}
searchFields = Collections.unmodifiableMap(searchFields);
synchronized (this) {
this.searchFields = searchFields;
this.searchFields = Collections.unmodifiableMap(copy);
}
fireWhereChange();
}
 
public Map<SQLField, SQLSearchMode> getSearchFields() {
public Map<IFieldPath, SearchField> getSearchFields() {
synchronized (this) {
return this.searchFields;
}
}
 
/**
* Set the search query. The query will be used to match rows using
* {@link #setSearchFields(Map)}. I.e. if there's no field set, this method won't have any
* effect.
*
* @param s the search query.
* @return <code>true</code> if the request changed.
*/
public boolean setSearch(String s) {
// no need to trim() since trailing empty strings are not returned
final List<String> split = Arrays.asList(QUERY_SPLIT_PATTERN.split(s));
boolean res = false;
synchronized (this) {
checkFrozen();
if (!split.equals(this.searchQuery)) {
this.searchQuery = split;
if (!this.getSearchFields().isEmpty()) {
res = true;
public synchronized final boolean isSearchable() {
return !this.getSearchFields().isEmpty();
}
 
public synchronized final void setSearchLimit(final int limit) {
this.searchLimit = limit;
}
 
public synchronized final int getSearchLimit() {
return this.searchLimit;
}
if (res)
this.fireWhereChange();
return res;
}
 
public final synchronized void setLockSelect(boolean lockSelect) {
checkFrozen();
445,54 → 464,221
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
// (eg by adding a where on a field of a non-displayed table)
return this.getFetcher().getReq().getFields();
public Set<SQLTable> getTables() {
final Set<SQLTable> res = new HashSet<SQLTable>();
for (final SQLRowValues v : this.getGraphToFetch().getGraph().getItems())
res.add(v.getTable());
return res;
}
 
protected abstract Collection<SQLField> getFields();
public final void addTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.addTableModifiedListener(l);
}
}
 
public final void removeTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.removeTableModifiedListener(l);
}
}
 
protected final List<SQLField> getFields() {
return this.getPrimaryTable().getFields(this.getGraph().getFields());
}
 
protected SQLSelect transformSelect(final SQLSelect sel) {
final Map<SQLField, SQLSearchMode> searchFields;
final List<String> searchQuery;
final ITransformer<SQLSelect, SQLSelect> transf = this.getSelectTransf();
return transf == null ? sel : transf.transformChecked(sel);
}
 
// @param searchQuery null means don't want to search in SQL (i.e. no WHERE, no LIMIT), empty
// means nothing to search (i.e. no WHERE but LIMIT).
protected final ITransformer<SQLSelect, SQLSelect> createSearchTransformer(final List<String> searchQuery, final Locale l, final Where forceInclude) {
if (searchQuery == null)
return null;
final Map<IFieldPath, SearchField> searchFields;
final int searchLimit;
final boolean searchable;
synchronized (this) {
searchFields = this.getSearchFields();
searchQuery = this.searchQuery;
searchLimit = this.getSearchLimit();
searchable = this.isSearchable();
}
if (!searchable) {
throw new IllegalArgumentException("Cannot search " + searchQuery);
}
// continue even if searchQuery is empty to apply the LIMIT
final List<String> immutableQuery = Collections.unmodifiableList(new ArrayList<String>(searchQuery));
return new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect sel) {
return transformSelectSearch(sel, searchFields, searchLimit, immutableQuery, l, forceInclude);
}
};
}
 
static protected final SQLSelect transformSelectSearch(final SQLSelect sel, final Map<IFieldPath, SearchField> searchFields, final int searchLimit, final List<String> searchQuery, final Locale l,
final Where forceInclude) {
final Where w;
final Set<String> matchScore = new HashSet<String>();
if (!searchFields.isEmpty()) {
if (!searchQuery.isEmpty()) {
final SQLSyntax syntax = sel.getSyntax();
Where where = null;
for (final String searchTerm : searchQuery) {
Where termWhere = null;
for (final FieldRef selF : sel.getSelectFields()) {
final SQLSearchMode mode = searchFields.get(selF.getField());
if (mode != null) {
termWhere = Where.createRaw(createWhere(selF, mode, searchTerm)).or(termWhere);
if (!mode.equals(SQLSearchMode.EQUALS))
matchScore.add("case when " + createWhere(selF, SQLSearchMode.EQUALS, searchTerm) + " then 1 else 0 end");
for (final SearchField searchField : searchFields.values()) {
final FieldRef selF = sel.followFieldPath(searchField.getField());
final SQLSearchMode mode = searchField.getMode();
final List<String> formatted = searchField.format(selF, l);
final String fieldWhere = createWhere(syntax, formatted, mode, searchTerm);
termWhere = Where.createRaw(fieldWhere).or(termWhere);
if (searchField.getScore() > 0 || !searchField.getHigherModes().isEmpty()) {
final CaseBuilder caseBuilder = syntax.createCaseWhenBuilder().setElse("0");
for (final Tuple2<SQLSearchMode, Integer> hm : searchField.getHigherModes()) {
caseBuilder.addWhen(createWhere(syntax, formatted, hm.get0(), searchTerm), String.valueOf(hm.get1()));
}
if (searchField.getScore() > 0) {
caseBuilder.addWhen(fieldWhere, String.valueOf(searchField.getScore()));
}
matchScore.add(caseBuilder.build());
}
}
where = Where.and(termWhere, where);
}
// only use forceInclude when there's a restriction otherwise the include transforms
// itself into a restrict
if (where != null)
where = where.or(forceInclude);
w = where;
} else {
w = null;
}
sel.andWhere(w);
if (forceInclude != null)
matchScore.add("case when " + forceInclude + " then 10000 else 0 end");
if (!matchScore.isEmpty())
sel.getOrder().add(0, CollectionUtils.join(matchScore, " + ") + " DESC");
if (searchLimit >= 0)
sel.setLimit(searchLimit);
 
final ITransformer<SQLSelect, SQLSelect> transf = this.getSelectTransf();
return transf == null ? sel : transf.transformChecked(sel);
return sel;
}
 
protected String createWhere(final FieldRef selF, final SQLSearchMode mode, final String searchQuery) {
return "lower(" + selF.getFieldRef() + ") " + mode.generateSQL(selF.getField().getDBRoot(), searchQuery.toLowerCase());
static protected final String createWhere(final SQLSyntax syntax, final List<String> formatted, final SQLSearchMode mode, final String searchQuery) {
return CollectionUtils.join(formatted, " OR ", new ITransformer<String, String>() {
@Override
public String transformChecked(String sqlExpr) {
return createWhere(sqlExpr, mode, syntax, searchQuery);
}
});
}
 
static public final List<String> defaultFormat(final FieldRef selF, final Locale l) {
final SQLType type = selF.getField().getType();
final SQLSyntax syntax = SQLSyntax.get(selF.getField());
if (type.getJavaType() == String.class) {
return Collections.singletonList(selF.getFieldRef());
} else if (type.getJavaType() == Boolean.class) {
return Collections.singletonList("case when " + selF.getFieldRef() + " then " + syntax.quoteString(TM.tr("true_key")) + " else " + syntax.quoteString(TM.tr("false_key")) + " end");
} else if (Timestamp.class.isAssignableFrom(type.getJavaType())) {
final String shortFmt = formatTime(selF, DateProp.SHORT_DATETIME_SKELETON, l, syntax);
final String longFmt = formatTime(selF, DateProp.LONG_DATETIME_SKELETON, l, syntax);
return Arrays.asList(shortFmt, longFmt);
} else if (Time.class.isAssignableFrom(type.getJavaType())) {
return Collections.singletonList(formatTime(selF, DateProp.TIME_SKELETON, l, syntax));
} else if (Date.class.isAssignableFrom(type.getJavaType())) {
final String shortFmt = formatTime(selF, DateProp.SHORT_DATE_SKELETON, l, syntax);
final String longFmt = formatTime(selF, DateProp.LONG_DATE_SKELETON, l, syntax);
return Arrays.asList(shortFmt, longFmt);
} else {
return Collections.singletonList(syntax.cast(selF.getFieldRef(), String.class));
}
}
 
static public final String formatTime(final FieldRef selF, final List<String> simpleFormat, final Locale l, final SQLSyntax syntax) {
return syntax.getFormatTimestampSimple(selF.getFieldRef(), DateProp.getBestPattern(simpleFormat, l), l);
}
 
static protected final String createWhere(final String sqlExpr, final SQLSearchMode mode, final SQLSyntax syntax, final String searchQuery) {
return "lower(" + sqlExpr + ") " + mode.generateSQL(syntax, searchQuery.toLowerCase());
}
 
static public class SearchField {
private final IFieldPath field;
private final SQLSearchMode mode;
private final int score;
private final List<Tuple2<SQLSearchMode, Integer>> higherModes;
 
public SearchField(IFieldPath field, SQLSearchMode mode) {
this(field, mode, 1);
}
 
/**
* Create a new search field.
*
* @param field which field to search.
* @param mode how to search.
* @param score the score (>0) to attribute if the field matches. Allow to rank fields
* between themselves.
*/
public SearchField(IFieldPath field, SQLSearchMode mode, int score) {
this(field, mode, score, -1, -1);
}
 
public SearchField(final IFieldPath field, final SQLSearchMode mode, final int score, final int score2, final int score3) {
super();
if (field.getField().getFieldGroup().getKeyType() != null)
throw new IllegalArgumentException("Field is a key : " + field);
this.field = field;
this.mode = mode;
/*
* for now we could pass <code>1</code> so that a row with more matches is higher ranked
* (e.g. if searching "a" ["ant", "cat"] is better than ["ant", "horse"]), or
* <code>0</code> to ignore the match count. But this only works because we have
* separate WHERE and ORDER BY ; if we had a computed column with "WHERE score > 0 ORDER
* BY score" this would be complicated.
*/
if (score < 1)
throw new IllegalArgumentException("Invalid score : " + score);
this.score = score;
final List<SQLSearchMode> higherModes = field.getField().getType().getJavaType() == String.class ? this.mode.getHigherModes() : Collections.<SQLSearchMode> emptyList();
if (higherModes.isEmpty()) {
this.higherModes = Collections.emptyList();
} else {
if (higherModes.size() > 2)
throw new IllegalStateException("Too many higher modes " + higherModes);
final List<Tuple2<SQLSearchMode, Integer>> tmp = new ArrayList<Tuple2<SQLSearchMode, Integer>>(2);
tmp.add(Tuple2.create(higherModes.get(0), score3 < 1 ? Math.max((int) (this.score * 1.5), this.score + 2) : score3));
if (higherModes.size() > 1)
tmp.add(Tuple2.create(higherModes.get(1), score2 < 1 ? Math.max((int) (this.score * 1.2), this.score + 1) : score2));
this.higherModes = Collections.unmodifiableList(tmp);
}
}
 
public final IFieldPath getField() {
return this.field;
}
 
public final SQLSearchMode getMode() {
return this.mode;
}
 
public final int getScore() {
return this.score;
}
 
public List<Tuple2<SQLSearchMode, Integer>> getHigherModes() {
return this.higherModes;
}
 
protected List<String> format(final FieldRef selF, final Locale l) {
if (getField().getField() != selF.getField())
throw new IllegalArgumentException("Wrong field");
return defaultFormat(selF, l);
}
}
 
public final synchronized ITransformer<SQLSelect, SQLSelect> getSelectTransf() {
return this.selTransf;
}
510,8 → 696,6
this.fireWhereChange();
}
 
protected abstract FieldExpander getShowAs();
 
public final SQLTable getPrimaryTable() {
return this.primaryTable;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLFieldTranslator.java
52,14 → 52,14
import java.util.Set;
import java.util.prefs.Preferences;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
/**
* Class to obtain a RowItemDesc from a table and a name.
*
149,21 → 149,12
this.unknownCodes = new HashSet<String>();
}
 
public SQLFieldTranslator(DBRoot base) {
this(base, null);
}
 
public SQLFieldTranslator(DBRoot base, InputStream inputStream) {
this(base, inputStream, null);
}
 
/**
* Create a new instance.
*
* @param root the default root for tables.
* @param inputStream the XML, can be <code>null</code>.
* @param dir the directory where to look for tables not in <code>root</code>, can be
* <code>null</code>.
* @param dir the directory where to look for tables not in <code>root</code>.
*/
public SQLFieldTranslator(DBRoot root, InputStream inputStream, SQLElementDirectory dir) {
try {
194,6 → 185,10
fetchAndPut(this.table, null);
}
 
public final SQLElementDirectory getDirectory() {
return this.dir;
}
 
/**
* Add all translations of <code>o</code> to this, note that if a table is present in both this
* and <code>o</code> its translations won't be changed.
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementDirectory.java
20,6 → 20,7
import org.openconcerto.sql.model.DBStructureItemNotFound;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.utils.CollectionMap2;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.SetMap;
43,6 → 44,8
 
import org.jdom2.JDOMException;
 
import net.jcip.annotations.GuardedBy;
 
/**
* Directory of SQLElement by table.
*
82,6 → 85,8
private String phrasesPkgName;
private final Map<String, SQLElementNames> elementNames;
 
@GuardedBy("this")
private SQLFieldTranslator translator;
private final ShowAs showAs;
 
public SQLElementDirectory() {
104,6 → 109,16
return this.showAs;
}
 
public final synchronized void setTranslator(SQLFieldTranslator translator) {
if (translator.getDirectory() != this)
throw new IllegalArgumentException("Not for this : " + translator);
this.translator = translator;
}
 
public synchronized final SQLFieldTranslator getTranslator() {
return this.translator;
}
 
private static <K> SQLTable getSoleTable(SetMap<K, SQLTable> m, K key) throws IllegalArgumentException {
final Collection<SQLTable> res = m.getNonNull(key);
if (res.size() > 1)
121,6 → 136,7
if (!this.contains(elem.getTable()))
this.addSQLElement(elem);
}
this.translator.putAll(o.getTranslator());
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/element/GroupSQLComponent.java
60,6 → 60,8
 
public class GroupSQLComponent extends BaseSQLComponent {
 
public static final String ITEM_RIGHT_CODE = "GROUP_ITEM_SHOW";
 
private final Group group;
private final int columns = 2;
private final Map<String, JComponent> labels = new HashMap<String, JComponent>();
462,8 → 464,8
JComponent label = this.labels.get(id);
if (label == null) {
label = createLabel(id);
if (!UserRightsManager.getCurrentUserRights().haveRight("GROUP_ITEM_SHOW", id)
|| !UserRightsManager.getCurrentUserRights().haveRight("GROUP_ITEM_SHOW", getElement().getTable().getName() + "." + id)) {
if (!UserRightsManager.getCurrentUserRights().haveRight(ITEM_RIGHT_CODE, id)
|| !UserRightsManager.getCurrentUserRights().haveRight(ITEM_RIGHT_CODE, getElement().getTable().getName() + "." + id)) {
label.setVisible(false);
}
 
475,7 → 477,7
return label;
}
 
private RowItemDesc getRIVDescForId(final String id) {
public RowItemDesc getRIVDescForId(final String id) {
if (TranslationManager.getInstance().getLocale() != null) {
final String t = TranslationManager.getInstance().getTranslationForItem(id);
if (t != null) {
501,8 → 503,8
JComponent editor = this.editors.get(id);
if (editor == null) {
editor = createEditor(id);
if (!UserRightsManager.getCurrentUserRights().haveRight("GROUP_ITEM_SHOW", id)
|| !UserRightsManager.getCurrentUserRights().haveRight("GROUP_ITEM_SHOW", getElement().getTable().getName() + "." + id)) {
if (!UserRightsManager.getCurrentUserRights().haveRight(ITEM_RIGHT_CODE, id)
|| !UserRightsManager.getCurrentUserRights().haveRight(ITEM_RIGHT_CODE, getElement().getTable().getName() + "." + id)) {
editor.setVisible(false);
}
this.editors.put(id, editor);
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElement.java
18,6 → 18,7
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.FieldExpander;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElementLink.LinkType;
import org.openconcerto.sql.element.TreesOfSQLRows.LinkToCut;
56,23 → 57,29
import org.openconcerto.sql.request.SQLCache;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.sql.sqlobject.SQLTextCombo;
import org.openconcerto.sql.ui.light.ConvertorModifer;
import org.openconcerto.sql.ui.light.GroupToLightUIConvertor;
import org.openconcerto.sql.ui.light.LightEditFrame;
import org.openconcerto.sql.ui.light.LightUIPanelFiller;
import org.openconcerto.sql.ui.light.SavableCustomEditorProvider;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.sql.view.list.IListeAction;
import org.openconcerto.sql.view.list.SQLTableModelColumn;
import org.openconcerto.sql.view.list.SQLTableModelColumnPath;
import org.openconcerto.sql.view.list.SQLTableModelSource;
import org.openconcerto.sql.view.list.SQLTableModelSourceOffline;
import org.openconcerto.sql.view.list.SQLTableModelSourceOnline;
import org.openconcerto.ui.group.Group;
import org.openconcerto.ui.light.ColumnsSpec;
import org.openconcerto.ui.light.ComboValueConvertor;
import org.openconcerto.ui.light.CustomEditorProvider;
import org.openconcerto.ui.light.IntValueConvertor;
import org.openconcerto.ui.light.LightUIComboBox;
import org.openconcerto.ui.light.LightUIComboBoxElement;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.Row;
import org.openconcerto.ui.light.LightUIFrame;
import org.openconcerto.ui.light.LightUIPanel;
import org.openconcerto.ui.light.StringValueConvertor;
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
import org.openconcerto.utils.CollectionUtils;
274,47 → 281,114
}
 
/**
* Create a Row to put in a TableContent
* Get the group based on the edit mode
*
* @param sqlRow SQLRowValues used to create the Row
* @param allCols Columns attach to this SQLElement
* @param columnsSpec Columns of the LightUITable attach to this SQLElement
* @param editMode
* @return
*/
public Group getEditGroup(final EditMode editMode) {
if (editMode.equals(EditMode.CREATION)) {
return this.getGroupForCreation();
} else {
return this.getGroupForModification();
}
}
 
/**
* Override this function in an element to show default values in edit frame
*
* @return New Row created from the SQLRowValues
* @param token - The security token of session
*
* @return a default SQLRowValues
*/
public final Row createRowFromSQLRow(final SQLRowValues sqlRow, final List<SQLTableModelColumn> allCols, final ColumnsSpec columnsSpec) {
final Row row = new Row(sqlRow.getID());
public SQLRowValues createDefaultRowValues(final String token) {
return new SQLRowValues(getTable());
}
 
final List<Object> values = new ArrayList<Object>();
final int colSize = allCols.size();
for (int i = 0; i < colSize; i++) {
final String columnId = columnsSpec.getColumn(i).getId();
final SQLTableModelColumn col = getColumnFromId(allCols, columnId);
this.configureConverter(col);
if (col != null) {
Object value = col.show(sqlRow);
if (col.getLightUIrenderer() != null) {
value = col.getLightUIrenderer().getLightUIElement(value, 0, i);
/**
* Create the edition frame for this SQLElement
*
* @param configuration current configuration
* @param parentFrame parent frame of the edit frame
* @param editMode edition mode (CREATION, MODIFICATION, READONLY)
* @param sqlRow SQLRowAccessor use for fill the edition frame
* @param sessionSecurityToken String, use for find session with an instance of LightServer
* @return the edition frame of this SQLElement
*/
public LightEditFrame createEditFrame(final PropsConfiguration configuration, final LightUIFrame parentFrame, final EditMode editMode, final SQLRowAccessor sqlRow,
final String sessionSecurityToken) {
final Group editGroup = this.getEditGroup(editMode);
if (editGroup == null) {
Log.get().severe("The edit group is null for this element : " + this);
return null;
}
values.add(value);
} else {
throw new IllegalArgumentException("column " + columnId + " is in ColumnsSpec but it is not found in SQLTableModelColumn");
 
final GroupToLightUIConvertor convertor = this.getGroupToLightUIConvertor(configuration, editMode, sqlRow, sessionSecurityToken);
final LightEditFrame editFrame = convertor.convert(editGroup, sqlRow, parentFrame, editMode);
 
if (editMode.equals(EditMode.CREATION)) {
editFrame.createTitlePanel(this.getCreationFrameTitle());
} else if (editMode.equals(EditMode.MODIFICATION)) {
editFrame.createTitlePanel(this.getModificationFrameTitle(sqlRow));
new LightUIPanelFiller(editFrame.getFirstChild(LightUIPanel.class)).fillFromRow(configuration, sqlRow);
} else if (editMode.equals(EditMode.READONLY)) {
editFrame.createTitlePanel(this.getReadOnlyFrameTitle(sqlRow));
new LightUIPanelFiller(editFrame.getFirstChild(LightUIPanel.class)).fillFromRow(configuration, sqlRow);
}
 
this.setEditFrameModifiers(editFrame, sessionSecurityToken);
 
return editFrame;
}
row.setValues(values);
return row;
 
/**
* Get title for read only mode
*
* @param sqlRow - SQLRowValues use for fill the edition frame
* @return The title for read only mode
*/
protected String getReadOnlyFrameTitle(final SQLRowAccessor sqlRow) {
return EditFrame.getReadOnlyMessage(this);
}
 
public final SQLTableModelColumn getColumnFromId(final List<SQLTableModelColumn> allCols, final String columnId) {
final int columnSize = allCols.size();
for (int i = 0; i < columnSize; i++) {
final SQLTableModelColumn tableModelColumn = allCols.get(i);
if (tableModelColumn.getIdentifier().equals(columnId)) {
return tableModelColumn;
/**
* Get title for modification mode
*
* @param sqlRow - SQLRowValues use for fill the edition frame
* @return The title for read only mode
*/
protected String getModificationFrameTitle(final SQLRowAccessor sqlRow) {
return EditFrame.getModifyMessage(this);
}
 
/**
* Get title for creation mode
*
* @param sqlRow - SQLRowValues use for fill the edition frame
* @return The title for read only mode
*/
protected String getCreationFrameTitle() {
return EditFrame.getCreateMessage(this);
}
return null;
 
/**
*
* @param configuration - The user SQL configuration
* @param editMode - Edit mode of the frame
* @param sqlRow - The row to update
* @param token - The session security token
*
* @return An initialized GroupToLightUIConvertor
*/
public GroupToLightUIConvertor getGroupToLightUIConvertor(final PropsConfiguration configuration, final EditMode editMode, final SQLRowAccessor sqlRow, final String token) {
final GroupToLightUIConvertor convertor = new GroupToLightUIConvertor(configuration);
if (editMode.equals(EditMode.CREATION)) {
convertor.putAllCustomEditorProvider(this.getCustomEditorProviderForCreation(configuration, token));
} else {
convertor.putAllCustomEditorProvider(this.getCustomEditorProviderForModification(configuration, sqlRow, token));
}
return convertor;
}
 
/**
* Override this function in an element and put new value in map for use ComboValueConvertor.
323,7 → 397,7
* @return Map which contains all ComboValueConvertors use for this SQLElement edition. Key: ID
* of group item / Value: ComboValueConvertor
*/
// FIXME: voir avec Sylvain pour les conversions
// TODO: use renderer instead of ValueConvertor
public Map<String, ComboValueConvertor<?>> getComboConvertors() {
return new HashMap<String, ComboValueConvertor<?>>();
}
332,81 → 406,167
* Override this function in an element and put new value in map for use ConvertorModifier. This
* one allow you to apply some change on LightUIElement before send it to client
*
* @return Map which contains all ConvertorModifier use for this SQLElement edition. Key: ID of
* group item / Value: ConvertorModeifier
*/
public Map<String, ConvertorModifer> getConvertorModifiers() {
return new HashMap<String, ConvertorModifer>();
// TODO: implement with IClosure
public void setEditFrameModifiers(final LightEditFrame frame, final String sessionToken) {
}
 
public Map<String, CustomEditorProvider> getCustomEditorProviderForCreation(final Configuration configuration, final long userId) {
return this.getDefaultCustomEditorProvider(configuration, null, userId);
public final Map<String, CustomEditorProvider> getCustomEditorProviderForCreation(final Configuration configuration, final String sessionToken) {
final Map<String, CustomEditorProvider> map = this.getDefaultCustomEditorProvider(configuration, null, sessionToken);
map.putAll(this._getCustomEditorProviderForCreation(configuration, sessionToken));
return map;
}
 
public Map<String, CustomEditorProvider> getCustomEditorProviderForModification(final Configuration configuration, final SQLRowValues sqlRow, final long userId) {
return this.getDefaultCustomEditorProvider(configuration, sqlRow, userId);
public final Map<String, CustomEditorProvider> getCustomEditorProviderForModification(final Configuration configuration, final SQLRowAccessor sqlRow, final String sessionToken) {
final Map<String, CustomEditorProvider> map = this.getDefaultCustomEditorProvider(configuration, sqlRow, sessionToken);
map.putAll(this._getCustomEditorProviderForModification(configuration, sqlRow, sessionToken));
return map;
}
 
protected Map<String, CustomEditorProvider> getDefaultCustomEditorProvider(final Configuration configuration, final SQLRowValues sqlRow, final long userId) {
protected Map<String, CustomEditorProvider> _getCustomEditorProviderForCreation(final Configuration configuration, final String sessionToken) {
return new HashMap<String, CustomEditorProvider>();
}
 
protected Map<String, CustomEditorProvider> _getCustomEditorProviderForModification(final Configuration configuration, final SQLRowAccessor sqlRow, final String sessionToken) {
return new HashMap<String, CustomEditorProvider>();
}
 
protected Map<String, CustomEditorProvider> _getDefaultCustomEditorProvider(final Configuration configuration, final SQLRowAccessor sqlRow, final String sessionToken) {
return new HashMap<String, CustomEditorProvider>();
}
 
private final Map<String, CustomEditorProvider> getDefaultCustomEditorProvider(final Configuration configuration, final SQLRowAccessor sqlRow, final String sessionToken) {
final Map<String, ComboValueConvertor<?>> comboConvertors = this.getComboConvertors();
final Map<String, CustomEditorProvider> result = new HashMap<String, CustomEditorProvider>();
for (final Entry<String, ComboValueConvertor<?>> entry : comboConvertors.entrySet()) {
result.put(entry.getKey(), new CustomEditorProvider() {
result.put(entry.getKey(), new SavableCustomEditorProvider() {
final ComboValueConvertor<?> convertor = entry.getValue();
 
@Override
public LightUIElement createUIElement(final String elementId) {
final LightUIComboBox uiCombo = new LightUIComboBox(elementId);
this.convertor.fillCombo(uiCombo);
 
if (sqlRow != null) {
if (sqlRow == null) {
this.convertor.fillCombo(uiCombo, null);
} else {
final SQLField field = configuration.getFieldMapper().getSQLFieldForItem(elementId);
LightUIComboBoxElement selectedValue = null;
if (this.convertor instanceof StringValueConvertor) {
final StringValueConvertor stringConvertor = (StringValueConvertor) this.convertor;
final String value = sqlRow.getString(field.getFieldName());
if (value != null) {
selectedValue = new LightUIComboBoxElement(stringConvertor.getIndexFromId(value));
selectedValue.setValue1(stringConvertor.convert(value));
}
((StringValueConvertor) this.convertor).fillCombo(uiCombo, sqlRow.getString(field.getFieldName()));
} else if (this.convertor instanceof IntValueConvertor) {
final IntValueConvertor intConvertor = (IntValueConvertor) this.convertor;
if (sqlRow.getObject(field.getFieldName()) != null) {
final int value = sqlRow.getInt(field.getFieldName());
selectedValue = new LightUIComboBoxElement(value);
selectedValue.setValue1(intConvertor.convert(value));
if (sqlRow.getObject(field.getFieldName()) == null) {
this.convertor.fillCombo(uiCombo, null);
} else {
((IntValueConvertor) this.convertor).fillCombo(uiCombo, sqlRow.getInt(field.getFieldName()));
}
}
uiCombo.setSelectedValue(selectedValue);
}
return uiCombo;
}
 
@Override
protected void _save(final SQLRowValues sqlRow, final SQLField sqlField, final LightUIElement uiElement) {
final LightUIComboBox combo = (LightUIComboBox) uiElement;
if (combo.hasSelectedValue()) {
if (this.convertor instanceof StringValueConvertor) {
sqlRow.put(sqlField.getName(), ((StringValueConvertor) this.convertor).getIdFromIndex(combo.getSelectedValue().getId()));
} else if (this.convertor instanceof IntValueConvertor) {
sqlRow.put(sqlField.getName(), combo.getSelectedValue().getId());
} else {
throw new IllegalArgumentException("the save is not implemented for the class: " + this.convertor.getClass().getName() + " - ui element id: " + uiElement.getId());
}
} else {
sqlRow.put(sqlField.getName(), null);
}
}
});
}
result.putAll(this._getDefaultCustomEditorProvider(configuration, sqlRow, sessionToken));
return result;
}
 
/**
* Add value converter on a SQLTableModelColumn to change the attached value in SQLRowValues
* when createRowFromRowValues is called
* Override this function in an element to execute some code just after inserted new row in
* database
*
* @param col SQLTableModelColumn that you want change the value
* @param editFrame - The edit frame of this SQLRow
* @param sqlRow - The row which was just inserted
* @param sessionToken Security token of session which allow to find session in LightServer
* instance
*
* @throws Exception
*/
protected void configureConverter(final SQLTableModelColumn col) {
public void doAfterLightInsert(final LightEditFrame editFrame, final SQLRow sqlRow, final String sessionToken) throws Exception {
 
}
 
/**
* Override this function in an element to execute some code just after inserted new row in
* Override this function in an element to execute some code just after deleted a row in
* database
*
* @param row The row which was just inserted
* @param frame - The current frame
* @param sqlRow - The row which was deleted
* @param sessionToken Security token of session which allow to find session in LightServer
* instance
*
* @throws Exception
*/
public void doAfterLightInsert(SQLRow row) {
public void doAfterLightDelete(final LightUIFrame frame, final SQLRowValues sqlRow, final String sessionToken) throws Exception {
 
}
 
/**
* Override this function in an element to execute some code before inserted new row in database
*
* @param frame - The current frame
* @param sqlRow - The row which will be deleted
* @param sessionToken - Security token of session which allow to find session in LightServer
* instance
*
* @throws Exception
*/
public void doBeforeLightDelete(final LightUIFrame frame, final SQLRowValues sqlRow, final String sessionToken) throws Exception {
 
}
 
/**
* Override this function in an element to execute some code before inserted new row in database
*
* @param editFrame - The edit frame of this SQLRowValues
* @param sqlRow - The row which was just inserted
* @param sessionToken - Security token of session which allow to find session in LightServer
* instance
*
* @throws Exception
*/
public void doBeforeLightInsert(final LightEditFrame editFrame, final SQLRowValues sqlRow, final String sessionToken) throws Exception {
 
}
 
/**
* Get ShowAs values of this SQLElement
*
* @param id - The id which you want to expand
*
* @return A SQLRowValues with data
*/
public SQLRowValues getValuesOfShowAs(final Number id) {
final SQLRowValues tmp = new SQLRowValues(this.getTable());
final ListMap<String, String> showAs = this.getShowAs();
 
for (final List<String> listStr : showAs.values()) {
tmp.putNulls(listStr);
}
this.getDirectory().getShowAs().expand(tmp);
 
final SQLRowValues fetched = SQLRowValuesListFetcher.create(tmp).fetchOne(id);
if (fetched == null) {
throw new IllegalArgumentException("Impossible to find Row in database - table: " + this.getTable().getName() + ", id: " + id);
}
 
return fetched;
}
 
/**
* Must be called if foreign/referent keys are added or removed.
*/
public synchronized void resetRelationships() {
1581,10 → 1741,19
}
}
 
protected ComboSQLRequest createComboRequest() {
return new ComboSQLRequest(this.getTable(), this.getComboFields());
public final ComboSQLRequest createComboRequest() {
return this.createComboRequest(null, null);
}
 
public final ComboSQLRequest createComboRequest(final List<String> fields, final Where w) {
final ComboSQLRequest res = new ComboSQLRequest(this.getTable(), fields == null ? this.getComboFields() : fields, w, this.getDirectory());
this._initComboRequest(res);
return res;
}
 
protected void _initComboRequest(final ComboSQLRequest req) {
}
 
// not all elements need to be displayed in combos so don't make this method abstract
protected List<String> getComboFields() {
return this.getListFields();
1668,34 → 1837,45
public final synchronized SQLTableModelSourceOnline getTableSource(final boolean create) {
if (!create) {
if (this.tableSrc == null) {
this.tableSrc = createAndInitTableSource();
this.tableSrc = createTableSource();
}
return this.tableSrc;
} else
return this.createAndInitTableSource();
return this.createTableSource();
}
 
public final SQLTableModelSourceOnline createTableSource() {
return createTableSource((Where) null);
}
 
public final SQLTableModelSourceOnline createTableSource(final List<String> fields) {
return initTableSource(new SQLTableModelSourceOnline(createListRequest(fields)));
return createTableSourceOnline(createListRequest(fields));
}
 
public final SQLTableModelSourceOnline createTableSource(final Where w) {
final SQLTableModelSourceOnline res = this.getTableSource(true);
res.getReq().setWhere(w);
return res;
return createTableSourceOnline(createListRequest(null, w, null));
}
 
private final SQLTableModelSourceOnline createAndInitTableSource() {
final SQLTableModelSourceOnline res = this.createTableSource();
public final SQLTableModelSourceOnline createTableSourceOnline(final ListSQLRequest req) {
return initTableSource(instantiateTableSourceOnline(req));
}
 
protected SQLTableModelSourceOnline instantiateTableSourceOnline(final ListSQLRequest req) {
return new SQLTableModelSourceOnline(req, this);
}
 
protected synchronized void _initTableSource(final SQLTableModelSource res) {
if (!this.additionalListCols.isEmpty())
res.getColumns().addAll(this.additionalListCols);
return initTableSource(res);
}
 
protected synchronized void _initTableSource(final SQLTableModelSourceOnline res) {
public final <S extends SQLTableModelSource> S initTableSource(final S res) {
return this.initTableSource(res, false);
}
 
public final synchronized SQLTableModelSourceOnline initTableSource(final SQLTableModelSourceOnline res) {
public final synchronized <S extends SQLTableModelSource> S initTableSource(final S res, final boolean minimal) {
// do init first since it can modify the columns
if (!minimal)
this._initTableSource(res);
// setEditable(false) on read only fields
// MAYBE setReadOnlyFields() on SQLTableModelSource, so that SQLTableModelLinesSource can
1708,12 → 1888,22
return res;
}
 
protected SQLTableModelSourceOnline createTableSource() {
// also create a new ListSQLRequest, otherwise it's a backdoor to change the behaviour of
// the new TableModelSource
return new SQLTableModelSourceOnline(this.createListRequest());
public final SQLTableModelSourceOffline createTableSourceOffline() {
return createTableSourceOfflineWithWhere(null);
}
 
public final SQLTableModelSourceOffline createTableSourceOfflineWithWhere(final Where w) {
return createTableSourceOffline(createListRequest(null, w, null));
}
 
public final SQLTableModelSourceOffline createTableSourceOffline(final ListSQLRequest req) {
return initTableSource(instantiateTableSourceOffline(req));
}
 
protected SQLTableModelSourceOffline instantiateTableSourceOffline(final ListSQLRequest req) {
return new SQLTableModelSourceOffline(req, this);
}
 
/**
* Whether to cache our tableSource.
*
2287,7 → 2477,7
if (parentLink.isJoin()) {
sel.addSelect(joinPK, null, "joinID");
} else {
sel.addRawSelect(syntax.cast("NULL", joinPK.getTypeDecl()), "joinID");
sel.addRawSelect(syntax.cast("NULL", joinPK.getType()), "joinID");
}
sel.addRawSelect(String.valueOf(listIter.previousIndex()), "fieldIndex");
sel.setArchivedPolicy(archiveMode);
/trunk/OpenConcerto/src/org/openconcerto/sql/element/BaseSQLComponent.java
24,8 → 24,6
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.sql.request.MutableRowItemView;
740,9 → 738,6
}
 
public void select(SQLRowAccessor r, Set<String> views) {
// FIXME force loading
if (r instanceof SQLRow)
((SQLRow) r).exists();
try {
// allow null to pass, ease some code (eg new ListOfSomething().getTable() even if we
// can't see the table)
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLComponent.java
52,7 → 52,7
final AlterTable res = new AlterTable(t);
if (!t.contains(READ_ONLY_FIELD)) {
// writable by default
res.addColumn(READ_ONLY_FIELD, "varchar(16) default " + t.getBase().quoteString(READ_WRITE_VALUE) + " NOT NULL");
res.addVarCharColumn(READ_ONLY_FIELD, 16, false, res.getSyntax().quoteString(READ_WRITE_VALUE), false);
res.addForeignColumn(READ_ONLY_USER_FIELD, UserManager.getInstance().getTable());
}
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/SQLBrowser.java
392,8 → 392,8
}
 
/**
* The selected rows of the last column which restricts the browser selection. Eg if the last
* column has "ALL" selected, return the selection of the previous column.
* The selected rows of the last column which restricts the browser selection. E.g. if the last
* column has "ALL" selected, return the selection of the previous column (except if searched).
*
* @return the last meaningful selection.
*/
403,21 → 403,25
}
 
/**
* The selected rows of all columns with meaningful selection, ie not ALL (except if searched)
* and not empty.
* The selected rows of the last column with a {@link #getLastMeaningfullRows() meaningful
* selection} and after that all of their ancestors.
*
* @return the selected rows of all columns.
* @return the selected rows and their ancestors, the root of the hierarchy last.
*/
public List<Set<SQLRow>> getMeaningfullRows() {
final SQLBrowserColumn<?, ?> col = getLastMeaningfullCol();
if (col == null)
if (col == null) {
return Collections.<Set<SQLRow>> emptyList();
else {
} else {
final List<Set<SQLRow>> res = new ArrayList<Set<SQLRow>>();
res.add(new HashSet<SQLRow>(col.getUserSelectedRows()));
SQLBrowserColumn<?, ?> c = col.previousRowsColumn();
while (c != null) {
res.add(c.getModel().getHighlighted());
// Use getHighlighted() instead of getUserSelectedRows() as we want ancestors, e.g.
// we don't want ALL but only the parent of the selection.
// highlighted becomes empty if the user selects a highlighted row.
final Set<SQLRow> highlighted = c.getModel().getHighlighted();
res.add(!highlighted.isEmpty() ? highlighted : new HashSet<SQLRow>(c.getUserSelectedRows()));
c = c.previousRowsColumn();
}
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_en.properties
129,6 → 129,9
infoPanel.dirs=Folders
infoPanel.logs=Logs
infoPanel.docs=Documents
infoPanel.dataDir=Data
infoPanel.prefsDir=Preferences
infoPanel.cacheDir=Cache
 
infoPanel.softwareTitle=Software
infoPanel.systemTitle=System information
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_fr.properties
130,6 → 130,9
infoPanel.dirs=Dossiers
infoPanel.logs=Journaux
infoPanel.docs=Documents
infoPanel.dataDir=Données
infoPanel.prefsDir=Préférences
infoPanel.cacheDir=Cache
 
infoPanel.softwareTitle=Logiciel
infoPanel.systemTitle=Informations système
/trunk/OpenConcerto/src/org/openconcerto/sql/model/MySQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/PGSQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/MSSQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBRoot.java
150,7 → 150,7
@Override
public Object handle(SQLDataSource ds) throws SQLException {
// don't create foreign constraints now, so we can insert undefined with cycles
final List<List<String>> createTablesSQL = ChangeTable.cat(undefinedNonDefaultValues.keySet(), getName(), EnumSet.of(ConcatStep.ADD_FOREIGN));
final List<List<String>> createTablesSQL = ChangeTable.cat(undefinedNonDefaultValues.keySet(), getName(), EnumSet.of(ConcatStep.ADD_CONSTRAINT));
for (final String sql : createTablesSQL.get(0))
ds.execute(sql);
final Map<SQLCreateTableBase<?>, Number> newUndefIDs;
312,16 → 312,20
this.getBase().fetchTables(TablesMap.createFromTables(this.getSchema().getName(), tableNames));
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSystem sys) {
return this.getDefinitionSQL(sys, true);
public final SQLCreateRoot getDefinitionSQL() {
return this.getDefinitionSQL(getDBSystemRoot().getSyntax());
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSystem sys, final boolean withTables) {
final SQLCreateRoot res = new SQLCreateRoot(sys.getSyntax(), this.getName());
public final SQLCreateRoot getDefinitionSQL(final SQLSyntax s) {
return this.getDefinitionSQL(s, true);
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSyntax s, final boolean withTables) {
final SQLCreateRoot res = new SQLCreateRoot(s, this.getName());
if (withTables) {
// order by name to be able to do diffs
for (final SQLTable table : new TreeMap<String, SQLTable>(this.getTablesMap()).values()) {
res.addTable(table.getCreateTable(sys));
res.addTable(table.getCreateTable(s));
}
}
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSearchMode.java
13,35 → 13,66
package org.openconcerto.sql.model;
 
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
 
public abstract class SQLSearchMode {
 
static public final SQLSearchMode EQUALS = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " = " + r.getBase().quoteString(term);
public String generateSQL(final SQLSyntax s, final String term) {
return " = " + s.quoteString(term);
}
};
 
static public final SQLSearchMode CONTAINS = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString("%" + SQLSyntax.get(r).getLitteralLikePattern(term) + "%");
public List<SQLSearchMode> getHigherModes() {
return Collections.emptyList();
}
};
static public final SQLSearchMode STARTS_WITH = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString(SQLSyntax.get(r).getLitteralLikePattern(term) + "%");
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString(s.getLitteralLikePattern(term) + "%");
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return Collections.singletonList(EQUALS);
}
};
 
static public final SQLSearchMode ENDS_WITH = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString("%" + SQLSyntax.get(r).getLitteralLikePattern(term));
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString("%" + s.getLitteralLikePattern(term));
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return Collections.singletonList(EQUALS);
}
};
 
public abstract String generateSQL(final DBRoot r, final String term);
private static final List<SQLSearchMode> CONTAINS_HIGHER_MODES = Arrays.asList(EQUALS, STARTS_WITH);
static public final SQLSearchMode CONTAINS = new SQLSearchMode() {
 
@Override
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString("%" + s.getLitteralLikePattern(term) + "%");
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return CONTAINS_HIGHER_MODES;
}
};
 
public final String generateSQL(final DBRoot r, final String term) {
return this.generateSQL(SQLSyntax.get(r), term);
}
 
public abstract String generateSQL(final SQLSyntax s, final String term);
 
// from highest to lowest
public abstract List<SQLSearchMode> getHigherModes();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRequestLogModel.java
30,14 → 30,18
 
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex < 5)
if (columnIndex == 9) {
return Integer.class;
}
if (columnIndex < 5) {
return Long.class;
}
return String.class;
}
 
@Override
public int getColumnCount() {
return 9;
return 10;
}
 
@Override
61,6 → 65,8
return "Connection";
case 8:
return "Thread";
case 9:
return "Returned rows";
}
return "??";
}
96,6 → 102,8
if (l.isInSwing())
return "Swing";
return l.getThreadId();
case 9:
return l.getResultCount();
}
return "";
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFieldsSet.java
57,10 → 57,10
return new SQLFieldsSet(res, false);
}
 
static private final SetMapItf<SQLTable, SQLField> toSetMap(final Collection<SQLField> fields) {
static private final SetMapItf<SQLTable, SQLField> toSetMap(final Collection<? extends FieldRef> fields) {
final SetMapItf<SQLTable, SQLField> res = createMap();
for (final SQLField f : fields)
res.add(f.getTable(), f);
for (final FieldRef f : fields)
res.add(f.getField().getTable(), f.getField());
return res;
}
 
92,7 → 92,7
*
* @param fields un ensemble de SQLField, l'ensemble n'est pas modifié.
*/
public SQLFieldsSet(final Collection<SQLField> fields) {
public SQLFieldsSet(final Collection<? extends FieldRef> fields) {
this(toSetMap(fields), false);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLField.java
17,14 → 17,17
package org.openconcerto.sql.model;
 
import static org.openconcerto.sql.model.SQLBase.quoteIdentifier;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLTable.FieldGroup;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.SQLKey.Type;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.Value;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.math.BigDecimal;
43,11 → 46,11
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.jdom2.Element;
 
/**
* Un champ SQL. Pour obtenir une instance de cette classe il faut utiliser
* {@link SQLTable#getField(String)}. Un champ connait sa table, son nom, son type et sa valeur par
241,7 → 244,7
* @see SQLSyntax#getType(SQLField)
*/
public final String getTypeDecl() {
return this.getServer().getSQLSystem().getSyntax().getType(this);
return this.getDBSystemRoot().getSyntax().getType(this);
}
 
/**
368,7 → 371,7
}
 
public boolean isKey() {
return this.getTable().getKeys().contains(this);
return this.isPrimaryKey() || this.isForeignKey();
}
 
/**
381,6 → 384,27
return this.getTable().getPrimaryKeys().equals(Collections.singleton(this));
}
 
/**
* Is this the one and only field in a foreign key of its table.
*
* @return <code>true</code> if this is part of a foreign key that has no other fields.
* @see #getFieldGroup()
*/
public boolean isForeignKey() {
final FieldGroup fieldGroup = getFieldGroup();
return fieldGroup.getKeyType() == Type.FOREIGN_KEY && fieldGroup.getSingleField() != null;
}
 
/**
* To which group this field belong.
*
* @return the group of this field.
* @see SQLTable#getFieldGroups()
*/
public FieldGroup getFieldGroup() {
return this.getTable().getFieldGroups().get(this.getName());
}
 
public final SQLTable getForeignTable() {
return this.getDBSystemRoot().getGraph().getForeignTable(this);
}
427,7 → 451,7
if (this.xml == null) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("<field name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\" >");
sb.append(this.type.toXML());
sb.append(XMLCodecUtils.encodeSimple(this.metadata));
468,9 → 492,9
* @return a map containing properties that differs and their values.
*/
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
if (o == null)
return Collections.singletonMap(null, "other field is null");
final Map<Properties, String> res = new HashMap<Properties, String>();
if (o == null)
res.put(null, "other field is null");
if (!this.getName().equals(o.getName()))
res.put(Properties.NAME, "name unequal : " + quoteIdentifier(this.getName()) + " != " + quoteIdentifier(o.getName()));
if (!this.getType().equals(o.getType(), otherSystem))
483,8 → 507,8
}
 
private boolean defaultEquals(SQLField o) {
final SQLSyntax syntax = this.getServer().getSQLSystem().getSyntax();
final SQLSyntax oSyntax = o.getServer().getSQLSystem().getSyntax();
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
final SQLSyntax oSyntax = o.getDBSystemRoot().getSyntax();
// don't compare actual default for auto fields, e.g. on MySQL it's null on PG it
// nextval(seq)
if (syntax.isAuto(this) && oSyntax.isAuto(o))
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFilter.java
71,10 → 71,11
 
private final SQLElementDirectory dir;
private final GraFFF filterGraph;
// the filter, from descendant to ancestor
@GuardedBy("filteredIDs")
private final List<Set<SQLRow>> filteredIDs;
// CopyOnWriteArrayList is simpler but rmListener() cannot be implemented
@GuardedBy("listeners")
@GuardedBy("this")
private List<SQLFilterListener> listeners;
 
public SQLFilter(SQLElementDirectory dir, final GraFFF filterGraph) {
192,7 → 193,7
}
 
final List<SQLFilterListener> dispatchingListeners;
synchronized (this.listeners) {
synchronized (this) {
dispatchingListeners = this.listeners;
}
for (final SQLFilterListener l : dispatchingListeners) {
208,7 → 209,7
}
 
public void addListener(SQLFilterListener l) {
synchronized (this.listeners) {
synchronized (this) {
final List<SQLFilterListener> newListeners = new ArrayList<SQLFilterListener>(this.listeners.size() + 1);
newListeners.addAll(this.listeners);
newListeners.add(l);
232,7 → 233,7
}
 
private void rmListener_(SQLFilterListener lToRm) {
synchronized (this.listeners) {
synchronized (this) {
final int stop = this.listeners.size();
int indexToRm = -1;
for (int i = 0; i < stop && indexToRm < 0; i++) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Where.java
14,6 → 14,7
package org.openconcerto.sql.model;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
24,10 → 25,10
import java.util.Map;
import java.util.Map.Entry;
 
import org.apache.commons.collections.functors.InstanceofPredicate;
 
import net.jcip.annotations.Immutable;
 
import org.apache.commons.collections.functors.InstanceofPredicate;
 
/**
* Une clause WHERE dans une requete SQL. Une clause peut être facilement combinée avec d'autre,
* exemple : prenomPasVide.and(pasIndéfini).and(age_sup_3.or(assez_grand)).
102,6 → 103,10
return AndCombiner.combine(where1, where2);
}
 
static public Where or(final Where where1, final Where where2) {
return OrCombiner.combine(where1, where2);
}
 
static public Where isNull(final FieldRef ref) {
return new Where(ref, "is", (Object) null);
}
144,7 → 149,7
 
static private final String comparison(final FieldRef ref, final String op, final String y) {
if (op == NULL_IS_DATA_EQ || op == NULL_IS_DATA_NEQ) {
return ref.getField().getServer().getSQLSystem().getSyntax().getNullIsDataComparison(ref.getFieldRef(), op == NULL_IS_DATA_EQ, y);
return ref.getField().getDBSystemRoot().getSyntax().getNullIsDataComparison(ref.getFieldRef(), op == NULL_IS_DATA_EQ, y);
} else {
return ref.getFieldRef() + " " + op + " " + y;
}
261,6 → 266,8
 
// raw ctor, see static methods
private Where(final String clause, final Collection<? extends FieldRef> refs) {
if (StringUtils.isEmpty(clause, true))
throw new IllegalArgumentException("No clause");
this.fields = Collections.unmodifiableList(new ArrayList<FieldRef>(refs));
this.clause = clause;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ColumnListHandlerGeneric.java
New file
0,0 → 1,116
/*
* 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 java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
/**
* <code>ResultSetHandler</code> implementation that converts one <code>ResultSet</code> column into
* a {@link List}.
*/
@Immutable
public class ColumnListHandlerGeneric<T> implements ResultSetHandler {
 
@GuardedBy("cache")
static private final Map<Class<?>, ColumnListHandlerGeneric<?>> cache = new LinkedHashMap<Class<?>, ColumnListHandlerGeneric<?>>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Class<?>, ColumnListHandlerGeneric<?>> eldest) {
return this.size() > 20;
}
};
 
public static <T> ColumnListHandlerGeneric<T> create(Class<T> clz) {
synchronized (cache) {
@SuppressWarnings("unchecked")
ColumnListHandlerGeneric<T> res = (ColumnListHandlerGeneric<T>) cache.get(clz);
if (res == null) {
res = create(1, clz);
assert res != null;
cache.put(clz, res);
}
return res;
}
}
 
// syntactic sugar
public static <T> ColumnListHandlerGeneric<T> create(int columnIndex, Class<T> clz) {
return new ColumnListHandlerGeneric<T>(columnIndex, clz);
}
 
/**
* The column number to retrieve.
*/
private final int columnIndex;
 
/**
* The column name to retrieve. Either columnName or columnIndex will be used but never both.
*/
private final String columnName;
 
private final Class<T> clz;
 
public ColumnListHandlerGeneric(int columnIndex, Class<T> clz) {
this(columnIndex, null, clz);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnName The name of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandlerGeneric(String columnName, Class<T> clz) {
this(-1, columnName, clz);
}
 
protected ColumnListHandlerGeneric(int columnIndex, String columnName, Class<T> clz) {
final boolean noName = columnName == null;
final boolean noIndex = columnIndex <= 0;
if (noName && noIndex)
throw new IllegalArgumentException("Missing column information");
assert noName || noIndex : "A constructor passed more than one argument";
this.columnIndex = columnIndex;
this.columnName = columnName;
this.clz = clz;
}
 
/**
* Returns one <code>ResultSet</code> column as a <code>List</code>. The elements are added to
* the <code>List</code> via {@link SQLResultSet#getValue(ResultSet, Class, int)}.
*
* @return a <code>List</code>, never <code>null</code>.
* @throws SQLException if one object couldn't be retrieved from the result set.
*/
@Override
public final List<T> handle(ResultSet rs) throws SQLException {
final List<T> result = new ArrayList<T>();
while (rs.next()) {
if (this.columnName == null) {
result.add(SQLResultSet.getValue(rs, this.clz, this.columnIndex));
} else {
result.add(SQLResultSet.getValue(rs, this.clz, this.columnName));
}
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLServer.java
247,8 → 247,7
final Set<String> allCats;
final Connection conn = this.getDS().getNewConnection();
try {
@SuppressWarnings("unchecked")
final List<String> allCatsList = (List<String>) SQLDataSource.COLUMN_LIST_HANDLER.handle(conn.getMetaData().getCatalogs());
final List<String> allCatsList = ColumnListHandlerGeneric.create(String.class).handle(conn.getMetaData().getCatalogs());
allCats = new HashSet<String>(allCatsList);
} finally {
this.getDS().returnConnection(conn);
417,8 → 416,8
final DBSystemRoot sysRoot = this.getDBSystemRoot();
if (sysRoot != null && !sysRoot.createNode(this, baseName))
throw new IllegalStateException(baseName + " is filtered, you must add it to rootsToMap");
final SQLBase base = this.getSQLSystem().getSyntax()
.createBase(this, baseName, coalesce(systemRootInit, this.systemRootInit), login == null ? this.login : login, pass == null ? this.pass : pass, coalesce(dsInit, this.dsInit));
final SQLBase base = new SQLBase(this, baseName, coalesce(systemRootInit, this.systemRootInit), login == null ? this.login : login, pass == null ? this.pass : pass,
coalesce(dsInit, this.dsInit));
return this.putBase(baseName, base, readCache);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRow.java
18,6 → 18,7
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
186,6 → 187,25
return res;
}
 
static final SQLRow createFromSelect(final SQLTable t, final VirtualFields vfs, final int id, final LockStrength l) {
final SQLSelect sel = new SQLSelect(true).addAllSelect(t.getFields(vfs));
sel.setLockStrength(l);
sel.setWhere(new Where(t.getKey(), "=", id));
return new SQLRow(t, id, t.getDBSystemRoot().getDataSource().execute1(sel.asString()));
}
 
/**
* Create an empty existing row (without checking the DB).
*
* @param t the table.
* @param id the ID.
* @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #getFields()
* empty} row.
*/
static final SQLRow createEmpty(final SQLTable t, final int id) {
return new SQLRow(t, id, Collections.<String, Object> emptyMap());
}
 
private final int ID;
private final Number idNumber;
private Map<String, Object> values;
226,7 → 246,7
private SQLRow(SQLTable table, final Number id, Map<String, ?> values) {
this(table, id == null ? getID(values, table, false) : id);
// faire une copie, sinon backdoor pour changer les valeurs sans qu'on s'en aperçoive
this.setValues(new HashMap<String, Object>(values));
this.setValues(values == null ? null : new HashMap<String, Object>(values));
}
 
// return ID, must always be present but may be null if <code>nullAllowed</code>
428,18 → 448,6
return this.getForeignRow(fieldName);
}
 
@Override
public Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final SQLRow foreignRow = this.getForeignRow(fieldName, SQLRowMode.NO_CHECK);
return foreignRow == null ? null : foreignRow.getIDNumber();
}
 
@Override
public boolean isForeignEmpty(String fieldName) {
final SQLRow foreignRow = this.getForeignRow(fieldName, SQLRowMode.NO_CHECK);
return foreignRow == null || foreignRow.isUndefined();
}
 
/**
* Retourne la ligne sur laquelle pointe le champ passé. Elle peut être archivé ou indéfinie.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxH2.java
28,6 → 28,7
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
36,8 → 37,15
 
class SQLSyntaxH2 extends SQLSyntax {
 
static protected final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>(DateProp.JAVA_DATE_SPECS_PURE);
DATE_SPECS.put(DateProp.MICROSECOND, "SSS000");
}
 
SQLSyntaxH2() {
super(SQLSystem.H2);
super(SQLSystem.H2, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Integer.class, "integer", "int", "int4", "mediumint");
this.typeNames.addAll(Byte.class, "tinyint");
47,7 → 55,8
this.typeNames.addAll(Float.class, "real");
this.typeNames.addAll(Double.class, "double precision", "float", "float4", "float8");
this.typeNames.addAll(Timestamp.class, "timestamp", "smalldatetime", "datetime");
this.typeNames.addAll(java.util.Date.class, "date");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "blob", "tinyblob", "mediumblob", "longblob", "image",
// byte[]
"bytea", "raw", "varbinary", "longvarbinary", "binary");
167,7 → 176,7
@Override
public void _loadData(final File f, final SQLTable t) {
checkServerLocalhost(t);
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
final String quotedPath = quoteString(f.getAbsolutePath());
t.getDBSystemRoot().getDataSource().execute("insert into " + t.getSQLName().quote() + " select * from CSVREAD(" + quotedPath + ", NULL, 'UTF8', ',', '\"', '\\', '\\N') ;");
}
 
174,8 → 183,8
@Override
protected void _storeData(final SQLTable t, final File f) {
checkServerLocalhost(t);
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
final String quotedSel = t.getBase().quoteString(SQLSyntaxPG.selectAll(t).asString());
final String quotedPath = quoteString(f.getAbsolutePath());
final String quotedSel = quoteString(SQLSyntaxPG.selectAll(t).asString());
t.getBase().getDataSource().execute("CALL CSVWRITE(" + quotedPath + ", " + quotedSel + ", 'UTF8', ',', '\"', '\\', '\\N', '\n');");
}
 
204,10 → 213,25
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "DAY_OF_WEEK(" + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "FORMATDATETIME(" + sqlTS + ", " + SQLBase.quoteStringStd(basic ? TS_BASIC_JAVA_FORMAT : TS_EXTENDED_JAVA_FORMAT) + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? TS_BASIC_JAVA_FORMAT : TS_EXTENDED_JAVA_FORMAT));
}
 
@Override
public final String getFormatTimestamp(String sqlTS, String format) {
return "FORMATDATETIME(" + sqlTS + ", " + format + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return SQLBase.quoteStringStd(text);
}
 
// (SELECT "C1" as "num", "C2" as "name" FROM VALUES(1, 'Hello'), (2, 'World')) AS V;
@Override
public String getConstantTable(List<List<String>> rows, String alias, List<String> columnsAlias) {
235,28 → 259,28
// src can be null since H2 supports alias to Java static functions
// perhaps join on FUNCTION_COLUMNS to find out parameters' types
final String src = "coalesce(\"SOURCE\", \"JAVA_CLASS\" || '.' || \"JAVA_METHOD\" ||' parameter(s): ' || \"COLUMN_COUNT\")";
return "SELECT ALIAS_SCHEMA as \"schema\", ALIAS_NAME as \"name\", " + src + " as \"src\" FROM \"INFORMATION_SCHEMA\".FUNCTION_ALIASES where ALIAS_CATALOG=" + b.quoteString(b.getMDName())
+ " and ALIAS_SCHEMA in (" + quoteStrings(b, schemas) + ")";
return "SELECT ALIAS_SCHEMA as \"schema\", ALIAS_NAME as \"name\", " + src + " as \"src\" FROM \"INFORMATION_SCHEMA\".FUNCTION_ALIASES where ALIAS_CATALOG=" + this.quoteString(b.getMDName())
+ " and ALIAS_SCHEMA in (" + quoteStrings(schemas) + ")";
}
 
@Override
public String getTriggerQuery(SQLBase b, TablesMap tables) {
return "SELECT \"TRIGGER_NAME\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"JAVA_CLASS\" as \"ACTION\", \"SQL\" from INFORMATION_SCHEMA.TRIGGERS " + getTablesMapJoin(b, tables) + " where "
return "SELECT \"TRIGGER_NAME\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"JAVA_CLASS\" as \"ACTION\", \"SQL\" from INFORMATION_SCHEMA.TRIGGERS " + getTablesMapJoin(tables) + " where "
+ getInfoSchemaWhere(b);
}
 
private String getTablesMapJoin(final SQLBase b, final TablesMap tables) {
return getTablesMapJoin(b, tables, SQLBase.quoteIdentifier("TABLE_SCHEMA"), SQLBase.quoteIdentifier("TABLE_NAME"));
private String getTablesMapJoin(final TablesMap tables) {
return getTablesMapJoin(tables, SQLBase.quoteIdentifier("TABLE_SCHEMA"), SQLBase.quoteIdentifier("TABLE_NAME"));
}
 
private final String getInfoSchemaWhere(SQLBase b) {
return "\"TABLE_CATALOG\" = " + b.quoteString(b.getMDName());
return "\"TABLE_CATALOG\" = " + this.quoteString(b.getMDName());
}
 
@Override
public String getColumnsQuery(SQLBase b, TablesMap tables) {
return "SELECT \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , \"CHARACTER_SET_NAME\", \"COLLATION_NAME\", \"SEQUENCE_NAME\" from INFORMATION_SCHEMA.\"COLUMNS\" " + getTablesMapJoin(b, tables) + " where " + getInfoSchemaWhere(b);
+ "\" , \"CHARACTER_SET_NAME\", \"COLLATION_NAME\", \"SEQUENCE_NAME\" from INFORMATION_SCHEMA.\"COLUMNS\" " + getTablesMapJoin(tables) + " where " + getInfoSchemaWhere(b);
}
 
@Override
266,7 → 290,7
//
+ "case \"CONSTRAINT_TYPE\" when 'REFERENTIAL' then 'FOREIGN KEY' else \"CONSTRAINT_TYPE\" end as \"CONSTRAINT_TYPE\", \"COLUMN_LIST\", \"CHECK_EXPRESSION\" AS \"DEFINITION\"\n"
//
+ "FROM INFORMATION_SCHEMA.CONSTRAINTS " + getTablesMapJoin(b, tables)
+ "FROM INFORMATION_SCHEMA.CONSTRAINTS " + getTablesMapJoin(tables)
// where
+ " where " + getInfoSchemaWhere(b);
// don't cache since we don't listen on system tables
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLType.java
18,7 → 18,7
 
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
 
import java.math.BigDecimal;
import java.math.BigInteger;
30,16 → 30,14
import java.sql.Types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.jdom2.Element;
 
/**
* The type of a SQL field. Allow one to convert a Java object to its SQL serialization.
*
50,7 → 48,7
@ThreadSafe
public abstract class SQLType {
 
private static Class<?> getClass(int type, final int size) {
private static Class<?> getClass(int type, final int size, SQLSyntax s) {
switch (type) {
case Types.BIT:
// As of MySQL 5.0.3, BIT is for storing bit-field values
74,13 → 72,22
case Types.TIMESTAMP:
return Timestamp.class;
case Types.DATE:
return java.util.Date.class;
return java.sql.Date.class;
case Types.TIME:
return java.sql.Time.class;
case Types.INTEGER:
return Integer.class;
case Types.SMALLINT:
/*
* http://download.oracle.com/otndocs/jcp/jdbc-4_1-mrel-spec/index.html Appendix B : The
* JDBC 1.0 specification defined the Java object mapping for the SMALLINT and TINYINT
* JDBC types to be Integer. The Java language did not include the Byte and Short data
* types when the JDBC 1.0 specification was finalized. The mapping of SMALLINT and
* TINYINT to Integer is maintained to preserve backwards compatibility
*/
return s.getSystem() == SQLSystem.H2 ? Short.class : Integer.class;
case Types.TINYINT:
return Integer.class;
return s.getSystem() == SQLSystem.H2 ? Byte.class : Integer.class;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
109,21 → 116,21
 
// useful when no SQLBase is known
public static SQLType getBoolean(final SQLSyntax s) {
// TODO use get() once it accepts a SQLSyntax
final SQLType res = new BooleanType(Types.BOOLEAN, 1, null, Boolean.class);
res.setSyntax(s);
return res;
return getFromSyntax(s, Types.BOOLEAN, 1);
}
 
public static SQLType get(final int type, final int size) {
return get(null, type, size, null, null);
public static SQLType getFromSyntax(final SQLSyntax s, final int type, final int size) {
return get(s, type, size, null, s.getTypeNames(getClass(type, size, s)).iterator().next());
}
 
public static SQLType get(final SQLBase base, final int type, final int size) {
return getFromSyntax(SQLSyntax.get(base), type, size);
}
 
/**
* Get the corresponding type.
*
* @param base the base of this type, can be <code>null</code> but {@link #toString(Object)}
* will have to use standard SQL which might not be valid for all bases (eg escapes).
* @param base the base of this type, cannot be <code>null</code>.
* @param type a value from java.sql.Types.
* @param size the size as COLUMN_SIZE is defined in
* {@link java.sql.DatabaseMetaData#getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String)}
133,11 → 140,15
* @throws IllegalStateException if type is unknown.
*/
public static SQLType get(final SQLBase base, final int type, final int size, Integer decDigits, final String typeName) {
final List<String> typeID = Arrays.asList(base == null ? null : base.getURL(), type + "", size + "", String.valueOf(decDigits), typeName);
return get(SQLSyntax.get(base), type, size, decDigits, typeName);
}
 
public static SQLType get(final SQLSyntax s, final int type, final int size, Integer decDigits, final String typeName) {
final List<String> typeID = Arrays.asList(s.toString(), type + "", size + "", String.valueOf(decDigits), typeName);
synchronized (instances) {
SQLType res = instances.get(typeID);
if (res == null) {
final Class<?> clazz = getClass(type, size);
final Class<?> clazz = getClass(type, size, s);
if (Boolean.class.isAssignableFrom(clazz))
res = new BooleanType(type, size, typeName, clazz);
else if (Number.class.isAssignableFrom(clazz))
154,7 → 165,8
else
// BLOB & CLOB and the rest
res = new UnknownType(type, size, typeName, clazz);
res.setBase(base);
if (s != null)
res.setSyntax(s);
instances.put(typeID, res);
}
return res;
170,17 → 182,6
return get(base, type, size, decDigits, typeName);
}
 
static void remove(final SQLBase base) {
synchronized (instances) {
final Iterator<Entry<List<String>, SQLType>> iter = instances.entrySet().iterator();
while (iter.hasNext()) {
final Entry<List<String>, SQLType> e = iter.next();
if (e.getValue().getBase() == base)
iter.remove();
}
}
}
 
// *** instance
 
// a value from java.sql.Types
195,8 → 196,6
private final Class<?> javaType;
 
@GuardedBy("this")
private SQLBase base;
@GuardedBy("this")
private SQLSyntax syntax;
 
@GuardedBy("this")
232,6 → 231,11
return this.decimalDigits;
}
 
/**
* The type returned from the DB.
*
* @return the java class returned by {@link SQLResultSet#getObject(int)}.
*/
public Class<?> getJavaType() {
return this.javaType;
}
245,34 → 249,19
return this.typeName;
}
 
// TODO remove once quoteString() is in SQLSyntax
private synchronized final void setBase(SQLBase base) {
// set only once
assert this.base == null;
if (base != null) {
this.base = base;
this.setSyntax(this.base.getServer().getSQLSystem().getSyntax());
}
}
 
private synchronized final void setSyntax(SQLSyntax s) {
// set only once
assert this.syntax == null;
if (s != null) {
if (this.syntax != null)
throw new IllegalStateException("Already set to " + this.syntax);
this.syntax = s;
}
}
 
private synchronized final SQLBase getBase() {
return this.base;
}
 
public synchronized final SQLSyntax getSyntax() {
return this.syntax;
}
 
protected final String quoteString(String s) {
return SQLBase.quoteString(this.getBase(), s);
return SQLSyntax.quoteString(this.getSyntax(), s);
}
 
@Override
350,7 → 339,7
sb.append(this.decimalDigits);
}
sb.append("\" typeName=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.typeName));
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.typeName));
sb.append("\"/>");
this.xml = sb.toString();
}
406,10 → 395,11
}
 
/**
* Test whether o is valid for this type. Ie does o is an instance of getJavaType().
* Test whether the passed parameter is valid for this type.
*
* @param o the object to test.
* @return <code>true</code> if o can be passed to {@link #toString(Object)}.
* @return <code>true</code> if o can be passed to {@link #toString(Object)}, always
* <code>true</code> for an instance of {@link #getJavaType()}.
* @see #check(Object)
*/
public boolean isValid(Object o) {
503,7 → 493,7
@Override
public final String toStringRaw(Object o) {
// time has no special characters to escape
return "'" + toCSVRaw(o) + "'";
return getSyntax().cast("'" + toCSVRaw(o) + "'", this);
}
 
static protected long getTime(Object o) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesListFetcher.java
28,6 → 28,7
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.Transformer;
 
import java.sql.ResultSet;
import java.sql.SQLException;
788,18 → 789,25
}
 
public final SQLSelect getReq() {
return this.getReq(null);
return this.getReq(null, null);
}
 
// see fetch() comment as to why the Where parameter isn't public.
private synchronized final SQLSelect getReq(final Where w) {
static private final SQLSelect checkTr(final List<String> origSelect, final SQLSelect tr) {
if (!origSelect.equals(tr.getSelect()))
throw new IllegalArgumentException("Select clause cannot be modified");
return tr;
}
 
public synchronized final SQLSelect getReq(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf) {
checkTable(w);
final boolean isNopTransf = selTransf == null || selTransf == Transformer.<SQLSelect> nopTransformer();
if (this.isFrozen()) {
if (w == null) {
if (w == null && isNopTransf) {
return this.frozen;
} else {
final SQLSelect res = new SQLSelect(this.frozen);
res.andWhere(w);
return res;
final SQLSelect copy = new SQLSelect(this.frozen);
final SQLSelect res = isNopTransf ? copy : checkTr(copy.getSelect(), selTransf.transformChecked(copy));
return res.andWhere(w);
}
}
 
841,11 → 849,14
 
if (this.getSelID() != null)
sel.andWhere(getIDWhere(t, this.getSelID()));
final List<String> origSel = new ArrayList<String>(sel.getSelect());
SQLSelect res = sel;
for (final ITransformer<SQLSelect, SQLSelect> tr : this.getSelectTransformers()) {
res = tr.transformChecked(res);
}
return res.andWhere(w);
if (!isNopTransf)
res = selTransf.transformChecked(res);
return checkTr(origSel, res).andWhere(w);
}
 
static private Where getIDWhere(final SQLTable t, final Number id) {
1147,17 → 1158,20
* table.
*/
public final List<SQLRowValues> fetch(final Where w, final Boolean unmodifiableRows) throws IllegalArgumentException {
return this.fetch(true, w, unmodifiableRows);
return this.fetch(w, null, unmodifiableRows);
}
 
private final List<SQLRowValues> fetch(final boolean merge, final Where w, final Boolean unmodifiableRows) throws IllegalArgumentException {
checkTable(w);
public final List<SQLRowValues> fetch(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
return this.fetch(true, w, selTransf, unmodifiableRows);
}
 
private final List<SQLRowValues> fetch(final boolean merge, final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
final SQLSelect req;
final Map<Path, Map<Path, SQLRowValuesListFetcher>> grafts;
final boolean freezeRows;
// the only other internal state used is this.descendantPath which is final immutable
synchronized (this) {
req = this.getReq(w);
req = this.getReq(w, selTransf);
grafts = this.getGrafts();
freezeRows = unmodifiableRows == null ? this.areReturnedRowsUnmodifiable() : unmodifiableRows.booleanValue();
}
1164,9 → 1178,7
// getName() would take 5% of ResultSetHandler.handle()
final List<FieldRef> selectFields = req.getSelectFields();
final int selectFieldsSize = selectFields.size();
final List<String> selectFieldsNames = new ArrayList<String>(selectFieldsSize);
for (final FieldRef f : selectFields)
selectFieldsNames.add(f.getField().getName());
final List<String> selectFieldsNames = req.getSelectNames();
final SQLTable table = getGraph().getTable();
 
// create a flat list of the graph nodes, we just need the table, field count and the index
1198,8 → 1210,8
// otherwise walk() would already have thrown an exception
assert fieldIndex.get() <= selectFieldsSize;
if (fieldIndex.get() != selectFieldsSize) {
throw new IllegalStateException(
"Fields have been added to the select (which is useless, since only fields specified by rows are returned) : " + selectFields.subList(fieldIndex.get(), selectFieldsSize));
throw new IllegalStateException("Items have been added to the select (which is useless, since only fields specified by rows are returned and WHERE cannot access SELECT columns) : "
+ selectFields.subList(fieldIndex.get(), selectFieldsSize));
}
assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize + "\n" + this.getGraph().printGraph();
 
1245,7 → 1257,7
final SQLRowValuesListFetcher graft = e.getValue();
 
// don't merge then...
final List<SQLRowValues> referentVals = graft.fetch(false, new Where(graft.getGraph().getTable().getKey(), ids), false);
final List<SQLRowValues> referentVals = graft.fetch(false, new Where(graft.getGraph().getTable().getKey(), ids), null, false);
// ...but now
merge(merged, referentVals, byRows, descendantPath);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMS.java
27,7 → 27,6
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.BufferedReader;
47,6 → 46,7
import java.sql.Types;
import java.util.Arrays;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
54,8 → 54,23
 
class SQLSyntaxMS extends SQLSyntax {
 
static private final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "yyyy");
DATE_SPECS.put(DateProp.MONTH_NAME, "MMMM");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "dd");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "dddd");
DATE_SPECS.put(DateProp.HOUR, "HH");
DATE_SPECS.put(DateProp.MINUTE, "mm");
DATE_SPECS.put(DateProp.SECOND, "ss");
DATE_SPECS.put(DateProp.MICROSECOND, "ffffff");
}
 
SQLSyntaxMS() {
super(SQLSystem.MSSQL);
super(SQLSystem.MSSQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "bit");
// tinyint is unsigned
this.typeNames.addAll(Short.class, "smallint", "tinyint");
63,8 → 78,8
this.typeNames.addAll(Long.class, "bigint");
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric", "smallmoney", "money");
this.typeNames.addAll(Float.class, "real");
this.typeNames.addAll(Double.class, "float");
this.typeNames.addAll(Timestamp.class, "smalldatetime", "datetime");
this.typeNames.addAll(Double.class, "double precision", "float");
this.typeNames.addAll(Timestamp.class, "datetime2", "datetime", "smalldatetime");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "image",
75,6 → 90,22
}
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// only use escape form if needed (=> equals with other systems most of the time)
boolean simpleASCII = true;
final int l = s.length();
for (int i = 0; simpleASCII && i < l; i++) {
final char c = s.charAt(i);
simpleASCII = c <= 0xFF;
}
// see http://msdn.microsoft.com/fr-fr/library/ms191200(v=sql.105).aspx
return simpleASCII ? res : "N" + res;
}
 
@Override
public int getMaximumIdentifierLength() {
// https://msdn.microsoft.com/en-us/library/ms143432.aspx
return 128;
81,11 → 112,6
}
 
@Override
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new MSSQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
@Override
public String getInitSystemRoot() {
final String sql;
try {
108,16 → 134,6
}
 
@Override
public String getDateAndTimeType() {
return "datetime2";
}
 
@Override
public String getBooleanType() {
return "bit";
}
 
@Override
public int getMaximumVarCharLength() {
// http://msdn.microsoft.com/en-us/library/ms176089(v=sql.105).aspx
return 8000;
165,7 → 181,7
final String s = enable ? "with check check constraint all" : "nocheck constraint all";
return "exec sp_MSforeachtable @command1 = 'ALTER TABLE ? " + s + "' , @whereand = " +
//
b.getBase().quoteString("and schema_id = SCHEMA_ID( " + b.getBase().quoteString(b.getName()) + " )");
quoteString("and schema_id = SCHEMA_ID( " + quoteString(b.getName()) + " )");
}
 
@Override
193,7 → 209,7
//
" join [test].[sys].[columns] cols on t.object_id = cols.object_id and cols.column_id = indexCols.column_id \n" +
//
" where schema_name(t.schema_id) = " + t.getBase().quoteString(t.getSchema().getName()) + " and t.name = " + t.getBase().quoteString(t.getName()) + "\n"
" where schema_name(t.schema_id) = " + quoteString(t.getSchema().getName()) + " and t.name = " + quoteString(t.getName()) + "\n"
//
+ "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
// don't cache since we don't listen on system tables
493,7 → 509,7
//
+ " FROM " + new SQLName(b.getName(), "sys", "objects") + "\n"
// scalar, inline table-valued, table-valued
+ " where type IN ('FN', 'IF', 'TF') and SCHEMA_NAME( schema_id ) in (" + quoteStrings(b, schemas) + ") ";
+ " where type IN ('FN', 'IF', 'TF') and SCHEMA_NAME( schema_id ) in (" + quoteStrings(schemas) + ") ";
}
 
@Override
505,7 → 521,7
//
+ "join " + new SQLName(b.getName(), "sys", "objects") + " tabl on trig.parent_id = tabl.object_id\n"
// requested tables
+ getTablesMapJoin(b, tables, "SCHEMA_NAME( tabl.schema_id )", "tabl.name");
+ getTablesMapJoin(tables, "SCHEMA_NAME( tabl.schema_id )", "tabl.name");
}
 
@Override
518,20 → 534,18
return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
// requested tables
getTablesMapJoin(b, tables, "TABLE_SCHEMA", "TABLE_NAME");
getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
}
 
@Override
public List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException {
final String where = getTablesMapJoin(b, tables, "SCHEMA_NAME(t.schema_id)", "t.name");
final String where = getTablesMapJoin(tables, "SCHEMA_NAME(t.schema_id)", "t.name");
final String sel = "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", case k.type when 'UQ' then 'UNIQUE' when 'PK' then 'PRIMARY KEY' end as \"CONSTRAINT_TYPE\", col_name(c.object_id, c.column_id) AS \"COLUMN_NAME\", c.key_ordinal AS \"ORDINAL_POSITION\", null AS [DEFINITION]\n"
+ "FROM sys.key_constraints k\n"
//
+ "JOIN sys.index_columns c ON c.object_id = k.parent_object_id AND c.index_id = k.unique_index_id\n"
//
+ "JOIN sys.tables t ON t.object_id = k.parent_object_id\n"
+ where
+ "\nUNION ALL\n"
+ "JOIN sys.tables t ON t.object_id = k.parent_object_id\n" + where + "\nUNION ALL\n"
//
+ "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", 'CHECK' as \"CONSTRAINT_TYPE\", col.name AS \"COLUMN_NAME\", 1 AS \"ORDINAL_POSITION\", k.[definition] AS [DEFINITION]\n"
+ "FROM sys.check_constraints k\n"
540,8 → 554,7
//
+ "left join sys.columns col on k.parent_column_id = col.column_id and col.object_id = t.object_id\n"
//
+ where
+ "\nUNION ALL\n"
+ where + "\nUNION ALL\n"
//
+ "SELECT SCHEMA_NAME(t.schema_id) AS [TABLE_SCHEMA], t.name AS [TABLE_NAME], k.name AS [CONSTRAINT_NAME], 'DEFAULT' as [CONSTRAINT_TYPE], col.name AS [COLUMN_NAME], 1 AS [ORDINAL_POSITION], k.[definition] AS [DEFINITION]\n"
+ "FROM sys.[default_constraints] k\n"
576,8 → 589,18
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "SELECT DATEPART(dw, " + sqlTS + ")";
}
 
@Override
public String getMonth(String sqlTS) {
return "SELECT DATEPART(month, " + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
final String extended = "CONVERT(nvarchar(30), CAST(" + sqlTS + " as datetime), 126) + '000'";
final String extended = "CONVERT(nvarchar(30), " + sqlTS + ", 126) + '000'";
if (basic) {
return "replace( replace( " + extended + ", '-', ''), ':' , '' )";
} else {
584,4 → 607,14
return extended;
}
}
 
@Override
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "FORMAT(" + sqlTS + ", " + nativeFormat + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return StringUtils.doubleQuote(text);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntax.java
29,8 → 29,8
import org.openconcerto.utils.NetUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IdentityHashSet;
 
import java.io.File;
import java.io.FileFilter;
40,20 → 40,31
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import com.ibm.icu.text.DateTimePatternGenerator;
 
import net.jcip.annotations.GuardedBy;
 
/**
* A class that abstract the syntax of different SQL systems. Type is an SQL datatype like 'int' or
* 'varchar', definition is the type plus default and constraints like 'int default 1 not null
67,6 → 78,7
static public final String ORDER_NAME = "ORDRE";
static public final String ARCHIVE_NAME = "ARCHIVE";
static public final String ID_NAME = "ID";
@GuardedBy("this")
static private final Map<SQLSystem, SQLSyntax> instances = new HashMap<SQLSystem, SQLSyntax>();
static public final String DATA_EXT = ".txt";
 
75,6 → 87,167
 
static private final StringUtils.Escaper DEFAULT_LIKE_ESCAPER = new StringUtils.Escaper('\\', '\\');
 
static public final class DateProp {
static public final String YEAR = new String("year with four digits");
static public final String MONTH_NAME = new String("full name of the month");
static public final String MONTH_NUMBER = new String("2 digits number of the month (starting at 1)");
static public final String DAY_IN_MONTH = new String("2 digits day number in the month");
static public final String DAY_NAME_IN_WEEK = new String("full name of day");
static public final String HOUR = new String("hour in day (00-23)");
static public final String MINUTE = new String("minute in hour");
static public final String SECOND = new String("second in minute");
static public final String MICROSECOND = new String("microseconds (000000-999999)");
 
static public final Set<String> ALL_INSTANCES = new IdentityHashSet<String>(Arrays.asList(YEAR, MONTH_NAME, MONTH_NUMBER, DAY_IN_MONTH, DAY_NAME_IN_WEEK, HOUR, MINUTE, SECOND, MICROSECOND));
static public final Set<String> LOCALE_SENSITIVE_INSTANCES = new IdentityHashSet<String>(Arrays.asList(MONTH_NAME, DAY_NAME_IN_WEEK));
 
static public final List<String> TIME_SKELETON = Arrays.asList(HOUR, MINUTE, SECOND);
static public final List<String> SHORT_DATE_SKELETON = Arrays.asList(DAY_IN_MONTH, MONTH_NUMBER, YEAR);
static public final List<String> LONG_DATE_SKELETON = Arrays.asList(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR);
static public final List<String> SHORT_DATETIME_SKELETON = Arrays.asList(DAY_IN_MONTH, MONTH_NUMBER, YEAR, HOUR, MINUTE);
static public final List<String> LONG_DATETIME_SKELETON = Arrays.asList(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR, HOUR, MINUTE, SECOND);
 
// pure format (i.e. no literal string)
static protected final IdentityHashMap<String, String> JAVA_DATE_SPECS_PURE;
static private final SortedMap<String, String> REVERSE_JAVA_SPEC;
static private final Pattern REVERSE_SPEC_PATTERN;
 
static {
JAVA_DATE_SPECS_PURE = new IdentityHashMap<String, String>();
JAVA_DATE_SPECS_PURE.put(YEAR, "yyyy");
JAVA_DATE_SPECS_PURE.put(MONTH_NAME, "MMMM");
JAVA_DATE_SPECS_PURE.put(MONTH_NUMBER, "MM");
JAVA_DATE_SPECS_PURE.put(DAY_IN_MONTH, "dd");
JAVA_DATE_SPECS_PURE.put(DAY_NAME_IN_WEEK, "EEEE");
JAVA_DATE_SPECS_PURE.put(HOUR, "HH");
JAVA_DATE_SPECS_PURE.put(MINUTE, "mm");
JAVA_DATE_SPECS_PURE.put(SECOND, "ss");
 
// reverse, so longer strings come first (e.g. MMMM|MM to match the longer one)
final SortedMap<String, String> m = new TreeMap<String, String>(Collections.reverseOrder());
REVERSE_JAVA_SPEC = CollectionUtils.invertMap(m, JAVA_DATE_SPECS_PURE);
assert REVERSE_JAVA_SPEC.size() == JAVA_DATE_SPECS_PURE.size() : "Duplicate values";
assert !JAVA_DATE_SPECS_PURE.containsKey(null) : "Null spec";
assert !JAVA_DATE_SPECS_PURE.containsValue(null) : "Null value";
 
REVERSE_SPEC_PATTERN = Pattern.compile(CollectionUtils.join(REVERSE_JAVA_SPEC.keySet(), "|"));
}
 
/**
* Return the best pattern matching the input skeleton.
*
* @param simpleFormat the fields needed (the string literals are ignored), e.g. [YEAR,
* DAY_IN_MONTH, MONTH_NUMBER].
* @param l the locale needed.
* @return the best match, e.g. [DAY_IN_MONTH, "/", MONTH_NUMBER, "/", YEAR] for
* {@link Locale#FRANCE} , [MONTH_NUMBER, "/", DAY_IN_MONTH, "/", YEAR] for
* {@link Locale#US}.
*/
public static List<String> getBestPattern(final List<String> simpleFormat, final Locale l) {
final StringBuilder sb = new StringBuilder(128);
for (final String p : simpleFormat) {
if (JAVA_DATE_SPECS_PURE.containsKey(p))
sb.append(JAVA_DATE_SPECS_PURE.get(p));
else if (ALL_INSTANCES.contains(p))
throw new IllegalArgumentException("Unsupported spec : " + p);
else
// ignore
Log.get().log(Level.FINE, "Ignore {0}", p);
}
// needs same length so our pattern works
final String bestPattern = DateTimePatternGenerator.getInstance(l).getBestPattern(sb.toString(), DateTimePatternGenerator.MATCH_ALL_FIELDS_LENGTH);
return parseJavaPattern(bestPattern);
}
 
static List<String> parseJavaPattern(final String bestPattern) {
final Matcher matcher = REVERSE_SPEC_PATTERN.matcher(bestPattern);
final Matcher quotedMatcher = SQLBase.quotedPatrn.matcher(bestPattern);
final List<String> res = new ArrayList<String>();
int index = 0;
while (index < bestPattern.length()) {
final int quoteIndex = bestPattern.indexOf('\'', index);
final int endSansQuote = quoteIndex < 0 ? bestPattern.length() : quoteIndex;
 
// parse quote-free string
matcher.region(index, endSansQuote);
while (matcher.find()) {
if (index < matcher.start())
res.add(bestPattern.substring(index, matcher.start()));
res.add(REVERSE_JAVA_SPEC.get(matcher.group()));
index = matcher.end();
}
assert index <= endSansQuote : "region() failed";
if (index < endSansQuote)
res.add(bestPattern.substring(index, endSansQuote));
index = endSansQuote;
 
// parse quoted string
if (index < bestPattern.length()) {
quotedMatcher.region(index, bestPattern.length());
if (!quotedMatcher.find() || quotedMatcher.start() != quotedMatcher.regionStart())
throw new IllegalStateException("Quoted string error : " + bestPattern.substring(quoteIndex));
res.add(SQLBase.unquoteStringStd(quotedMatcher.group()));
index = quotedMatcher.end();
}
}
return res;
}
}
 
static public final class CaseBuilder {
// null for "case when"
private final String oneExpr;
private final List<String> expressions;
private String elseExpression;
 
CaseBuilder(final String oneExpr) {
this.oneExpr = oneExpr;
this.expressions = new ArrayList<String>();
this.elseExpression = null;
}
 
public final CaseBuilder addWhen(final String test, final String val) {
this.expressions.add(test);
this.expressions.add(val);
return this;
}
 
public CaseBuilder setElse(String elseExpression) {
this.elseExpression = elseExpression;
return this;
}
 
public final String build() {
if (this.expressions.size() == 0)
return null;
final StringBuilder sb = new StringBuilder(150);
this.build(sb);
return sb.toString();
}
 
public final void build(final StringBuilder sb) {
sb.append("CASE ");
if (this.oneExpr != null) {
sb.append(this.oneExpr);
sb.append(' ');
}
final int stop = this.expressions.size();
for (int i = 0; i < stop; i += 2) {
sb.append("WHEN ");
sb.append(this.expressions.get(i));
sb.append(" THEN ");
sb.append(this.expressions.get(i + 1));
sb.append(' ');
}
if (this.elseExpression != null) {
sb.append("ELSE ");
sb.append(this.elseExpression);
sb.append(' ');
}
sb.append("END");
}
}
 
static public enum ConstraintType {
CHECK, FOREIGN_KEY("FOREIGN KEY"), PRIMARY_KEY("PRIMARY KEY"), UNIQUE,
/**
105,38 → 278,69
}
 
static {
register(new SQLSyntaxPG());
register(new SQLSyntaxH2());
register(new SQLSyntaxMySQL());
register(new SQLSyntaxMS());
 
DEFAULT_LIKE_ESCAPER.add('_', '_');
DEFAULT_LIKE_ESCAPER.add('%', '%');
}
 
static private void register(SQLSyntax s) {
instances.put(s.getSystem(), s);
public final static SQLSyntax get(DBStructureItemDB sql) {
return sql.getDBSystemRoot().getSyntax();
}
 
public final static SQLSyntax get(DBStructureItem<?> sql) {
return get(sql.getServer().getSQLSystem());
public final static SQLSyntax get(SQLIdentifier sql) {
return sql.getDBSystemRoot().getSyntax();
}
 
public final static SQLSyntax get(SQLSystem system) {
final SQLSyntax res = instances.get(system);
/**
* Get the default syntax for the passed system. NOTE : when needing a syntax for system
* currently accessible, {@link DBSystemRoot#getSyntax()} should be used so that server options
* can be read. Otherwise constructors of subclasses should be used to specify options.
*
* @param system a SQL system.
* @return the default syntax.
*/
synchronized final static SQLSyntax get(SQLSystem system) {
SQLSyntax res = instances.get(system);
if (res == null) {
res = create(system, null);
if (res == null)
throw new IllegalArgumentException("unsupported system: " + system);
instances.put(system, res);
}
return res;
}
 
static SQLSyntax create(DBSystemRoot sysRoot) {
return create(sysRoot.getServer().getSQLSystem(), sysRoot);
}
 
private static SQLSyntax create(final SQLSystem sys, final DBSystemRoot sysRoot) {
final SQLSyntax res;
if (sys == SQLSystem.POSTGRESQL)
res = new SQLSyntaxPG();
else if (sys == SQLSystem.H2)
res = new SQLSyntaxH2();
else if (sys == SQLSystem.MYSQL)
res = SQLSyntaxMySQL.create(sysRoot);
else if (sys == SQLSystem.MSSQL)
res = new SQLSyntaxMS();
else
res = null;
assert res == null || res.getSystem() == sys;
return res;
}
 
private final SQLSystem sys;
// list to specify the preferred first
protected final ListMap<Class<?>, String> typeNames;
// need identity since we use plain strings
protected final IdentityHashMap<String, String> dateSpecifiers;
 
protected SQLSyntax(final SQLSystem sys) {
protected SQLSyntax(final SQLSystem sys, final IdentityHashMap<String, String> dateSpecifiers) {
this.sys = sys;
this.typeNames = new ListMap<Class<?>, String>();
if (!dateSpecifiers.keySet().equals(DateProp.ALL_INSTANCES))
throw new IllegalArgumentException("Not all instances : " + dateSpecifiers.keySet());
this.dateSpecifiers = dateSpecifiers;
}
 
/**
153,6 → 357,21
return this.sys;
}
 
/**
* Quote an SQL string.
*
* @param s an arbitrary string, e.g. "salut\ l'ami".
* @return the quoted form, e.g. "'salut\\ l''ami'".
* @see SQLBase#quoteStringStd(String)
*/
public String quoteString(String s) {
return SQLBase.quoteStringStd(s);
}
 
public final static String quoteString(SQLSyntax b, String s) {
return b == null ? SQLBase.quoteStringStd(s) : b.quoteString(s);
}
 
public abstract int getMaximumIdentifierLength();
 
public String getInitSystemRoot() {
354,7 → 573,7
 
public final String getFieldDecl(SQLField f) {
String res = "";
final SQLSyntax fs = SQLSyntax.get(f.getServer().getSQLSystem());
final SQLSyntax fs = SQLSyntax.get(f);
if (fs.isAuto(f))
res += this.getAuto();
else {
398,7 → 617,7
}
 
static final String getNormalizedDefault(SQLField f) {
final SQLSyntax fs = f.getServer().getSQLSystem().getSyntax();
final SQLSyntax fs = SQLSyntax.get(f);
final String stdDefault = fs.transfDefaultSQL2Common(f);
if (stdDefault == null) {
return null;
457,7 → 676,7
}
 
public final String getType(SQLField f) {
final SQLSyntax fs = f.getServer().getSQLSystem().getSyntax();
final SQLSyntax fs = SQLSyntax.get(f);
final SQLType t = f.getType();
 
final String sqlType;
491,23 → 710,10
}
} else if (t.getJavaType() == BigDecimal.class) {
sqlType = "DECIMAL(" + t.getSize() + "," + t.getDecimalDigits() + ")";
} else if (t.getJavaType() == Boolean.class) {
sqlType = getBooleanType();
} else if (Number.class.isAssignableFrom(t.getJavaType())) {
if (Double.class.isAssignableFrom(t.getJavaType())) {
// this is the standard name (the only one accepted by pg)
sqlType = "double precision";
} else if (Float.class.isAssignableFrom(t.getJavaType())) {
// MySQL needs REAL_AS_FLOAT mode (e.g. from ANSI)
sqlType = "real";
} else {
// always remove unsigned they're a pain to maintain across systems
// use standard SQL types
sqlType = typeName.replace("unsigned", "").replace("int2", "smallint").replace("integer", "int").replace("int4", "int").replace("int8", "bigint").trim();
// don't care for qualifiers (like unsigned) they're a pain to maintain across systems
sqlType = this.getTypeNames(t.getJavaType()).iterator().next();
}
} else {
sqlType = typeName;
}
return sqlType;
}
 
536,14 → 742,10
*
* @return the type that store both the date and time.
*/
public String getDateAndTimeType() {
return "timestamp";
public final String getDateAndTimeType() {
return this.getTypeNames(Timestamp.class).iterator().next();
}
 
public String getBooleanType() {
return "boolean";
}
 
/**
* The maximum number of characters in a column. Can be less than that if there are other
* columns.
595,10 → 797,26
*/
protected abstract Tuple2<Boolean, String> getCast();
 
/**
* How to write a cast.
*
* @param expr the expression to cast.
* @param type the keyword (some systems don't use regular type names).
* @return the CAST expression.
* @see #cast(String, Class)
*/
public String cast(final String expr, final String type) {
return "CAST( " + expr + " AS " + type + " )";
}
 
public String cast(final String expr, final Class<?> javaType) {
return this.cast(expr, this.getTypeNames(javaType).iterator().next());
}
 
public final String cast(final String expr, final SQLType type) {
return this.cast(expr, type.getJavaType());
}
 
// JDBC says: ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION
public List<Map<String, Object>> getIndexInfo(final SQLTable t) throws SQLException {
final List<?> indexesInfo = t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<?>, SQLException>() {
854,10 → 1072,6
throw new IllegalArgumentException("the server of " + t + " is not this computer: " + t.getServer());
}
 
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new SQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
/**
* The function to return the character with the given ASCII code.
*
933,8 → 1147,36
return x + (eq ? " IS NOT DISTINCT FROM " : " IS DISTINCT FROM ") + y;
}
 
public final CaseBuilder createCaseWhenBuilder() {
return new CaseBuilder(null);
}
 
public final CaseBuilder createCaseBuilder(final String oneExpr) {
if (oneExpr == null)
throw new IllegalArgumentException("Missing expression");
return new CaseBuilder(oneExpr);
}
 
/**
* Return the SQL expression to get the day of the week for the passed date.
*
* @param sqlTS an SQL expression of type time stamp.
* @return 1 for Sunday through 7 for Saturday.
*/
public abstract String getDayOfWeek(final String sqlTS);
 
/**
* Return the SQL expression to get the month of the passed date.
*
* @param sqlTS an SQL expression of type time stamp.
* @return 1 for January through 12 for December.
*/
public String getMonth(final String sqlTS) {
return "MONTH(" + sqlTS + ")";
}
 
public final String getFormatTimestamp(final Timestamp ts, final boolean basic) {
return this.getFormatTimestamp(SQLBase.quoteStringStd(ts.toString()), basic);
return this.getFormatTimestamp(SQLType.getFromSyntax(this, Types.TIMESTAMP, 0).toString(ts), basic);
}
 
/**
952,6 → 1194,120
*/
public abstract String getFormatTimestamp(final String sqlTS, final boolean basic);
 
/**
* Return the native format from the passed simple one. If names are required, the server locale
* must be set correctly or {@link #getFormatTimestampSimple(DBRoot, String, List, Locale)}
* should be used.
*
* @param simpleFormat a list of either {@link DateProp} or literal string to include.
* @param useServerLocale <code>true</code> to allow the server to format strings (e.g. month
* names).
* @return the native format (to use with {@link #getFormatTimestamp(String, String)}).
* @throws IllegalArgumentException if <code>useServerLocale</code> is <code>false</code> and
* <code>simpleFormat</code> contains some name format properties.
*/
public final String getTimestampFormat(final List<String> simpleFormat, final boolean useServerLocale) throws IllegalArgumentException {
final StringBuilder res = new StringBuilder();
final StringBuilder literal = new StringBuilder();
for (final String s : simpleFormat) {
if (!useServerLocale && DateProp.LOCALE_SENSITIVE_INSTANCES.contains(s))
throw new IllegalArgumentException("passed locale sensitive property : " + s);
final String spec = this.dateSpecifiers.get(s);
if (spec == null) {
// we can't append multiple literal : 'foo' then 'bar' makes 'foo''bar' i.e.
// "foo'bar" instead of the wanted "foobar"
literal.append(s);
} else {
if (literal.length() > 0) {
res.append(this.quoteForTimestampFormat(literal.toString()));
literal.setLength(0);
}
res.append(spec);
}
}
if (literal.length() > 0) {
res.append(this.quoteForTimestampFormat(literal.toString()));
}
return quoteString(res.toString());
}
 
public abstract String quoteForTimestampFormat(final String text);
 
/**
* Return the SQL expression that format the passed time stamp.
*
* @param sqlTS an SQL expression of type time stamp.
* @param nativeFormat a SQL varchar for a native format to this syntax, e.g. obtained from
* {@link #getTimestampFormat(DBRoot, List, boolean)} .
* @return the SQL needed to format the passed parameter, e.g. FORMATDATETIME(CURRENT_TIMESTAMP,
* 'yyyy').
*/
public abstract String getFormatTimestamp(final String sqlTS, final String nativeFormat);
 
public final String getFormatTimestampSimple(String sqlTS, List<String> format) {
return this.getFormatTimestampSimple(sqlTS, format, Locale.getDefault());
}
 
static private final int[] CALENDAR_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
 
/**
* Return the SQL expression that format the passed time stamp.
*
* @param sqlTS an SQL expression of type time stamp.
* @param simpleFormat a list of either {@link DateProp} or literal string to include, e.g. [
* "year is ", {@link DateProp#YEAR}].
* @param l the locale to use, <code>null</code> meaning use the server.
* @return the SQL needed to format the passed parameter.
*/
public final String getFormatTimestampSimple(String sqlTS, List<String> simpleFormat, final Locale l) {
final boolean useServerLocale = l == null;
if (!useServerLocale) {
final List<String> statements = new ArrayList<String>();
// minimize function calls
final List<String> nonLocalSensitive = new ArrayList<String>();
for (final String s : simpleFormat) {
if (!DateProp.LOCALE_SENSITIVE_INSTANCES.contains(s)) {
nonLocalSensitive.add(s);
} else {
if (!nonLocalSensitive.isEmpty()) {
statements.add(this.getFormatTimestamp(sqlTS, this.getTimestampFormat(nonLocalSensitive, useServerLocale)));
nonLocalSensitive.clear();
}
final StringBuilder sb = new StringBuilder(512);
final DateFormatSymbols symbols = DateFormatSymbols.getInstance(l);
if (s == DateProp.DAY_NAME_IN_WEEK) {
final CaseBuilder caseBuilder = createCaseBuilder(this.getDayOfWeek(sqlTS));
final String[] weekdays = symbols.getWeekdays();
for (int j = 0; j < CALENDAR_DAYS.length; j++) {
// SQL function begins at 1
caseBuilder.addWhen(String.valueOf(j + 1), quoteString(weekdays[CALENDAR_DAYS[j]]));
}
caseBuilder.setElse(quoteString("unknown week day name"));
caseBuilder.build(sb);
} else if (s == DateProp.MONTH_NAME) {
final CaseBuilder caseBuilder = createCaseBuilder(this.getMonth(sqlTS));
int i = 1;
for (final String m : symbols.getMonths()) {
caseBuilder.addWhen(String.valueOf(i), quoteString(m));
i++;
}
caseBuilder.setElse(quoteString("unknown month name"));
caseBuilder.build(sb);
} else {
throw new IllegalStateException("Unknown prop : " + s);
}
statements.add(sb.toString());
}
}
if (!nonLocalSensitive.isEmpty()) {
statements.add(this.getFormatTimestamp(sqlTS, this.getTimestampFormat(nonLocalSensitive, useServerLocale)));
}
return CollectionUtils.join(statements, this.getConcatOp());
} else {
return this.getFormatTimestamp(sqlTS, this.getTimestampFormat(simpleFormat, useServerLocale));
}
}
 
public final String getInsertOne(final SQLName tableName, final List<String> fields, String... values) {
return this.getInsertOne(tableName, fields, Arrays.asList(values));
}
999,7 → 1355,25
return sb.toString();
}
 
public final String getConstantTableStatement(final List<List<String>> rows) {
return this.getConstantTableStatement(rows, -1);
}
 
/**
* Return a complete statement to return the passed list of rows.
*
* @param rows the rows with the SQL expression for each cell.
* @param colCount the number of columns the rows must have, -1 meaning infer it from
* <code>rows</code>.
* @return the complete SQL expression that can be executed as is.
* @see #getValues(List, int)
* @see #getConstantTable(List, String, List)
*/
public String getConstantTableStatement(final List<List<String>> rows, int colCount) {
return this.getValues(rows, colCount);
}
 
/**
* Get a constant table usable as a join.
*
* @param rows the SQL values for the table, e.g. [["1", "'one'"], ["2", "'two'"]].
1027,15 → 1401,15
return sb.toString();
}
 
protected final String getTablesMapJoin(final SQLBase b, final TablesMap tables, final String schemaExpr, final String tableExpr) {
protected final String getTablesMapJoin(final TablesMap tables, final String schemaExpr, final String tableExpr) {
final List<List<String>> rows = new ArrayList<List<String>>();
for (final Entry<String, Set<String>> e : tables.entrySet()) {
final String schemaName = b.quoteString(e.getKey());
final String schemaName = this.quoteString(e.getKey());
if (e.getValue() == null) {
rows.add(Arrays.asList(schemaName, "NULL"));
} else {
for (final String tableName : e.getValue())
rows.add(Arrays.asList(schemaName, b.quoteString(tableName)));
rows.add(Arrays.asList(schemaName, this.quoteString(tableName)));
}
}
final String tableAlias = "tables";
1079,11 → 1453,11
*/
public abstract List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException;
 
protected static final String quoteStrings(final SQLBase b, Collection<String> c) {
protected final String quoteStrings(Collection<String> c) {
return CollectionUtils.join(c, ", ", new ITransformer<String, String>() {
@Override
public String transformChecked(String s) {
return b.quoteString(s);
return quoteString(s);
}
});
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValues.java
17,7 → 17,6
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
import org.openconcerto.sql.model.SQLTable.FieldGroup;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
34,7 → 33,6
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Tuple2;
44,6 → 42,8
import org.openconcerto.utils.cc.LinkedIdentitySet;
import org.openconcerto.utils.cc.TransformedMap;
import org.openconcerto.utils.convertor.NumberConvertor;
import org.openconcerto.utils.convertor.ValueConvertor;
import org.openconcerto.utils.convertor.ValueConvertorFactory;
 
import java.math.BigDecimal;
import java.sql.Connection;
56,7 → 56,6
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
70,6 → 69,8
import java.util.Map.Entry;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
 
/**
* A class that represent a row of a table that can be modified before being inserted or updated.
* The row might not actually exists in the database, and it might not define all the fields. One
149,21 → 150,77
}
};
 
private static boolean checkValidity = true;
static public enum ValidityCheck {
/**
* The check is never performed.
*/
FORBIDDEN {
@Override
public boolean shouldCheck(final Boolean asked) {
return false;
}
},
/**
* The check is only performed if requested.
*/
FALSE_BY_DEFAULT {
@Override
public boolean shouldCheck(Boolean asked) {
return asked == null ? false : asked.booleanValue();
}
},
/**
* The check is performed unless specified.
*/
TRUE_BY_DEFAULT {
@Override
public boolean shouldCheck(Boolean asked) {
return asked == null ? true : asked.booleanValue();
}
},
/**
* The check is always performed. This is not generally recommended as some methods of the
* framework will fail.
*/
FORCED {
@Override
public boolean shouldCheck(Boolean asked) {
return true;
}
};
 
public static void setValidityChecked(final boolean b) {
checkValidity = b;
public abstract boolean shouldCheck(final Boolean asked);
}
 
@GuardedBy("this")
private static ValidityCheck checkValidity;
 
/**
* Whether or not {@link #getInvalid()} is called before each data modification.
* Set how {@link #getInvalid()} should be called before each data modification. Initially set
* to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : that the check also makes sure that
* referenced rows are not archived, so if it isn't performed a row could point to an archived
* row.
*
* @param vc the new mode, <code>null</code> to set the default.
*/
public synchronized static void setValidityChecked(final ValidityCheck vc) {
checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
}
 
/**
* Whether or not {@link #getInvalid()} should be called.
*
* @param asked what the caller requested.
* @return <code>true</code> if the validity is checked.
*/
public static boolean isValidityChecked() {
return checkValidity;
public synchronized static boolean isValidityChecked(final Boolean asked) {
return checkValidity.shouldCheck(asked);
}
 
static {
setValidityChecked(null);
}
 
private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
 
// i.e. no re-hash for up to 6 entries (8*0.8=6.4)
688,45 → 745,6
}
}
 
/**
* Returns the foreign table of <i>fieldName</i>.
*
* @param fieldName the name of a foreign field, e.g. "ID_ARTICLE_2".
* @return the table the field points to (never <code>null</code>), e.g. |ARTICLE|.
* @throws IllegalArgumentException if <i>fieldName</i> is not a foreign field.
*/
private final SQLTable getForeignTable(String fieldName) throws IllegalArgumentException {
return this.getForeignLink(Collections.singletonList(fieldName)).getTarget();
}
 
private final Link getForeignLink(final List<String> fieldsNames) throws IllegalArgumentException {
final DatabaseGraph graph = this.getTable().getDBSystemRoot().getGraph();
final Link foreignLink = graph.getForeignLink(this.getTable(), fieldsNames);
if (foreignLink == null)
throw new IllegalArgumentException(fieldsNames + " are not a foreign key of " + this.getTable());
return foreignLink;
}
 
@Override
public boolean isForeignEmpty(String fieldName) {
// don't use getForeign() to avoid creating a SQLRow
// keep getForeignTable at the 1st line since it does the check
final SQLTable foreignTable = this.getForeignTable(fieldName);
final Object val = this.getContainedObject(fieldName);
if (val instanceof SQLRowValues) {
return ((SQLRowValues) val).isUndefined();
} else {
final Number undefID = foreignTable.getUndefinedIDNumber();
return NumberUtils.areNumericallyEqual((Number) val, undefID);
}
}
 
@Override
public Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final SQLRowAccessor foreign = getForeign(fieldName);
return foreign == null ? null : foreign.getIDNumber();
}
 
public boolean isDefault(String fieldName) {
return SQL_DEFAULT.equals(this.getObject(fieldName));
}
810,7 → 828,9
final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
for (final String fieldName : toRm) {
if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
this._put(fieldName, null);
// name is OK since it is a foreign key
// value null is also OK
this._put(fieldName, null, false, ValueOperation.CHECK);
}
if (fields == null && !protectGraph) {
assert !retain && toRm.equals(this.values.keySet());
931,11 → 951,12
return this.put(fieldName, value, true);
}
 
// table.contains() can take up to 35% of this method
SQLRowValues put(String fieldName, Object value, final boolean checkName) {
if (checkName && !this.getTable().contains(fieldName))
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
_put(fieldName, value);
SQLRowValues put(String fieldName, Object value, final boolean check) {
return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
}
 
SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
_put(fieldName, value, checkName, checkValue);
// if there's no graph, there can't be any listeners
final SQLRowValuesCluster graph = this.getGraph(false);
if (graph != null)
943,42 → 964,47
return this;
}
 
private void _put(String fieldName, Object value) {
if (value == SQL_EMPTY_LINK)
// keep getForeignTable since it does the check
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
// use assertion since check() is not perfect
assert check(fieldName, value);
checkFrozen();
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
static public enum ValueOperation {
CONVERT, CHECK, PASS
}
 
private boolean check(String fieldName, Object value) {
if (value == null || value == SQL_DEFAULT)
return true;
final SQLField field = getTable().getField(fieldName);
final Class<?> javaType = field.getType().getJavaType();
// createStatement() does some conversion so don't be too strict
if (value instanceof Number) {
checkGroup(Number.class, value, field, javaType);
} else if (value instanceof Date) {
checkGroup(Date.class, value, field, javaType);
} else if (value instanceof SQLRowValues) {
if (!getTable().getForeignKeys().contains(field))
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
} else if (!javaType.isInstance(value))
throw new IllegalArgumentException("Wrong type for " + field + ", expected " + javaType + " but got " + value.getClass());
return true;
// TODO support java.time.LocalDateTime in Java 8
static private <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
if (conv == null)
throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
assert source == value.getClass();
@SuppressWarnings("unchecked")
final T tVal = (T) value;
return conv.convert(tVal);
}
 
private void checkGroup(final Class<?> superClass, final Object value, final SQLField field, final Class<?> javaType) {
if (superClass.isInstance(value)) {
if (!superClass.isAssignableFrom(javaType))
throw new IllegalArgumentException("Incompatible type for " + field + ", expected " + javaType + " but got " + value.getClass() + "(" + superClass + ")");
private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
// table.contains() can take up to 35% of this method
if (checkName && !this.getTable().contains(fieldName))
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
if (value == SQL_EMPTY_LINK) {
// keep getForeignTable since it does the check
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
} else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
final SQLField field = this.getTable().getField(fieldName);
if (value instanceof SQLRowValues) {
if (!field.isForeignKey())
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
} else {
throw new IllegalStateException("value is not an instance of the superclass for " + field);
final Class<?> javaType = field.getType().getJavaType();
if (!javaType.isInstance(value)) {
if (checkValue == ValueOperation.CONVERT) {
value = convert(value.getClass(), value, javaType);
} else {
throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
}
}
}
}
checkFrozen();
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
}
 
public SQLRowValues put(String fieldName, int value) {
return this.put(fieldName, Integer.valueOf(value));
1252,7 → 1278,8
toLoad.keySet().removeAll(this.getFields());
}
for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
this._put(e.getKey(), e.getValue());
// names are checked at the start
this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
}
// if there's no graph, there can't be any listeners
final SQLRowValuesCluster graph = this.getGraph(false);
1737,9 → 1764,8
// *** modify
 
void checkValidity() {
if (!checkValidity)
return;
// this checks archived which the DB doesn't with just foreign constraints
// it also locks foreign rows so that they don't *become* archived
final Object[] pb = this.getInvalid();
if (pb != null)
throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
1756,16 → 1782,15
* </ol>
*/
public Object[] getInvalid() {
final Map<SQLField, Link> foreignLinks = new HashMap<SQLField, Link>();
final Map<String, Link> foreignLinks = new HashMap<String, Link>();
for (final Link foreignLink : this.getTable().getForeignLinks()) {
for (final SQLField f : foreignLink.getFields()) {
for (final String f : foreignLink.getCols()) {
foreignLinks.put(f, foreignLink);
}
}
 
for (final String fieldName : this.values.keySet()) {
final SQLField field = this.getTable().getField(fieldName);
final Link foreignLink = foreignLinks.remove(field);
final Link foreignLink = foreignLinks.remove(fieldName);
if (foreignLink != null) {
final SQLTable foreignTable = foreignLink.getTarget();
if (foreignTable.isRowable()) {
1805,7 → 1830,7
*/
public SQLRow insert() throws SQLException {
// remove unwanted fields, keep ARCHIVE
return this.insert(false, false);
return this.store(SQLRowValuesCluster.StoreMode.INSERT);
}
 
/**
1817,14 → 1842,18
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
*/
public SQLRow insertVerbatim() throws SQLException {
return this.insert(true, true);
return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
}
 
public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
return this.getGraph().store(new SQLRowValuesCluster.Insert(insertPK, insertOrder)).getStoredRow(this);
return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
}
 
SQLTableEvent insertJustThis(final Set<SQLField> autoFields) throws SQLException {
public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
return this.getGraph().store(mode).getStoredRow(this);
}
 
SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
 
try {
1846,7 → 1875,7
assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
if (this.getTable().isRowable()) {
// pour pouvoir avoir les valeurs des champs non précisés
return new SQLTableEvent(getChangedRow(fieldsAndID.get1().intValue()), Mode.ROW_ADDED, fieldsAndID.get0());
return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
} else
return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
} catch (SQLException e) {
1854,12 → 1883,19
}
}
 
private SQLRow getChangedRow(final int newID) {
private SQLRow getEventRow(final int newID, final boolean fetch) {
final SQLRow res;
if (fetch) {
// don't read the cache since no event has been fired yet
// don't write to it since the transaction isn't committed yet, so other threads
// should not see the new values.
return new SQLRow(getTable(), newID).fetchValues(false);
res = new SQLRow(getTable(), newID).fetchValues(false);
} else {
res = SQLRow.createEmpty(getTable(), newID);
}
assert res.isFilled();
return res;
}
 
// * update
 
1878,11 → 1914,12
/**
* Permet de mettre à jour une ligne existante avec les valeurs courantes.
*
* @param fetchStoredRow <code>true</code> to fetch the just stored row.
* @param id l'id à mettre à jour.
* @return the updated row.
* @throws SQLException si pb lors de la maj.
*/
SQLTableEvent updateJustThis(final int id) throws SQLException {
SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
if (id == this.getTable().getUndefinedID()) {
throw new IllegalArgumentException("can't update undefined with " + this);
}
1901,17 → 1938,19
final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
final long timeMs = System.currentTimeMillis();
final long time = System.nanoTime();
pStmt.get0().executeUpdate();
final int updateCount = 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();
if (updateCount > 1)
throw new IllegalStateException(updateCount + " rows updated with ID " + id);
return updateCount == 0 ? null : pStmt.get1();
}
});
}
 
return new SQLTableEvent(getChangedRow(id), Mode.ROW_UPDATED, updatedCols);
return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
}
 
// * commit
1924,14 → 1963,14
* @throws SQLException
*/
public SQLRow commit() throws SQLException {
return this.getGraph().store(SQLRowValuesCluster.StoreMode.COMMIT).getStoredRow(this);
return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
}
 
SQLTableEvent commitJustThis() throws SQLException {
SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
if (!hasID()) {
return this.insertJustThis(Collections.<SQLField> emptySet());
return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
} else
return this.updateJustThis(this.getID());
return this.updateJustThis(fetchStoredRow, this.getID());
}
 
/**
2237,10 → 2276,6
} else
toIns = value;
// sql index start at 1
if (toIns instanceof Date) {
// to convert from java.util to java.sql, needed for pg and MS
pStmt.setObject(i + 1, new Timestamp(((Date) toIns).getTime()));
} else
pStmt.setObject(i + 1, toIns);
i++;
}
2349,8 → 2384,8
* @throws SQLException if an error occurs while inserting.
*/
@SuppressWarnings("unchecked")
public static final Insertion<Object[]> insert(final SQLTable t, final String sql) throws SQLException {
return (Insertion<Object[]>) insert(t, sql, ReturnMode.ALL_FIELDS);
public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBFileCache.java
15,6 → 15,7
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.StringUtils;
50,18 → 51,18
private static final String FILE_STRUCT_VERSION = "20080904-1411";
 
static private final File getFwkSaveDir() {
final File confDir;
final BaseDirs confDir;
// require at least an application name, since settings might influence what is to be saved
// MAYBE pass the server and allow it to have an ID
// (this would handle the case when one app needs two different connections to a server)
if (Configuration.getInstance() != null) {
confDir = Configuration.getInstance().getConfDir();
confDir = Configuration.getInstance().getBaseDirs();
} else if (ProductInfo.getInstance() != null) {
confDir = new File(Configuration.getDefaultConfDir(), ProductInfo.getInstance().getName());
confDir = BaseDirs.create(ProductInfo.getInstance());
} else {
return null;
}
return new File(confDir, "DBCache/" + FILE_STRUCT_VERSION);
return new File(confDir.getCacheFolder(), "DBCache/" + FILE_STRUCT_VERSION);
}
 
static private final File getValidFwkSaveDir() {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFunctionField.java
New file
0,0 → 1,76
/*
* 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;
 
/**
* A field and a function in an SQL statement, such as 'sum(obs.CONSTAT)'.
*
* @author vincent
*/
public class SQLFunctionField implements FieldRef {
 
public static class SQLFunction {
public static final SQLFunction LOWER = new SQLFunction("LOWER");
public static final SQLFunction UPPER = new SQLFunction("UPPER");
public static final SQLFunction COUNT = new SQLFunction("COUNT");
public static final SQLFunction SUM = new SQLFunction("SUM");
 
private final String sqlString;
 
public SQLFunction(final String sqlString) {
this.sqlString = sqlString;
}
 
public String getSQL() {
return this.sqlString;
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " " + this.getSQL();
}
}
 
private final SQLFunction function;
private final FieldRef field;
 
public SQLFunctionField(final SQLFunction function, final FieldRef field) {
this.function = function;
this.field = field;
}
 
public SQLFunction getFunction() {
return this.function;
}
 
@Override
public SQLField getField() {
return this.field.getField();
}
 
@Override
public String getAlias() {
return this.field.getAlias();
}
 
@Override
public String getFieldRef() {
return this.getFunction().getSQL() + "(" + this.field.getFieldRef() + ")";
}
 
@Override
public TableRef getTableRef() {
return this.field.getTableRef();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLDataSource.java
30,6 → 30,8
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLNonTransientException;
import java.sql.SQLTransientException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.Arrays;
46,7 → 48,6
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
 
import org.apache.commons.dbcp.AbandonedConfig;
69,6 → 70,7
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.h2.constant.ErrorCode;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
 
94,11 → 96,6
DRIVERS.put(SQLSystem.DERBY, "org.apache.derby.jdbc.ClientDriver");
DRIVERS.put(SQLSystem.H2, "org.h2.Driver");
DRIVERS.put(SQLSystem.MSSQL, "com.microsoft.sqlserver.jdbc.SQLServerDriver");
 
// by default h2 convert database name to upper case (we used to work around it with
// SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by equals())
// see http://code.google.com/p/h2database/issues/detail?id=204
System.setProperty("h2.databaseToUpper", "false");
}
 
// timeouts in seconds
151,6 → 148,7
// all thread safe
public static final ColumnListHandler COLUMN_LIST_HANDLER = new ColumnListHandler();
public static final ArrayListHandler ARRAY_LIST_HANDLER = new ArrayListHandler();
public static final ListListHandlerGeneric<Object> LIST_LIST_HANDLER = ListListHandlerGeneric.create(Object.class, null);
public static final ArrayHandler ARRAY_HANDLER = new ArrayHandler();
public static final ScalarHandler SCALAR_HANDLER = new ScalarHandler();
public static final MapListHandler MAP_LIST_HANDLER = new MapListHandler(ROW_PROC);
204,7 → 202,7
@GuardedBy("this")
private boolean checkOnceDBTxIsolation;
 
private final ReentrantLock testLock = new ReentrantLock();
private final Object testLock = new String("testLock");
 
public SQLDataSource(DBSystemRoot sysRoot, String base, String login, String pass) {
this(sysRoot, sysRoot.getServer().getURL(base), login, pass, Collections.<SQLTable> emptySet());
250,7 → 248,7
this.setLoginTimeout(loginTimeOut);
this.setSocketTimeout(socketTimeOut);
this.setTCPKeepAlive(true);
this.setRetryWait(3000);
this.setRetryWait(7000);
// ATTN DO NOT call execute() or any method that might create a connection
// since at this point dsInit() has not been called and thus connection properties might be
// missing (eg allowMultiQueries). And the faulty connection will stay in the pool.
271,7 → 269,9
if (this.getSystem() == SQLSystem.MYSQL) {
this.addConnectionProperty("socketTimeout", timeout + "000");
} else if (this.getSystem() == SQLSystem.H2) {
this.addConnectionProperty("QUERY_TIMEOUT", timeout + "000");
// org.h2.util.NetUtils.createSocket() doesn't use setSoTimeout(), so this is the next
// best thing. But it isn't checked everywhere, see DbSettings.maxQueryTimeout
this.addConnectionProperty("MAX_QUERY_TIMEOUT", timeout + "000");
} else if (this.getSystem() == SQLSystem.POSTGRESQL) {
this.addConnectionProperty("socketTimeout", timeout + "");
} else {
310,6 → 310,10
updateCache();
}
 
private synchronized Set<SQLTable> getTables() {
return this.tables;
}
 
private synchronized void updateCache() {
if (this.cache != null)
this.cache.getSupp().die();
492,8 → 496,8
* @see MapHandler
* @see #execute(String)
*/
public Map execute1(String query) {
return (Map) this.execute(query, MAP_HANDLER);
public Map<String, Object> execute1(String query) {
return (Map<String, Object>) this.execute(query, MAP_HANDLER);
}
 
/**
582,7 → 586,7
final List<Object> key = cache == null ? null : getCacheKey(query, rsh);
final CacheResult<Object> cacheRes;
if (key != null) {
final Set<? extends SQLData> data = irsh == null || irsh.getCacheModifiers() == null ? this.tables : irsh.getCacheModifiers();
final Set<? extends SQLData> data = irsh == null || irsh.getCacheModifiers() == null ? this.getTables() : irsh.getCacheModifiers();
cacheRes = cache.check(key, readCache, canWriteCache, data);
if (cacheRes.getState() == CacheResult.State.INTERRUPTED)
throw new RTInterruptedException("interrupted while waiting for the cache");
601,6 → 605,7
QueryInfo info = null;
final long afterCache = System.nanoTime();
final long afterQueryInfo, afterExecute, afterHandle;
int count = 0;
try {
info = new QueryInfo(query, changeState, passedConn);
try {
622,9 → 627,10
if (this.getSystem() == SQLSystem.DERBY || this.getSystem() == SQLSystem.POSTGRESQL) {
rs = new SQLResultSet(rs);
}
 
result = rsh.handle(rs);
count = SQLResultSet.getRowProcessedCount(rs);
}
 
afterHandle = System.nanoTime();
 
stmt.close();
649,7 → 655,7
throw e;
}
 
SQLRequestLog.log(query, "", info.getConnection(), timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime());
SQLRequestLog.log(query, "", info.getConnection(), timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime(), count);
 
return result;
}
789,11 → 795,15
return "cancel of " + System.identityHashCode(getConnection()) + " failed for " + getQuery();
}
 
final boolean canObtainNewConnection() {
return this.privateConnection;
}
 
// an error has occured, try within another connection if possible
final Connection obtainNewConnection() {
if (!this.privateConnection)
if (!this.canObtainNewConnection()) {
return null;
else {
} else {
// ATTN should be sure that our connection was not already closed,
// see #closeConnection()
closeConnection(this.getConnection());
939,15 → 949,31
try {
res = executeOnce(query, queryInfo.getConnection());
} catch (SQLException exn) {
// TODO only retry for transient errors
if (State.DEBUG)
State.INSTANCE.addFailedRequest(query);
// only retry for transient errors
final boolean retry;
if (exn instanceof SQLTransientException) {
retry = true;
} else if (exn instanceof SQLNonTransientException) {
retry = false;
} else if (getSystem() == SQLSystem.H2) {
// 1. server was killed, maybe it will be restarted
// 2. client network interface was brought down, maybe it will be brought up again
retry = exn.getErrorCode() == ErrorCode.CONNECTION_BROKEN_1;
} else if (getSystem() == SQLSystem.POSTGRESQL) {
// Class 08 — Connection Exception (e.g. SocketException)
// Class 57 — Operator Intervention (e.g. server shutdown)
retry = exn.getSQLState().startsWith("08") || exn.getSQLState().startsWith("57");
} else {
retry = getSystem() == SQLSystem.MYSQL;
}
// maybe this was a network problem, so wait a little
final int retryWait = this.retryWait;
if (retryWait < 0)
if (!retry || retryWait < 0 || !queryInfo.canObtainNewConnection())
throw exn;
try {
Thread.sleep(this.retryWait);
Thread.sleep(retryWait);
} catch (InterruptedException e) {
throw new RTInterruptedException(e.getMessage() + " : " + query, exn);
}
954,10 → 980,7
// and try to obtain a new connection
try {
final Connection otherConn = queryInfo.obtainNewConnection();
if (otherConn != null) {
res = executeOnce(query, otherConn);
} else
throw exn;
} catch (Exception e) {
if (e == exn)
throw exn;
1291,11 → 1314,21
*/
private final Connection borrowConnection(final boolean test) throws NoSuchElementException {
if (test) {
this.testLock.lock();
synchronized (this.testLock) {
// invalidate all bad connections
setTestOnBorrow(true);
try {
return this._borrowConnection(test);
} finally {
setTestOnBorrow(false);
}
try {
}
} else {
return this._borrowConnection(test);
}
}
 
private final Connection _borrowConnection(final boolean test) throws NoSuchElementException {
// when we call borrowConnection() with test, it's because there was an error so this
// call is already a second try, thus getRawConnection() shouldn't try a third time.
final Connection res = this.getRawConnection(!test);
1306,13 → 1339,7
this.closeConnection(res);
throw e;
}
} finally {
if (test) {
setTestOnBorrow(false);
this.testLock.unlock();
}
}
}
 
// initialize the passed connection if needed
protected final void initConnection(final Connection res) {
1363,7 → 1390,7
throw new RTInterruptedException(e1);
}
final int retryWait = retry ? this.retryWait : -1;
if (retryWait < 0)
if (retryWait < 0 || e1 instanceof SQLNonTransientException)
getRawConnectionThrow(e1, null);
try {
// on attend un petit peu
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLResultSet.java
40,15 → 40,90
import org.apache.commons.collections.map.LazyMap;
 
/**
* A resultSet that wraps onto another one, caching name to index translation, and using a
* ResultSetFullnameHelper.
* A resultSet that wraps onto another one, caching name to index translation,
* and using a ResultSetFullnameHelper.
*
* @author Sylvain
*/
public class SQLResultSet implements ResultSet {
 
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final int columnIndex)
throws SQLException {
final Object res;
if (clz == Object.class)
res = rs.getObject(columnIndex);
else if (Integer.class.isAssignableFrom(clz))
res = rs.getInt(columnIndex);
else if (Long.class.isAssignableFrom(clz))
res = rs.getLong(columnIndex);
else if (String.class.isAssignableFrom(clz))
res = rs.getString(columnIndex);
else if (Boolean.class.isAssignableFrom(clz))
res = rs.getBoolean(columnIndex);
else if (java.sql.Date.class.isAssignableFrom(clz))
res = rs.getDate(columnIndex);
else if (java.util.Date.class.isAssignableFrom(clz))
res = rs.getTimestamp(columnIndex);
else if (Short.class.isAssignableFrom(clz))
res = rs.getShort(columnIndex);
else if (Byte.class.isAssignableFrom(clz))
res = rs.getByte(columnIndex);
else if (Double.class.isAssignableFrom(clz))
res = rs.getDouble(columnIndex);
else if (Float.class.isAssignableFrom(clz))
res = rs.getFloat(columnIndex);
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnIndex);
else
res = rs.getObject(columnIndex);
return clz.cast(res);
}
 
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final String columnLabel)
throws SQLException {
final Object res;
if (clz == Object.class)
res = rs.getObject(columnLabel);
else if (Integer.class.isAssignableFrom(clz))
res = rs.getInt(columnLabel);
else if (Long.class.isAssignableFrom(clz))
res = rs.getLong(columnLabel);
else if (String.class.isAssignableFrom(clz))
res = rs.getString(columnLabel);
else if (Boolean.class.isAssignableFrom(clz))
res = rs.getBoolean(columnLabel);
else if (java.sql.Date.class.isAssignableFrom(clz))
res = rs.getDate(columnLabel);
else if (java.util.Date.class.isAssignableFrom(clz))
res = rs.getTimestamp(columnLabel);
else if (Short.class.isAssignableFrom(clz))
res = rs.getShort(columnLabel);
else if (Byte.class.isAssignableFrom(clz))
res = rs.getByte(columnLabel);
else if (Double.class.isAssignableFrom(clz))
res = rs.getDouble(columnLabel);
else if (Float.class.isAssignableFrom(clz))
res = rs.getFloat(columnLabel);
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnLabel);
else
res = rs.getObject(columnLabel);
return clz.cast(res);
}
 
static public final int getRowProcessedCount(final ResultSet rs) {
if (rs instanceof SQLResultSet) {
return ((SQLResultSet) rs).getRowProcessedCount();
} else {
// ResultSet.getRow() always return 0 after the last row
return 0;
}
}
 
private final ResultSet delegate;
private final ResultSetFullnameHelper helper;
private final Map indexes;
private int rowProcessedCount;
 
public SQLResultSet(ResultSet delegate) {
this.delegate = delegate;
421,9 → 496,14
}
 
public boolean next() throws SQLException {
rowProcessedCount++;
return getDelegate().next();
}
 
public int getRowProcessedCount() {
return rowProcessedCount;
}
 
public boolean previous() throws SQLException {
return getDelegate().previous();
}
849,4 → 929,15
public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
getDelegate().updateSQLXML(columnLabel, xmlObject);
}
 
@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
return getDelegate().getObject(columnIndex, type);
}
 
@Override
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
return getDelegate().getObject(columnLabel, type);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSchema.java
13,6 → 13,8
package org.openconcerto.sql.model;
 
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
 
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.ChangeTable.ClauseType;
22,7 → 24,6
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.CopyOnWriteMap;
import org.openconcerto.utils.change.CollectionChangeEventCreator;
import org.openconcerto.xml.JDOMUtils;
 
import java.io.IOException;
import java.sql.DatabaseMetaData;
38,11 → 39,11
import java.util.Map.Entry;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
 
import org.apache.commons.dbutils.ResultSetHandler;
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
 
public final class SQLSchema extends SQLIdentifier {
 
/**
78,7 → 79,7
sb.append(' ');
sb.append(VERSION_XMLATTR);
sb.append("=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(version));
sb.append(OUTPUTTER.escapeAttributeEntities(version));
sb.append('"');
}
}
319,7 → 320,7
sb.append("<schema ");
if (this.getName() != null) {
sb.append(" name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append('"');
}
synchronized (getTreeMutex()) {
330,13 → 331,13
sb.append("<procedures>\n");
for (final Entry<String, String> e : this.procedures.entrySet()) {
sb.append("<proc name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(e.getKey()));
sb.append(OUTPUTTER.escapeAttributeEntities(e.getKey()));
sb.append("\" ");
if (e.getValue() == null) {
sb.append("/>");
} else {
sb.append("><src>");
sb.append(JDOMUtils.OUTPUTTER.escapeElementEntities(e.getValue()));
sb.append(OUTPUTTER.escapeElementEntities(e.getValue()));
sb.append("</src></proc>\n");
}
}
381,7 → 382,7
return Tuple2.create(false, null);
 
final SQLSystem sys = getServer().getSQLSystem();
final SQLSyntax syntax = sys.getSyntax();
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
final SQLDataSource ds = this.getDBSystemRoot().getDataSource();
synchronized (this.getTreeMutex()) {
// don't refresh until after the insert, that way if the refresh triggers an access to
448,6 → 449,7
}
 
final String updateVersion(boolean createTable) throws SQLException {
// don't use the VM time as it can vary between clients, use the server time
return this.setFwkMetadata(SQLSchema.VERSION_MDKEY, getVersionSQL(SQLSyntax.get(this)), createTable).get1();
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/StructureSource.java
169,7 → 169,7
// DatabaseMetaData param to avoid re-asking it
protected final Set<String> getJDBCSchemas(final DatabaseMetaData metaData) throws SQLException {
// getSchemas(this.getBase().getMDName(), null) not implemented by pg
final Set<String> res = new HashSet<String>((List) SQLDataSource.COLUMN_LIST_HANDLER.handle(metaData.getSchemas()));
final Set<String> res = new HashSet<String>(ColumnListHandlerGeneric.create(String.class).handle(metaData.getSchemas()));
// if db does not support schemas
if (res.isEmpty() && !this.getBase().getServer().getSQLSystem().getLevels().contains(HierarchyLevel.SQLSCHEMA))
res.add(null);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/Link.java
16,7 → 16,7
*/
package org.openconcerto.sql.model.graph;
 
import static org.openconcerto.xml.JDOMUtils.OUTPUTTER;
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.model.SQLField;
48,7 → 48,7
* @author ILM Informatique 13 mai 2004
*/
@ThreadSafe
public class Link extends DirectedEdge<SQLTable> {
public final class Link extends DirectedEdge<SQLTable> {
 
public static enum Direction {
FOREIGN {
282,16 → 282,21
 
@Override
public boolean equals(Object other) {
if (!(other instanceof Link)) {
if (this == other)
return true;
if (other == null || getClass() != other.getClass())
return false;
}
Link o = (Link) other;
final Link o = (Link) other;
return this.getFields().equals(o.getFields()) && this.getRefFields().equals(o.getRefFields()) && this.getUpdateRule() == o.getUpdateRule() && this.getDeleteRule() == o.getDeleteRule();
}
 
@Override
public int hashCode() {
return this.getFields().hashCode() + this.getRefFields().hashCode();
final int prime = 31;
int result = 1;
result = prime * result + this.getFields().hashCode();
result = prime * result + this.getRefFields().hashCode();
return result;
}
 
// instead of using SQLField, this class only uses String and SQLName
351,7 → 356,7
pWriter.write("\"");
}
 
static Link fromXML(final SQLTable t, final Element linkElem) {
static public Link fromXML(final SQLTable t, final Element linkElem) {
final SQLName to = SQLName.parse(linkElem.getAttributeValue("to"));
final SQLTable foreignTable = t.getDBSystemRoot().getDesc(to, SQLTable.class);
final String linkName = linkElem.getAttributeValue("name");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/DatabaseGraph.java
16,7 → 16,7
*/
package org.openconcerto.sql.model.graph;
 
import static org.openconcerto.xml.JDOMUtils.OUTPUTTER;
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.Log;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/DirectedEdge.java
13,10 → 13,10
package org.openconcerto.sql.model.graph;
 
import org.jgrapht.Graph;
 
import net.jcip.annotations.ThreadSafe;
 
import org.jgrapht.Graph;
 
@ThreadSafe
public class DirectedEdge<V> {
 
51,17 → 51,22
}
 
@Override
public boolean equals(Object other) {
if (!(other instanceof DirectedEdge)) {
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
}
final DirectedEdge<?> o = (DirectedEdge<?>) other;
final DirectedEdge<?> o = (DirectedEdge<?>) obj;
return this.getSource().equals(o.getSource()) && this.getTarget().equals(o.getTarget());
}
 
@Override
public int hashCode() {
return this.getSource().hashCode() + this.getTarget().hashCode();
final int prime = 31;
int result = 1;
result = prime * result + this.getSource().hashCode();
result = prime * result + this.getTarget().hashCode();
return result;
}
 
public static final <V, E extends DirectedEdge<V>> void addEdge(Graph<V, E> g, E e) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Constraint.java
15,7 → 15,7
 
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.utils.cc.HashingStrategy;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.util.HashMap;
22,11 → 22,11
import java.util.List;
import java.util.Map;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
import org.jdom2.Element;
 
@Immutable
public final class Constraint {
 
89,7 → 89,7
public synchronized String toXML() {
// this is immutable so only compute once the XML
if (this.xml == null)
this.xml = "<constraint name=\"" + JDOMUtils.OUTPUTTER.escapeAttributeEntities(getName()) + "\" >" + XMLCodecUtils.encodeSimple(this.m) + "</constraint>";
this.xml = "<constraint name=\"" + JDOM2Utils.OUTPUTTER.escapeAttributeEntities(getName()) + "\" >" + XMLCodecUtils.encodeSimple(this.m) + "</constraint>";
return this.xml;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBStructureItem.java
112,7 → 112,7
}
 
public final boolean contains(String childName) {
return this.getChildrenNames().contains(childName);
return this.getChildrenMap().containsKey(childName);
}
 
/**
176,15 → 176,21
}
 
private final D getAncestor(int level) {
if (level < 0)
if (level < 0) {
throw new IllegalArgumentException("negative level: " + level);
else if (level == 0)
} else if (level == 0) {
return thisAsD();
else if (this.getParent() == null)
} else {
// need to upcast parent since D extends this class and private methods aren't inherited
// http://stackoverflow.com/questions/7719843/type-parameterized-field-of-a-generic-class-becomes-invisible-after-upgrading-to
// http://stackoverflow.com/questions/15062841/java-casting-with-method-calls
final DBStructureItem<D> parentForJavac = this.getParent();
if (parentForJavac == null)
throw new IllegalArgumentException(this + " is the root, can't go up of " + level);
else
return this.getParent().getAncestor(level - 1);
return parentForJavac.getAncestor(level - 1);
}
}
 
/**
* The youngest common ancestor.
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLBase.java
52,11 → 52,11
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
* Une base de donnée SQL. Une base est unique, pour obtenir une instance il faut passer par
* SQLServer. Une base permet d'accéder aux tables qui la composent, ainsi qu'à son graphe.
67,7 → 67,7
* @see #getGraph()
*/
@ThreadSafe
public class SQLBase extends SQLIdentifier {
public final class SQLBase extends SQLIdentifier {
 
/**
* Boolean system property, if <code>true</code> then the structure and the graph of SQL base
154,7 → 154,6
protected synchronized void onDrop() {
// allow schemas (and their descendants) to be gc'd even we aren't
this.schemas.clear();
SQLType.remove(this);
super.onDrop();
}
 
782,6 → 781,7
static private final Pattern percent = Pattern.compile("%.");
 
private final static String quote(final SQLBase b, final String pattern, Object... params) {
final SQLSyntax s = b == null ? null : SQLSyntax.get(b);
final Matcher m = percent.matcher(pattern);
final StringBuffer sb = new StringBuffer();
int i = 0;
794,7 → 794,7
} else {
final Object param = params[i++];
if (modifier == 's') {
replacement = quoteString(b, param.toString());
replacement = SQLSyntax.quoteString(s, param.toString());
} else if (modifier == 'i') {
if (param instanceof SQLName)
replacement = ((SQLName) param).quote();
832,11 → 832,11
* @see #quoteStringStd(String)
*/
public String quoteString(String s) {
return quoteStringStd(s);
return SQLSyntax.get(this).quoteString(s);
}
 
static private final Pattern singleQuote = Pattern.compile("'", Pattern.LITERAL);
static private final Pattern quotedPatrn = Pattern.compile("^'(('')|[^'])*'$");
static public final Pattern quotedPatrn = Pattern.compile("'(('')|[^'])*'");
static private final Pattern twoSingleQuote = Pattern.compile("''", Pattern.LITERAL);
 
/**
864,15 → 864,11
* @see #quoteStringStd(String)
*/
public final static String unquoteStringStd(String s) {
if (!quotedPatrn.matcher(s).find())
if (!quotedPatrn.matcher(s).matches())
throw new IllegalArgumentException("Invalid quoted string " + s);
return twoSingleQuote.matcher(s.substring(1, s.length() - 1)).replaceAll("'");
}
 
public final static String quoteString(SQLBase b, String s) {
return b == null ? quoteStringStd(s) : b.quoteString(s);
}
 
// * quoteIdentifier
 
static private final Pattern doubleQuote = Pattern.compile("\"");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMySQL.java
26,7 → 26,6
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.BufferedReader;
48,6 → 47,8
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
63,12 → 64,50
*
* @author Sylvain CUAZ
*/
class SQLSyntaxMySQL extends SQLSyntax {
public class SQLSyntaxMySQL extends SQLSyntax {
 
private static final Pattern INT_PATTERN = Pattern.compile("(bigint|smallint|int)");
final static SQLSyntax create(DBSystemRoot sysRoot) {
final boolean noBackslashEscapes;
if (sysRoot == null) {
noBackslashEscapes = false;
} else {
final String modes = (String) sysRoot.getDataSource().executeScalar("SELECT @@global.sql_mode;");
noBackslashEscapes = modes.contains("NO_BACKSLASH_ESCAPES");
}
return new SQLSyntaxMySQL(noBackslashEscapes);
}
 
SQLSyntaxMySQL() {
super(SQLSystem.MYSQL);
static private final IdentityHashMap<String, String> DATE_SPECS;
static private final Map<Class<?>, String> CAST_TYPES;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "%Y");
DATE_SPECS.put(DateProp.MONTH_NAME, "%M");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "%m");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "%d");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "%W");
DATE_SPECS.put(DateProp.HOUR, "%H");
DATE_SPECS.put(DateProp.MINUTE, "%i");
DATE_SPECS.put(DateProp.SECOND, "%S");
DATE_SPECS.put(DateProp.MICROSECOND, "%f");
CAST_TYPES = new HashMap<Class<?>, String>();
CAST_TYPES.put(Short.class, "signed integer");
CAST_TYPES.put(Integer.class, "signed integer");
CAST_TYPES.put(Long.class, "signed integer");
CAST_TYPES.put(BigDecimal.class, "decimal");
CAST_TYPES.put(Timestamp.class, "datetime");
CAST_TYPES.put(java.sql.Date.class, "date");
CAST_TYPES.put(java.sql.Time.class, "time");
CAST_TYPES.put(Blob.class, "binary");
CAST_TYPES.put(String.class, "char");
}
 
private final boolean noBackslashEscapes;
 
public SQLSyntaxMySQL(final boolean noBackslashEscapes) {
super(SQLSystem.MYSQL, DATE_SPECS);
this.noBackslashEscapes = noBackslashEscapes;
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Short.class, "smallint");
this.typeNames.addAll(Integer.class, "integer", "int");
76,8 → 115,9
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric");
this.typeNames.addAll(Float.class, "float");
this.typeNames.addAll(Double.class, "double precision", "real");
this.typeNames.addAll(Timestamp.class, "timestamp");
this.typeNames.addAll(java.util.Date.class, "time");
this.typeNames.addAll(Timestamp.class, "datetime", "timestamp");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "blob", "tinyblob", "mediumblob", "longblob", "varbinary", "binary");
this.typeNames.addAll(Clob.class, "text", "tinytext", "mediumtext", "longtext", "varchar", "char");
this.typeNames.addAll(String.class, "varchar", "char");
84,6 → 124,21
}
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// ATTN if noBackslashEscapes is changed for the session,
// then SQL can be injected :
// toto \'; drop table ;
// is quoted to :
// 'toto \''; drop table ;'
// and since DDL is not transactional in MySQL the table is forever dropped.
// escape \ by replacing them with \\
return !this.noBackslashEscapes ? SQLSyntaxPG.BACKSLASH_PATTERN.matcher(res).replaceAll(SQLSyntaxPG.TWO_BACKSLASH_REPLACEMENT) : res;
}
 
@Override
public int getMaximumIdentifierLength() {
// http://dev.mysql.com/doc/refman/5.7/en/identifiers.html
return 64;
104,11 → 159,6
}
 
@Override
public String getDateAndTimeType() {
return "datetime";
}
 
@Override
protected String getAutoDateType(SQLField f) {
return "timestamp";
}
125,9 → 175,9
}
 
@Override
public String cast(String expr, String type) {
public String cast(String expr, Class<?> javaType) {
// MySQL doesn't use types but keywords
return super.cast(expr, INT_PATTERN.matcher(type).replaceAll("integer").replace("integer", "signed integer"));
return this.cast(expr, CAST_TYPES.get(javaType));
}
 
@Override
276,13 → 326,12
// 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 base.quoteString(input.getName());
return quoteString(input.getName());
}
});
final File tmp = File.createTempFile(SQLSyntaxMySQL.class.getSimpleName() + "storeData", ".txt");
292,7 → 341,7
tmp.delete();
final SQLSelect sel = new SQLSelect(true).addSelectStar(t);
// store the data in the temp file
base.getDataSource().execute("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE " + base.quoteString(tmp.getAbsolutePath()) + " " + getDATA_OPTIONS(base) + ";");
t.getDBSystemRoot().getDataSource().execute("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE " + quoteString(tmp.getAbsolutePath()) + " " + getDATA_OPTIONS() + ";");
// then read it to remove superfluous escape char and convert to utf8
final BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(tmp), charset));
Writer w = null;
342,8 → 391,8
}
}
 
private static String getDATA_OPTIONS(final SQLBase b) {
return "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY " + b.quoteString("\\") + " LINES TERMINATED BY '\n' ";
private String getDATA_OPTIONS() {
return "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY " + quoteString("\\") + " LINES TERMINATED BY '\n' ";
}
 
@Override
367,7 → 416,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 ", f.getAbsolutePath(), t) + charsetClause + getDATA_OPTIONS(t.getBase()) + " IGNORE 1 LINES;");
ds.execute(t.getBase().quote("LOAD DATA LOCAL INFILE %s INTO TABLE %f ", f.getAbsolutePath(), t) + charsetClause + getDATA_OPTIONS() + " IGNORE 1 LINES;");
return null;
}
});
377,11 → 426,6
}
 
@Override
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new MySQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
@Override
public String getNullIsDataComparison(String x, boolean eq, String y) {
final String nullSafe = x + " <=> " + y;
if (eq)
391,10 → 435,34
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "DAYOFWEEK(" + sqlTS + ")";
}
 
@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") + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? "%Y%m%dT%H%i%s.%f" : "%Y-%m-%dT%H:%i:%s.%f"));
}
 
@Override
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "DATE_FORMAT(" + sqlTS + ", " + nativeFormat + ")";
}
 
static private final Pattern PERCENT_PATTERN = Pattern.compile("(%+)");
 
@Override
public String quoteForTimestampFormat(String text) {
return PERCENT_PATTERN.matcher(text).replaceAll("$1$1");
}
 
@Override
public String getConstantTableStatement(List<List<String>> rows, int colCount) {
if (colCount < 0)
colCount = rows.get(0).size();
return getConstantTable(rows, null, Collections.<String> nCopies(colCount, null));
}
 
private final void getRow(StringBuilder sb, List<String> row, final int requiredColCount, List<String> columnsAlias) {
// should be OK since requiredColCount is computed from columnsAlias in getConstantTable()
assert columnsAlias == null || requiredColCount == columnsAlias.size();
403,7 → 471,7
throw new IllegalArgumentException("Wrong number of columns, should be " + requiredColCount + " but row is " + row);
for (int i = 0; i < actualColCount; i++) {
sb.append(row.get(i));
if (columnsAlias != null) {
if (columnsAlias != null && columnsAlias.get(i) != null) {
sb.append(" as ");
sb.append(SQLBase.quoteIdentifier(columnsAlias.get(i)));
}
421,7 → 489,9
if (colCount < 1)
throw new IllegalArgumentException("Empty columns will cause a syntax error");
final StringBuilder sb = new StringBuilder(rows.size() * 64);
sb.append("( SELECT ");
if (alias != null)
sb.append("( ");
sb.append("SELECT ");
// aliases needed only for the first row
getRow(sb, rows.get(0), colCount, columnsAlias);
for (int i = 1; i < rowCount; i++) {
428,8 → 498,10
sb.append("\nUNION ALL\nSELECT ");
getRow(sb, rows.get(i), colCount, null);
}
if (alias != null) {
sb.append(" ) as ");
sb.append(SQLBase.quoteIdentifier(alias));
}
return sb.toString();
}
 
459,7 → 531,7
translated = new TablesMap(1);
translated.put(b.getMDName(), tables.get(null));
}
return getTablesMapJoin(b, translated, schemaCol, tableCol);
return getTablesMapJoin(translated, schemaCol, tableCol);
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBSystemRoot.java
76,6 → 76,9
private boolean incoherentPath;
private final PropertyChangeListener coherenceListener;
 
@GuardedBy("this")
private SQLSyntax syntax;
 
private final LoadingChangeSupport loadingListenersSupp;
 
DBSystemRoot(DBStructureItemJDBC delegate) {
97,6 → 100,7
}
};
this.loadingListenersSupp = new LoadingChangeSupport(this);
this.syntax = null;
}
 
private synchronized void rootsChanged(PropertyChangeEvent evt) {
271,7 → 275,7
public synchronized final void setUseCache(final boolean useCache) throws SQLException {
if (this.hasDataSource() && useCache) {
// null if we shouldn't alter the base
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(this.getServer().getSQLSystem().getSyntax());
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(this.getSyntax());
final TablesMap m = new TablesMap();
for (final DBRoot r : this.getChildrenMap().values()) {
// works because when created a root is always fully loaded (we don't allow roots
390,6 → 394,12
return this.ds != null;
}
 
public synchronized final SQLSyntax getSyntax() {
if (this.syntax == null)
this.syntax = SQLSyntax.create(this);
return this.syntax;
}
 
@Override
protected synchronized void onDrop() {
this.rmChildrenListener(this.coherenceListener);
536,7 → 546,7
* @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))
for (final String s : new SQLCreateRoot(this.getSyntax(), rootName).asList(rootName, false, true))
getDataSource().execute(s);
return this.addRoot(rootName);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowListRSH.java
13,6 → 13,7
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.utils.Tuple2;
 
import java.sql.ResultSet;
181,7 → 182,9
tables.add(ref.getTable());
}
 
return new IResultSetHandler(create(indexes), readCache, writeCache) {
// the SELECT requesting a lock means the caller expects the DB to be accessed
final boolean acquireLock = sel.getLockStrength() != LockStrength.NONE;
return new IResultSetHandler(create(indexes), readCache && !acquireLock, writeCache && !acquireLock) {
@Override
public Set<? extends SQLData> getCacheModifiers() {
return tables;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/XMLStructureSource.java
23,8 → 23,6
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
40,10 → 38,6
 
public class XMLStructureSource extends StructureSource<IOException> {
 
/**
* Date format used in xml files.
*/
public static final DateFormat XMLDATE_FMT = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
public static final String version = "20141001-1155";
 
private final Map<String, Element> xmlSchemas;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Trigger.java
13,17 → 13,17
package org.openconcerto.sql.model;
 
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.util.HashMap;
import java.util.Map;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
import org.jdom2.Element;
 
@Immutable
public final class Trigger {
 
82,7 → 82,7
public synchronized String toXML() {
// this is immutable so only compute once the XML
if (this.xml == null)
this.xml = "<trigger name=\"" + JDOMUtils.OUTPUTTER.escapeAttributeEntities(getName()) + "\">" + XMLCodecUtils.encodeSimple(this.m) + "</trigger>";
this.xml = "<trigger name=\"" + JDOM2Utils.OUTPUTTER.escapeAttributeEntities(getName()) + "\">" + XMLCodecUtils.encodeSimple(this.m) + "</trigger>";
return this.xml;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSystem.java
155,7 → 155,10
// jdbc:h2:~/test
sep = "";
}
return s.getName() + sep + base;
// by default h2 convert database name to upper case (we used to work around it
// with SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by
// equals()) see http://code.google.com/p/h2database/issues/detail?id=204
return s.getName() + sep + base + ";DATABASE_TO_UPPER=false";
}
};
}
508,7 → 511,10
}
 
/**
* The syntax for this system.
* The default syntax for this system. NOTE : when needing a syntax for a system currently
* accessible, {@link DBSystemRoot#getSyntax()} should be used so that server options can be
* read. Otherwise constructors of {@link SQLSyntax} subclasses should be used to specify
* options.
*
* @return the syntax for this system, or <code>null</code> if none exists.
*/
/trunk/OpenConcerto/src/org/openconcerto/sql/model/JDBCStructureSource.java
113,7 → 113,7
 
// create metadata table here to avoid a second refresh
// null if we shouldn't alter the base
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(getBase().getServer().getSQLSystem().getSyntax());
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(getBase().getDBSystemRoot().getSyntax());
final boolean useCache = getBase().getDBSystemRoot().useCache();
Statement stmt = null;
try {
271,7 → 271,7
}
// try to find out more about those procedures
if (proceduresBySchema.size() > 0) {
final String sel = system.getSyntax().getFunctionQuery(getBase(), proceduresBySchema.keySet());
final String sel = getBase().getDBSystemRoot().getSyntax().getFunctionQuery(getBase(), proceduresBySchema.keySet());
if (sel != null) {
// don't cache since we don't listen on system tables
for (final Object o : (List) getBase().getDataSource().execute(sel, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false))) {
321,7 → 321,7
@Override
protected Object getQuery(SQLBase b, TablesMap tables) {
try {
return b.getServer().getSQLSystem().getSyntax().getTriggerQuery(b, tables);
return b.getDBSystemRoot().getSyntax().getTriggerQuery(b, tables);
} catch (SQLException e) {
return e;
}
340,7 → 340,7
 
@Override
protected String getQuery(SQLBase b, TablesMap tables) {
return b.getServer().getSQLSystem().getSyntax().getColumnsQuery(b, tables);
return b.getDBSystemRoot().getSyntax().getColumnsQuery(b, tables);
}
 
@Override
357,7 → 357,7
@Override
protected Object getQuery(SQLBase b, TablesMap tables) {
try {
return b.getServer().getSQLSystem().getSyntax().getConstraints(b, tables);
return b.getDBSystemRoot().getSyntax().getConstraints(b, tables);
} catch (Exception e) {
return e;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTable.java
13,8 → 13,11
package org.openconcerto.sql.model;
 
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.graph.DatabaseGraph;
38,7 → 41,6
import org.openconcerto.utils.cc.CopyOnWriteMap;
import org.openconcerto.utils.cc.CustomEquals;
import org.openconcerto.utils.change.CollectionChangeEventCreator;
import org.openconcerto.xml.JDOMUtils;
 
import java.math.BigDecimal;
import java.sql.DatabaseMetaData;
214,7 → 216,7
l.add(Arrays.asList(b.quoteString(tableName), undefSQL));
}
}
final SQLSyntax syntax = system.getSyntax();
final SQLSyntax syntax = schema.getDBSystemRoot().getSyntax();
if (toInsert.size() > 0) {
// INSERT
SQLRowValues.insertCount(undefT, "(" + SQLSyntax.quoteIdentifiers(Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD)) + ") " + syntax.getValues(toInsert, 2));
310,7 → 312,7
private String version;
private final CopyOnWriteMap<String, SQLField> fields;
@GuardedBy("this")
private final Set<SQLField> primaryKeys;
private Set<SQLField> primaryKeys;
// the vast majority of our code use getKey(), so cache it for performance
@GuardedBy("this")
private SQLField primaryKey;
363,8 → 365,7
}
};
assert isOrdered(this.fields);
// order matters (eg for indexes)
this.primaryKeys = new LinkedHashSet<SQLField>();
this.primaryKeys = Collections.emptySet();
this.primaryKey = null;
this.primaryKeyOK = true;
this.keys = null;
512,7 → 513,7
 
// must be called in setState() after fields have been set (for isRowable())
private int fetchUndefID() {
final int res;
int res;
final SQLField pk;
synchronized (this) {
pk = isRowable() ? this.getKey() : null;
520,8 → 521,14
if (pk != null) {
final Tuple2<Boolean, Number> currentValue = getUndefID(this.getSchema(), this.getName());
if (!currentValue.get0()) {
try {
// no row
res = this.findMinID(pk);
} catch (Exception e) {
// we ***** don't care
e.printStackTrace();
res = SQLRow.NONEXISTANT_ID;
}
} else {
// a row
final Number id = currentValue.get1();
546,8 → 553,9
// empty table
throw new IllegalStateException(this + " is empty, can not infer UNDEFINED_ID");
} else {
final String update = SQLSyntax.get(this).getInsertOne(new SQLName(this.getDBRoot().getName(), undefTable), Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD),
getBase().quoteString(this.getName()), String.valueOf(undef));
final SQLSyntax syntax = SQLSyntax.get(this);
final String update = syntax.getInsertOne(new SQLName(this.getDBRoot().getName(), undefTable), Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD),
syntax.quoteString(this.getName()), String.valueOf(undef));
Log.get().config("the first row (which should be the undefined):\n" + update);
return undef.intValue();
}
630,9 → 638,11
}
}
 
this.primaryKeys.clear();
// order matters (e.g. for indexes)
final Set<SQLField> newPK = new LinkedHashSet<SQLField>();
for (final String pk : primaryKeys)
this.primaryKeys.add(this.getField(pk));
newPK.add(this.getField(pk));
this.primaryKeys = Collections.unmodifiableSet(newPK);
this.primaryKey = primaryKeys.size() == 1 ? this.getField(primaryKeys.get(0)) : null;
this.primaryKeyOK = primaryKeys.size() <= 1;
 
762,7 → 772,7
* @return the fields (SQLField) which are the keys of this table, can be empty.
*/
public synchronized Set<SQLField> getPrimaryKeys() {
return Collections.unmodifiableSet(this.primaryKeys);
return this.primaryKeys;
}
 
public final Set<Link> getForeignLinks() {
1117,7 → 1127,7
}
}
}
return res;
return Collections.unmodifiableSet(res);
}
 
public final Set<String> getFieldsNames(final VirtualFields vfs) {
1128,6 → 1138,23
return res;
}
 
public final List<SQLField> getFields(final Collection<String> names) {
return this.getFields(names, new ArrayList<SQLField>());
}
 
public final <T extends Collection<SQLField>> T getFields(final Collection<String> names, final T res) {
return this.getFields(names, res, true);
}
 
public final <T extends Collection<SQLField>> T getFields(final Collection<String> names, final T res, final boolean required) {
for (final String name : names) {
final SQLField f = required ? this.getField(name) : this.getFieldRaw(name);
if (f != null)
res.add(f);
}
return res;
}
 
/**
* Retourne les champs du contenu de cette table. C'est à dire ni la clef primaire, ni les
* champs d'archive et d'ordre.
1267,7 → 1294,7
* archivée.
*/
public SQLRow checkValidity(int ID) {
SQLRow row = this.getUncheckedRow(ID);
final SQLRow row = SQLRow.createFromSelect(this, VirtualFields.PRIMARY_KEY.union(VirtualFields.ARCHIVE), ID, LockStrength.SHARE);
// l'inverse de getValidRow()
return row.isValid() ? null : row;
}
1684,13 → 1711,13
public synchronized String toXML() {
final StringBuilder sb = new StringBuilder(16000);
sb.append("<table name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\"");
 
final String schemaName = this.getSchema().getName();
if (schemaName != null) {
sb.append(" schema=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(schemaName));
sb.append(OUTPUTTER.escapeAttributeEntities(schemaName));
sb.append('"');
}
 
1698,7 → 1725,7
 
if (getType() != null) {
sb.append(" type=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(getType()));
sb.append(OUTPUTTER.escapeAttributeEntities(getType()));
sb.append('"');
}
 
1706,7 → 1733,7
 
if (this.getComment() != null) {
sb.append("<comment>");
sb.append(JDOMUtils.OUTPUTTER.escapeElementEntities(this.getComment()));
sb.append(OUTPUTTER.escapeElementEntities(this.getComment()));
sb.append("</comment>\n");
}
for (SQLField field : this.fields.values()) {
1924,11 → 1951,11
}
 
public final SQLCreateMoveableTable getCreateTable() {
return this.getCreateTable(this.getServer().getSQLSystem());
return this.getCreateTable(SQLSyntax.get(this));
}
 
public synchronized final SQLCreateMoveableTable getCreateTable(final SQLSystem system) {
final SQLSyntax syntax = SQLSyntax.get(system);
public synchronized final SQLCreateMoveableTable getCreateTable(final SQLSyntax syntax) {
final SQLSystem system = syntax.getSystem();
final SQLCreateMoveableTable res = new SQLCreateMoveableTable(syntax, this.getDBRoot().getName(), this.getName());
for (final SQLField f : this.getOrderedFields()) {
res.addColumn(f);
1954,7 → 1981,6
// not null" in addUniqueConstraint(). Thus when converting to another system we must
// parse indexes to recreate actual constraints.
final boolean convertMSIndex = this.getServer().getSQLSystem() == SQLSystem.MSSQL && system != SQLSystem.MSSQL;
final Set<List<SQLField>> foreignKeysFields = getForeignKeysFields();
for (final Index i : this.getIndexes(true)) {
Value<String> msWhere = null;
if (convertMSIndex && (msWhere = i.getMSUniqueWhere()).hasValue()) {
1961,12 → 1987,12
if (msWhere.getValue() != null)
Log.get().warning("MS filter might not be valid in " + system + " : " + msWhere.getValue());
res.addUniqueConstraint(i.getName(), i.getCols(), msWhere.getValue());
} else if (!system.autoCreatesFKIndex() || !foreignKeysFields.contains(i.getFields())) {
} else {
// partial unique index sometimes cannot be handled natively by the DB system
if (i.isUnique() && i.getFilter() != null && !system.isIndexFilterConditionSupported())
res.addUniqueConstraint(i.getName(), i.getCols(), i.getFilter());
else
res.addOutsideClause(syntax.getCreateIndex(i));
res.addIndex(i);
}
}
} catch (SQLException e) {
2040,13 → 2066,13
* automatically.
*
* @return the list of indexes.
* @throws SQLException if an error occurs.
* @throws SQLException if an error occurs while accessing the DB.
*/
public synchronized final List<Index> getIndexes() throws SQLException {
public final List<Index> getIndexes() throws SQLException {
return this.getIndexes(false);
}
 
protected synchronized final List<Index> getIndexes(final boolean normalized) throws SQLException {
public synchronized final List<Index> getIndexes(final boolean normalized) throws SQLException {
// in pg, a unique constraint creates a unique index that is not removeable
// (except of course if we drop the constraint)
// in mysql unique constraints and indexes are one and the same thing
2065,7 → 2091,7
 
final List<Index> indexes = new ArrayList<Index>();
Index currentIndex = null;
for (final Map<String, Object> norm : this.getServer().getSQLSystem().getSyntax().getIndexInfo(this)) {
for (final Map<String, Object> norm : this.getDBSystemRoot().getSyntax().getIndexInfo(this)) {
final Index index = new Index(norm);
final short seq = ((Number) norm.get("ORDINAL_POSITION")).shortValue();
if (seq == 1) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTableEvent.java
145,15 → 145,13
}
 
/**
* Return the rowValues that has changed. NOTE: if this event was generated by
* {@link SQLRowValues} the result will be linked with all rows committed at the same time.
* If this event was generated by {@link SQLRowValues} the result will be linked with all rows
* committed at the same time.
*
* @return the rowValues that has changed.
* @return the frozen rowValues that has changed, <code>null</code> if not generated by
* {@link SQLRowValues}.
*/
public final SQLRowValues getRowValues() {
if (this.vals == null) {
this.vals = this.getRow().asRowValues();
}
return this.vals;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesCluster.java
360,20 → 360,16
}
 
public final StoreResult insert() throws SQLException {
return this.insert(false, false);
return this.store(StoreMode.INSERT);
}
 
public final StoreResult insert(boolean insertPK, boolean insertOrder) throws SQLException {
return this.store(new Insert(insertPK, insertOrder));
}
 
public final StoreResult store(final StoreMode mode) throws SQLException {
return this.store(mode, true);
return this.store(mode, null);
}
 
// checkValidity false useful when we want to avoid loading the graph
public final StoreResult store(final StoreMode mode, final boolean checkValidity) throws SQLException {
return this.store(mode, null, null, checkValidity);
public final StoreResult store(final StoreMode mode, final Boolean checkValidity) throws SQLException {
return this.store(mode, null, null, checkValidity, true);
}
 
/**
383,12 → 379,15
* @param start when storing a subset, the start of <code>pruneGraph</code> in this, can be
* <code>null</code>.
* @param pruneGraph the maximum graph to store, can be <code>null</code>.
* @param checkValidity <code>true</code> to check if foreign keys point to valid rows.
* @param checkValidity whether to ask for checking if foreign keys point to valid rows, see
* {@link SQLRowValues#setValidityChecked(SQLRowValues.ValidityCheck)}.
* @param fireEvent <code>false</code> if stored rows shouldn't be fetched and
* {@link SQLTableEvent} should not be fired.
* @return the store result.
* @throws SQLException if an exception occurs.
* @see {@link #prune(SQLRowValues, SQLRowValues)}
*/
public final StoreResult store(final StoreMode mode, final SQLRowValues start, final SQLRowValues pruneGraph, final boolean checkValidity) throws SQLException {
public final StoreResult store(final StoreMode mode, final SQLRowValues start, final SQLRowValues pruneGraph, final Boolean checkValidity, final boolean fireEvent) throws SQLException {
final Map<SQLRowValues, SQLRowValues> prune2orig;
final SQLRowValuesCluster toStore;
final boolean prune = pruneGraph != null;
412,7 → 411,7
}
// check validity first, avoid beginning a transaction for nothing
// do it after reset otherwise check previous values
if (checkValidity)
if (SQLRowValues.isValidityChecked(checkValidity))
for (final Node n : nodes.values()) {
n.noLink.checkValidity();
}
482,9 → 481,9
 
if (n.isStored()) {
// if there's a cycle, we have to update an already inserted row
res.add(n.update());
res.add(n.update(fireEvent));
} else {
res.add(n.store(mode));
res.add(n.store(fireEvent, mode));
final SQLRow r = n.getStoredRow();
 
// fill the noLink of referent nodes with the new ID
499,7 → 498,7
// link together the new values
// if there is a cycle not all foreign keys can be stored at the same time, so
// wait for the last DB access
if (lastDBAccess)
if (lastDBAccess) {
for (final Map.Entry<String, SQLRowValues> e : toStore.getSrc().getForeigns().entrySet()) {
final SQLRowValues foreign = nodes.get(e.getValue()).getStoredValues();
assert foreign != null : "since this the last db access for this row, all foreigns should have been inserted";
509,15 → 508,23
n.getStoredValues().put(e.getKey(), foreign);
}
}
}
// all nodes share the same graph, so pick any and freeze the graph
// null if !fireEvent or if non-rowable table
final SQLRowValues graphFetched = nodes.values().iterator().next().getStoredValues();
if (graphFetched != null)
graphFetched.getGraph().freeze();
return res;
}
});
// fire events
if (fireEvent) {
for (final SQLTableEvent n : events) {
// MAYBE put a Map<SQLRowValues, SQLTableEvent> to know how our fellow values have been
// affected
// MAYBE put a Map<SQLRowValues, SQLTableEvent> to know how our fellow values have
// been affected
n.getTable().fire(n);
}
}
 
return new StoreResult(res);
}
963,6 → 970,7
return map;
}
 
// TODO handle referents (and decide how to handle multiple paths to the same node)
final void grow(final SQLRowValues start, final SQLRowValues toGrow, final boolean checkFields) {
this.containsCheck(start);
if (!start.getTable().equals(toGrow.getTable()))
975,7 → 983,6
final SQLRowValues leaf = toGrow.assurePath(input.getPath());
if (leaf.hasID()) {
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(input.getCurrent());
fetcher.setSelID(leaf.getID());
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
985,7 → 992,7
return input;
}
});
final SQLRowValues fetched = CollectionUtils.getSole(fetcher.fetch());
final SQLRowValues fetched = fetcher.fetchOne(leaf.getIDNumber());
if (fetched == null)
throw new IllegalArgumentException("no row for " + fetcher);
leaf.load(fetched, null);
1524,12 → 1531,19
this.noLink = new SQLRowValues(vals, ForeignCopyMode.NO_COPY);
}
 
private SQLTableEvent store(StoreMode mode) throws SQLException {
private SQLTableEvent store(final boolean fetchStoredRow, StoreMode mode) throws SQLException {
return this.store(fetchStoredRow, mode, true);
}
 
private SQLTableEvent store(final boolean fetchStoredRow, StoreMode mode, final boolean setRowValues) throws SQLException {
assert !this.isStored();
return this.addEvent(mode.execOn(this.noLink));
final SQLTableEvent evt = this.addEvent(mode.execOn(this.noLink, fetchStoredRow));
if (fetchStoredRow && evt.getRow() != null && setRowValues)
evt.setRowValues(evt.getRow().asRowValues());
return evt;
}
 
private SQLTableEvent update() throws SQLException {
private SQLTableEvent update(final boolean fetchStoredRow) throws SQLException {
assert this.isStored();
 
// fields that have been updated since last store
1540,13 → 1554,15
final SQLRowValues updatingVals = this.getStoredRow().createEmptyUpdateRow();
updatingVals.load(this.noLink, fieldsToUpdate);
 
final SQLTableEvent evt = new Node(updatingVals).store(StoreMode.COMMIT);
final SQLTableEvent evt = new Node(updatingVals).store(fetchStoredRow, StoreMode.COMMIT, false);
// Update previous rowValues, and use it for the new event
// that way there's only one graph of rowValues (with the final values) for all events.
// Load all fields since updating 1 field might change the value of another (e.g.
// with a trigger).
if (fetchStoredRow && evt.getRow() != null) {
this.getStoredValues().load(evt.getRow(), null);
evt.setRowValues(this.getStoredValues());
}
return this.addEvent(evt);
}
 
1574,7 → 1590,8
}
 
private final SQLTableEvent addEvent(SQLTableEvent evt) {
assert evt != null;
if (evt == null)
throw new IllegalStateException("Couldn't update missing row " + this.noLink);
this.modif.add(evt);
return evt;
}
1591,9 → 1608,11
* @author Sylvain
*/
public static abstract class StoreMode {
abstract SQLTableEvent execOn(SQLRowValues vals) throws SQLException;
abstract SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException;
 
public static final StoreMode COMMIT = new Commit();
public static final StoreMode INSERT = new Insert(false, false);
public static final StoreMode INSERT_VERBATIM = new Insert(true, true);
}
 
public static class Insert extends StoreMode {
1608,20 → 1627,21
}
 
@Override
SQLTableEvent execOn(SQLRowValues vals) throws SQLException {
SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException {
final Set<SQLField> autoFields = new HashSet<SQLField>();
if (!this.insertPK)
autoFields.addAll(vals.getTable().getPrimaryKeys());
if (!this.insertOrder)
autoFields.add(vals.getTable().getOrderField());
return vals.insertJustThis(autoFields);
return vals.insertJustThis(fetchStoredRow, autoFields);
}
}
 
public static class Commit extends StoreMode {
 
@Override
SQLTableEvent execOn(SQLRowValues vals) throws SQLException {
return vals.commitJustThis();
SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException {
return vals.commitJustThis(fetchStoredRow);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRequestLog.java
51,6 → 51,8
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
 
import net.jcip.annotations.GuardedBy;
 
public class SQLRequestLog {
 
private static final Color BG_PINK = new Color(254, 240, 240);
69,10 → 71,12
private String threadId;
private static List<ChangeListener> listeners = new ArrayList<ChangeListener>(2);
private static JLabel textInfo = new JLabel("Total: ");
@GuardedBy("EDT")
private static final DateFormat sdt = new SimpleDateFormat("HH:mm:ss.SS");
private static final DecimalFormat dformat = new DecimalFormat("##0.00");
 
private boolean isHighlighted = false;
private int rs_count;
 
private static final String format(final Object nano) {
final long l = ((Number) nano).longValue();
84,7 → 88,7
}
 
public SQLRequestLog(String query, String comment, int connectionId, long starAtMs, String ex, boolean inSwing, long startTime, long afterCache, long afterQueryInfo, long afterExecute,
long afterHandle, long endTime) {
long afterHandle, long endTime, int count) {
this.query = query;
this.comment = comment;
this.connectionId = connectionId;
95,6 → 99,7
this.afterExecute = afterExecute;
this.afterHandle = afterHandle;
this.endTime = endTime;
this.rs_count = count;
this.stack = ex;
this.inSwing = inSwing;
this.forShare = query.contains("FOR SHARE");
104,9 → 109,10
this.threadId = "[" + Thread.currentThread().getId() + "] " + Thread.currentThread().getName();
}
 
static long count = 0;
static long total_count = 0;
 
public static void log(String query, String comment, int connectionId, long starAtMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
public static void log(String query, String comment, int connectionId, long starAtMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime,
int count) {
 
if (enabled) {
if (list.size() < 50000) {
113,7 → 119,7
final String ex = ExceptionUtils.getStackTrace(new Exception());
 
list.add(new SQLRequestLog(query, comment, connectionId, starAtMs, ex, SwingUtilities.isEventDispatchThread(), startTime, afterCache, afterQueryInfo, afterExecute, afterHandle,
endTime));
endTime, count));
fireEvent();
}
 
122,7 → 128,7
}
 
public static void log(String query, String comment, long starAtMs, long startTime) {
log(query, comment, 0, starAtMs, startTime, startTime, startTime, startTime, startTime, startTime);
log(query, comment, 0, starAtMs, startTime, startTime, startTime, startTime, startTime, startTime, 0);
}
 
public static void log(PreparedStatement pStmt, String comment, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
129,7 → 135,7
// 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);
log(pStmt.toString(), comment, pStmt.getConnection(), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime, 0);
} catch (Exception e) {
// never propagate exceptions
Log.get().log(Level.WARNING, "Couldn't log " + pStmt, e);
137,8 → 143,9
}
}
 
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);
public static void log(String query, String comment, Connection conn, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime,
int count) {
log(query, comment, System.identityHashCode(conn), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime, count);
}
 
private static void fireEvent() {
152,7 → 159,7
final long totalMs = getTotalMs();
final long totalSQLMs = getTotalSQLMs();
textInfo.setText("Total: " + totalMs + " ms, Swing: " + getTotalSwing() + " ms, SQL: " + totalSQLMs + " ms, processing: " + (totalMs - totalSQLMs) + " ms , " + getNbConnections()
+ " conn., " + getNbThread() + " threads. Total: " + list.size() + " / " + count);
+ " conn., " + getNbThread() + " threads. Total: " + list.size() + " / " + total_count);
}
});
}
474,11 → 481,11
public static synchronized void clear() {
list.clear();
fireEvent();
count = 0;
total_count = 0;
}
 
public static long getCount() {
return count;
return total_count;
}
 
public static synchronized int getSize() {
550,6 → 557,10
return this.forShare;
}
 
public int getResultCount() {
return rs_count;
}
 
public void printStack() {
System.err.println("Stacktrace of : " + this.query);
System.err.println(this.stack);
564,6 → 575,7
}
 
private static void showStack(final SQLRequestLogModel model, TableRowSorter<TableModel> sorter, int s) {
assert SwingUtilities.isEventDispatchThread();
if (s >= 0 && s < model.getRowCount()) {
final SQLRequestLog rowAt = model.getRowAt(sorter.convertRowIndexToModel(s));
rowAt.printStack();
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ColumnListHandler.java
14,100 → 14,57
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
 
package org.openconcerto.sql.model;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
* <code>ResultSetHandler</code> implementation that converts one
* <code>ResultSet</code> column into a <code>List</code> of
* <code>Object</code>s. This class is thread safe.
* <code>ResultSetHandler</code> implementation that converts one <code>ResultSet</code> column into
* a <code>List</code> of <code>Object</code>s. This class is thread safe.
*
* @see ResultSetHandler
* @since DbUtils 1.1
*/
public class ColumnListHandler implements ResultSetHandler {
public class ColumnListHandler extends ColumnListHandlerGeneric<Object> {
 
/**
* The column number to retrieve.
* Creates a new instance of ColumnListHandler. The first column of each row will be returned
* from <code>handle()</code>.
*/
private int columnIndex = 1;
 
/**
* The column name to retrieve. Either columnName or columnIndex
* will be used but never both.
*/
private String columnName = null;
 
/**
* Creates a new instance of ColumnListHandler. The first column of each
* row will be returned from <code>handle()</code>.
*/
public ColumnListHandler() {
super();
this(1);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnIndex The index of the column to retrieve from the
* <code>ResultSet</code>.
* @param columnIndex The index of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandler(int columnIndex) {
this.columnIndex = columnIndex;
this(columnIndex, null);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnName The name of the column to retrieve from the
* <code>ResultSet</code>.
* @param columnName The name of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandler(String columnName) {
this.columnName = columnName;
this(-1, columnName);
}
 
/**
* Returns one <code>ResultSet</code> column as a <code>List</code> of
* <code>Object</code>s. The elements are added to the <code>List</code> via
* the <code>ResultSet.getObject()</code> method.
*
* @return A <code>List</code> of <code>Object</code>s, never
* <code>null</code>.
*
* @throws SQLException
*
* @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
*/
public Object handle(ResultSet rs) throws SQLException {
 
List result = new ArrayList();
 
while (rs.next()) {
if (this.columnName == null) {
result.add(rs.getObject(this.columnIndex));
} else {
result.add(rs.getObject(this.columnName));
private ColumnListHandler(int columnIndex, String columnName) {
super(columnIndex, columnName, Object.class);
}
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxPG.java
23,8 → 23,8
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.File;
43,9 → 43,11
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.apache.commons.dbcp.DelegatingConnection;
76,10 → 78,25
 
private static final int MAX_VARCHAR_L = (MAX_FIELD_SIZE - MAX_LENGTH_BYTES) / MAX_BYTES_PER_CHAR;
 
static private final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "YYYY");
DATE_SPECS.put(DateProp.MONTH_NAME, "TMmonth");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "DD");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "TMday");
DATE_SPECS.put(DateProp.HOUR, "HH24");
DATE_SPECS.put(DateProp.MINUTE, "MI");
DATE_SPECS.put(DateProp.SECOND, "SS");
DATE_SPECS.put(DateProp.MICROSECOND, "US");
}
 
SQLSyntaxPG() {
super(SQLSystem.POSTGRESQL);
super(SQLSystem.POSTGRESQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Short.class, "smallint");
this.typeNames.addAll(Short.class, "smallint", "int2");
this.typeNames.addAll(Integer.class, "integer", "int", "int4");
this.typeNames.addAll(Long.class, "bigint", "int8");
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric");
87,13 → 104,29
this.typeNames.addAll(Double.class, "double precision", "float8");
// since 7.3 default is without timezone
this.typeNames.addAll(Timestamp.class, "timestamp", "timestamp without time zone");
this.typeNames.addAll(java.util.Date.class, "time", "time without time zone", "date");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time", "time without time zone");
this.typeNames.addAll(Blob.class, "bytea");
this.typeNames.addAll(Clob.class, "varchar", "char", "character varying", "character", "text");
this.typeNames.addAll(String.class, "varchar", "char", "character varying", "character", "text");
}
 
static final Pattern BACKSLASH_PATTERN = Pattern.compile("\\", Pattern.LITERAL);
static final String TWO_BACKSLASH_REPLACEMENT = Matcher.quoteReplacement("\\\\");
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// see PostgreSQL Documentation 4.1.2.1 String Constants
// escape \ by replacing them with \\
final Matcher matcher = BACKSLASH_PATTERN.matcher(res);
// only use escape form if needed (=> equals with other systems most of the time)
return matcher.find() ? "E" + matcher.replaceAll(TWO_BACKSLASH_REPLACEMENT) : res;
}
 
@Override
public int getMaximumIdentifierLength() {
// http://www.postgresql.org/docs/9.1/static/sql-syntax-lexical.html
return 63;
183,7 → 216,7
//
+ "WHERE ci.relkind IN ('i','') AND n.nspname <> 'pg_catalog' AND n.nspname !~ '^pg_toast'\n"
//
+ " AND n.nspname = " + t.getBase().quoteString(t.getSchema().getName()) + " AND ct.relname = " + t.getBase().quoteString(t.getName()) + "\n"
+ " AND n.nspname = " + quoteString(t.getSchema().getName()) + " AND ct.relname = " + quoteString(t.getName()) + "\n"
//
+ "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
// don't cache since we don't listen on system tables
275,7 → 308,7
 
@Override
public void _loadData(final File f, final SQLTable t) throws IOException, SQLException {
final String copy = "COPY " + t.getSQLName().quote() + " FROM STDIN " + getDataOptions(t.getBase()) + ";";
final String copy = "COPY " + t.getSQLName().quote() + " FROM STDIN " + getDataOptions() + ";";
final Number count = t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
@Override
public Number handle(SQLDataSource ds) throws SQLException, IOException {
299,8 → 332,8
}
}
 
private static String getDataOptions(final SQLBase b) {
return " WITH NULL " + b.quoteString("\\N") + " CSV HEADER QUOTE " + b.quoteString("\"") + " ESCAPE AS " + b.quoteString("\\");
private String getDataOptions() {
return " WITH NULL " + quoteString("\\N") + " CSV HEADER QUOTE " + quoteString("\"") + " ESCAPE AS " + quoteString("\\");
}
 
@Override
317,7 → 350,7
});
// you can't specify line separator to pg, so use STDOUT as it always use \n
try {
final String sql = "COPY (" + selectAll(t).asString() + ") to STDOUT " + getDataOptions(t.getBase()) + " FORCE QUOTE " + cols + " ;";
final String sql = "COPY (" + selectAll(t).asString() + ") to STDOUT " + getDataOptions() + " FORCE QUOTE " + cols + " ;";
t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
@Override
public Number handle(SQLDataSource ds) throws SQLException, IOException {
361,16 → 394,31
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "EXTRACT(DOW from " + sqlTS + ") + 1";
}
 
@Override
public String getMonth(String sqlTS) {
return "EXTRACT(MONTH from " + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "to_char(cast(" + sqlTS + " as timestamp), " + SQLBase.quoteStringStd(basic ? "YYYYMMDD\"T\"HH24MISS.US" : "YYYY-MM-DD\"T\"HH24:MI:SS.US") + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? "YYYYMMDD\"T\"HH24MISS.US" : "YYYY-MM-DD\"T\"HH24:MI:SS.US"));
}
 
@Override
public SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new PGSQLBase(server, name, systemRootInit, login, pass, dsInit);
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "to_char(" + sqlTS + ", " + nativeFormat + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return StringUtils.doubleQuote(text, false);
}
 
@Override
public final String getCreateSynonym(final SQLTable t, final SQLName newName) {
String res = super.getCreateSynonym(t, newName);
 
406,7 → 454,7
@Override
public String getFunctionQuery(SQLBase b, Set<String> schemas) {
return "SELECT ROUTINE_SCHEMA as \"schema\", ROUTINE_NAME as \"name\", ROUTINE_DEFINITION as \"src\" FROM \"information_schema\".ROUTINES where ROUTINE_CATALOG='" + b.getMDName()
+ "' and ROUTINE_SCHEMA in (" + quoteStrings(b, schemas) + ")";
+ "' and ROUTINE_SCHEMA in (" + quoteStrings(schemas) + ")";
}
 
@Override
419,7 → 467,7
// schema
"INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" +
// requested tables
getTablesMapJoin(b, tables, "n.nspname", "c.relname") +
getTablesMapJoin(tables, "n.nspname", "c.relname") +
// where
"\nwhere not t." + (b.getVersion()[0] >= 9 ? "tgisinternal" : "tgisconstraint");
}
429,7 → 477,7
return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
// requested tables
getTablesMapJoin(b, tables, "TABLE_SCHEMA", "TABLE_NAME");
getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
}
 
@Override
442,7 → 490,7
+ "from pg_catalog.pg_constraint c\n" + "join pg_namespace nsp on nsp.oid = c.connamespace\n" + "left join pg_class rel on rel.oid = c.conrelid\n"
+ "left join pg_attribute att on att.attrelid = c.conrelid and att.attnum = ANY(c.conkey)\n"
// requested tables
+ getTablesMapJoin(b, tables, "nsp.nspname", "rel.relname")
+ getTablesMapJoin(tables, "nsp.nspname", "rel.relname")
// order
+ "\norder by nsp.nspname, rel.relname, c.conname";
// don't cache since we don't listen on system tables
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSelect.java
208,6 → 208,10
return getSystemRoot().getServer().getSQLSystem();
}
 
public final SQLSyntax getSyntax() {
return getSystemRoot().getSyntax();
}
 
public String asString() {
final SQLSystem sys = this.getSQLSystem();
 
231,7 → 235,7
archive = Where.and(getUndefWhere(fromTable, alias), archive);
}
// archive == null si pas d'archive et pas d'undefined
if (archive != null && archive.getClause() != "") {
if (archive != null) {
result.append("\n WHERE ");
result.append(archive.getClause());
}
430,19 → 434,22
}
 
/**
* Add an ORDER BY {@link SQLTable#getOrderField() t.ORDER}.
* Add an ORDER BY for the passed table.
*
* @param t the table.
* @param fieldMustExist if <code>true</code> then <code>t</code> must be
* {@link SQLTable#isOrdered() ordered}.
* {@link SQLTable#isOrdered() ordered} or have a {@link SQLTable#isRowable() numeric
* primary key}.
* @return this.
* @throws IllegalArgumentException if <code>t</code> isn't ordered and <code>mustExist</code>
* is <code>true</code>.
* @throws IllegalArgumentException if <code>t</code> has no usable order field and
* <code>mustExist</code> is <code>true</code>.
*/
public SQLSelect addOrder(final TableRef t, final boolean fieldMustExist) {
final SQLField orderField = t.getTable().getOrderField();
if (orderField != null)
this.addFieldOrder(t.getField(orderField.getName()));
else if (t.getTable().isRowable())
this.addFieldOrder(t.getKey());
else if (fieldMustExist)
throw new IllegalArgumentException("table is not ordered : " + t);
return this;
977,6 → 984,14
return current;
}
 
public final FieldRef followFieldPath(final IFieldPath fp) {
return this.followFieldPath(fp.getPath().getFirst().getAlias(), fp);
}
 
public final FieldRef followFieldPath(final String tableAlias, final IFieldPath fp) {
return this.followPath(tableAlias, fp.getPath()).getField(fp.getFieldName());
}
 
public boolean isExcludeUndefined() {
return this.generalExcludeUndefined;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ListListHandlerGeneric.java
New file
0,0 → 1,75
/*
* 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 java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
 
import net.jcip.annotations.Immutable;
 
/**
* <code>ResultSetHandler</code> implementation that converts a <code>ResultSet</code> into a
* {@link List} of lists.
*
* @see ArrayListHandler
*/
@Immutable
public class ListListHandlerGeneric<A> implements ResultSetHandler {
 
public static ListListHandlerGeneric<Object> create(final List<Class<?>> classes) {
return create(Object.class, classes);
}
 
public static <A> ListListHandlerGeneric<A> create(final Class<A> arrayClass, final int colCount) {
return create(arrayClass, Collections.<Class<? extends A>> nCopies(colCount, arrayClass));
}
 
// syntactic sugar, MAYBE cache instances
public static <A> ListListHandlerGeneric<A> create(final Class<A> arrayClass, final List<Class<? extends A>> classes) {
return new ListListHandlerGeneric<A>(arrayClass, classes);
}
 
private final Class<A> arrayClass;
private final List<Class<? extends A>> classes;
 
public ListListHandlerGeneric(final Class<A> arrayClass, final List<Class<? extends A>> classes) {
if (arrayClass == null)
throw new NullPointerException("Missing array component class");
this.arrayClass = arrayClass;
this.classes = classes == null ? null : new ArrayList<Class<? extends A>>(classes);
}
 
@Override
public final List<List<A>> handle(ResultSet rs) throws SQLException {
final int cols = this.classes == null ? rs.getMetaData().getColumnCount() : this.classes.size();
final List<List<A>> result = new ArrayList<List<A>>();
while (rs.next()) {
final List<A> array = new ArrayList<A>();
for (int i = 0; i < cols; i++) {
if (this.classes == null)
array.add(this.arrayClass.cast(rs.getObject(i + 1)));
else
array.add(SQLResultSet.getValue(rs, this.classes.get(i), i + 1));
}
result.add(array);
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/HierarchyLevel.java
78,16 → 78,16
throw new IllegalArgumentException(clazz + " hierarchy unknown");
}
 
static private Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> byClass;
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> byClass;
 
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> getByClass() {
// java : Cannot refer to the static enum field HierarchyLevel.byClass within an initializer
// thus not in ctor but on demand.
if (byClass == null) {
static {
byClass = new HashMap<Class<? extends DBStructureItemJDBC>, HierarchyLevel>();
for (HierarchyLevel l : HierarchyLevel.values())
for (HierarchyLevel l : HierarchyLevel.values()) {
byClass.put(l.getJDBCClass(), l);
}
}
 
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> getByClass() {
return byClass;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowAccessor.java
14,10 → 14,14
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Step;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.HashingStrategy;
import org.openconcerto.utils.convertor.StringClobConvertor;
 
26,12 → 30,15
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
 
/**
* A class that represent a row of a table. The row might not acutally exists in the database, and
115,6 → 122,14
*/
public abstract class SQLRowAccessor implements SQLData {
 
@Deprecated
static public final String ACCESS_DB_IF_NEEDED_PROP = "SQLRowAccessor.accessDBIfNeeded";
static private final boolean ACCESS_DB_IF_NEEDED = Boolean.parseBoolean(System.getProperty(ACCESS_DB_IF_NEEDED_PROP, "false"));
 
public static boolean getAccessDBIfNeeded() {
return ACCESS_DB_IF_NEEDED;
}
 
static private final HashingStrategy<SQLRowAccessor> ROW_STRATEGY = new HashingStrategy<SQLRowAccessor>() {
@Override
public int computeHashCode(SQLRowAccessor object) {
271,7 → 286,7
// MAYBE change paramter to enum MissingMode = THROW_EXCEPTION, ADD, RETURN_NULL
public final Object getObject(String fieldName, final boolean mustBePresent) throws IllegalArgumentException {
if (mustBePresent && !this.getFields().contains(fieldName))
throw new IllegalArgumentException("Field " + fieldName + " not present in this : " + this.getFields());
throw new IllegalArgumentException("Field " + fieldName + " not present in this : " + this.getFields() + " table " + this.getTable().getName());
return this.getObject(fieldName);
}
 
385,6 → 400,25
}
 
/**
* Returns the foreign table of <i>fieldName</i>.
*
* @param fieldName the name of a foreign field, e.g. "ID_ARTICLE_2".
* @return the table the field points to (never <code>null</code>), e.g. |ARTICLE|.
* @throws IllegalArgumentException if <i>fieldName</i> is not a foreign field.
*/
protected final SQLTable getForeignTable(String fieldName) throws IllegalArgumentException {
return this.getForeignLink(Collections.singletonList(fieldName)).getTarget();
}
 
protected final Link getForeignLink(final List<String> fieldsNames) throws IllegalArgumentException {
final DatabaseGraph graph = this.getTable().getDBSystemRoot().getGraph();
final Link foreignLink = graph.getForeignLink(this.getTable(), fieldsNames);
if (foreignLink == null)
throw new IllegalArgumentException(fieldsNames + " are not a foreign key of " + this.getTable());
return foreignLink;
}
 
/**
* Return the foreign row, if any, for the passed field.
*
* @param fieldName name of the foreign field.
395,6 → 429,26
public abstract SQLRowAccessor getForeign(String fieldName);
 
/**
* Return the non empty foreign row, if any, for the passed field.
*
* @param fieldName name of the foreign field.
* @return <code>null</code> if the value of <code>fieldName</code> is
* {@link #isForeignEmpty(String) empty}, otherwise a SQLRowAccessor with the value of
* <code>fieldName</code> as its ID.
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public final SQLRowAccessor getNonEmptyForeign(String fieldName) {
if (this.isForeignEmpty(fieldName)) {
return null;
} else {
final SQLRowAccessor res = this.getForeign(fieldName);
assert res != null;
return res;
}
}
 
/**
* Return the ID of a foreign row.
*
* @param fieldName name of the foreign field.
408,17 → 462,61
}
 
/**
* Return the ID of a foreign row.
* Return the ID of a foreign row. NOTE : there's two cases when the result can be
* <code>null</code> :
* <ol>
* <li><code>field</code> is defined and has the value <code>null</code></li>
* <li><code>field</code> is defined and has an SQLRowValues value without an ID</li>
* </ol>
* In the second case, <code>field</code> is *not* {@link #isForeignEmpty(String) empty}, an ID
* is just missing.
*
* @param fieldName name of the foreign field.
* @return the value of <code>fieldName</code> or {@link #getIDNumber()} if the value is a
* {@link SQLRowAccessor}, <code>null</code> if the actual value is.
* {@link SQLRowValues}, <code>null</code> if the actual value is.
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public abstract Number getForeignIDNumber(String fieldName) throws IllegalArgumentException;
public final Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final Value<Number> res = getForeignIDNumberValue(fieldName);
return res.hasValue() ? res.getValue() : null;
}
 
/**
* Return the ID of a foreign row.
*
* @param fieldName name of the foreign field.
* @return {@link Value#getNone()} if there's a {@link SQLRowValues} without
* {@link SQLRowValues#hasID() ID}, otherwise the value of <code>fieldName</code> or
* {@link #getIDNumber()} if the value is a {@link SQLRowValues}, never
* <code>null</code> (the {@link Value#getValue()} is <code>null</code> when
* <code>fieldName</code> is).
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public final Value<Number> getForeignIDNumberValue(final String fieldName) throws IllegalArgumentException {
fetchIfNeeded(fieldName);
// don't use getForeign() to avoid creating a SQLRow
final Object val = this.getContainedObject(fieldName);
if (val instanceof SQLRowValues) {
final SQLRowValues vals = (SQLRowValues) val;
return vals.hasID() ? Value.getSome(vals.getIDNumber()) : Value.<Number> getNone();
} else {
if (!this.getTable().getField(fieldName).isForeignKey())
throw new IllegalArgumentException(fieldName + "is not a foreign key of " + this.getTable());
return Value.getSome((Number) val);
}
}
 
private void fetchIfNeeded(String fieldName) {
if (getAccessDBIfNeeded() && (this instanceof SQLRow) && !getFields().contains(fieldName)) {
assert false : "Missing " + fieldName + " in " + this;
Log.get().log(Level.WARNING, "Missing " + fieldName + " in " + this, new IllegalStateException());
((SQLRow) this).fetchValues();
}